Contributing to MacTools
August 19, 2026 · View on GitHub
[中文] [English]
Thanks for your interest in MacTools. Please keep each contribution small and clear: explain the problem, provide verifiable changes, and avoid mixing unrelated refactors into the same pull request.
Ways to Contribute
- Bug reports should include reproduction steps, expected behavior, actual behavior, macOS version, and relevant logs or screenshots.
- Feature suggestions should describe the use case, target users, and expected interaction. For large plugins or interaction changes, open an issue first to align on scope.
- Changes involving file deletion, system permissions, global shortcuts, display control, signing, or update flows should explain risks, safeguards, and rollback options.
Development Environment
- Xcode and
xcodegenare required. The project supports macOS 14.0 and later. - First-time setup: run
make setup, then editLocalConfig.xcconfigand fill inDEVELOPMENT_TEAMand a stable, non-placeholderBUNDLE_IDENTIFIER_PREFIX. Debug builds fail early when either value is missing so macOS cannot register a malformed duplicate app identity. - Use
make runfor local app testing. It installs the canonical Debug app at~/Applications/MacTools Dev.appand unregisters otherMacTools Devbuild copies from LaunchServices. - Common commands:
make generategenerates the Xcode project,make buildvalidates compilation, andmake runinstalls the verified Debug bundle at~/Applications/MacTools Dev.appbefore running it locally. - Plugin development:
make runincrementally builds the app and plugins, then syncs the latest Debug plugin packages to the local development marketplace. A full sync moves packages absent from the current checkout into the recoverable Debug quarantine; a filteredPLUGIN=...sync leaves unrelated packages untouched.make sync-debug-pluginsonly syncs already built plugins.make build-pluginis reserved for validating dynamic plugin packages or release flows; to build one plugin, runmake build-plugin PLUGIN=calendar. - Do not commit local or generated files:
MacTools.xcodeproj,MacTools.xcworkspace,LocalConfig.xcconfig,build/, orscripts/release.local.env.
Project Structure
Sources/App/: app entry point, menu bar status item, settings pages, and window routing.Sources/Core/: shared infrastructure such as the plugin host, dynamic plugin loading, shortcuts, permissions, logging, and updates.Sources/MacToolsPluginKit/: plugin APIs, declarative UI models, and runtime context.Plugins/<PluginName>/: plugin manifest, source code, bundle entry point, resources, and adjacent tests.Tests/: XCTest coverage for shared App/Core logic. Plugin tests should live inside the corresponding plugin directory when possible.project.yml: root XcodeGen project source, only for the App, PluginKit, and shared aggregate entry points. Plugin targets are generated automatically.Plugins/<PluginName>/project.yml: optional per-plugin build overrides, only when a plugin needs extra frameworks, include paths, bundle resources, helper/tool targets, or target overrides.docs/plugins/: plugin packages, catalogs, local debugging, and release flow documentation.docs/icon-gallery/: checked-in menu-bar icon catalog, previews, animation frames, and archives; rendering-mode rules are documented indocs/icon-gallery.md.docs/superpowers/: larger product, interaction, or implementation design documents.
Development Guidelines
- Add new plugins under
Plugins/<PluginName>/with at leastplugin.json,Sources/, andBundle/. - Ordinary plugins only need to define
plugin.json, source code, and a bundle entry point.make generatescansPlugins/*/plugin.jsonand generates localConfigs/GeneratedPlugins.yml; do not edit generated files manually. - Features that require macOS app extensions, such as Finder Sync, must add the extension target to the root
project.ymland embed it in the main app. Use the dynamic plugin only for the MacTools panel/settings surface. - Command workflows for adding and updating plugins are documented in the Development Steps section of
docs/plugins/local-native-plugins.md. - Keep documentation short and task-focused. User-visible behavior changes should update
README.mdor the relevant file underdocs/; plugin package, catalog, or release flow changes should updatedocs/plugins/. - Keep
CHANGELOG.mdas the canonical release history. Do not editSources/Resources/ReleaseHistory.jsonby hand; release preparation regenerates it, andpython3 scripts/changelog.py export-historyrepairs it after intentional historical edits. - Icon gallery assets must explicitly declare
renderingMode; usetemplateonly for black artwork on transparency, and useoriginalfor color or grayscale detail. Third-party static assets must pin their upstream revision and catalog mapping indocs/icon-gallery/sources/manifest.json, with the corresponding license underSources/Resources/ThirdPartyNotices/. Run the gallery generation and related tests after catalog changes. - Plugins implement
MacToolsPlugin; menu panel plugins implementPluginPrimaryPanel, and component panel plugins implementPluginComponentPanel. plugin.json.idmust be stable, readable, and exactly match the runtimePluginMetadata.id; each plugin package should return exactly one plugin instance.- Plugin display state should be expressed through
PluginPanelState,PluginPanelDetail,PluginPanelControl, and related models. Do not bypass the existing panel framework. - Prefer
PluginSettingsPage.formwith declarative sections and typed controls. Use a custom form section for a complex region andPluginSettingsPage.workspaceonly for task-oriented managers or editors that need the full content area. Permissions, shortcuts, page chrome, search, validation, and backgrounds remain host-owned. - Publish executable plugin capabilities through stable
PluginActionProvidingdefinitions. Keep action discovery and execution in the host-owned registry/executor, and keep ordinary global bindings inShortcutAssignmentService; workflows, Run Links, and Action Grid must reference those actions instead of defining parallel command, shortcut, or URL paths. The architecture and automated test matrix are documented indocs/actions-automation.md. - A plugin may implement
PluginActionExposureProvidingto veto a canonical action on a host-owned system surface such as App Intents. Treat.automaticas delegation to the host's conservative eligibility policy, never as an allowlist override. Unknown surfaces and provider failures fail closed, and the executor rechecks the live policy immediately before starting the action. Run Link policy remains a separate contract. - When a plugin adopts an action-surface or other newly exported PluginKit type, set its
plugin.json.minHostVersionto the first compatible app release. Compatibility metadata belongs in the source manifest. When local plugin validation requires that unreleased host version,MARKETING_VERSIONmay predeclare it; the app release helper treats a source version ahead of the latest app tag as the default release target and remains responsible for advancingCURRENT_PROJECT_VERSION. Release tooling owns plugin package version bumps. - If ordinary plugin resources rarely change, prefer bundling them into the executable. If extra bundle resources are needed, declare the smallest necessary differences in the plugin's own
project.yml. - Custom plugin settings views must reuse
MacToolsPluginKit.PluginSettingsThemeand.pluginSettingsCardBackground(.standard/.recessed). Do not copy private plugin settings styles, and do not make plugins depend onSources/App/SettingsStyle.swift. - Call
onStateChange?()after plugin state changes. Long-running scans, file system work, and system calls should not block the main thread for extended periods. - User-facing copy is primarily Chinese. Keep it concise, clear, and close to native macOS wording.
- Localize user-facing copy with
.xcstrings. App/Core copy belongs underSources/Resources/Localization, PluginKit copy underSources/MacToolsPluginKit/Resources, and plugin copy underPlugins/<PluginName>/Resources. Pluginplugin.jsonfiles should keepdisplayName/summaryas fallbacks and addlocalizedMetadatafor marketplace and unloaded-plugin presentation. - New plugins should provide localization whenever practical, at minimum for panel copy, settings copy, permission text, and plugin metadata.
- Prefer Apple native frameworks. When adding system frameworks, private include paths, or helper executables inside a plugin bundle, declare the smallest necessary differences in the plugin's own
project.yml. Bundle resource executables that need separate signing should be listed inplugin.json.package.signPaths. - Plugins that use private Apple frameworks must load them dynamically at runtime and validate the required classes and selectors. Do not statically link private frameworks, and surface unsupported-system errors instead of crashing.
- Plugins that intercept pointer events must declare Accessibility permission, stop their event tap on deactivation, and re-enable a tap disabled by macOS.
Testing
- Behavioral changes should add or update adjacent XCTest coverage. Test files should be named
<TypeName>Tests.swift. - Full test command:
xcodebuild -project MacTools.xcodeproj -scheme MacTools -configuration Debug -derivedDataPath build/DerivedData test -quiet. - Single test class: append
-only-testing:MacToolsTests/<TestClassName>to the full test command. - File system tests should use temporary directories or fake stores. Disk cleanup tests must not delete real user directories.
Pull Request Checklist
- Keep the PR focused, and explain the purpose, verification, and user impact.
- Prefer English for commit messages, pull request titles/descriptions, and issues.
- Build or tests have passed. If they could not be run, explain why in the PR.
- User-visible behavior changes are reflected in
README.mdor the relevant design documentation. - User-visible app or plugin changes include a concise English changelog fragment in
changes/unreleased/*.md. - Plugin manifest
capabilities.settings(none,form, orworkspace) matches the runtimesettingsPagelayout. - High-risk features cover safety checks, error states, and missing-permission cases.
- The PR does not include unrelated formatting, generated files, local configuration, certificates, or release credentials.
Release
- Releases are handled by maintainers. Do not create tags, publish GitHub Releases, or commit release artifacts in ordinary contributions.
- For GitHub-based releases, prefer
Actions->Prepare Release. Entertype, targetversion, and whether torelease; whenreleaseis enabled, the workflow continues to the actual release workflow after bumping, committing, and creating the tag. - For quick releases, prefer
make release. The command interactively choosesapporplugin, analyzes the nextpatch/minor/majorversion, previews the bump, then only after confirmation runsgit pull --rebase, lightweight checks, version updates, commit, tag creation, and tag push. - App releases update
MARKETING_VERSIONandCURRENT_PROJECT_VERSIONinConfigs/AppVersion.xcconfig. The app and embedded extensions inherit this shared version config. After pushing av*.*.*tag, theReleaseworkflow builds, signs, notarizes, uploads the DMG, marks the stable App release as GitHub Latest, and updates the Appcast plus website download metadata. - Plugin releases push a
plugins-*batch tag. The defaultautomode uses the production catalog to find new plugins, already bumped plugins, and package-related plugin changes; it updatesplugin.json.versionwhen needed, then thePlugin Releaseworkflow builds plugins and merges the signed catalog. Plugin batch releases are never marked as GitHub Latest. When a batch raises individual plugins'minHostVersion, publish and verify the signed plugin catalog before releasing that host version; the catalog keeps its oldest schema-compatible host floor and older apps leave newer entries unavailable. - On first launch, a new app version checks installed plugins and automatically updates them from the signed production catalog. It does not normally install plugins the user has not installed. The only exception is a host-declared feature-extraction migration: when an installed source plugin still owns a legacy preference, the host may runtime-validate the replacement package and update the source as one rollback-protected operation. Manually installing that replacement also coordinates retirement of an older source package, even before the legacy preference has been written.
- Non-interactive examples:
make release ARGS="--type app --version 1.0.7 --yes"ormake release ARGS="--type plugin --version 1.0.10 --plugin-mode selected --plugin calendar --yes". - Add
--dry-runto preview the steps. The working tree must be clean before a real release. - Before local release builds, copy
scripts/release.local.env.sampletoscripts/release.local.envand fill in at leastDEVELOPER_ID_APPLICATION. - If Apple notarization is needed, store credentials first with
xcrun notarytool store-credentials. - Version numbers default to
MARKETING_VERSIONandCURRENT_PROJECT_VERSIONinConfigs/AppVersion.xcconfig. - Local production builds can still use the lower-level script:
./scripts/release-local.sh; before publishing to GitHub Releases, rungh auth login, then./scripts/release-local.sh --publish. - Plugin library releases are triggered by
plugins-*batch tags through thePlugin Releaseworkflow. Within one PluginKit ABI line, plugins with bumped versions are built and uploaded, then merged into that line's catalog. Changes underSources/MacToolsPluginKit/require rebuilding and bumping every plugin so the catalog cannot retain binaries linked against an older shared framework. The standardmake releaseflow performs these manifest bumps in the release commit; feature PRs should not pre-bump unrelated plugins. The first release of a new ABI also rebuilds every plugin and writes a versioned catalog. MacTools through 1.1.6 keeps reading the immutable PluginKit v4 catalog atdocs/plugins/v4/catalog.json; MacTools 1.2 and later use PluginKit v5 atdocs/plugins/v5/catalog.json. Publish the v5 plugin batch and catalog first, wait for Pages to serve the committed signed catalog, and only then prepare or publish the 1.2 app. The app release helper and final release workflow fail closed unless that deployed catalog exactly matches the committed catalog and has a valid signature. The catalog private key, Developer ID certificate, and GitHub token must come from CI secrets or local environment variables. - GitHub Actions build and release configuration is documented in
docs/github-actions.md; plugin catalog, package structure, and batch release flows are documented indocs/plugins/plugin-catalog.md.