Files
Termind/AGENTS.md
T
2026-10-07 11:10:01 +08:00

139 lines
12 KiB
Markdown

# 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`.
The agent runtime is `AgentKit`, a separate open-source package developed in the sibling `../AgentKit` repository and consumed as a versioned remote SPM dependency on `github.com/AkinoKaede/AgentKit`, pinned in `Package.resolved`. It owns the loop, the approval gate, the provider adapters, the MCP client, and the built-in tools that know nothing about hosts. Termind owns the SSH/SFTP tools and registers them through `TermindAgentTools`. Never add SSH, SFTP, UI, or the name Termind to that package.
Xcode builds the pinned tag, not the sibling working tree, so an uncommitted change in `../AgentKit` is invisible to the app. A change that spans both sides takes three steps: make and test the package change, tag and push a release from `../AgentKit`, then bump `minimumVersion` in `project.pbxproj` and re-resolve so `Package.resolved` records the new revision. Both files belong in the same commit as the app-side change that needs them.
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.
`libssh2-spm/` is the maintenance checkout of `AkinoKaede/libssh2-spm`,
tracking `termind`. Xcode consumes its remote exact-version package.
`Vendor/libssh2` is a clean submodule of official `libssh2/libssh2`; its gitlink
pins native source. CI exports it into ignored `.build/patched-native`, applies
numbered `Patches/libssh2/` patches, and publishes a checksummed XCFramework.
Never commit full native snapshots or generated patched source files. The
handwritten `Source/Exports.swift` re-exports the binary under the existing
`CSSH2` API. Update the submodule or patches, run `python3 Script/test-source.py` and
native CMake/CTest, then dispatch the package's Release XCFramework workflow.
It builds and tests the Apple platform matrix, publishes the binary, and commits
the manifest URL/checksum and package tag. Update the Termind maintenance gitlink,
Xcode exact version and `Package.resolved` together after release. Keep the
external-signature API and its native regression tests.
## 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.
- `cd ../AgentKit && swift build && swift test` builds and tests the agent package on its own.
- `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. AgentKit passes the same command over `Sources Tests` and must keep passing it.
- `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.
Run `Scripts/check-appstrings.sh` alongside the Chinese localization check.
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-zh-localization.sh`
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 and does not touch AgentKit.
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`. Text owned by the agent runtime lives in `../AgentKit/Localizations/Localizable.xcstrings`; after editing it run `../AgentKit/Scripts/build-localizations.sh`, because `swift build` copies resources verbatim instead of compiling string catalogs. 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-zh-localization.sh` (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-zh-localization.sh` 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:START -->
## 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.
<!-- OPENWIKI:END -->