Files

10 KiB

Repository Guidelines

Project Structure & Module Organization

Termind.xcodeproj is the single project. TFM/ contains the Swift 6 macOS app, TFI/ the iPhone/iPad app. Their targets and schemes are TFM and TFI. Frameworks/ contains static TermindDomain, TermindApplication, TermindInfrastructure, and TermindPersistence frameworks. All SwiftData models, historical schemas, and the container factory belong exclusively to Persistence. Frameworks/Resources/ is compiled into each app bundle. Platform adapters and UI stay with their app; shared modules must never depend on an app target. TermindMCPBridge/ builds termind-mcp for macOS only. Tests are separated into FrameworkTests/, TFMTests/, and TFITests/ with matching targets. com.akinokaede.termind/ is the public website and assets submodule. Its root is an independent Astro project; assets/ and design/ inside it own marketing and brand assets. Initialize it with git submodule update --init com.akinokaede.termind only for website or asset work. Native app builds do not require it. Utilities and screenshot tooling stay in the main repository; see openwiki/operations/marketing-assets-and-app-icons.md for the asset workflow. Commit and push asset changes via SSH before updating the main repository's gitlink; keep the public HTTPS clone URL in .gitmodules.

AgentKit, libssh2-spm, and libtailscale-spm are remote SPM dependencies, not Termind submodules. Xcode consumes the versions pinned in Package.resolved; keep the project requirements and resolved pins aligned when updating dependencies. Termind owns the SSH/SFTP tools and registers them through TermindAgentTools.

Ghostty is consumed directly from Lakr233/libghostty-spm as a remote SPM dependency with an up-to-next-major version requirement. Package.resolved locks the released version and revision. There is no local Ghostty maintenance submodule. Keep the project requirement and resolved pin aligned when updating. The package exposes separate iOS touchMenuItems(for:) and touchSelectionMenuItems(for:) hooks; grouping and localization belong to Termind.

Architecture Records

Treat openwiki/ as durable just-in-time engineering context. Start with openwiki/quickstart.md before changing dependencies, navigation ownership, state lifetimes, persistence, agent execution, permissions, secret handling, or release operations, then follow its topic-specific page to current source and tests. Source code and tests remain authoritative. Refresh invalidated OpenWiki content through the OpenWiki lifecycle and keep ArchitectureBoundaryTests aligned; do not maintain a parallel refactoring log.

Build, Test, and Development Commands

  • xcodebuild -project Termind.xcodeproj -scheme TFM -destination 'platform=macOS' -onlyUsePackageVersionsFromResolvedFile build builds the app, the MCP bridge, and AgentKit. Keep the flag: resolving without it silently bumps pinned dependencies.
  • xcodebuild -project Termind.xcodeproj -scheme TFI -destination 'generic/platform=iOS Simulator' -onlyUsePackageVersionsFromResolvedFile build builds the mobile app and shared frameworks. Run its tests with an available iPhone or iPad simulator destination.
  • xcodebuild -project Termind.xcodeproj -scheme TFM -destination 'platform=macOS' -onlyUsePackageVersionsFromResolvedFile test runs the default macOS and shared Swift tests. Tests that show windows or take focus are opt-in.
  • xcrun swift-format lint --strict --recursive Frameworks TFM TFI FrameworkTests TFMTests TFITests TermindMCPBridge checks Swift formatting.
  • cd com.akinokaede.termind && npm ci && npm run dev installs locked web dependencies and starts Astro locally.
  • cd com.akinokaede.termind && npm run build type-checks and emits the production site to com.akinokaede.termind/dist/.

Coding Style & Naming Conventions

Follow .swift-format: four spaces and 120 columns. Use PascalCase types, camelCase members, behavior-based test names, and matching type/file names. Preserve ArchitectureBoundaryTests restrictions. The site uses strict TypeScript.

Testing Guidelines

Use Swift Testing (@Suite, @Test, #expect) in the three test directories. Add regression coverage and run the default test suite before submission. Web changes must pass npm run build; verify English and /zh/ routes visually.

Assert observable behavior and regressions. Avoid assertions that only repeat fixture values, compare identical constructions, or impose arbitrary PNG byte-count thresholds. Keep screenshot export helpers opt-in for visual review.

Test SwiftData migrations by opening historical stores and verifying retained data after reopening. Do not assert that the latest schema equals a hard-coded version number; adding a migration must not invalidate unrelated tests.

macOS tests that present windows or activate the app must use .requiresVisibleWindow on their suite or individual test. They are skipped by default; keep this opt-in off for routine verification. To opt in from the command line, prefix the test command with TEST_RUNNER_TERMIND_WINDOW_TESTS=1 (Xcode forwards it as TERMIND_WINDOW_TESTS=1 to the test host), preferably with -only-testing:TFMTests/<Suite> to limit the run. In Xcode, set TERMIND_WINDOW_TESTS=1 in the test action's environment. Screenshot tests with existing dedicated opt-in variables keep those gates. Offscreen layout and logic tests remain enabled by default.

Commerce access and paywall-state behavior are covered by RevenueCatCommerceTests, ProPaywallStateTests, and feature-access tests. StoreKit/Termind.storekit is a manual development fixture; live products, trial eligibility, and purchase/restore behavior require RevenueCat and App Store Sandbox validation.

Mobile key-window integration suites are skipped by default because they share UIKit's process-wide presentation state. Run them explicitly with TEST_RUNNER_TERMIND_MOBILE_UI_TESTS=1 (Xcode forwards it as TERMIND_MOBILE_UI_TESTS=1), -parallel-testing-enabled NO, and narrow -only-testing:TFITests/<Suite> filters.

Localization Guidelines

Define app-facing copy as LocalizedStringResource entries in Frameworks/TermindPresentation/Strings, including copy used by only one platform. Reuse semantic entries and shared formatting functions from TFM and TFI; keep protocol values, identifiers, and diagnostic codes with their existing owners.

The two shared app catalogs are manually managed: every entry must have extractionState: "manual". A single target's extraction does not see all shared and platform-specific copy, so its stale flags are not evidence that a key is unused. Add keys and translations explicitly; verify references across both apps and shared modules before removing a key. After editing a catalog, run Scripts/normalize-string-catalogs.py --write to apply Xcode-compatible ordering and formatting without changing translations. Scripts/check-localization.py also runs the normalizer in read-only --check mode. Do not use jq -S or a generic JSON formatter on these files: their whitespace differs from Xcode's. The normalizer defaults to the two app catalogs. Keep STRING_CATALOG_GENERATE_SYMBOLS = NO: AppStrings owns the Swift API, and generating symbols for every manual English key introduces naming collisions.

Put user-facing app text in Frameworks/Resources/Localizable.xcstrings and bundle metadata in InfoPlist.xcstrings. New or changed English strings require finished zh-Hans and zh-Hant translations in the same commit. Preserve placeholders and Chinese punctuation conventions. Before committing, run Scripts/check-localization.py (requires jq); it rejects invalid catalogs, missing or unfinished translations, and spacing errors. Run AppLanguageTests for localization behavior changes.

Commit & Pull Request Guidelines

Use Conventional Commits: <type>[optional scope]: <description>, with a lowercase type and a short, imperative English description (for example, feat: add host groups or fix(assistant): restore sidebar navigation round-trip). Choose the type that matches the change:

  • feat: new features.
  • fix: bug fixes.
  • docs: documentation-only changes.
  • refactor: code restructuring without changing behavior.
  • perf: performance improvements.
  • test: test additions or updates.
  • style: formatting-only changes without changing behavior.
  • build: build system or dependency changes.
  • ci: CI configuration or workflow changes.
  • chore: maintenance that does not fit another type; use chore(release): bump to X.Y.Z for releases.
  • revert: revert a previous commit.

Mark breaking changes with ! before the colon (for example, feat(api)!: remove deprecated endpoint) and explain the impact in a BREAKING CHANGE: footer.

Keep commits focused. Always include Scripts/check-localization.py in the commit pipeline, even without intentional localization changes. PRs should describe the outcome, affected layers, verification, and issues. Include bilingual screenshots for UI changes when applicable. Never commit secrets, .env files, build products, or user-specific Xcode data.

OpenWiki

This repository has a generated openwiki/ evidence index. It is optional just-in-time context, not required startup reading.

  • Do not enumerate, preload, or search wikis at task start. Use retrieval when the user asks for it, when unfamiliar architecture or dependency behavior materially affects the task, or when source inspection leaves an important uncertainty. Stop once the question is grounded.
  • When those conditions apply and OpenWiki retrieval tools are available, use openwiki_search for just-in-time context and openwiki_read for the relevant complete sections. If search returns workspace_required, ask which listed workspace to use and retry with its ID.
  • Use openwiki_list_workspaces or openwiki_list_wikis when workspace membership itself needs to be discovered.
  • If the retrieval tools are unavailable, read openwiki/quickstart.md and follow its links to the relevant pages.
  • Treat source code and tests as authoritative. A brief's unknowns and review items are verification gaps, not automatic requirements.
  • Prefer the narrowest quiet validation that proves the changed behavior. Preserve complete failure output.

The scheduled OpenWiki GitHub Actions workflow refreshes the repository wiki. Do not hand-edit generated OpenWiki pages unless explicitly asked; prefer updating source code/docs and letting OpenWiki regenerate.