SaneApps operator overlay. This file shows the full internal production workflow used by SaneApps. Public adopters can use it as a reference, but should start with
README.md,DEVELOPMENT.md, and their own projectAGENTS.mdrather than copying SaneApps-specific hosts, accounts, or release assumptions verbatim.
Speak in plain English. Keep it short and direct. Use I/me/my — never we/us/our.
Skip full startup only when the task does not edit files, does not run build/test/release/support/account workflows, and can be answered from one local file or one local command. Otherwise run Session Start steps 1-5.
- Read
SESSION_HANDOFF.mdif it exists — recent work, pending tasks, gotchas - Check Serena memories (
read_memory) for project-specific learnings - Read the active client skill registry — Codex:
~/.codex/SKILLS_REGISTRY.md, Claude:~/.claude/SKILLS_REGISTRY.md - Run
ruby ~/SaneApps/infra/SaneProcess/scripts/validation_report.rb - Launch Xcode only for explicit local IDE work. For SaneApps app inspection, build, test, screenshots, and runtime verification, use the Mac Mini first.
- Save learnings via Serena
write_memoryand update the knowledge graph for any bug, process, policy, tooling, or release-status fact that changed this session - Update
SESSION_HANDOFF.md— include: open GitHub issues (gh issue list), research.md topics, feature requests - Run
ruby scripts/SaneMaster.rb sop_review --jsonwhen the session changed code, tools, policy, docs, support, release, or UI/runtime behavior - Append SOP rating to
outputs/sop_ratings.csvwith evidence and any cap reason; do not rate above the objective cap fromsop_review
Do not wait until session end.
- When you find a new bug, issue cluster, regression, or root-cause change, update Serena memory and the knowledge graph immediately.
- When you fix, close, merge, or downgrade an issue, update the same memory/graph entries immediately.
- When you change hooks, tools, automation, skills, templates, or durable docs like
AGENTS.md,CLAUDE.md,README.md,DEVELOPMENT.md, orARCHITECTURE.md, update Serena memory andSESSION_HANDOFF.mdimmediately. Do not treat this as optional cleanup. - When you add a new durable document, either fold it into the core docs +
AGENTS.mdstandard or record why it exists and where future sessions should look for it. - Keep bug memory live enough that it can be used directly for support replies, App Store submissions, website release notes, and future debugging without re-discovery.
- Before any release, audit release notes against recent support promises, recent GitHub replies, and
research.md. If a customer-visible fix shipped, the notes should mention it. - Every product website must have a public privacy policy URL before release. Missing privacy pages are release blockers for App Store products and should be treated as SOP violations for every product.
## Session Summary
### Done: [1-3 bullets]
### Docs: [Updated/Current/Needs attention]
### SOP: X/10
### Next: [Follow-up items]
| # | Rule | What It Means |
|---|---|---|
| 0 | NAME IT BEFORE YOU TAME IT | State which rule applies before acting |
| 1 | STAY IN LANE, NO PAIN | No edits outside project without asking. Explicit cleanup/admin approval applies only to the named path, host, and task |
| 2 | VERIFY, THEN TRY | Check uncertain APIs/tools before using. Write durable findings to the project research cache with TTL |
| 3 | TWO STRIKES? STOP AND CHECK | Failed twice → STOP, read the error, research |
| 4 | GREEN MEANS GO | Tests must pass before "done" |
| 5 | HOUSE RULES, USE TOOLS | Use canonical wrappers for stateful build/test/release/launch/email workflows |
| 6 | BUILD, KILL, LAUNCH, LOG | Full build/kill/launch/log cycle after runtime code changes; docs/tooling changes get matching tests/evals |
| 7 | NO TEST? NO REST | Every fix gets a test. No tautologies (#expect(true) is useless) |
| 8 | BUG FOUND? WRITE IT DOWN | Update Serena memory + knowledge graph when bugs are found, reclassified, fixed, or closed |
| 9 | NEW FILE? GEN THE PILE | Use scaffolding tools and templates |
| 10 | FIVE HUNDRED'S FINE, EIGHT'S THE LINE | Max 500 lines, must split at 800 |
| 11 | TOOL BROKE? FIX THE YOKE | Fix broken tools, don't work around them |
| 12 | TALK WHILE I WALK | Subagents for heavy work, stay responsive, and close completed/stale agents promptly |
| 13 | CONTEXT OR CHAOS | Maintain AGENTS.md, plus CLAUDE.md only when Claude-specific overlay guidance is needed |
| 14 | PROMPT LIKE A PRO | Specific prompts with file paths, constraints, context |
| 15 | REVIEW BEFORE YOU SHIP | Self-review for security, edge cases, correctness |
| 16 | DON'T FRAGMENT, INTEGRATE | Upgrade existing files. Core standard is README, DEVELOPMENT, ARCHITECTURE, SESSION_HANDOFF, and AGENTS; add CLAUDE only when needed. No orphan files. New tooling/docs must be recorded in memory + handoff |
Workflow: PLAN → VERIFY → BUILD → TEST → CONFIRM → PROPOSE COMMIT
Do not commit or push unless the user asks, the task explicitly includes release/PR/publish, or a project workflow requires it. For completed implementation work, propose the verified diff for commit when intent is unclear. Keep unrelated dirty files out of any commit.
Circuit Breaker: After 3 consecutive failures: STOP. Read error messages. Research the actual API.
Research gate: Local inspection is always required before editing. If the active client/runtime enforces stricter research gates, obey the stricter gate. Otherwise use docs, web, and GitHub when APIs are uncertain, external facts may have changed, third-party behavior matters, or the decision is durable/high-stakes. Do not run broad research just because the task contains discussion words.
Subagent hygiene: Before spawning Codex subagents, close stale/completed agents that are no longer needed. After a subagent returns, capture the useful result and close it unless it is actively needed for a follow-up. If spawning fails because the agent limit is reached, cleanup is the required first step.
Subagent prompt contract: Every Codex subagent prompt must include:
- Read relevant repo hooks in
scripts/hooks/and active client config when present before doing work. - If a hook blocks, stop immediately and report the block to the parent; do not retry or work around it.
- Never build or launch SaneApps locally on the MacBook Air. Use
ssh minifor build/test/runtime work unless the parent documents an approved local exception for that exact task. - Follow the same wrapper, approval, and evidence rules as the parent session.
Before I say a tool is missing, choose a new canonical tool path, install/upgrade tooling, or switch to a repeated workaround, I must:
- Check the active client skill registry — Codex:
~/.codex/SKILLS_REGISTRY.md, Claude:~/.claude/SKILLS_REGISTRY.md - Run
ruby ~/SaneApps/infra/SaneProcess/scripts/SaneMaster.rb tool_discovery --query "..."so the receipt captures registry, doctor, validation, and local-path checks - Search
scripts/, hooks, skills, and the core docs +AGENTS.mdstandard for an existing path - If the capability is still missing and the workflow repeats, add it to SaneProcess, document it, and make it the standard path
- Prefer the canonical tool paths in
DEVELOPMENT.mdinstead of ad hoc tool hunting
Mentioning "workaround", "fragmentation", or "what am I missing" inside a policy or design audit does not trigger tool discovery by itself.
If I cannot name which of those checks I ran, I have not checked enough.
If the user explicitly names a skill or directly matches a trigger phrase in the active registry, that skill workflow is mandatory. If multiple skills match, use the most specific match; if specificity is tied, state the chosen priority before proceeding.
- Do not freehand the job.
- Do not replace it with a nearby manual bash chain.
- Invoke the skill first, then run the canonical runner or proof command for that skill when one exists.
- If the workflow is runner-backed, the session is not complete until that runner is actually used.
Canonical runner-backed paths in this repo:
status→ruby scripts/SaneMaster.rb statusevolve→ruby scripts/SaneMaster.rb tool_discovery --query "..."verify→ruby scripts/SaneMaster.rb verifyship→ruby scripts/SaneMaster.rb release_preflightcheck-inbox→ruby scripts/SaneMaster.rb check_inbox
Green tests are not enough for customer-facing UI claims.
- For UI/runtime/customer-facing verification, capture one clean saved Mini screenshot per app-owned view or state touched, changed, or claimed verified.
- Inspect every saved screenshot for balance, clarity, confusing copy, clipping, overlap, contrast, dark-mode quality, and obvious functional state.
- Obstructed, clipped, partial, or helper-window-contaminated screenshots are invalid evidence.
- If a UI/runtime flow is stuck, loading, contradictory, or surprising, assume a hidden macOS prompt/sheet may be blocking it. Capture/check the full desktop and AX tree for permission, SecurityAgent, TCC, file-access, or system dialogs before retrying, changing code, or judging the app.
- App-window-only screenshots are not enough blocker evidence because prompts can appear outside the crop or behind the target window. Click the actual prompt action required by the customer flow, then re-capture the app’s final state.
- Record the screenshot paths and verdict in
SESSION_HANDOFF.mdor anoutputs/visual-audit*/receipt before saying the surface works. - Current hook coverage:
sanepromptmarks visual verification requirements,sanetrackrecords UI edits and screenshot/audit evidence, andsanestopplustask_completed_gateblock completion when required proof is missing.
- For any SaneApps settings, About, license, updater, button-style, or typography work, inspect
~/SaneApps/infra/SaneUI/Sources/SaneUICatalog/SaneUICatalogApp.swiftfirst. - Shared settings chrome belongs in
~/SaneApps/infra/SaneUI/, not in app-local clones. - App repos should compose shared
SaneSettingsContainer,SaneAboutView,LicenseSettingsView, andSaneSparkleRowinstead of redefining them. - In shared settings surfaces, all text must be bright white and at least 13pt.
- Do not ship
.secondary/gray helper text,mailto:bug-report paths,Manage Accesscopy, app-local updater rows, localSaneSparkleRowdefinitions, or.buttonStyle(.bordered)in settings/About/license/update UI. - Current automated coverage:
ruby scripts/SaneMaster.rb saneui_guardcatches app-local settings chrome drift, localSaneSparkleRow,mailto:support links,Manage Accesscopy, and.buttonStyle(.bordered). Typography, opacity-based gray text, and broader visual drift still require human review until the guard checks them directly.
The following sections describe the private SaneApps operator environment. They are useful as an example of how SaneProcess is used in production, but public adopters do not need the Mac Mini, SaneApps accounts, or SaneApps release keys.
- The Mini admin password is stored on the Mini only in
~/.config/nv/envasSANE_MINI_ADMIN_PASSWORDandMINI_ADMIN_PASSWORD. - Do not ask the user for the Mini password during normal SaneApps testing. Source that env file and use the stored variable for required Mini admin actions.
- Do not assume the Mini password applies to the local Mac. If local sudo is needed, the user types it into the terminal prompt; do not ask them to paste it into chat.
- Never print the value. For sudo, use
printf "%s\n" "$SANE_MINI_ADMIN_PASSWORD" | sudo -S .... - Do not pass the password through AppleScript error-prone text that can echo secrets into logs. Prefer shell/sudo paths or UI typing with suppressed stderr when a macOS admin sheet is unavoidable.
Use ./scripts/SaneMaster.rb for stateful SaneApps workflows: build/test,
release/deploy, app launch, support/email, sales/download/conversion analytics,
lead research, process metrics, cleanup, and release readiness. Read-only shell
diagnostics are fine. If you bypass a wrapper, state why. Run
./scripts/SaneMaster.rb help <category> for command details.
Critical routes:
- Build/test →
SaneMaster.rb verify - App launch/runtime →
sane_test.rborSaneMaster.rb test_mode - Release →
SaneMaster.rb release_preflight, thenrelease.sh - App Store lane →
SaneMaster.rb appstore_preflightonly when enabled - Sales/downloads/funnel →
sales,downloads,events - Email/support →
check-inbox.sh/SaneMaster.rb check_inbox - Tool/process misses →
tool_discovery,near_miss_review,verify_failure_review - Cleanup →
machine_cleanupbefore manual process/disk cleanup
- Before closing, resolving, or summarizing a customer-reported GitHub issue, cross-check the work-email history for the same app, reporter identity, and issue keywords with
check-inbox.sh(whois,context,read, andcheck-replyas needed). - If email confirms the issue is fixed, report it as "email confirmation received; issue resolved" and close/update GitHub accordingly. If email says it is still broken, keep or reopen the GitHub issue.
- Never describe a GitHub issue as "no follow-up" until the related email trail has also been checked.
- In user-facing summaries, anonymize email senders. Do not include names, personal email addresses, or other identifying details from customer email unless the user explicitly asks or a legal/compliance context requires identity.
When the user says something matching these, run the command/skill immediately:
| User Says | Action |
|---|---|
| "how are sales", "revenue" | SaneMaster.rb sales + events |
| "download stats", "how many downloads" | SaneMaster.rb downloads |
| "conversions", "upgrades", "new users", "funnel", "source of sales" | SaneMaster.rb events |
| "leads", "prospects", "research sites", "research companies" | SaneMaster.rb leads --query "..." |
| "check email", "inbox" | SaneMaster.rb check_inbox |
| "missing tool", "install/upgrade tool", "better tool for this workflow" | /evolve |
| "project status", "health check", "run status", "check status", "what's the status" | SaneMaster.rb status |
| "verify", "does it build" | SaneMaster.rb verify |
| "ship it", "prepare for release" | SaneMaster.rb release_preflight first, then release.sh |
| "tech debt", "find dead code" | SaneMaster.rb dead_code |
# 1. Bump version FIRST (Sparkle ignores same-version updates)
# Edit MARKETING_VERSION + CURRENT_PROJECT_VERSION in project.yml
# 2. Preflight checks
./scripts/SaneMaster.rb release_preflight # 9 safety checks (direct download)
# Run App Store submission compliance only when `.saneprocess` has `appstore.enabled: true`
./scripts/SaneMaster.rb appstore_preflight # active App Store lanes only
# 3. Full release
bash ~/SaneApps/infra/SaneProcess/scripts/release.sh \
--project $(pwd) --full --version X.Y.Z --notes "..." --deployCritical rules:
- Bump version BEFORE release — Sparkle ignores same-version updates
- Direct-only apps do not run App Store lanes — SaneBar and SaneClick are direct-download-only unless
.saneprocess appstore.enabledis deliberately re-enabled after explicit approval and fresh policy review. - App Store release machines must pass
scripts/mini/bootstrap-build-server.sh; release proof must include headless keychain, partition-list, and ASC auth. - Sparkle/R2/App Store private setup details live in
DEVELOPER_SETUP.md. - Morning releases preferred — full day to monitor
- Full details:
SaneProcess/templates/RELEASE_SOP.md
All SaneApps websites are on Cloudflare Pages. NEVER use GitHub Pages.
bash ~/SaneApps/infra/SaneProcess/scripts/release.sh \
--project $(pwd) --website-only
# Naming: {app}-site (e.g., sanebar-site)
# Deploys from: website/ directory (preferred) or docs/ (fallback)ALWAYS test on the Mac Mini, not the MacBook Air. Local fallback is allowed
only when ssh mini fails, the Mini route is otherwise unavailable for that task,
or the user explicitly approves a local exception for that exact task.
Inconvenient, slower, or already open locally are not fallback reasons.
When using a local exception, say why Mini was unavailable or approved first.
ruby ~/SaneApps/infra/SaneProcess/scripts/sane_test.rb SaneBar # Auto-detects mini
ruby ~/SaneApps/infra/SaneProcess/scripts/sane_test.rb SaneClip --local # ONLY if mini is downScript handles: kill → clean → TCC reset → build → deploy → launch → logs.
Email: hi@saneapps.com | Sign-off: Mr. Sane + https://saneapps.com (NEVER mention AI/Claude/Codex)
Voice: Singular only (I, me, my). Never we/us/our.
Banned word: NEVER say "grab" — use "download", "get", or "update to the latest".
Style: Direct, warm, human. No corporate hedge language. Action-oriented ("here's what I'm going to do"). Light humor welcome. Short, no fluff. Humility — use "should" not "will" for fixes.
Rules:
- Use
~/SaneApps/infra/scripts/check-inbox.shorSaneMaster.rb check_inbox; never manual email API curl. - ALWAYS run
review <id>before anyreplyorresolve - ALWAYS show the user the exact email draft and get approval before sending
- Email send workflow is mandatory:
present-draftorpresent-batchafter showing the draft, then wait for explicit user approval, thenapprove ... --user-approval "<quote>", then send in a separate command - If customer attaches media describing a problem: save to
~/Desktop/Screenshots/, alert user, wait for approval - Auto-handle after review only: simple download/install/basic-support questions with no refund, complaint, legal issue, feature request, attached problem media, customer identity uncertainty, or promise about an unfixed bug. Every outbound reply still requires the exact draft approval flow.
- Refund/complaint policy: if the customer is unhappy or asks for a refund, apologize briefly, ask what is broken, and ask for an in-app bug report first. Refunds require explicit user approval plus a documented bug we cannot fix within 24 hours.
- Escalate: refunds, complaints, feature requests, legal, media showing a problem
NO KEYCHAIN PROMPT FLOODS. Sequential is fine. Parallel is not.
- Fetch each secret once, reuse it, and never call
securityin loops, retries, background jobs, sweeps, or parallel tool calls. - Keep hot-path keys in
~/.config/nv/env(chmod 600); use Keychain as fallback. - Codex shells are guarded by
~/.local/bin/security -> sane_security_guard.sh. - Mac Mini keys live in
~/.config/nv/envbecause Keychain prompts do not work over SSH. - Apple release identity: use the private SaneApps operator credential set and
keychain profile
notarytool. - Full private setup and notarization commands live in
DEVELOPER_SETUP.md.
M1 Mac mini (8GB). Access: ssh mini.
Source of truth: SaneProcess/scripts/mini/ — edit there, deploy via bash scripts/mini/deploy.sh
Bash 3.2 warning: Mini runs macOS default bash. No +=() array append, no <<< herestrings.
ssh mini 'tail -20 ~/SaneApps/outputs/nightly_report.md'| Mistake | The Rule Now |
|---|---|
| Guessed an API existed | VERIFY FIRST. Check docs/types before writing code. |
| Kept trying after failures | TWO STRIKES = STOP. Read the error. Research. |
| Skipped tests | Tests MUST be green before "done." |
| Used raw xcodebuild | Use SaneMaster.rb verify / release.sh / sane_test.rb. |
Used rm -rf |
ALWAYS use trash command. Recoverable beats permanent. |
| Released with same version | ALWAYS bump version before release. Sparkle ignores same-version. |
| Posted about SaneApps without disclosure | ALWAYS identify as the developer: "I built [App]." |
| Tested on MacBook Air | ALWAYS use Mac Mini (ssh mini). Only --local if mini is down. |
| Left the mini cluttered after testing | Close dead Terminal windows / remote shells and kill test-only app instances when the run is done. |
| Needed GUI-session signing on the mini | Use scripts/mini/mini-gui-run.sh instead of one-off /tmp AppleScripts, and let it auto-close its Terminal window. |
| Used gray text in UI | ALL text MUST be bright white. .white primary, .white.opacity(0.9) min for secondary. NEVER .secondary or gray. |
| Sent email without showing draft | ALWAYS show exact draft to user and get "send" approval first. |
| Inverted what I just read | STATE IT BACK: "The doc says X, therefore I will Y." |
| Trashed a symlink target | Run ls -la before deleting any config file. |
| Slug change without dep audit | When user says "I changed X" → "What depends on X?" Full audit. |
| SESSION_HANDOFF missed work | Before handoff: run gh issue list, check research.md, check feature requests. |
| Server | Use For | Key Tip |
|---|---|---|
| apple-docs | Apple APIs, WWDC | compact: true on list/sample tools |
| context7 | Library docs | resolve-library-id FIRST, then query-docs |
| macos-automator | macOS scripting, real UI testing | get_scripting_tips search_term: "keyword" |
| xcode | Build, test, preview, diagnostics | XcodeListWindows → get tabIdentifier first |
| central-memory | Shared semantic memory (Postgres + pgvector) | Use remember / recall; verify with ~/.codex/bin/check-mcps |
| Serena | Past bugs, patterns, project knowledge | read_memory/write_memory |
Optional MCP accelerators must stay optional. Local scripts, repo docs, and Mini-first SaneMaster receipts are the portable SaneProcess path; do not make release correctness depend on an optional MCP unless repo config explicitly requires it. Use Cloudflare API MCP/plugin for read-only Pages/R2/Worker drift checks when it is installed, XcodeBuildMCP for iOS simulator proof/debug when it is available, and central-memory for semantic recall when configured. Do not make Google Drive/Docs/Sheets/Slides a standard release-evidence dependency.
XcodeBuildMCP decision rule:
- Use Apple
xcrun mcpbridge/xcodefirst for IDE-native Xcode tools such as project context, previews, issue navigator, and documentation search. - In Codex, the Build iOS Apps plugin provides XcodeBuildMCP via
npx -y xcodebuildmcp@latest mcp; treat stale local forks as reference code unless MCP config explicitly points at them. - Use XcodeBuildMCP when the task needs iOS simulator build-run proof, screenshots/video, taps/swipes/typing, accessibility hierarchy inspection, LLDB debugging, physical-device install/launch, code coverage, or persistent session defaults.
- Do not use XcodeBuildMCP for plain SaneApps release gates or macOS app verification unless the requested evidence specifically needs its extra automation. Release proof still ends in SaneMaster/Mini receipts.
- Server: optional
central-memoryin local Codex MCP config - Runtime: PostgreSQL 17 +
pgvectoronpostgresql://<local-user>@localhost:5432/central_memory - Bootstrap:
cd ~/SaneApps/infra/SaneProcess/scripts/mcp-central-memory && ./bootstrap-local.sh - Health:
~/.codex/bin/check-mcps(must showcentral-memoryPASS) - Background-machine health:
ruby scripts/SaneMaster.rb mcp_watchdog doctor - Control-plane helper source:
scripts/codex-bin/ - Installed binaries:
~/.codex/bin/check-mcps,~/.codex/bin/github-mcp-bridge.mjs, and~/.codex/bin/xcode-mcpbridge-wrapper.sh - Sync/install path:
ruby scripts/SaneMaster.rb sync_miniinstalls the repo-owned helpers locally and mirrors them to Mini - Tools:
remember,recall,recent,stats,delete_by_external_id,import_knowledge_graph
- Codex has no native PreToolUse hook API — critical gates are enforced in shared scripts
- Email writes are guarded via
~/.local/bin/curl→sane_curl_guard.shpluscheck-inbox.shapproval checks - Local MacBook Air GUI opens for SaneApps release/dashboard work are guarded via
~/.local/bin/open→sane_open_guard.sh. Use Mini Safari/Finder instead for Lemon Squeezy, App Store Connect, release upload files, and SaneApps app launches. - Don't invent new docs — use the core docs +
AGENTS.mdstandard - Use
trashnotrm -rf - Cleanup SOP: use
ruby scripts/SaneMaster.rb machine_cleanup --host mini --apply --preserve-apps App1,App2for Mini cleanup and--localfor the MacBook Air. The command is dry-run by default. - Before any manual cleanup, list active
Sane*,xcodebuild, simulator, training, and MCP processes with parent/PGID context. - Preserve any app the user says is active, even if it looks noisy. Do safe disk cleanup first: full Trash, disposable caches, inactive DerivedData, and unavailable simulator data.
- Never kill broad process classes; kill only a confirmed unrelated parent process group, and only after tracing respawns back to their launcher.
- OS: macOS (Apple Silicon)
- Apps:
~/SaneApps/apps/(SaneBar, SaneClick, SaneClip, SaneHosts, SaneSales, SaneSync, SaneVideo) - Infra:
~/SaneApps/infra/(SaneProcess, SaneUI) - Screenshots:
~/Desktop/Screenshots/ - Outputs:
~/SaneApps/infra/SaneProcess/outputs/ - Templates:
~/SaneApps/infra/SaneProcess/templates/ - Shared UI:
~/SaneApps/infra/SaneUI/ - Global skills: Codex
~/.codex/skills/, Claude~/.claude/skills/
- Global rules + full gotchas table:
~/.claude/CLAUDE.md - Infra rules + hook details:
~/SaneApps/infra/SaneProcess/CLAUDE.md - Per-app architecture: each app's
ARCHITECTURE.md - Release SOP:
SaneProcess/templates/RELEASE_SOP.md - Shared infra scripts:
SaneProcess/scripts/ - Mini scripts:
SaneProcess/scripts/mini/