Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .agents/external-skills.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
19 changes: 19 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
set -euo pipefail

# Format staged Swift files in-place, then re-stage them.
# Note: this formats working-tree content, not the staged blob.
# Partially-staged files will be fully re-staged after formatting.

staged=$(git diff --cached --name-only --diff-filter=ACMR -- '*.swift')

if [ -n "$staged" ]; then
git diff --cached --name-only --diff-filter=ACMR -z -- '*.swift' \
| xargs -0 mise exec -- swiftformat

git diff --cached --name-only --diff-filter=ACMR -z -- '*.swift' \
| xargs -0 git add
fi

# Sync AGENTS.md + .agents/skills → CLAUDE.md + .claude/skills.
./sync-agents --git-add
11 changes: 10 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,19 @@ concurrency:
cancel-in-progress: true

jobs:
format:
name: SwiftFormat Lint
runs-on: macos-26
steps:
- uses: actions/checkout@v4
- uses: jdx/mise-action@v3
- run: ./swiftformat --lint

test:
name: Build & Test
needs: format
runs-on: macos-26
steps:
- uses: actions/checkout@v4
- uses: jdx/mise-action@v3
- run: tuist test
- run: mise exec -- tuist test
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Tuist managed
Derived/

## Generated agent files (re-create via ./sync-agents --install)
CLAUDE.md
.claude/skills/

# Xcode
build/
DerivedData/
Expand Down
1 change: 1 addition & 0 deletions .mise.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
[tools]
tuist = "4.40.0"
swiftformat = "0.60.1"
5 changes: 5 additions & 0 deletions .swiftformat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
--swiftversion 6.1
--indent 4
--indentcase true
--extension-acl on-declarations
--exclude Derived
36 changes: 31 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,43 @@

## Build system

| Tool | Version | Pinned via |
|-------|---------|--------------|
| Tuist | 4.40.0 | `.mise.toml` |
| Tool | Version | Pinned via |
|-------------|---------|--------------|
| Tuist | 4.40.0 | `.mise.toml` |
| SwiftFormat | 0.60.1 | `.mise.toml` |

Tuist manifests live at the repo root (`Project.swift`, `Tuist.swift`).
Run `./ide` (or `./ide -i` to also install dependencies) to regenerate the
Xcode project.
Xcode project, install external agent skills, and point Git at `.githooks/`.

Root dev scripts: `ide`, `swiftformat` (runs SwiftFormat via mise), and
`sync-agents` (keeps Claude Code–oriented files in sync with `AGENTS.md`).

## Formatting

- **SwiftFormat** uses [`.swiftformat`](.swiftformat). Run `./swiftformat` to
format the tree, or `./swiftformat --lint` to check only (as in CI).
- The pre-commit hook (enabled by `./ide` via `core.hooksPath`) formats staged
`*.swift` files in place and re-stages them.

## Agent instructions sync

`AGENTS.md` is the source of truth for AI agent instructions. Cursor reads
`AGENTS.md` natively; Claude Code uses `CLAUDE.md` and `.claude/skills/`.
Generated files (`CLAUDE.md`, `.claude/skills/`) are gitignored and produced
by `./sync-agents`.

- `./sync-agents` — generate `CLAUDE.md` next to each `AGENTS.md` and mirror
`.agents/skills/` into `.claude/skills/`.
- `./sync-agents --install` — fetch external skills listed in
`.agents/external-skills.json` (run automatically by `./ide`).
- `./sync-agents --add <url> [name]` — add an external skill from GitHub.
- `./sync-agents --update` — re-fetch all external skills to the latest commit.

## Targets

_No apps or modules yet._ Add targets to `Project.swift` using `macApp()` or `framework()` helpers.
- **StuffCore** — macOS framework for shared code (`StuffCore/Sources/`), with unit tests under `StuffCore/Tests/` (Swift Testing).
- Add more targets in `Project.swift` using `macApp()` or `framework()` helpers.

## Deployment

Expand Down
16 changes: 8 additions & 8 deletions Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ let macDeployment: DeploymentTargets = .macOS("26.0")
func framework(
_ name: String,
bundleIdSuffix: String,
dependencies: [TargetDependency] = []
dependencies: [TargetDependency] = [],
) -> [Target] {
[
.target(
Expand All @@ -16,7 +16,7 @@ func framework(
bundleId: "com.stuff.\(bundleIdSuffix)",
deploymentTargets: macDeployment,
sources: ["\(name)/Sources/**"],
dependencies: dependencies
dependencies: dependencies,
),
.target(
name: "\(name)Tests",
Expand All @@ -25,7 +25,7 @@ func framework(
bundleId: "com.stuff.\(bundleIdSuffix).tests",
deploymentTargets: macDeployment,
sources: ["\(name)/Tests/**"],
dependencies: [.target(name: name)]
dependencies: [.target(name: name)],
),
]
}
Expand All @@ -34,7 +34,7 @@ func macApp(
_ name: String,
bundleIdSuffix: String,
infoPlist: [String: Plist.Value] = [:],
dependencies: [TargetDependency] = []
dependencies: [TargetDependency] = [],
) -> [Target] {
[
.target(
Expand All @@ -46,7 +46,7 @@ func macApp(
infoPlist: .extendingDefault(with: infoPlist),
sources: ["\(name)/Sources/**"],
resources: ["\(name)/Resources/**"],
dependencies: dependencies
dependencies: dependencies,
),
.target(
name: "\(name)Tests",
Expand All @@ -55,7 +55,7 @@ func macApp(
bundleId: "com.stuff.\(bundleIdSuffix).tests",
deploymentTargets: macDeployment,
sources: ["\(name)/Tests/**"],
dependencies: [.target(name: name)]
dependencies: [.target(name: name)],
),
]
}
Expand All @@ -64,7 +64,7 @@ let project = Project(
name: "Stuff",
options: .options(
defaultKnownRegions: ["en"],
developmentRegion: "en"
developmentRegion: "en",
),
targets: []
targets: framework("StuffCore", bundleIdSuffix: "stuffcore"),
)
25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Random apps and stuff.
## Requirements

- Xcode 26+
- [mise](https://mise.jdx.dev) (manages Tuist version)
- [mise](https://mise.jdx.dev) (pins Tuist and SwiftFormat)
- iOS 26.0+

## Getting started
Expand All @@ -14,21 +14,36 @@ Random apps and stuff.
# Install mise (if needed)
brew install mise

# Generate the Xcode project
# Install pinned tools (Tuist, SwiftFormat)
mise install

# Generate the Xcode project (also sets Git hooks and runs sync-agents --install)
./ide

# Or install dependencies first, then generate
# Or install Tuist package dependencies first, then generate
./ide -i
```

Run tests with `mise exec -- tuist test` (or open the generated workspace in Xcode).

The `./ide` script sets `core.hooksPath` to `.githooks`. The pre-commit hook
formats staged Swift with SwiftFormat and runs `./sync-agents --git-add` so
generated Claude files stay in sync with `AGENTS.md`.

## Project structure

```
Project.swift Tuist project manifest
Tuist.swift Tuist configuration
.mise.toml Pins Tuist 4.40.0
ide Dev script – regenerates Xcode project
.mise.toml Pins Tuist 4.40.0 and SwiftFormat 0.60.1
.swiftformat SwiftFormat rules
ide Dev script – hooks, sync-agents, tuist generate
swiftformat Run SwiftFormat via mise (default: format `.`)
sync-agents Sync AGENTS.md → CLAUDE.md and .claude/skills/
.githooks/ Git hooks (pre-commit)
.agents/ External skills manifest (`external-skills.json`)
AGENTS.md Repository shape for AI agents
StuffCore/ Shared macOS framework (Sources/, Tests/)
```

## License
Expand Down
4 changes: 4 additions & 0 deletions StuffCore/Sources/StuffCore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
public enum StuffCore {
/// Placeholder until shared code lands here.
public static let version = 1
}
7 changes: 7 additions & 0 deletions StuffCore/Tests/StuffCoreTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import StuffCore
import Testing

@Test
func versionIsDefined() {
#expect(StuffCore.version == 1)
}
7 changes: 7 additions & 0 deletions ide
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#!/bin/bash
set -euo pipefail

# CI runs formatting via the workflow's `format` job;
# this only configures the local pre-commit hook.
git config core.hooksPath .githooks

INSTALL=false

for arg in "$@"; do
Expand All @@ -16,5 +20,8 @@ if [ "$INSTALL" = true ]; then
mise exec -- tuist install
fi

echo "==> sync-agents --install"
./sync-agents --install

echo "==> tuist generate"
mise exec -- tuist generate
14 changes: 14 additions & 0 deletions swiftformat
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash
set -euo pipefail

has_positional=false

for arg in "$@"; do
[[ "$arg" != -* ]] && has_positional=true && break
done

if $has_positional; then
mise exec -- swiftformat "$@"
else
mise exec -- swiftformat "$@" .
fi
Loading
Loading