This fork is primarily for onevcat-specific customizations; before doing any release work, read docs-ai/001-fork-bootstrap-and-release-pipeline/release-runbook.md (and the upstream ledger docs-ai/017-upstream-sync-process/upstream-ledger.md) for fork publishing guidance. Fork history and design decisions are recorded under docs-ai/ (see docs-ai/README.md).
make build-ghostty-xcframework # Rebuild GhosttyKit from Zig source (requires mise)
make build-app # Build macOS app (Debug) via xcodebuild
make run-app # Build and launch Debug app
make install-dev-build # Build and copy to /Applications (Debug)
make install-release # Build Release, sign locally, install to /Applications
make format-changed # Run swift-format on changed Swift files only
make format # Run full-tree swift-format cleanup
make lint # Run swiftlint only
make check # Run changed-file format, swift-format lint, and swiftlint
make test # Run all tests
make benchmark-build # Benchmark CI-like clean/warm-CAS build and test time
make bench # Run performance benchmarks with -O; append absolute medians to ~/Library/Logs/Prowl/measurements/bench/
make measure-cpu # Steady-state CPU + per-symbol attribution of the running Prowl Debug app
make capture-spike # Sample the running Prowl Debug app when CPU crosses a threshold
make measure-titles # Black-box check that animated tab titles stay coalesced (~1 change/s)
make agent-versions # Compare installed tier-A agent CLI versions with the managed-hook attestation (docs-ai 064)
make test-agent-contracts # Zero-inference runtime inventory; AGENT_CONTRACT_ARGS="--mode preflight --runtime codex"
make log-stream # Stream app logs (subsystem: com.onevcat.prowl)
make build-cli # Build CLI (prowl) via SwiftPM
make test-cli-smoke # Run CLI executable smoke tests
make test-cli-unit # Run CLI unit tests
make test-cli-integration # Run CLI integration tests (socket round-trip)
make bump-version # Bump version (date-based YYYY.M.DD) and create git tag; used by release.shDebug builds are ad-hoc signed by default, so building needs no certificate. An ad-hoc signature's designated requirement is its cdhash, which changes on every rebuild, so macOS re-asks for Desktop/Documents/Downloads access from Prowl Debug — and from the commands running in its panes — after each build. If your worktrees live in those folders, set PROWL_DEVELOPMENT_TEAM=<Team ID> (environment or Config/Secrets.env) and make build-app / make test sign the Debug app and test host with your Apple Development identity instead; the Team ID is the certificate's OU, not the ID in parentheses after your name. With it set, replace the CODE_SIGNING_* settings in ad-hoc xcodebuild test invocations like the one below with DEVELOPMENT_TEAM=<Team ID> so the test host keeps the same signature.
Run a single test class or method:
xcodebuild test -project supacode.xcodeproj -scheme supacode -destination "platform=macOS" \
-only-testing:supacodeTests/TerminalTabManagerTests \
CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" -skipMacroValidationSwift Testing vs XCTest -only-testing format: Swift Testing (@Test) requires trailing () in the test identifier. Without it, xcodebuild silently matches nothing and reports TEST SUCCEEDED with zero tests run.
# XCTest (func testFoo)
-only-testing:supacodeTests/FooTests/testBar
# Swift Testing (@Test func bar)
-only-testing:"supacodeTests/FooTests/bar()"Requires mise for zig, swiftlint, and xcsift tooling.
make log-stream shows no TCA action lines by default: per-action logging — the action label plus a full app-state snapshot and diff — is gated off because it runs on every action and shows up as steady main-thread cost. Launch with PROWL_LOG_TCA_ACTIONS=1 (scheme env var, or exported before open) to trace the action stream through the unified log.
Prowl is a macOS orchestrator for running multiple coding agents in parallel, using GhosttyKit as the underlying terminal.
AppFeature (root TCA store)
├─ RepositoriesFeature (repos + worktrees)
├─ CommandPaletteFeature
├─ SettingsFeature (appearance, updates, repo settings)
└─ UpdatesFeature (Sparkle auto-updates)
WorktreeTerminalManager (global @Observable terminal state)
├─ selectedWorktreeID (tracks current selection for bell logic)
└─ WorktreeTerminalState (per worktree)
└─ TerminalTabManager (tab/split management)
└─ GhosttySurfaceState[] (one per terminal surface)
GhosttyRuntime (shared singleton)
└─ ghostty_app_t (single C instance)
└─ ghostty_surface_t[] (independent terminal sessions)
The terminal layer (WorktreeTerminalManager) is @Observable but outside TCA. Communication uses TerminalClient:
Reducer → terminalClient.send(Command) → WorktreeTerminalManager
↓
Reducer ← .terminalEvent(Event) ← AsyncStream<Event>
- Commands:
createTab,closeFocusedTab,prune,setSelectedWorktreeID, etc. - Events:
notificationReceived,tabCreated,tabClosed,focusChanged,taskStatusChanged - Wired in
supacodeApp.swift, subscribed inAppFeature.task
- TCA (swift-composable-architecture): App state, reducers, side effects
- GhosttyKit: Terminal emulator (built from Zig source in ThirdParty/ghostty)
- Sparkle: Auto-update framework
- swift-dependencies: Dependency injection for TCA clients
- PostHog: Analytics
- Sentry: Error tracking
- Ghostty keybindings are handled via runtime action callbacks in
GhosttySurfaceBridge, not by app menu shortcuts. - App-level tab actions should be triggered by Ghostty actions (
GHOSTTY_ACTION_NEW_TAB/GHOSTTY_ACTION_CLOSE_TAB) to honor user custom bindings. GhosttySurfaceView.performKeyEquivalentroutes bound keys to Ghostty first; only unbound keys fall through to the app.
- Target macOS 26.0+, Swift 6.2+
- Before doing a big feature or when planning, consult with pfw (pointfree) skills on TCA, Observable best practices first.
- Use
@ObservableStatefor TCA feature state; use@Observablefor non-TCA shared stores; neverObservableObject - Always mark
@Observableclasses with@MainActor - Modern SwiftUI only:
foregroundStyle(),NavigationStack,ButtonoveronTapGesture() - Before changing a window toolbar control, its grouping, or Liquid Glass, read
docs-ai/061-native-toolbar-controls/toolbar-controls.mdand perform a Debug visual verification. - When a new logic changes in the Reducer, always add tests
- In unit tests, never use
Task.sleep; useTestClock(or an injected clock) and drive time withadvance. - Prefer Swift-native APIs over Foundation where they exist (e.g.,
replacing()notreplacingOccurrences()) - Avoid
GeometryReaderwhencontainerRelativeFrame()orvisualEffect()would work - Do not use NSNotification to communicate between reducers.
- Prefer
@Shareddirectly in reducers for app storage and shared settings; do not introduce new dependency clients solely to wrap@Shared. - Use
SupaLoggerfor all logging. Never useprint()oros.Loggerdirectly.SupaLoggerprints in DEBUG and usesos.Loggerin release.
-
2-space indentation, 120 character line length (enforced by
.swift-format.json) -
swift-formatis the source of truth for trailing commas: multi-element collection literals keep trailing commas, while single-element collection literals may have them removed. -
SwiftLint runs in strict mode; never disable lint rules without permission
-
Custom SwiftLint rule:
store_state_mutation_in_views— do not mutatestore.*directly in view files; send actions instead -
Before creating a PR, run
make check. Usemake formatonly for intentional full-tree formatting cleanup. -
If
make checkfails withswift-format: command not found, the Xcode toolchain is not onPATH. The Makefile invokesswift-formatunqualified, and the binary ships inside Xcode rather than in a standard bin directory. Prepend it for the invocation:export PATH="$(dirname "$(xcrun --find swift-format)"):$PATH" make check
make lintis unaffected — it already runs SwiftLint throughmise exec.
- Buttons must have tooltips explaining the action and associated hotkey
- Use Dynamic Type, avoid hardcoded font sizes
- Components should be layout-agnostic (parents control layout, children control appearance)
- Never use custom colors, always use system provided ones.
- We use
.monospaced()modifier on fonts when appropriate
- After a task, ensure the app builds:
make build-app - When working on CLI code (
ProwlCLI/,ProwlCLITests/,Package.swift), runmake build-cli,make test-cli-smoke,make test-cli-unit, andmake test-cli-integrationbefore committing. - When you change user-facing behavior (keyboard shortcuts, settings, the
prowlCLI, or a feature's UX), update the matching file underdocs/in the same change. For a full audit, run thesync-docsskill. docs-ai/is curated, durable product/design documentation — never a working-note archive. Use thewrite-ai-docskill only for a substantial feature or a non-trivial fix whose design and result must guide future implementation. Do not create entries for reviews, audits, routine research or investigations, status reports, test runs, or docs-only work unless onevcat explicitly asks for adocs-ai/record. When uncertain, do not create an entry. For qualifying work, createdocs-ai/NNN-<slug>/000-plan.mdbefore coding and complete001-action.mdafter implementation. Follow-up work on the same topic amends the existing entry (seedocs-ai/README.md).- When implementing a new feature or fixing a bug that is unrelated to the current branch's active work, first create a dedicated branch from the latest
origin/main; then work, commit, push, and open a PR from that branch. - Automatically commit your changes and your changes only. Do not use
git add . - Before you go on your task, check the current git branch name, if it's something generic like an animal name, name it accordingly. Do not do this for main branch
- After implementing an execplan, always submit a PR if you're not in the main branch
- PRs must target
onevcat/Prowl(this fork), never the upstreamsupabitapp/supacode, unless explicitly requested. - Fork releases must be notarized. Never publish non-notarized releases (
ENABLE_NOTARIZATION=0is forbidden).
ThirdParty/ghostty(https://github.com/ghostty-org/ghostty): Source dependency used to buildFrameworks/GhosttyKit.xcframeworkand terminal resources.Resources/git-wt(https://github.com/khoi/git-wt.git): BundledwtCLI used by Prowl Git worktree flows at runtime.