diff --git a/ROADMAP.md b/ROADMAP.md index 6d33d3b8..40f4e3d8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -774,6 +774,7 @@ Completed Milestone 23 by adding the `cloud-inference-skills` child plugin, ship - [x] Add a `productivity-skills:maintain-project-docs` umbrella workflow after `maintain-project-roadmap` owns small-ticket tracking. It should run the individual docs skills together, enforce the splits between `README.md`, `CONTRIBUTING.md`, `AGENTS.md`, `ACCESSIBILITY.md`, and `ROADMAP.md`, and prevent repeated content from drifting across files. - [x] Add a first `productivity-skills:design-agent-automation-workflow` planning skill for agent and automation design. It chooses between Codex app automations, `codex exec`, Codex subagents, OpenAI Agents SDK services, LangGraph graphs, Hermes-specific workflows, or no automation yet while delegating stack-specific implementation to the owning plugin. - [x] Added `productivity-skills:design-agent-eval-workflow` for agent, skill, prompt, and automation eval planning, and skewed automation guidance toward safe full automation with exact escalation gates instead of broad human review. +- [x] Aligned Apple Xcode app guidance around strict MVVM source structure, including view-adjacent view models/controllers, directional `Services/` subdirectories, and `sync-xcode-project-guidance` structure-audit reporting for repeatable downstream drift detection. - [x] Create a quicker full-auto Socket patch-refresh script for trusted maintainer use. It should bump the shared patch version, validate metadata, satisfy release-ready and subtree gates, push `main` and any required subtree split, tag and publish the GitHub release, verify branch accounting, and run `codex plugin marketplace upgrade socket`. - [x] Reduce hand-carried patch-release work by capturing commit-bound temporary `CODEX_HOME` marketplace smoke evidence and the final Dependabot alert query, then incorporating both into generated release notes without changing release-ready, subtree, branch-accounting, tag, GitHub release, or final marketplace-upgrade gates. - [ ] Explore steward-assisted release and worktree orchestration. Start with Socket Steward release preflights and cache-refresh checks, then evaluate whether `swift-steward` or sibling roles should handle read-only release readiness, PR merge sequencing, branch accounting, and parallel worktree status reports while the main thread keeps write, merge, tag, and publish ownership. diff --git a/docs/maintainers/apple-swift-structure-guidance-alignment.md b/docs/maintainers/apple-swift-structure-guidance-alignment.md index 70e9a32d..cf07dce5 100644 --- a/docs/maintainers/apple-swift-structure-guidance-alignment.md +++ b/docs/maintainers/apple-swift-structure-guidance-alignment.md @@ -1,7 +1,7 @@ # Apple Swift Structure Guidance Alignment -This note records the first alignment pass for Gale's preferred Swift and -Apple-platform project structure across Socket's Swift-related skills. +This note records Gale's preferred Swift and Apple-platform project structure +across Socket's Swift-related skills. ## Current Fit @@ -15,53 +15,72 @@ The existing guidance is already close to the preferred shape: - `server-side-swift` owns SwiftPM-first server work and should keep app, scene, preview, simulator, and Xcode-specific concerns delegated away. -The useful next move is to make the preferred app structure more explicit -inside Apple app guidance, not to rename the whole stack to MVVM-C. +The useful direction is strict Apple-app MVVM for Xcode app projects, while +keeping Swift package and server-side Swift guidance focused on their own +non-app boundaries. ## Preferred Structure -Use a coordinator-shaped, MVVM-C-adjacent model only where it removes real -ownership confusion: +Use strict Apple-app MVVM for Xcode app projects: - `App` declares app entry, app-wide lifecycle, and the top-level scene list. +- App-wide `@Observable` state lives beside the app entry point in + `WhateverNameApp+ViewModel.swift`, containing + `@Observable final class WhateverNameAppViewModel`. - `Scene` owns per-window, per-document, or managed scene lifecycle and context. -- Small controller or coordinator objects own navigation, presentation, command, - focus, and workflow state for a view cluster or scene boundary. - SwiftUI views stay component UI: render state, expose local interactions, and avoid becoming broad app coordinators. -- View models are typed view-driving state when they clarify a real view or - view-cluster boundary; they are not mandatory wrappers around every model. -- Coordinators are useful for scene, command, workflow, navigation, or - presentation ownership; they should not become a second hidden scene graph. +- View-local state and actions stay in the view where feasible. +- View models are typed view-driving state paired with exactly one owning view + or app entry point. +- SwiftUI view models live beside their matching view as + `SomeKindaView+Model.swift`. +- UIKit and AppKit view-controller support lives beside the matching view as + `SomeKindaView+Controller.swift`. - Models preserve source-of-truth naming and raw persistence or wire shape until a real semantic boundary requires mapping. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` + types, app datamodels, DTOs, and shared transfer or persistence shapes. +- `Sources/Services/Consumed`, `Sources/Services/Internal`, and + `Sources/Services/Provided` own services by direction. +- The main app-wide service, when present, lives under + `Sources/Services/Internal/` as `WhateverNameAppService.swift`. +- There is no root `Controllers/` directory in ordinary Xcode app structure. - Data transformation should prefer small composable functions and functional pipelines when that keeps flow readable. -## Guidance Changes To Consider +## Implemented Guidance Changes ### `apple-dev-skills:swiftui-app-architecture-workflow` -Add a focused reference or section for "coordinator-shaped SwiftUI" that: +The workflow now treats strict app MVVM as the app-project source layout: -- describes controllers/coordinators as local ownership tools, not mandatory - architecture ceremony - routes app lifecycle to `App`, scene lifecycle to `Scene`, and component rendering to `View` -- distinguishes view-driving state from navigation or workflow coordination +- distinguishes view-driving state from services, persistence models, and view + controller support - warns against environment-object dumping and hidden router state - keeps native SwiftUI scene actions, focused values, and selection-driven `NavigationSplitView` as the first choices when they fit ### `apple-dev-skills:sync-xcode-project-guidance` -Update the reusable Xcode project guidance snippet so downstream app repos can -name this preference directly: +The reusable Xcode project guidance now names the structure directly and the +sync runtime emits a `structure_audit` payload for downstream repos: -- small focused controller or coordinator classes are encouraged when they own - a real view cluster, scene, command, or workflow boundary -- broad global coordinators, mandatory view-model wrappers, and duplicate - mapping layers remain anti-patterns +- missing `Sources/Views/Shared`, `Sources/Views/macOS`, `Sources/Views/iOS`, + `Sources/Models`, `Sources/Services/Consumed`, `Sources/Services/Internal`, + or `Sources/Services/Provided` +- legacy `Sources/Controllers` +- unpaired `ViewModel.swift` files +- view model or controller support files outside `Sources/Views` +- missing app-entry view model files +- missing internal app service files when a strict app entry exists + +The sync skill still owns downstream repo guidance alignment. Socket Steward and +`productivity-skills:maintain-project-docs` own umbrella Socket docs audits and +proposal reports; they should point at this sync skill when Apple app repo +guidance drift is the concrete finding. ### `apple-dev-skills:sync-swift-package-guidance` @@ -74,11 +93,12 @@ AppKit modules. Keep the shared Swift layer focused on source organization and functional data flow. It should not learn Apple scene, command, or navigation ownership details. -## Validation For A Future Implementation Slice +## Validation - Run the Apple Dev child validation added for the touched skill references. - Run `uv run scripts/validate_socket_metadata.py` from the Socket root after metadata, marketplace, or exported skill descriptions change. - Review generated or copied guidance text for consistent vocabulary: - `controller`, `coordinator`, `view-driving state`, `scene`, `view cluster`, - and `component UI`. + `strict Apple-app MVVM`, `view-driving state`, `Services/Consumed`, + `Services/Internal`, `Services/Provided`, `Models`, `Views`, and + `component UI`. diff --git a/docs/maintainers/automation-suitability.md b/docs/maintainers/automation-suitability.md index 75baf199..f789b920 100644 --- a/docs/maintainers/automation-suitability.md +++ b/docs/maintainers/automation-suitability.md @@ -151,7 +151,7 @@ as the default Socket maintainer automation runtime. | `maintain-project-repo` | `codex exec` first; code-owned service for coordinated rollout | It installs managed scripts and CI wrappers. It is deterministic, but the write surface is broad enough that every repo should get a PR. | | `maintain-project-roadmap` | App check-only or `codex exec`; code-owned service only for planning sync | Roadmaps reflect human priorities. Automate stale-structure fixes, but keep milestone meaning human-reviewed. | | `sync-swift-package-guidance` | `codex exec` for one repo; code-owned service for SwiftPM fleet sync | It classifies repo shape, writes `AGENTS.md`, and refreshes `maintain-project-repo`. It is suitable for PR-based automation after the Socket-cache companion discovery fix. | -| `sync-xcode-project-guidance` | `codex exec` for one repo; code-owned service for Apple-app fleet sync | It has the same shape as the SwiftPM sync skill but needs more caution around Xcode project state and Apple docs gates. | +| `sync-xcode-project-guidance` | `codex exec` for one repo; code-owned service for Apple-app fleet sync | It has the same shape as the SwiftPM sync skill, reports strict MVVM Xcode app-structure drift through `structure_audit`, and needs more caution around Xcode project state and Apple docs gates. | ## Dedicated Skill Recommendation @@ -176,7 +176,7 @@ SDK code generation. Start with a two-phase system: -1. Use Codex app automations for nightly or weekly inventory reports. These should run check-only audits across a repo list and produce a ranked queue: docs drift, missing maintainer scripts, stale AGENTS guidance, failing issue triage, and Swift/Xcode sync candidates. +1. Use Codex app automations for nightly or weekly inventory reports. These should run check-only audits across a repo list and produce a ranked queue: docs drift, missing maintainer scripts, stale AGENTS guidance, failing issue triage, Swift/Xcode sync candidates, and strict Xcode app-structure drift reported by `sync-xcode-project-guidance`. 2. Use `codex exec` jobs for one-repo PR creation. Each job should receive the repo path, the exact skill, the requested run mode, validation command, and PR expectations. It should never batch unrelated repos into one execution. Move to a code-owned agent service when the queue itself becomes the product. diff --git a/plugins/apple-dev-skills/ROADMAP.md b/plugins/apple-dev-skills/ROADMAP.md index 6011bd4c..d5f1a059 100644 --- a/plugins/apple-dev-skills/ROADMAP.md +++ b/plugins/apple-dev-skills/ROADMAP.md @@ -783,6 +783,8 @@ Completed Milestone 53 by adding `devicecheck-app-attest-workflow`, focused refe - Tightened Xcode project guidance so tracked `.pbxproj` diffs produced by Xcode, XcodeGen, or other project-aware workflows are treated as critical project state that must be reviewed, staged, and committed before push, merge, release, or cleanup. - Updated standalone install guidance so `apple-dev-skills` defaults to Codex's Git-backed marketplace add/upgrade flow without an explicit ref, documents the optional `socket` marketplace path for Gale's broader plugin set, and keeps manual local clone marketplaces as development and fallback paths. - Tightened the Swift public API guidance across shared snippets, skill-local snippet copies, and generated `AGENTS.md` templates so public call sites default to streamlined typed APIs, optional defaulted parameters over overloads, request/options structs at four or more public parameters, and enum-backed choice modeling. +- Aligned Xcode app-project guidance around strict Apple-app MVVM source structure: view-local models beside views as `+Model.swift`, UIKit/AppKit controller support beside views as `+Controller.swift`, app-wide `@Observable` state in `App+ViewModel.swift`, directional `Services/Consumed`, `Services/Internal`, and `Services/Provided` folders, and no root `Controllers/` directory. +- Added strict app-structure drift reporting to `sync-xcode-project-guidance` and updated the XcodeGen bootstrap scaffold so new SwiftUI app projects start with `Sources/Views/Shared`, `Sources/Views/macOS`, `Sources/Views/iOS`, `Sources/Models`, and directional `Sources/Services` folders. - Registered Xcode's built-in MCP bridge through the Codex plugin manifest so installed `apple-dev-skills` plugins expose the Xcode-owned MCP path without bundling a separate server package. - Added an experimental `xcode_lldb` MCP config entry for Xcode 27 beta-era `xcrun lldb-mcp` investigation while keeping the dedicated debugger workflow planned until startup validation succeeds. - Clarified the Apple `maintain-project-repo` branch-protection contract so generated and synced repos require the `validate` Actions check context instead of the workflow-title display string. diff --git a/plugins/apple-dev-skills/shared/agents-snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/shared/agents-snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/shared/agents-snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/shared/agents-snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/appkit-app-architecture-workflow/SKILL.md b/plugins/apple-dev-skills/skills/appkit-app-architecture-workflow/SKILL.md index ec802635..12ae3f80 100644 --- a/plugins/apple-dev-skills/skills/appkit-app-architecture-workflow/SKILL.md +++ b/plugins/apple-dev-skills/skills/appkit-app-architecture-workflow/SKILL.md @@ -19,6 +19,12 @@ archiving, AppKit MVC, Observation interop, and mixed AppKit/SwiftUI composition It is not the Apple-docs router, not the SwiftUI architecture workflow, not the accessibility workflow, and not the Xcode execution workflow. +For Xcode app source layout, keep UIKit/AppKit controller support view-adjacent: +`Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` own view +surfaces, and controller support files are named `+Controller.swift` +beside their matching view. Do not collect ordinary app controller support in a +root `Controllers/` directory. + ## When To Use - Use this skill when the user wants help structuring an AppKit or mixed @@ -118,6 +124,8 @@ accessibility workflow, and not the Xcode execution workflow. - repositories, stores, service layers, mirrored DTOs, view-model cache layers, or wrapper objects inserted between SwiftData and SwiftUI-owned screens + - a root `Controllers/` directory used for ordinary view-controller support + instead of `+Controller.swift` beside the matching view - app-wide runtime work hidden in a view controller - menu or status-item behavior hidden in a leaf view - responder-chain actions replaced by a broad command bus without a real need diff --git a/plugins/apple-dev-skills/skills/appkit-app-architecture-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/appkit-app-architecture-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/appkit-app-architecture-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/appkit-app-architecture-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/apple-typography-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/apple-typography-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/apple-typography-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/apple-typography-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/apple-ui-accessibility-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/apple-ui-accessibility-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/apple-ui-accessibility-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/apple-ui-accessibility-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/avaudio-engine-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/avaudio-engine-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/avaudio-engine-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/avaudio-engine-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/avfaudio-session-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/avfaudio-session-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/avfaudio-session-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/avfaudio-session-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/avfoundation-media-pipeline-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/avfoundation-media-pipeline-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/avfoundation-media-pipeline-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/avfoundation-media-pipeline-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/SKILL.md b/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/SKILL.md index 3d3d018c..4c693db9 100644 --- a/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/SKILL.md +++ b/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/SKILL.md @@ -69,6 +69,11 @@ This skill can be discovered from a standalone `apple-dev-skills` install, but i - for `xcodegen`, let `scripts/bootstrap_xcode_app_project.py` generate the repo scaffold from `templates/xcodegen/swiftui-app/`, including `project.yml`, checked-in `.xcconfig` files, source files, tests, and `AGENTS.md`, then run `xcodegen generate` - create the standard top-level Xcode app layout: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/` - keep app-owned implementation/resources/support under `Sources/`, tests under `Tests/`, reusable app/extension source under `Shared/`, extension target roots under `Extensions/`, `.xcconfig` layers under `Configurations/`, project-local automation under `Scripts/`, and justified local Swift package boundaries under `Packages/` + - inside `Sources/`, create the strict app structure: `Views/Shared`, `Views/macOS`, `Views/iOS`, `Models`, `Services/Consumed`, `Services/Internal`, and `Services/Provided` + - create the app entry point as `App.swift` unless the supplied app name already ends in `App`, then pair it with `App+ViewModel.swift` containing the app-wide `@Observable final class AppViewModel` + - create SwiftUI views under `Sources/Views/Shared`, with view-local view models beside their view as `+Model.swift` + - put the main app-wide service under `Sources/Services/Internal` as `AppService.swift` + - do not create a root `Controllers/` directory; UIKit and AppKit controller support belongs beside the matching view as `+Controller.swift` - install `.codex/environments/xcode-project.toml` from `templates/codex-local-environments/xcode-project.toml` and replace the scheme placeholder with the generated app target name - keep the generated `project.yml` aligned with the current XcodeGen project spec concepts: project `options`, `configs`, `configFiles`, targets, sources, schemes, packages, project references, test-plan references, and `minimumXcodeGenVersion` - keep the generated `minimumXcodeGenVersion` on the recent validated baseline declared by the templates; when the baseline is raised, update the templates, docs, and tests together @@ -81,6 +86,8 @@ This skill can be discovered from a standalone `apple-dev-skills` install, but i 8. Validate the scaffold: - verify the expected app files exist - verify the standard top-level directories exist + - verify the strict app source directories exist under `Sources/Views`, `Sources/Models`, and `Sources/Services` + - verify the app entry point, app view model, shared content view, shared content view model, and internal app service use the strict naming contract - verify `.swiftformat` exists - verify `AGENTS.md` exists when enabled - verify `.codex/environments/xcode-project.toml` exists and uses the generated app target name for Codex GUI actions diff --git a/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/assets/AGENTS.md b/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/assets/AGENTS.md index 1b1d070b..fc39ffb6 100644 --- a/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/assets/AGENTS.md +++ b/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/assets/AGENTS.md @@ -21,11 +21,18 @@ - Prefer Xcode-aware tooling or `xcodebuild` over ad hoc filesystem assumptions when project structure or target membership is involved. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` pairs with `WhateverNameApp+ViewModel.swift`, containing `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`; do not create or preserve a root `Controllers/` directory. - Use the standard top-level Xcode app repository layout: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable; do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos. +- Inside `Sources/`, use strict app structure by default: `Views/`, `Models/`, and `Services/`. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` for shared, macOS-specific, and iOS/iPadOS-specific UI. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and shared transfer or persistence shapes. +- `Sources/Services/` owns services by direction: `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - For new Xcode app, framework, and workspace repositories, prefer XcodeGen plus synced source folders, checked-in `.xcconfig` files, and checked-in entitlement files by default unless there is a concrete reason to avoid that generator dependency. - If this repo is XcodeGen-backed, treat `project.yml`, `project.yaml`, and any included XcodeGen specs as the source of truth for generated targets, schemes, build settings, build configurations, Swift packages, test-plan references, and file membership. - For XcodeGen-backed projects, edit the spec set and rerun `xcodegen generate` instead of hand-editing generated `.pbxproj` files. diff --git a/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/scripts/bootstrap_xcode_app_project.py b/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/scripts/bootstrap_xcode_app_project.py index 1394ee30..f91ad2e4 100755 --- a/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/scripts/bootstrap_xcode_app_project.py +++ b/plugins/apple-dev-skills/skills/bootstrap-xcode-app-project/scripts/bootstrap_xcode_app_project.py @@ -39,6 +39,16 @@ "Packages", ) +STANDARD_SOURCE_DIRECTORIES = ( + "Sources/Views/Shared", + "Sources/Views/macOS", + "Sources/Views/iOS", + "Sources/Models", + "Sources/Services/Consumed", + "Sources/Services/Internal", + "Sources/Services/Provided", +) + def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description=__doc__) @@ -101,12 +111,22 @@ def install_xcodegen_templates(target_dir: Path, name: str, platform: str, bundl def install_standard_directories(target_dir: Path) -> list[str]: installed_paths: list[str] = [] - for relative_path in STANDARD_TOP_LEVEL_DIRECTORIES: + for relative_path in STANDARD_TOP_LEVEL_DIRECTORIES + STANDARD_SOURCE_DIRECTORIES: directory = target_dir / relative_path directory.mkdir(parents=True, exist_ok=True) installed_paths.append(str(directory)) - for relative_path in ("Shared/.gitkeep", "Extensions/.gitkeep", "Scripts/.gitkeep", "Packages/.gitkeep"): + for relative_path in ( + "Shared/.gitkeep", + "Extensions/.gitkeep", + "Scripts/.gitkeep", + "Packages/.gitkeep", + "Sources/Views/macOS/.gitkeep", + "Sources/Views/iOS/.gitkeep", + "Sources/Models/.gitkeep", + "Sources/Services/Consumed/.gitkeep", + "Sources/Services/Provided/.gitkeep", + ): placeholder = target_dir / relative_path write_text(placeholder, "") installed_paths.append(str(placeholder)) @@ -129,32 +149,77 @@ def install_local_environment(target_dir: Path, scheme_name: str) -> str: return str(target_path) +def app_entry_type_name(name: str) -> str: + return name if name.endswith("App") else f"{name}App" + + def render_app_file(name: str) -> str: + app_type = app_entry_type_name(name) return f"""import SwiftUI @main -struct {name}: App {{ +struct {app_type}: App {{ + @State private var viewModel = {app_type}ViewModel(service: {app_type}Service()) + var body: some Scene {{ WindowGroup {{ ContentView() + .environment(viewModel) }} }} }} """ +def render_app_view_model(name: str) -> str: + app_type = app_entry_type_name(name) + return f"""import Observation + +@Observable +final class {app_type}ViewModel {{ + let service: {app_type}Service + + init(service: {app_type}Service) {{ + self.service = service + }} +}} +""" + + +def render_app_service(name: str) -> str: + app_type = app_entry_type_name(name) + return f"""struct {app_type}Service {{ + func bootstrapMessage() -> String {{ + "Hello, world!" + }} +}} +""" + + def render_content_view() -> str: return """import SwiftUI struct ContentView: View { + @State private var model = ContentViewModel() + var body: some View { - Text("Hello, world!") + Text(model.title) .padding() } } """ +def render_content_view_model() -> str: + return """import Observation + +@Observable +final class ContentViewModel { + var title = "Hello, world!" +} +""" + + def render_test_file(name: str) -> str: return f"""import XCTest @testable import {name} @@ -261,8 +326,12 @@ def main() -> int: print(json.dumps(payload, indent=2, sort_keys=True)) return 1 - write_text(target_dir / "Sources/App/App.swift", render_app_file(args.name)) - write_text(target_dir / "Sources/App/ContentView.swift", render_content_view()) + app_type = app_entry_type_name(args.name) + write_text(target_dir / f"Sources/{app_type}.swift", render_app_file(args.name)) + write_text(target_dir / f"Sources/{app_type}+ViewModel.swift", render_app_view_model(args.name)) + write_text(target_dir / f"Sources/Services/Internal/{app_type}Service.swift", render_app_service(args.name)) + write_text(target_dir / "Sources/Views/Shared/ContentView.swift", render_content_view()) + write_text(target_dir / "Sources/Views/Shared/ContentView+Model.swift", render_content_view_model()) write_text(target_dir / f"Tests/{args.name}Tests/{args.name}Tests.swift", render_test_file(args.name)) try: local_environment_path = install_local_environment(target_dir, args.name) diff --git a/plugins/apple-dev-skills/skills/core-animation-layer-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/core-animation-layer-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/core-animation-layer-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/core-animation-layer-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/coreaudio-modernization-repair-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/coreaudio-modernization-repair-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/coreaudio-modernization-repair-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/coreaudio-modernization-repair-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/coremedia-timing-samplebuffer-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/coremedia-timing-samplebuffer-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/coremedia-timing-samplebuffer-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/coremedia-timing-samplebuffer-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/devicecheck-app-attest-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/devicecheck-app-attest-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/devicecheck-app-attest-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/devicecheck-app-attest-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/explore-apple-swift-docs/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/explore-apple-swift-docs/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/explore-apple-swift-docs/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/explore-apple-swift-docs/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/format-swift-sources/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/format-swift-sources/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/format-swift-sources/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/format-swift-sources/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/migrate-xcode-project-to-xcodegen/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/migrate-xcode-project-to-xcodegen/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/migrate-xcode-project-to-xcodegen/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/migrate-xcode-project-to-xcodegen/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/safari-extension-control-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/safari-extension-control-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/safari-extension-control-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/safari-extension-control-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/sf-symbols-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/sf-symbols-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/sf-symbols-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/sf-symbols-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/structure-swift-sources/SKILL.md b/plugins/apple-dev-skills/skills/structure-swift-sources/SKILL.md index 15e9328e..afaddacf 100644 --- a/plugins/apple-dev-skills/skills/structure-swift-sources/SKILL.md +++ b/plugins/apple-dev-skills/skills/structure-swift-sources/SKILL.md @@ -69,9 +69,12 @@ Use this skill as the top-level workflow for structural cleanup inside existing - when the task is file-header normalization or a full cleanup pass that includes headers, use `scripts/normalize_swift_file_headers.py` to audit or apply the documented header shape 6. Apply repo-shape rules: - for Swift packages, prefer directories grouped by layer and feature, such as `API//.swift` and `Features//.swift` - - for Xcode app projects, ensure important app-facing source directories such as `Views/`, `Controllers/`, and `Models/` - - for SwiftUI views, keep view files in `Views/`, require exactly one SwiftUI `View` component per file, and keep that component's Xcode SwiftUI preview in the same file + - for Xcode app projects, ensure important app-facing source directories such as `Views/`, `Models/`, and `Services/`, and do not preserve a root `Controllers/` directory + - for SwiftUI views, keep view files in `Views/Shared`, `Views/macOS`, or `Views/iOS`, require exactly one SwiftUI `View` component per file, and keep that component's Xcode SwiftUI preview in the same file - when splitting grouped SwiftUI views, create one `.swift` file per view component, require any SwiftUI view model for that component to live in `+Model.swift`, and place view-specific modifiers in `+Modifier.swift` + - for UIKit and AppKit view-controller support, use `+Controller.swift` beside the matching view + - for app-wide `@Observable` state, use `App+ViewModel.swift` beside `App.swift` + - for services, use `Services/Consumed`, `Services/Internal`, and `Services/Provided`, with the main app-wide service under `Services/Internal` as `AppService.swift` 7. Finish with `format-swift-sources` again so the moved or split files end in a normalized state. ## Inputs diff --git a/plugins/apple-dev-skills/skills/structure-swift-sources/references/layout-rules.md b/plugins/apple-dev-skills/skills/structure-swift-sources/references/layout-rules.md index aed1b58b..e0ade9be 100644 --- a/plugins/apple-dev-skills/skills/structure-swift-sources/references/layout-rules.md +++ b/plugins/apple-dev-skills/skills/structure-swift-sources/references/layout-rules.md @@ -11,13 +11,20 @@ ## Xcode App Projects -- Ensure app-facing source directories such as `Views/`, `Controllers/`, and `Models/`. -- Keep SwiftUI views in `Views/`. +- Ensure app-facing source directories such as `Views/`, `Models/`, and `Services/`. +- Do not create or preserve a root `Controllers/` directory for ordinary app structure. +- `Sources/Views/` must contain `Shared/`, `macOS/`, and `iOS/` subdirectories. +- Keep SwiftUI views and UIKit/AppKit view surfaces in `Views/` or the appropriate platform/shared subdirectory. - Require one SwiftUI `View` component per file, named `.swift`, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one file; split related child views into their own files instead. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` beside the view and must not be shared with any other SwiftUI view. - Do not use grouped model files, shared view-model files, or view-cluster models for SwiftUI views. - When a view needs extracted modifiers, use `+Modifier.swift`. +- UIKit and AppKit view-controller support files must live beside their matching view as `+Controller.swift`. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` pairs with `WhateverNameApp+ViewModel.swift`. +- `Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and shared transfer or persistence shapes. +- `Services/` owns app-wide and boundary-facing services with `Consumed/`, `Internal/`, and `Provided/` subdirectories. +- Put the main app-wide service, when present, under `Services/Internal/` as `WhateverNameAppService.swift`. - Move files into their proper layer directory rather than leaving them flat at the project root. ## Shared Rule diff --git a/plugins/apple-dev-skills/skills/structure-swift-sources/references/source-organization-rules.md b/plugins/apple-dev-skills/skills/structure-swift-sources/references/source-organization-rules.md index f9135382..67289977 100644 --- a/plugins/apple-dev-skills/skills/structure-swift-sources/references/source-organization-rules.md +++ b/plugins/apple-dev-skills/skills/structure-swift-sources/references/source-organization-rules.md @@ -37,12 +37,21 @@ - Require exactly one SwiftUI `View` component per file. Do not group multiple `View` component types in one Swift file, even when the views are small, private, nested, or part of the same feature. - Name each view file after its component, such as `.swift`, and keep that component's Xcode SwiftUI preview in the same file. -- SwiftUI view models are always per-view, with no exceptions: if `.swift` has a view model, it must live in `+Model.swift` and belong only to that one `View` component. +- SwiftUI view models are always per-view, with no exceptions: if `.swift` has a view model, it must live beside the view in `+Model.swift` and belong only to that one `View` component. - Do not share one SwiftUI view model across multiple views, view families, screens, flows, or view clusters, and do not collect multiple SwiftUI view models in one shared model file. - When an existing file contains multiple SwiftUI view components, split it into one file per view before adding more behavior. - Once any one view has more than `3` chained modifiers, strongly consider extracting a custom `ViewModifier`. - Place that modifier in `+Modifier.swift` when the modifier belongs to one view family. +## Xcode App MVVM Rule + +- Use strict Apple-app MVVM for Xcode app source layout: views own their own view-local state and actions where feasible, and every view model or controller support file is paired with one owning view or app entry point. +- Keep `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` as the default UI roots. +- Place UIKit and AppKit view-controller support beside the matching view as `+Controller.swift`; do not collect controllers in `Sources/Controllers`. +- Place app-wide `@Observable` state beside the app entry point as `WhateverNameApp+ViewModel.swift`, containing `@Observable final class WhateverNameAppViewModel`. +- Put Core Data persistence models, SwiftData `@Model` types, datamodels, DTOs, and app-wide transfer or persistence shapes in `Sources/Models`. +- Put services in `Sources/Services/Consumed`, `Sources/Services/Internal`, or `Sources/Services/Provided` according to direction. Main app-wide services belong in `Sources/Services/Internal` as `WhateverNameAppService.swift`. + ## Documentation Boundary - Keep this workflow focused on source layout and declaration organization. diff --git a/plugins/apple-dev-skills/skills/swiftui-animation-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/swiftui-animation-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/swiftui-animation-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/swiftui-animation-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/swiftui-app-architecture-workflow/SKILL.md b/plugins/apple-dev-skills/skills/swiftui-app-architecture-workflow/SKILL.md index b9120c31..5bb8fed9 100644 --- a/plugins/apple-dev-skills/skills/swiftui-app-architecture-workflow/SKILL.md +++ b/plugins/apple-dev-skills/skills/swiftui-app-architecture-workflow/SKILL.md @@ -19,10 +19,12 @@ It is not the Apple-docs router, not the accessibility workflow, and not the Xco Each SwiftUI `View` component must live in its own Swift file named for that view, and that file must carry the view's own Xcode SwiftUI preview. Do not group multiple `View` component types in one file, even when the views are small, related, nested, or currently used only by one parent. Split them into separate files so Xcode previews remain discoverable, isolated, and reliable. -SwiftUI view models are always per-view, with no exceptions. If a SwiftUI view has a view model, that model belongs to exactly that `View` component and must live in the matching `+Model.swift` file. Do not share a SwiftUI view model across multiple views, view families, screens, flows, or view clusters, and do not place multiple SwiftUI view models in one shared model file. +SwiftUI view models are always per-view, with no exceptions. If a SwiftUI view has a view model, that model belongs to exactly that `View` component and must live beside the matching view in the matching `+Model.swift` file. Do not share a SwiftUI view model across multiple views, view families, screens, flows, or view clusters, and do not place multiple SwiftUI view models in one shared model file. Keep supporting code in explicit paired files instead of bundling extra view types together: use `+Model.swift` for that view's model, `+Modifier.swift` for view-specific modifiers, and other narrowly named support files when needed. A file may contain private helper values or small non-`View` helpers for that one component, but it must not contain another SwiftUI `View` component. +For Xcode app projects, use strict Apple-app MVVM source layout: `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` own UI, `Sources/Models` owns persistence and transfer shapes, and `Sources/Services/Consumed`, `Sources/Services/Internal`, and `Sources/Services/Provided` own app services by direction. App-wide `@Observable` state lives beside the app entry point in `App+ViewModel.swift`, containing `@Observable final class AppViewModel`. UIKit and AppKit view-controller support lives beside the matching view as `+Controller.swift`; do not introduce a root `Controllers/` directory. + ## When To Use - Use this skill when the user wants help structuring a SwiftUI app across `App`, `Scene`, `WindowGroup`, `Window`, `Settings`, or `DocumentGroup`. @@ -88,6 +90,8 @@ Keep supporting code in explicit paired files instead of bundling extra view typ - control flow hidden in modifiers that obscure who owns the action - multiple SwiftUI `View` component types grouped into one file, especially when that prevents one file-local Xcode preview per component - one SwiftUI view model shared across a view cluster or stored outside the matching `+Model.swift` file + - a root `Controllers/` directory used for UIKit or AppKit view-controller support instead of `+Controller.swift` beside the matching view + - app-wide state hidden in a service, leaf view, or shared environment object instead of an app-entry `App+ViewModel.swift` 6. Return one recommendation path with: - the ownership boundary - the chosen transport diff --git a/plugins/apple-dev-skills/skills/swiftui-app-architecture-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/swiftui-app-architecture-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/swiftui-app-architecture-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/swiftui-app-architecture-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/SKILL.md b/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/SKILL.md index d3d6bb92..831fdaf9 100644 --- a/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/SKILL.md +++ b/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/SKILL.md @@ -58,7 +58,13 @@ This skill can be discovered from a standalone `apple-dev-skills` install, but i 4. Apply the shared Xcode-project policy before making repo-guidance changes: - apply the detailed local policy in `references/snippets/apple-xcode-project-core.md` - preserve its simplicity-first Swift, SwiftUI, Xcode-managed project, XcodeGen-backed project, test-plan, file-membership, tracked `.pbxproj` commit, and Debug/Release guidance -5. Run `scripts/run_workflow.py` to normalize inputs, detect whether the repo is really Xcode-managed, and shape the sync plan. +5. Run `scripts/run_workflow.py` to normalize inputs, detect whether the repo is really Xcode-managed, shape the sync plan, and report strict Xcode app structure drift: + - missing `Sources/Views/Shared`, `Sources/Views/macOS`, `Sources/Views/iOS`, `Sources/Models`, `Sources/Services/Consumed`, `Sources/Services/Internal`, or `Sources/Services/Provided` + - legacy `Sources/Controllers` + - unpaired view-model files + - `+Model.swift` or `+Controller.swift` files outside `Sources/Views` + - missing app-entry `App+ViewModel.swift` + - missing internal app service when the app declares a strict app entry point 6. Apply the sync path: - if `AGENTS.md` is missing, copy `assets/AGENTS.md` - if `AGENTS.md` exists and already contains the managed section, keep the file unchanged @@ -116,6 +122,7 @@ This skill can be discovered from a standalone `apple-dev-skills` install, but i - installed or preserved `.codex/environments/xcode-project.toml` - refreshed `maintain-project-repo` paths - validation result + - strict app-structure audit result and findings - one concise next step or handoff ## Guards and Stop Conditions diff --git a/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/assets/AGENTS.md b/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/assets/AGENTS.md index 72f3b598..ffc8ee3f 100644 --- a/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/assets/AGENTS.md +++ b/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/assets/AGENTS.md @@ -23,11 +23,18 @@ - Prefer Xcode-aware tooling or `xcodebuild` over ad hoc filesystem assumptions when project structure or target membership is involved. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` pairs with `WhateverNameApp+ViewModel.swift`, containing `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`; do not create or preserve a root `Controllers/` directory. - Use the standard top-level Xcode app repository layout: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable; do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos. +- Inside `Sources/`, use strict app structure by default: `Views/`, `Models/`, and `Services/`. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` for shared, macOS-specific, and iOS/iPadOS-specific UI. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and shared transfer or persistence shapes. +- `Sources/Services/` owns services by direction: `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - For new Xcode app, framework, and workspace repositories, prefer XcodeGen plus synced source folders, checked-in `.xcconfig` files, and checked-in entitlement files by default unless there is a concrete reason to avoid that generator dependency. - If this repo is XcodeGen-backed, treat `project.yml`, `project.yaml`, and any included XcodeGen specs as the source of truth for generated targets, schemes, build settings, build configurations, Swift packages, test-plan references, and file membership. - For XcodeGen-backed projects, edit the spec set and rerun `xcodegen generate` instead of hand-editing generated `.pbxproj` files. diff --git a/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/assets/append-section.md b/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/assets/append-section.md index cf9e721f..76817fb2 100644 --- a/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/assets/append-section.md +++ b/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/assets/append-section.md @@ -21,11 +21,18 @@ - Prefer Xcode-aware tooling or `xcodebuild` over ad hoc filesystem assumptions when project structure or target membership is involved. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` pairs with `WhateverNameApp+ViewModel.swift`, containing `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`; do not create or preserve a root `Controllers/` directory. - Use the standard top-level Xcode app repository layout: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable; do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos. +- Inside `Sources/`, use strict app structure by default: `Views/`, `Models/`, and `Services/`. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` for shared, macOS-specific, and iOS/iPadOS-specific UI. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and shared transfer or persistence shapes. +- `Sources/Services/` owns services by direction: `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - For new Xcode app, framework, and workspace repositories, prefer XcodeGen plus synced source folders, checked-in `.xcconfig` files, and checked-in entitlement files by default unless there is a concrete reason to avoid that generator dependency. - If this repo is XcodeGen-backed, treat `project.yml`, `project.yaml`, and any included XcodeGen specs as the source of truth for generated targets, schemes, build settings, build configurations, Swift packages, test-plan references, and file membership. - For XcodeGen-backed projects, edit the spec set and rerun `xcodegen generate` instead of hand-editing generated `.pbxproj` files. diff --git a/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/scripts/run_workflow.py b/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/scripts/run_workflow.py index e293fd63..e99739e5 100755 --- a/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/scripts/run_workflow.py +++ b/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/scripts/run_workflow.py @@ -35,6 +35,39 @@ def discover_xcode_state(repo_root: Path) -> dict: } +def audit_xcode_app_structure(repo_root: Path) -> dict: + required_directories = [ + "Sources/Views/Shared", + "Sources/Views/macOS", + "Sources/Views/iOS", + "Sources/Models", + "Sources/Services/Consumed", + "Sources/Services/Internal", + "Sources/Services/Provided", + ] + findings = [ + { + "code": "missing-directory", + "path": relative_path, + "message": f"Expected Xcode app structure directory is missing: {relative_path}", + } + for relative_path in required_directories + if not (repo_root / relative_path).is_dir() + ] + if (repo_root / "Sources" / "Controllers").exists(): + findings.append( + { + "code": "legacy-controllers-directory", + "path": "Sources/Controllers", + "message": "Move UIKit/AppKit controller files beside their matching view under Sources/Views as +Controller.swift.", + } + ) + return { + "status": "passed" if not findings else "needs-attention", + "findings": findings, + } + + def blocked_payload( repo_root: str, detected_state: dict, @@ -85,6 +118,7 @@ def main() -> int: repo_root = Path(args.repo_root or ".").expanduser().resolve() detected_state = discover_xcode_state(repo_root) + structure_audit = audit_xcode_app_structure(repo_root) agents_path = repo_root / "AGENTS.md" write_mode, copy_missing, append_existing, report_only = normalize_write_mode(settings.get("writeMode", "sync-if-needed")) normalized_inputs = { @@ -187,6 +221,7 @@ def main() -> int: "detected_state": detected_state, "normalized_inputs": normalized_inputs, "validation_result": "skipped (--dry-run)", + "structure_audit": structure_audit, "actions": actions, "next_step": "Run without --dry-run to sync AGENTS.md guidance for this Xcode repo.", } @@ -202,6 +237,7 @@ def main() -> int: "detected_state": detected_state, "normalized_inputs": normalized_inputs, "validation_result": "skipped (writeMode=report-only)", + "structure_audit": structure_audit, "actions": actions, "next_step": "Rerun with a mutating write mode if you want this workflow to create or append AGENTS.md guidance.", } diff --git a/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/scripts/sync_xcode_project_guidance.py b/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/scripts/sync_xcode_project_guidance.py index 335c0f73..ecdffb8e 100755 --- a/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/scripts/sync_xcode_project_guidance.py +++ b/plugins/apple-dev-skills/skills/sync-xcode-project-guidance/scripts/sync_xcode_project_guidance.py @@ -21,6 +21,20 @@ "Scripts/repo-maintenance/validate-all.sh", "Scripts/repo-maintenance/sync-shared.sh", "Scripts/repo-maintenance/release.sh", + "Sources/Views/Shared", + "Sources/Services/Internal", + "WhateverNameApp+ViewModel.swift", + "+Controller.swift", +] + +REQUIRED_XCODE_APP_DIRECTORIES = [ + "Sources/Views/Shared", + "Sources/Views/macOS", + "Sources/Views/iOS", + "Sources/Models", + "Sources/Services/Consumed", + "Sources/Services/Internal", + "Sources/Services/Provided", ] @@ -138,11 +152,105 @@ def validate_agents(text: str) -> tuple[bool, list[str]]: return not missing, missing +def audit_xcode_app_structure(repo_root: Path) -> dict: + findings: list[dict[str, str]] = [] + + for relative_path in REQUIRED_XCODE_APP_DIRECTORIES: + if not (repo_root / relative_path).is_dir(): + findings.append( + { + "code": "missing-directory", + "path": relative_path, + "message": f"Expected Xcode app structure directory is missing: {relative_path}", + } + ) + + controllers_dir = repo_root / "Sources" / "Controllers" + if controllers_dir.exists(): + findings.append( + { + "code": "legacy-controllers-directory", + "path": "Sources/Controllers", + "message": "Move UIKit/AppKit controller files beside their matching view under Sources/Views as +Controller.swift.", + } + ) + + sources_dir = repo_root / "Sources" + if sources_dir.is_dir(): + for view_model_path in sorted(sources_dir.rglob("*ViewModel.swift")): + if view_model_path.name.endswith("+ViewModel.swift"): + continue + relative_path = view_model_path.relative_to(repo_root).as_posix() + findings.append( + { + "code": "unpaired-view-model-file", + "path": relative_path, + "message": "View model files should be paired with their owning app or view as +ViewModel.swift or +Model.swift.", + } + ) + + for model_path in sorted(sources_dir.rglob("*+Model.swift")): + if "Sources/Views/" not in model_path.relative_to(repo_root).as_posix(): + relative_path = model_path.relative_to(repo_root).as_posix() + findings.append( + { + "code": "view-model-outside-views", + "path": relative_path, + "message": "View-local +Model.swift files should live beside their matching view under Sources/Views.", + } + ) + + for controller_path in sorted(sources_dir.rglob("*+Controller.swift")): + if "Sources/Views/" not in controller_path.relative_to(repo_root).as_posix(): + relative_path = controller_path.relative_to(repo_root).as_posix() + findings.append( + { + "code": "controller-outside-views", + "path": relative_path, + "message": "UIKit/AppKit +Controller.swift files should live beside their matching view under Sources/Views.", + } + ) + + app_files = [ + path for path in sorted(sources_dir.glob("*App.swift")) + if path.is_file() and not path.name.endswith("+ViewModel.swift") + ] + for app_path in app_files: + paired_model = app_path.with_name(f"{app_path.stem}+ViewModel.swift") + if not paired_model.is_file(): + relative_path = paired_model.relative_to(repo_root).as_posix() + findings.append( + { + "code": "missing-app-view-model", + "path": relative_path, + "message": "App-wide @Observable state should live beside the app entry point as App+ViewModel.swift.", + } + ) + + internal_services_dir = repo_root / "Sources" / "Services" / "Internal" + if app_files and internal_services_dir.is_dir(): + service_files = sorted(internal_services_dir.glob("*AppService.swift")) + if not service_files: + findings.append( + { + "code": "missing-internal-app-service", + "path": "Sources/Services/Internal", + "message": "When the app has a main app-wide service, place it under Sources/Services/Internal as AppService.swift.", + } + ) + + return { + "status": "passed" if not findings else "needs-attention", + "findings": findings, + } + + def main() -> int: args = build_parser().parse_args() repo_root = Path(args.repo_root).expanduser().resolve() agents_path = repo_root / "AGENTS.md" detected_state = discover_xcode_state(repo_root) + structure_audit = audit_xcode_app_structure(repo_root) actions: list[str] = [] if not detected_state["is_xcode_repo"]: @@ -153,6 +261,7 @@ def main() -> int: "agents_path": str(agents_path), "detected_state": detected_state, "validation_result": None, + "structure_audit": structure_audit, "actions": actions, "stderr": "The repository does not contain an .xcodeproj or .xcworkspace marker.", "next_step": "Run this workflow on an existing Xcode app repo.", @@ -168,6 +277,7 @@ def main() -> int: "agents_path": str(agents_path), "detected_state": detected_state, "validation_result": None, + "structure_audit": structure_audit, "actions": actions, "stderr": "The target AGENTS.md path exists but is not a regular file.", "next_step": "Resolve the AGENTS.md path conflict and rerun the workflow.", @@ -184,6 +294,7 @@ def main() -> int: "agents_path": str(agents_path), "detected_state": detected_state, "validation_result": None, + "structure_audit": structure_audit, "actions": actions, "stderr": "AGENTS.md is missing and template copy is disabled.", "next_step": "Enable template copy or create AGENTS.md manually before rerunning.", @@ -208,6 +319,7 @@ def main() -> int: "agents_path": str(agents_path), "detected_state": detected_state, "validation_result": None, + "structure_audit": structure_audit, "actions": actions, "stderr": "AGENTS.md exists but the bounded Xcode guidance section is missing and append behavior is disabled.", "next_step": "Enable append behavior or merge the guidance section manually before rerunning.", @@ -227,6 +339,7 @@ def main() -> int: "agents_path": str(agents_path), "detected_state": detected_state, "validation_result": "failed", + "structure_audit": structure_audit, "actions": actions, "stderr": f"Synced AGENTS.md is missing required guidance: {', '.join(missing)}", "next_step": "Fix the guidance template or section content, then rerun the workflow.", @@ -245,6 +358,7 @@ def main() -> int: "agents_path": str(agents_path), "detected_state": detected_state, "validation_result": validation_result, + "structure_audit": structure_audit, "actions": actions, "stderr": str(exc), "next_step": "Resolve the Codex local environment template or target path issue, then rerun sync-xcode-project-guidance.", @@ -262,6 +376,7 @@ def main() -> int: "agents_path": str(agents_path), "detected_state": detected_state, "validation_result": validation_result, + "structure_audit": structure_audit, "actions": actions, "stderr": str(exc), "next_step": "Install productivity-skills alongside apple-dev-skills, or add the socket marketplace and enable both plugin entries from the Socket catalog, then rerun sync-xcode-project-guidance.", @@ -291,6 +406,7 @@ def main() -> int: "agents_path": str(agents_path), "detected_state": detected_state, "validation_result": validation_result, + "structure_audit": structure_audit, "actions": actions, "stdout": proc_install_toolkit.stdout, "stderr": proc_install_toolkit.stderr, @@ -307,6 +423,7 @@ def main() -> int: "agents_path": str(agents_path), "detected_state": detected_state, "validation_result": validation_result, + "structure_audit": structure_audit, "actions": actions, "next_step": "Use xcode-build-run-workflow for active Xcode build or run work, use xcode-testing-workflow for test-focused work, and rerun sync-xcode-project-guidance after substantial plugin updates.", } diff --git a/plugins/apple-dev-skills/skills/xcode-app-project-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/xcode-app-project-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/xcode-app-project-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/xcode-app-project-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/xcode-build-run-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/xcode-build-run-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/xcode-build-run-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/xcode-build-run-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/xcode-coding-intelligence-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/xcode-coding-intelligence-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/xcode-coding-intelligence-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/xcode-coding-intelligence-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/skills/xcode-testing-workflow/references/snippets/apple-xcode-project-core.md b/plugins/apple-dev-skills/skills/xcode-testing-workflow/references/snippets/apple-xcode-project-core.md index 9e821519..0ccf5b20 100644 --- a/plugins/apple-dev-skills/skills/xcode-testing-workflow/references/snippets/apple-xcode-project-core.md +++ b/plugins/apple-dev-skills/skills/xcode-testing-workflow/references/snippets/apple-xcode-project-core.md @@ -59,10 +59,13 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard ## SwiftUI and State Architecture - Treat SwiftUI views as component UI: keep them small, composable, reusable, and easy to scan from top to bottom. +- Use a strict Apple-app MVVM shape for Xcode app projects: views own their own view-local state and actions where feasible, view models stay paired with their owning app or view, persistence and transfer shapes live in `Models/`, and app-wide services live under `Sources/Services/`. - Require one SwiftUI `View` component per file, named for that component, with that component's Xcode SwiftUI preview in the same file. - Do not group multiple SwiftUI view components into one Swift file, even when the views are small, private, related, nested, or currently used only by one parent. -- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live in `+Model.swift` and must not be shared with any other SwiftUI view. -- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, or view-cluster models. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- SwiftUI view models are always per-view, with no exceptions: the model for `.swift` must live beside the view in `+Model.swift` and must not be shared with any other SwiftUI view. +- Do not create shared SwiftUI view-model files, grouped SwiftUI view-model files, view-cluster models, or unpaired `ViewModel.swift` files. Split shared state into explicit inputs, bindings, environment values, focused values, SwiftData model objects, or a non-SwiftUI boundary when that boundary is genuinely outside the view layer. +- Put app-wide `@Observable` state beside the app entry point: `WhateverNameApp.swift` owns the single app lifecycle entry, and `WhateverNameApp+ViewModel.swift` owns `@Observable final class WhateverNameAppViewModel`. +- Put UIKit and AppKit view-controller support beside the matching view under `Sources/Views/` as `+Controller.swift`. Do not create or preserve a root `Controllers/` directory for ordinary app structure. - Prefer straight, top-down data flow with state owned at the narrowest view, scene, or app boundary that matches the behavior. - Do not build monolithic views, monolithic controllers, or broad shared mutable state when a smaller component boundary would be clearer. - Keep updates to view-driving state minimal and localized. @@ -85,6 +88,12 @@ Use this snippet in repository `AGENTS.md` files when you want baseline standard - Use the standard top-level Xcode app repository layout when creating or normalizing native app repos: `Sources/`, `Tests/`, `Shared/`, `Extensions/`, `Configurations/`, `Scripts/`, and `Packages/`. - `Sources/` owns the main app target implementation and app-owned resources/support files. `Tests/` owns all test targets. `Shared/` owns reusable source intended to be compiled into the app and extension targets. `Extensions/` owns extension target roots, one folder per extension. `Configurations/` owns `.xcconfig` layers. `Scripts/` owns project-local automation and build helper scripts. `Packages/` owns local Swift packages only when a real package boundary is justified. - Keep those top-level roots stable. Do not invent parallel names such as `AppSources`, `TestSources`, `Config`, `BuildScripts`, or `LocalPackages` for ordinary Xcode app repos unless the existing repo already has a deliberate, documented convention. +- Inside `Sources/`, use this strict app structure by default: `Views/`, `Models/`, and `Services/`. Do not create a root `Controllers/` directory. +- `Sources/Views/` owns SwiftUI views and UIKit/AppKit view surfaces. Use `Sources/Views/Shared`, `Sources/Views/macOS`, and `Sources/Views/iOS` so shared, macOS-specific, and iOS/iPadOS-specific UI have clear homes. +- View files are named `.swift`. View-local view models are named `+Model.swift`. UIKit and AppKit controller support files are named `+Controller.swift`. Each paired model or controller file lives beside its matching view in `Sources/Views/` or the appropriate platform/shared subdirectory. +- `Sources/Models/` owns Core Data persistence models, SwiftData `@Model` types, app datamodels, DTOs, and transfer or persistence shapes that are shared across the app. Service-private request/response shapes may stay inside the owning service folder only when they are not meaningful outside that integration. +- `Sources/Services/` owns app-wide and boundary-facing services. Use `Consumed/` for external services the app calls, `Internal/` for services owned only by the app, and `Provided/` for services the app exposes to extensions, helpers, plugins, integrations, or other clients. +- When an app has a single main app-wide service, put it under `Sources/Services/Internal/` as `WhateverNameAppService.swift`. - Use `xcodebuild` for Apple platform integration validation, including scheme, destination or SDK, and configuration-specific build or test runs. - Keep `xcodebuild` invocations reproducible in automation by passing explicit schemes, destinations or SDKs, and configurations when relevant. - For Codex GUI worktree-first Xcode repos, use a portable `.codex/environments/*.toml` local environment file when the repo wants shared app setup or action buttons. Start from `apple-dev-skills/templates/codex-local-environments/xcode-project.toml`, keep paths repo-relative, and prefer `-derivedDataPath ./DerivedData` or another ignored repo-local build directory instead of user-global DerivedData. diff --git a/plugins/apple-dev-skills/tests/test_xcode_app_bootstrap_workflow.py b/plugins/apple-dev-skills/tests/test_xcode_app_bootstrap_workflow.py index bceef612..736e048f 100644 --- a/plugins/apple-dev-skills/tests/test_xcode_app_bootstrap_workflow.py +++ b/plugins/apple-dev-skills/tests/test_xcode_app_bootstrap_workflow.py @@ -211,8 +211,24 @@ def test_xcodegen_path_can_succeed_with_fake_tools(self) -> None: self.assertIn("parallelizable: true", project_yml) for top_level_dir in ("Sources", "Tests", "Shared", "Extensions", "Configurations", "Scripts", "Packages"): self.assertTrue((target / top_level_dir).is_dir(), top_level_dir) + for source_dir in ( + "Sources/Views/Shared", + "Sources/Views/macOS", + "Sources/Views/iOS", + "Sources/Models", + "Sources/Services/Consumed", + "Sources/Services/Internal", + "Sources/Services/Provided", + ): + self.assertTrue((target / source_dir).is_dir(), source_dir) for placeholder in ("Shared/.gitkeep", "Extensions/.gitkeep", "Scripts/.gitkeep", "Packages/.gitkeep"): self.assertTrue((target / placeholder).exists(), placeholder) + self.assertTrue((target / "Sources" / "DemoApp.swift").exists()) + self.assertTrue((target / "Sources" / "DemoApp+ViewModel.swift").exists()) + self.assertTrue((target / "Sources" / "Views" / "Shared" / "ContentView.swift").exists()) + self.assertTrue((target / "Sources" / "Views" / "Shared" / "ContentView+Model.swift").exists()) + self.assertTrue((target / "Sources" / "Services" / "Internal" / "DemoAppService.swift").exists()) + self.assertFalse((target / "Sources" / "Controllers").exists()) self.assertTrue((target / "Sources" / "Support" / "DemoApp.entitlements").exists()) self.assertTrue((target / "Sources" / "Resources" / "Assets.xcassets" / "Contents.json").exists()) self.assertTrue( @@ -302,6 +318,10 @@ def test_xcodegen_path_can_succeed_with_fake_tools(self) -> None: self.assertIn("XcodeGen plus synced source folders", agents_text) self.assertIn("Use the standard top-level Xcode app repository layout", agents_text) self.assertIn("`Shared/` owns reusable source intended to be compiled into the app and extension targets", agents_text) + self.assertIn("Sources/Views/Shared", agents_text) + self.assertIn("Sources/Services/Internal", agents_text) + self.assertIn("WhateverNameApp+ViewModel.swift", agents_text) + self.assertIn("+Controller.swift", agents_text) self.assertIn("A standard app target gets one `Sources` source entry", agents_text) self.assertIn("Every native app target must have exactly one app lifecycle entry point", agents_text) self.assertIn("Keep XcodeGen specs readable as project structure", agents_text) diff --git a/plugins/apple-dev-skills/tests/test_xcode_guidance_sync_workflow.py b/plugins/apple-dev-skills/tests/test_xcode_guidance_sync_workflow.py index b18322b4..5a5e44e1 100644 --- a/plugins/apple-dev-skills/tests/test_xcode_guidance_sync_workflow.py +++ b/plugins/apple-dev-skills/tests/test_xcode_guidance_sync_workflow.py @@ -88,6 +88,14 @@ def test_sync_creates_agents_template(self) -> None: self.assertIn("Xcode SwiftUI preview in the same file", agents_text) self.assertIn("SwiftUI view models are always per-view, with no exceptions", agents_text) self.assertIn("+Model.swift", agents_text) + self.assertIn("Sources/Views/Shared", agents_text) + self.assertIn("Sources/Services/Internal", agents_text) + self.assertIn("WhateverNameApp+ViewModel.swift", agents_text) + self.assertIn("+Controller.swift", agents_text) + self.assertEqual(payload["structure_audit"]["status"], "needs-attention") + self.assertTrue( + any(finding["path"] == "Sources/Views/Shared" for finding in payload["structure_audit"]["findings"]) + ) self.assertTrue(Path(tmpdir, ".swiftformat").is_file()) self.assertTrue(Path(tmpdir, "Scripts/repo-maintenance/hooks/pre-commit.sample").is_file()) self.assertTrue(Path(tmpdir, "Scripts/repo-maintenance/validate-all.sh").is_file()) @@ -136,6 +144,10 @@ def test_sync_appends_section_to_existing_agents(self) -> None: self.assertIn("Xcode SwiftUI preview in the same file", agents_text) self.assertIn("SwiftUI view models are always per-view, with no exceptions", agents_text) self.assertIn("+Model.swift", agents_text) + self.assertIn("Sources/Views/Shared", agents_text) + self.assertIn("Sources/Services/Internal", agents_text) + self.assertIn("WhateverNameApp+ViewModel.swift", agents_text) + self.assertIn("+Controller.swift", agents_text) self.assertTrue(Path(tmpdir, ".swiftformat").is_file()) self.assertTrue(Path(tmpdir, "Scripts/repo-maintenance/hooks/pre-commit.sample").is_file()) self.assertTrue(Path(tmpdir, "Scripts/repo-maintenance/release.sh").is_file()) @@ -193,6 +205,54 @@ def test_report_only_mode_returns_non_mutating_success(self) -> None: self.assertEqual(payload["status"], "success") self.assertEqual(payload["path_type"], "fallback") self.assertIn("report that AGENTS.md is missing", payload["actions"][0]) + self.assertEqual(payload["structure_audit"]["status"], "needs-attention") + + def test_structure_audit_flags_legacy_controllers_directory(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + repo_root = Path(tmpdir) + (repo_root / "Demo.xcodeproj").mkdir() + for relative_path in ( + "Sources/Views/Shared", + "Sources/Views/macOS", + "Sources/Views/iOS", + "Sources/Models", + "Sources/Services/Consumed", + "Sources/Services/Internal", + "Sources/Services/Provided", + "Sources/Controllers", + ): + (repo_root / relative_path).mkdir(parents=True) + + code, payload = self.run_script("--repo-root", tmpdir, "--dry-run") + + self.assertEqual(code, 0) + self.assertEqual(payload["structure_audit"]["status"], "needs-attention") + self.assertTrue( + any( + finding["code"] == "legacy-controllers-directory" + for finding in payload["structure_audit"]["findings"] + ) + ) + + def test_structure_audit_passes_strict_source_layout(self) -> None: + with tempfile.TemporaryDirectory() as tmpdir: + repo_root = Path(tmpdir) + (repo_root / "Demo.xcodeproj").mkdir() + for relative_path in ( + "Sources/Views/Shared", + "Sources/Views/macOS", + "Sources/Views/iOS", + "Sources/Models", + "Sources/Services/Consumed", + "Sources/Services/Internal", + "Sources/Services/Provided", + ): + (repo_root / relative_path).mkdir(parents=True) + + code, payload = self.run_script("--repo-root", tmpdir, "--dry-run") + + self.assertEqual(code, 0) + self.assertEqual(payload["structure_audit"], {"status": "passed", "findings": []}) def test_sync_discovers_socket_cache_sibling_productivity_runner(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: