Skip to content
Merged
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
91 changes: 91 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# CLAUDE.md

Orientation for Claude Code (and other AI agents) working in **Azimuth**. This is the top-level
summary; the per-directory **`AGENTS.md` files are the detailed source of truth** — read the
`AGENTS.md` in a directory before changing files there.

## What this is

Azimuth is a menu-bar **window manager for macOS** (Swift + AppKit, programmatic entry at
`Azimuth/main.swift`, no storyboard). It uses the Accessibility (AX) API to move/resize other
apps' focused windows via global hotkeys (Carbon) and menu commands. It auto-updates via
Sparkle 2 and ships Developer ID–signed, Apple-notarized, and EdDSA-verified.

## Layout

| Path | What |
|------|------|
| `Azimuth/` | App source (per-dir `AGENTS.md`); flow: hotkey/menu → `Commands/WindowCommandExecutor` → `WindowAccess` + `Commands/FrameCalculator` + `WindowAccess/WindowUndoStore` |
| `Tests/` | Pure-logic command-engine regression tests (single-file swiftc harness) |
| `scripts/` | `build`/`run`/`lint`/`format`/`test`/`coverage`/`secrets` shell scripts |
| `docs/` | GitHub Pages source (landing + manual) |
| `.github/` | CI workflows, `FUNDING.yml`, PR template, `CODEOWNERS` |

## Build / test / lint

| Command | Use |
|---------|-----|
| `make run` | Build a **signed** app and launch it — use for anything needing Accessibility |
| `make build` | Compile-only (ad-hoc signed; CI/compile checks) — **not** for permission testing |
| `make test` | Pure-logic command-engine tests (swiftc, AppKit-free); prints `PASS — all N checks` |
| `make coverage` | LLVM source-based line coverage on the pure-logic layer; gate **≥90%** (`COVERAGE_MIN`) |
| `make lint` / `make format` | SwiftLint (strict) / SwiftFormat |
| `make secrets` | gitleaks secret scan |
| `make install-hooks` | pre-commit hook: SwiftFormat `--lint` + SwiftLint `--strict` |

Before opening a PR: `make build && make lint && make test` (CI runs the same, plus gitleaks).

## Non-negotiable rules

- **Never bypass macOS permissions or security.** Request AX through the official API; the user
grants it in System Settings. No SIP disabling, no TCC tampering, no undocumented workarounds.
Apple's own `tccutil reset` is fine. Fix root causes the OS-sanctioned way.
- **Test anything permission-related with `make run`** (stable Apple Development signing).
`make build` is ad-hoc → its cdhash changes every build → TCC resets the grant. Ad-hoc builds
are for compile/CI only.
- **`.docs/` is internal — never commit or push it** (it is gitignored).
- **GUI smoke tests:** do not read other apps' `kCGWindowName` (Screen Recording TCC gate that
has frozen WindowServer). Confirm liveness via process + AX role checks. Never busy-loop to
wait — poll a condition.

## Code conventions

- `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`: every type is `@MainActor` by default. Keep pure,
thread-agnostic logic explicitly `nonisolated` in the AppKit-free layer so `make test` can
exercise it (this is why geometry/command logic lives in `Commands/` and `Shared/`).
- Window geometry is handled in **AX coordinates** (top-left origin, Y down); convert to/from
Cocoa work areas via `Shared/CoordinateSpace`.
- SwiftLint strict: no force-unwrap / force-cast (narrow, commented exceptions only), 120-column
lines, function/type body-length limits. Match surrounding comment density and idiom.
- **Conventional Commits** (`feat(scope): …`, `fix: …`, `docs: …`, `refactor: …`, `chore: …`).
Branch off `main`, keep PRs focused, **squash-merge**.
- New source files under `Azimuth/` are auto-included via the Xcode **file-system synchronized
group** — no `.pbxproj` edit needed. Adding a new target or SPM dependency still needs the
pbxproj / Xcode GUI. Deployment target: macOS **26.3**.

## Docs (`docs/` is the GitHub Pages source)

- `index.html` (landing) and `manual.html` (user manual) are **bilingual**: each string exists as
a `data-en` attribute, a `data-ko` attribute, **and** the visible inner text (a JS toggle swaps
it). Any copy change must update **all three**, or the languages drift.
- Strings like "N commands" / "N shortcuts" must match `WindowCommand.menuCommands`
(currently **34**). Bumping a command means updating both HTML files and the README.
- Merging to `main` triggers the **"pages build and deployment"** workflow. The live site
(`ai-screams.github.io/Azimuth`) lags until that run finishes — poll it to `completed`/`success`
before verifying live content; catching the old page mid-deploy is expected.

## Funding / community

- GitHub Sponsors → the org **`ai-screams`** (`github.com/sponsors/ai-screams`); Ko-fi →
**`pignuante`** (`ko-fi.com/pignuante`). These are independent handles; see `.github/FUNDING.yml`.
The repo ♡ Sponsor button also requires **Settings → General → Features → Sponsorships** enabled,
not just `FUNDING.yml`.
- Community health files live at the repo root: `SECURITY.md`, `CONTRIBUTING.md`, `SUPPORT.md`,
`CODE_OF_CONDUCT.md`.

## Environment gotchas

- The shell is **zsh**: `status` is a read-only variable — do not use it as a variable name in
Bash-tool scripts (use `st`, etc.).
- Ko-fi and GitHub pages return **HTTP 403 to `curl`** (bot protection) — that is not a real
failure signal. Verify shields.io badge URLs return **200** before committing new badges.
Loading