Files
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

14 KiB

Agent Instructions for MacTools

Instruction Scope

  • This file is the canonical agent guide for this repository and applies to the entire repo.
  • If a closer AGENTS.md appears in a subdirectory in the future, the closer file takes precedence.
  • CLAUDE.md and GEMINI.md are compatibility entry points only; shared rules should be maintained here first.

Project Overview

  • MacTools is a native macOS menu-bar utility collection for frequent, lightweight, non-disruptive system tasks.
  • The stack is Swift 6 with SwiftUI + AppKit, targeting macOS 14.0 or later.
  • Features are organized as plugins. Current plugin source lives under Plugins/<PluginName>/ and is integrated into the host through MacToolsPluginKit, dynamic plugin packages, and catalogs.
  • User-facing copy is currently primarily Chinese. New copy should stay concise, clear, and close to native macOS phrasing.

Key Directories

  • Sources/App/: app entry point, menu-bar status item, panels, settings pages, and window routing.
  • Sources/Core/Plugins/: plugin host, dynamic plugin loading, package installation, catalog validation, and display preferences.
  • Sources/Core/Shortcuts/: global shortcut models, storage, and management.
  • Sources/Core/Permissions/: system permission checks.
  • Sources/Core/Diagnostics/: shared logging entry points.
  • Sources/Core/Updates/: Sparkle update checks and About-page update state.
  • Sources/MacToolsPluginKit/: plugin protocols, declarative UI models, shortcut models, and runtime context.
  • Plugins/<PluginName>/: plugin manifest, source, bundle entry point, resources, and adjacent tests.
  • Tests/: XCTest coverage for shared App/Core logic; plugin tests should prefer the corresponding plugin directory.
  • Configs/: Xcode build settings and Info.plist.
  • docs/plugins/: plugin package, catalog, local debugging, and release-process documentation.
  • docs/superpowers/: larger product/interaction specs and implementation plans.
  • scripts/: release, signing, notarization, and GitHub Release helper scripts.

Build And Run

  • Run make setup first to initialize LocalConfig.xcconfig, then fill in DEVELOPMENT_TEAM and BUNDLE_IDENTIFIER_PREFIX.
  • project.yml is the root XcodeGen source. Plugin targets and schemes are generated locally into Configs/GeneratedPlugins.yml by scripts/plugins/generate-plugin-project-config.rb, which scans Plugins/*/plugin.json; that generated file is not committed.
  • Generate the project with make generate. Do not run bare xcodegen generate, because it can miss the latest generated plugin configuration.
  • Validate compilation with make build.
  • Run locally with make run; it syncs the latest Debug plugin packages and generates the local development catalog.
  • Sync only already-built Debug plugin packages and the local development catalog with make sync-debug-plugins.
  • Build the local plugin packages and generate the Debug catalog with make build-plugin.
  • Build one plugin with make build-plugin PLUGIN=<plugin directory name or plugin ID>.
  • Run the full test suite with xcodebuild -project MacTools.xcodeproj -scheme MacTools -configuration Debug -derivedDataPath build/DerivedData test -quiet.
  • Run one test class by appending -only-testing:MacToolsTests/<TestClassName> to the full test command.
  • Use ./scripts/release-local.sh only when a release is needed; confirm user intent before signing, notarizing, publishing, or tagging.

Architecture Conventions

  • Add new plugins under Plugins/<PluginName>/ with at least plugin.json, Sources/, and Bundle/.
  • Plugins implement MacToolsPlugin; menu-bar primary panels implement PluginPrimaryPanel, and component panels implement PluginComponentPanel.
  • plugin.json.id must be stable, readable, and exactly match the runtime PluginMetadata.id; each .mactoolsplugin package must return exactly one plugin instance.
  • PluginHost owns plugin ordering, visibility, shortcuts, permission cards, and derived display state. Individual plugins should not manipulate host UI directly.
  • Plugin UI should be expressed through declarative models such as PluginPanelState, PluginPanelDetail, and PluginPanelControl. Except for PluginComponentPanel.makeView, avoid bypassing the existing panel framework with custom menu-bar UI.
  • Plugin state and UI-related code should normally run on @MainActor. Long-running scans, filesystem work, or system calls should not block the main thread for extended periods. primaryPanelState and componentPanelState should read existing snapshots whenever possible, not synchronously scan hardware, filesystems, or networks from getters.
  • PluginHost is responsible only for deriving common display state such as panel items, component items, and settings items. It caches component views and coalesces short-window state rebuilds; business-data snapshots, cache invalidation, and refresh timing remain the plugin or component's responsibility.
  • After plugin state changes, call onStateChange?() so the host can rebuild derived state. If state can change due to external system events such as display hot-plugging, permission changes, filesystem changes, or calendar authorization changes, wire an explicit observer or refresh entry point with debounce/throttling. Do not depend on users expanding a panel, switching settings pages, or a full refreshAll() to get fresh data. Exception: high-frequency event sources such as input statistics or sampling counters may update only their own snapshots instead of calling onStateChange?() for every event; they must throttle UI notifications by visibility or a fixed time window and ensure refresh() or user panel opening can read the latest snapshot.
  • External state changes with cross-plugin value should be abstracted into Core-layer protocols or observers first. For example, display-topology changes should use DisplayConfigurationObserving to notify the host, then refresh display-related plugins that implement DisplayTopologyRefreshing.
  • Control IDs, plugin IDs, and shortcut IDs must be stable, readable, and preferably centralized in private constants within the feature.
  • Ordinary new plugins do not need root project.yml changes. Keep plugin.json.build.scheme pointing to the bundle scheme; the generator creates the core target, bundle target, test dependencies, and plugin scheme. If a plugin needs extra frameworks, include paths, bundle resources, or target overrides, declare only the minimal delta in Plugins/<PluginName>/project.yml.

Plugin Settings UI Guidelines

  • Plugin settings pages should use the host settings-page framework by default. PluginConfiguration should provide only the current plugin's configuration content; page title, icon, description, permission cards, shortcut cards, and other common regions are derived and rendered by PluginHost/SettingsView. Do not duplicate a full-page title inside custom plugin configuration.
  • Prefer declarative models such as settingsSections, permissionRequirements, and shortcutDefinitions for new settings. Use PluginConfiguration.makeView only for complex interactions, lists, drag and drop, charts, or dedicated managers.
  • Settings theme constants must use MacToolsPluginKit.PluginSettingsTheme. Plugin targets must not depend on Sources/App/SettingsStyle.swift or copy private settings-style definitions. When the theme needs extension, add it to PluginSettingsTheme first so dependencies remain host App -> PluginKit and plugin -> PluginKit.
  • Use FanControlPresetManagerView as the visual baseline for custom plugin settings typography, expressed through PluginSettingsTheme.Typography: pageTitle for page titles and pageDescription for page descriptions; section headers should use Label + SF Symbol + sectionTitle + .foregroundStyle(.secondary); normal row titles should use rowTitle; emphasized row titles or table headers should use emphasizedRowTitle; descriptions, help text, and subtitles should use rowDescription; status badges should use statusBadge; fixed-width numeric readings should use monospacedValue. These tokens should map to Apple platform semantic fonts such as .title2, .body, and .subheadline whenever possible, instead of scattering raw font sizes across plugins.
  • Host settings-page headers use PluginSettingsTheme.Typography.pageTitle + pageDescription. Custom plugin configuration content starts at sections and should not introduce another page-level title.
  • Base custom-configuration layout on Fan Control and prefer PluginSettingsTheme.Spacing: section for outer section spacing, sectionHeaderContent between a section header and content, rowHorizontal for card/list row horizontal padding, rowVertical for normal row vertical padding, interactiveRowVertical for rows containing editors or sliders, rowTitleDescription between row title and description, and rowContentControl between text and controls.
  • Prefer PluginSettingsTheme.Palette and Radius for card/list containers. Use background color, spacing, and corner radius to separate regions; do not add strokes to ordinary settings cards. Host settings cards use cardBackground, custom plugin lists may use macOS native nativeCardBackground, Radius.card is preferred, and large host cards may use Radius.hostCard.
  • Keep control layout stable: buttons should use system .bordered/.borderedProminent styles with .controlSize(.small), switches should use .toggleStyle(.switch), and sliders, pickers, text fields, and similar controls should have explicit minimum/ideal/maximum widths. Numeric text should have fixed width. Long titles and paths should use lineLimit, fixedSize, or text selection to avoid compression and layout jumps during window resizing.
  • Copy should remain Chinese, short, and close to native macOS phrasing. Titles should name the object or setting, subtitles should explain effect or current state, and operation instructions should not become long prose blocks.

Swift Code Style

  • Follow the existing Swift style: small types, clear names, early returns, and minimal global state.
  • Prefer Apple native frameworks. Explain the reason before adding a third-party dependency. Plugin-private system frameworks or include paths should be declared in the plugin's own Plugins/<PluginName>/project.yml.
  • Add OSLog categories through AppLog; avoid bare print in app code.
  • When interacting with AppKit, CoreGraphics, IOKit, EventKit, or other system APIs, preserve failure branches and fallback paths.
  • Validate external inputs such as files, paths, permissions, display IDs, and shortcut bindings before use.
  • Do not write local sensitive configuration such as signing certificates, notarization credentials, bundle prefixes, or development team IDs into the repository.

Feature Safety Boundaries

  • Disk cleanup: do not bypass DiskCleanSafetyPolicy, allowlists, sensitive-path protection, or pre-execution secondary validation. Expanding cleanup scope requires tests.
  • Physical clean mode: preserve an exit path, Accessibility permission guidance, multi-display overlays, and safe exit after sleep or lock.
  • Hide notch: do not destroy the user's original wallpaper; account for multi-display, Space switching, and wallpaper-change scenarios.
  • Display brightness: keep the Apple-native, DDC/CI, and Gamma/Shade fallback chain; external-display failures must not crash.
  • Display resolution: confirm the display is still connected and the target mode still exists before switching; errors should be converted into user-understandable state.
  • Calendar: do not assume permission is granted; insufficient permission should show clear guidance instead of failing silently.
  • Update release: keep Sparkle appcast, version, signing, and notarization changes small and careful; avoid committing local release artifacts.

Testing Requirements

  • Behavior changes should prefer adjacent XCTest additions or updates. Test files should use <TypeName>Tests.swift.
  • Local and agent validation should default to the smallest relevant test method or class, such as -only-testing:MacToolsTests/<TestClassName> or -only-testing:MacToolsTests/<TestClassName>/<testMethod>. Do not run the full suite for narrow changes unless the scope justifies it.
  • Plugin tests should prefer Plugins/<PluginName>/Tests/; shared Core/App tests should live under the corresponding Tests/Core/ or Tests/App/ path.
  • Filesystem tests must use temporary directories or fake stores, and must never delete real user directories.
  • Plugin interaction tests should cover PluginPanelAction, derived PluginPanelState, permission state, and error state.
  • If tests cannot be run, explicitly state the reason and suggest the local verification command in the final response.

Documentation And Resources

  • User-visible feature changes should update README.md.
  • User-visible app or plugin changes should add or update one concise English changelog fragment under changes/unreleased/*.md. Use release: app for app releases and release: plugin for plugin batch releases, plus type: added, changed, fixed, security, removed, deprecated, maintenance, or summary. If one change needs both release channels, write one app fragment for the host/app impact and one plugin fragment for the plugin-package impact; do not duplicate the same sentence. Keep entries user-facing, avoid duplicate wording, and do not describe implementation details. Pure refactors, tests, and local-only release mechanics do not need a fragment unless users or maintainers need to know about them.
  • Plugin directories, manifests, catalogs, or release-flow changes should update docs/plugins/ and CONTRIBUTING.md.
  • Large product/interaction changes may add date-prefixed documents under docs/superpowers/specs/ or docs/superpowers/plans/.
  • Icons, asset catalogs, LocalConfig.xcconfig, and release env files are usually maintained by the user or generation flow; avoid unrelated changes.

Agent Workflow

  • Before modifying code, use rg/rg --files to quickly locate existing patterns and prefer adjacent implementations.
  • Keep changes focused. Do not opportunistically refactor unrelated modules or overwrite existing user edits.
  • After changing project.yml, run or recommend running make generate.
  • Start verification from the smallest related test method or class. Consider full tests or make build only for cross-module changes, shared infrastructure changes, pre-release checks, or explicit user requests.
  • Do not automatically commit, create branches, tag, publish releases, or clean user files unless the user explicitly asks.