Skip to content

Project setup

Goal of this chapter: an empty folder turns into a project that builds, runs, and tests from toolbar buttons — and that an agent can work on without asking where anything is.

The Xcode project is generated from a declarative project.yml with xcodegen. That keeps the project definition diffable, mergeable, and maintainable by the agent — nobody resolves .pbxproj conflicts by hand. 4Notice’s definition starts like this (abridged):

name: FourNotice
options:
bundleIdPrefix: com.better-apps
developmentLanguage: en
deploymentTarget:
macOS: "26.0"
iOS: "26.0"
settings:
base:
CODE_SIGN_STYLE: Automatic
SWIFT_STRICT_CONCURRENCY: complete
targets:
FourNotice:
type: application
platform: macOS
sources: [Sources/Shared, Sources/macOS, Resources]
FourNotice-iOS:
type: application
platform: iOS
sources: [Sources/Shared, Sources/iOS, Resources]

Two platforms, one Sources/Shared — the SwiftUI app lives there, each platform adds a thin layer. Then:

Terminal window
xcodegen generate

The generated FourNotice.xcodeproj is not committed — it is a build artifact of project.yml. Every build script regenerates it first, so it can never be stale.

Terminal window
git init -b main
git add -A && git commit -m "Generate FourNotice project from project.yml"
gh repo create 4Notice --private --source . --push

A private repo on day one costs nothing and gives every later step — atomic commits, skill provenance, CI — something to hang on to.

Open the folder as a project in iKanban AI. The app writes the agent setup: .agent/AGENT.md (the workflow contract), pointer files CLAUDE.md and AGENTS.md, and the empty plans/ and board/ folders. From this moment, any terminal agent you start in this directory knows the rules of the house — and the app shows whatever the agent does.

Recurring commands become actions in .agent/actions.json. The real 4Notice entries (abridged):

[
{ "name": "Build (macOS)", "command": "sh scripts/build.sh",
"toolbar": true, "shortcut": "cmd-b" },
{ "name": "Run (macOS)", "command": "sh scripts/run.sh",
"toolbar": true, "shortcut": "cmd-r" },
{ "name": "Test (macOS)", "command": "sh scripts/test.sh",
"toolbar": true, "shortcut": "cmd-u" }
]

They execute through the local exec server (allowlisted, project-rooted). Note that every command is sh scripts/….sh — that’s a lesson, not a style choice: the exec server runs argv without a shell, so &&, pipes, and $(…) in an action command would arrive as literal arguments. Compound logic lives in scripts:

#!/bin/sh
# scripts/build.sh — Build the macOS app (Debug). DerivedData stays in
# ~/Library: a build folder inside this iCloud-synced directory gets
# Finder xattrs and breaks codesign.
set -eu
cd "$(dirname "$0")/.."
xcodegen generate --quiet
xcodebuild -project FourNotice.xcodeproj -scheme FourNotice \
-configuration Debug -destination 'platform=macOS' \
-allowProvisioningUpdates build

That comment about DerivedData is the second real lesson: 4Notice lives on an iCloud-synced Desktop, and an in-project build/ folder picked up Finder extended attributes that broke codesign. Build products stay in ~/Library/Developer/Xcode/DerivedData; run.sh asks xcodebuild -showBuildSettings for BUILT_PRODUCTS_DIR and launches the app from there.

A click on ▶ in the iKanban AI toolbar builds and launches the app; ⌘U runs the tests; everything the setup consists of — project.yml, scripts/, .agent/ — is committed and readable. Time to decide what to build: a plan and a board.