Skip to content

Latest commit

 

History

History
269 lines (204 loc) · 14.3 KB

File metadata and controls

269 lines (204 loc) · 14.3 KB

Repository Guidelines

Last updated: 2026-03-24

Project Snapshot

  • App: Pindrop (menu bar macOS app, LSUIElement behavior)
  • Stack: Swift 5.9+, SwiftUI, SwiftData, Swift Testing, XCTest UI tests
  • Platform target: macOS 14+
  • Main dependency path: Pindrop.xcodeproj + SwiftPM
  • Entry points: Pindrop/PindropApp.swift, Pindrop/AppCoordinator.swift

Source Layout

  • App code: Pindrop/
  • Services: Pindrop/Services/
  • UI: Pindrop/UI/
  • Persistence models: Pindrop/Models/
  • Utilities/logging: Pindrop/Utils/
  • Tests: PindropTests/
  • Test doubles: PindropTests/TestHelpers/
  • Build automation: justfile, scripts/, .github/workflows/

Required Local Tooling

  • Xcode with command-line tools (xcodebuild)
  • just for all routine workflows: brew install just
  • Optional: swiftlint, swiftformat, create-dmg
  • Apple Developer signing configured in Xcode for signed local/release builds; CI recipes use explicit unsigned overrides

Build and Run Commands

Prefer just recipes over ad-hoc shell commands.

just build                 # Debug build (ALWAYS use this when testing builds)
just build-release         # Release build
just export-app            # Developer ID export for distribution
just dmg                   # Signed DMG for distribution
just test                  # Unit test plan
just test-integration      # Integration test plan (opt-in)
just test-ui               # UI test plan
just test-all              # Unit + integration + UI
just test-coverage         # Unit tests with coverage
just dev                   # clean + build + test
just ci                    # clean + unsigned build + unsigned test + unsigned release build
just run                   # open Xcode project
just xcode                 # open Xcode project

Direct focused test commands:

xcodebuild test -project Pindrop.xcodeproj -scheme Pindrop -testPlan Unit -destination 'platform=macOS'
xcodebuild test -project Pindrop.xcodeproj -scheme Pindrop -testPlan UI -destination 'platform=macOS'
xcodebuild test -project Pindrop.xcodeproj -scheme Pindrop -destination 'platform=macOS' -only-testing:PindropTests/AudioRecorderTests
xcodebuild test -project Pindrop.xcodeproj -scheme Pindrop -destination 'platform=macOS' -only-testing:PindropTests/AudioRecorderTests/testStartRecordingRequestsPermission

Coding Conventions

  • Follow existing file header style (Created on YYYY-MM-DD)
  • Use final class for services and most concrete implementations
  • Actor isolation pattern: services are usually @MainActor
  • Known exception: hotkey internals with Carbon/event constraints
  • Use @Observable for reactive services where compatible
  • SettingsStore intentionally uses ObservableObject + @AppStorage
  • Keep import groups consistent with existing files

Service Patterns

  • Dependency injection via initializer arguments (avoid hidden globals)
  • Protocol abstractions for hardware/system boundaries
  • Example protocol seam: AudioCaptureBackend in Pindrop/Services/AudioRecorder.swift
  • Keep async boundaries explicit (async / async throws)
  • Avoid fire-and-forget tasks unless they are UI/lifecycle orchestration

Error Handling

  • Define domain errors as enum ...: Error, LocalizedError
  • Keep user-facing messaging in errorDescription
  • Catch at boundaries, log with context, then rethrow typed errors when possible
  • Do not swallow errors with empty catch blocks

Localization

  • String Catalogs: Pindrop/Localization/Localizable.xcstrings (in-app copy) and Pindrop/Localization/InfoPlist.xcstrings (privacy strings, bundle display name). Both are in the app target’s Copy Bundle Resources. The top-level Localization/ tree is the source of truth for the YAML-first pipeline.
  • Runtime API: localized("English key", locale: locale) in Pindrop/AppLocalization.swift now resolves through generated stable-key metadata before falling back to Bundle; SettingsStore.selectedAppLocale drives UI locale and SettingsStore.selectedAppLanguage drives dictation/transcription language.
  • New user-facing strings: Add an entry to the YAML source tree under Localization/, then run just l10n-sync so the catalogs and generated Swift stay in sync.
  • New language (locale): Add the locale with just l10n-add-locale <locale> (or edit Localization/locales.yml), then populate the relevant Localization/app/*.yml and Localization/infoplist/*.yml files before syncing.
  • Interface vs dictation language: The General settings UI now separates interface language from dictation language. Keep AppLocale-driven UI locale changes away from AppLanguage/transcription behavior.
  • AI enhancement prompts: AIEnhancementSettingsView localizes default prompts for display; if the user saves without editing, the localized prompt text can be persisted and sent to the API—expect models to follow non-English system prompts, or keep defaults in English if you change that flow.
  • Localization tooling: Use just l10n-import-current, just l10n-sync, and just l10n-lint. The old scripts/translate_xcstrings.py helper is obsolete.

Logging

  • Use Log categories from Pindrop/Utils/Logger.swift
  • Categories include: audio, transcription, model, output, hotkey, app, ui, update, aiEnhancement, context
  • Log intent and failure context; avoid noisy per-frame spam

SwiftData and Persistence

  • Models use SwiftData macros (@Model, @Attribute(.unique))
  • Keep schema-related changes coordinated with schema files under Pindrop/Models/
  • Use in-memory model containers for unit tests when testing store logic

Testing Conventions

  • Test files: *Tests.swift
  • Unit tests use Swift Testing with @Suite / @Test; macOS UI coverage stays in PindropUITests/ with XCTest UI APIs
  • Standard naming: sut for system under test
  • Prefer local fixture builders over shared setUp / tearDown; use PindropTests/TestSupport.swift for reusable test helpers
  • Use protocol mocks from PindropTests/TestHelpers/ for hardware/system APIs
  • Integration tests are gated (see PINDROP_RUN_INTEGRATION_TESTS pattern)
  • Test mode signal exists in runtime (PINDROP_TEST_MODE)
  • UI tests run through PINDROP_UI_TEST_MODE and deterministic fixture surfaces in Pindrop/AppTestMode.swift

Change Scope Rules

  • Keep fixes minimal and local; do not refactor unrelated code in bugfixes
  • Preserve architecture boundaries (UI -> coordinator -> services -> models)
  • Do not introduce alternate command systems when just recipes already exist
  • Prefer extending existing services over adding parallel duplicate services

Release and Distribution

  • Local release helpers: just build-release, just export-app, just dmg, just dmg-self-signed (fallback only)
  • Manual release flow is just release <X.Y.Z> (local execution, not CI-driven)
    1. Create/edit contextual release notes (release-notes/vX.Y.Z.md)
    2. Run tests
    3. Build signed release DMG (just dmg exports a Developer ID-signed app first)
    4. Generate appcast.xml
    5. Create + push tag
    6. Create GitHub release via gh with notes + DMG + appcast.xml
  • CI workflows under .github/workflows/ are for build/test validation; release publishing is manual
  • Sparkle appcast generation is scripted via just appcast <dmg-path>
  • Keep just build-self-signed / just dmg-self-signed only as a fallback when Apple signing is unavailable

Quick PR Checklist

  • Build passes: just build
  • Relevant tests pass: just test (and integration when touched)
  • No new warnings from your change scope
  • Docs/comments updated only when behavior changes
  • Keep diffs focused; avoid opportunistic formatting-only churn

Important Paths

  • App lifecycle: Pindrop/PindropApp.swift
  • Service composition: Pindrop/AppCoordinator.swift
  • Settings and keychain: Pindrop/Services/SettingsStore.swift
  • Audio capture core: Pindrop/Services/AudioRecorder.swift
  • Transcription orchestration: Pindrop/Services/TranscriptionService.swift
  • Logging facade: Pindrop/Utils/Logger.swift
  • Localization: Pindrop/AppLocalization.swift, Pindrop/Generated/LocalizationMetadata.swift, Pindrop/Generated/L10nKeys.swift, Pindrop/Localization/Localizable.xcstrings, Pindrop/Localization/InfoPlist.xcstrings, Localization/
  • Localization tooling: scripts/localization.py, justfile
  • Build recipes: justfile
  • Contributor docs: README.md, CONTRIBUTING.md, BUILD.md

Notes for Agents

  • Use just commands in examples unless a direct xcodebuild form is required
  • When adding tests, mirror structure from the nearest existing test file first
  • Prefer Swift Testing assertions (#expect, #require, Issue.record) for unit tests; keep XCTest only for UI automation
  • When touching settings, verify both app behavior and test-mode behavior
  • When touching model or schema code, verify migration and read/write behavior
  • When adding or changing user-visible strings, update both Swift localized(...) keys and Localizable.xcstrings (and InfoPlist.xcstrings for permission / bundle strings) for all shipped locales

GitNexus — Code Intelligence

This project is indexed by GitNexus as pindrop (715 symbols, 699 relationships, 0 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.

If any GitNexus tool warns the index is stale, run npx gitnexus analyze in terminal first.

Always Do

  • MUST run impact analysis before editing any symbol. Before modifying a function, class, or method, run gitnexus_impact({target: "symbolName", direction: "upstream"}) and report the blast radius (direct callers, affected processes, risk level) to the user.
  • MUST run gitnexus_detect_changes() before committing to verify your changes only affect expected symbols and execution flows.
  • MUST warn the user if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
  • When exploring unfamiliar code, use gitnexus_query({query: "concept"}) to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
  • When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use gitnexus_context({name: "symbolName"}).

When Debugging

  1. gitnexus_query({query: "<error or symptom>"}) — find execution flows related to the issue
  2. gitnexus_context({name: "<suspect function>"}) — see all callers, callees, and process participation
  3. READ gitnexus://repo/pindrop/process/{processName} — trace the full execution flow step by step
  4. For regressions: gitnexus_detect_changes({scope: "compare", base_ref: "main"}) — see what your branch changed

When Refactoring

  • Renaming: MUST use gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true}) first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with dry_run: false.
  • Extracting/Splitting: MUST run gitnexus_context({name: "target"}) to see all incoming/outgoing refs, then gitnexus_impact({target: "target", direction: "upstream"}) to find all external callers before moving code.
  • After any refactor: run gitnexus_detect_changes({scope: "all"}) to verify only expected files changed.

Never Do

  • NEVER edit a function, class, or method without first running gitnexus_impact on it.
  • NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
  • NEVER rename symbols with find-and-replace — use gitnexus_rename which understands the call graph.
  • NEVER commit changes without running gitnexus_detect_changes() to check affected scope.

Tools Quick Reference

Tool When to use Command
query Find code by concept gitnexus_query({query: "auth validation"})
context 360-degree view of one symbol gitnexus_context({name: "validateUser"})
impact Blast radius before editing gitnexus_impact({target: "X", direction: "upstream"})
detect_changes Pre-commit scope check gitnexus_detect_changes({scope: "staged"})
rename Safe multi-file rename gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})
cypher Custom graph queries gitnexus_cypher({query: "MATCH ..."})

Impact Risk Levels

Depth Meaning Action
d=1 WILL BREAK — direct callers/importers MUST update these
d=2 LIKELY AFFECTED — indirect deps Should test
d=3 MAY NEED TESTING — transitive Test if critical path

Resources

Resource Use for
gitnexus://repo/pindrop/context Codebase overview, check index freshness
gitnexus://repo/pindrop/clusters All functional areas
gitnexus://repo/pindrop/processes All execution flows
gitnexus://repo/pindrop/process/{name} Step-by-step execution trace

Self-Check Before Finishing

Before completing any code modification task, verify:

  1. gitnexus_impact was run for all modified symbols
  2. No HIGH/CRITICAL risk warnings were ignored
  3. gitnexus_detect_changes() confirms changes match expected scope
  4. All d=1 (WILL BREAK) dependents were updated

Keeping the Index Fresh

After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it:

npx gitnexus analyze

If the index previously included embeddings, preserve them by adding --embeddings:

npx gitnexus analyze --embeddings

To check whether embeddings exist, inspect .gitnexus/meta.json — the stats.embeddings field shows the count (0 means no embeddings). Running analyze without --embeddings will delete any previously generated embeddings.

Claude Code users: A PostToolUse hook handles this automatically after git commit and git merge.

CLI

Task Read this skill file
Understand architecture / "How does X work?" .claude/skills/gitnexus/gitnexus-exploring/SKILL.md
Blast radius / "What breaks if I change X?" .claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md
Trace bugs / "Why is X failing?" .claude/skills/gitnexus/gitnexus-debugging/SKILL.md
Rename / extract / split / refactor .claude/skills/gitnexus/gitnexus-refactoring/SKILL.md
Tools, resources, schema reference .claude/skills/gitnexus/gitnexus-guide/SKILL.md
Index, status, clean, wiki CLI commands .claude/skills/gitnexus/gitnexus-cli/SKILL.md