Skip to content

Latest commit

 

History

History
163 lines (128 loc) · 11.2 KB

File metadata and controls

163 lines (128 loc) · 11.2 KB

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).

Build Commands

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.sh

Debug 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="" -skipMacroValidation

Swift 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.

Architecture

Prowl is a macOS orchestrator for running multiple coding agents in parallel, using GhosttyKit as the underlying terminal.

Core Data Flow

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)

TCA ↔ Terminal Communication

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 in AppFeature.task

Key Dependencies

  • 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 Handling

  • 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.performKeyEquivalent routes bound keys to Ghostty first; only unbound keys fall through to the app.

Code Guidelines

  • 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 @ObservableState for TCA feature state; use @Observable for non-TCA shared stores; never ObservableObject
  • Always mark @Observable classes with @MainActor
  • Modern SwiftUI only: foregroundStyle(), NavigationStack, Button over onTapGesture()
  • Before changing a window toolbar control, its grouping, or Liquid Glass, read docs-ai/061-native-toolbar-controls/toolbar-controls.md and perform a Debug visual verification.
  • When a new logic changes in the Reducer, always add tests
  • In unit tests, never use Task.sleep; use TestClock (or an injected clock) and drive time with advance.
  • Prefer Swift-native APIs over Foundation where they exist (e.g., replacing() not replacingOccurrences())
  • Avoid GeometryReader when containerRelativeFrame() or visualEffect() would work
  • Do not use NSNotification to communicate between reducers.
  • Prefer @Shared directly in reducers for app storage and shared settings; do not introduce new dependency clients solely to wrap @Shared.
  • Use SupaLogger for all logging. Never use print() or os.Logger directly. SupaLogger prints in DEBUG and uses os.Logger in release.

Formatting & Linting

  • 2-space indentation, 120 character line length (enforced by .swift-format.json)

  • swift-format is 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 mutate store.* directly in view files; send actions instead

  • Before creating a PR, run make check. Use make format only for intentional full-tree formatting cleanup.

  • If make check fails with swift-format: command not found, the Xcode toolchain is not on PATH. The Makefile invokes swift-format unqualified, 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 lint is unaffected — it already runs SwiftLint through mise exec.

UX Standards

  • 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

Rules

  • After a task, ensure the app builds: make build-app
  • When working on CLI code (ProwlCLI/, ProwlCLITests/, Package.swift), run make build-cli, make test-cli-smoke, make test-cli-unit, and make test-cli-integration before committing.
  • When you change user-facing behavior (keyboard shortcuts, settings, the prowl CLI, or a feature's UX), update the matching file under docs/ in the same change. For a full audit, run the sync-docs skill.
  • docs-ai/ is curated, durable product/design documentation — never a working-note archive. Use the write-ai-doc skill 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 a docs-ai/ record. When uncertain, do not create an entry. For qualifying work, create docs-ai/NNN-<slug>/000-plan.md before coding and complete 001-action.md after implementation. Follow-up work on the same topic amends the existing entry (see docs-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 upstream supabitapp/supacode, unless explicitly requested.
  • Fork releases must be notarized. Never publish non-notarized releases (ENABLE_NOTARIZATION=0 is forbidden).

Submodules

  • ThirdParty/ghostty (https://github.com/ghostty-org/ghostty): Source dependency used to build Frameworks/GhosttyKit.xcframework and terminal resources.
  • Resources/git-wt (https://github.com/khoi/git-wt.git): Bundled wt CLI used by Prowl Git worktree flows at runtime.