This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Swift CLI tool (mcs) that configures Claude Code with MCP servers, plugins, skills, hooks, and settings. Pure pack management engine with zero bundled content — all features come from external tech packs. Distributed via Homebrew.
# Development
swift build # Build the CLI
swift test # Run tests
swift build -c release --arch arm64 --arch x86_64 # Universal binary
# CLI usage (after install)
mcs sync [path] # Sync project: multi-select packs, compose artifacts (default command)
mcs sync --pack ios # Non-interactive: apply specific packs (repeatable)
mcs sync --all # Apply all registered packs without prompts
mcs sync --dry-run # Preview what would change
mcs sync --customize # Per-pack component selection
mcs sync --global # Sync global scope (MCP servers, brew, plugins to ~/.claude/)
mcs sync --lock # Checkout locked versions from mcs.lock.yaml
mcs sync --update # [DEPRECATED — use 'mcs update'] Fetch latest and force-write mcs.lock.yaml
mcs update # Fetch latest pack versions and re-apply across every configured scope
mcs update --global # Refresh only the global scope
mcs update --project # Refresh only the current project's scope
mcs update --all-projects # Refresh global + every project tracked in ~/.mcs/projects.yaml (asks confirmation)
mcs update --dry-run # Preview what would change
mcs doctor # Diagnose installation health
mcs doctor --fix # Diagnose and auto-fix issues
mcs doctor --pack ios # Only check a specific pack
mcs doctor --global # Check globally-configured packs only
mcs pack add <source> # Add a tech pack (git URL, GitHub shorthand, or local path)
mcs pack add user/repo # GitHub shorthand → https://github.com/user/repo.git
mcs pack add /path/to/pack # Add a local pack (read in-place, no clone)
mcs pack add <url> --ref <tag> # Add at a specific tag, branch, or commit (git only)
mcs pack add <url> --preview # Preview pack contents without installing
mcs pack remove <name> # Remove an external tech pack
mcs pack remove <name> --force # Remove without confirmation
mcs pack list # List registered external packs
mcs pack update [name] # Refresh pack registry only (low-level fetch; use 'mcs update' for fetch + apply)
mcs pack validate [source] # Validate a tech pack (path, identifier, or current directory)
mcs cleanup # Find and delete backup files
mcs cleanup --force # Delete backups without confirmation
mcs export <dir> # Export current config as a tech pack
mcs export <dir> --global # Export global scope (~/.claude/)
mcs export <dir> --identifier id # Set pack identifier (prompted if omitted)
mcs export <dir> --non-interactive # Include everything without prompts
mcs export <dir> --dry-run # Preview what would be exported
mcs check-updates # Check for pack and CLI updates
mcs check-updates --hook # Run as SessionStart hook (24-hour cooldown, respects config)
mcs check-updates --json # Machine-readable JSON output
mcs config list # Show all settings with current values
mcs config get <key> # Get a specific setting value
mcs config set <key> <value> # Set a configuration value (true/false)- Package.swift — swift-tools-version: 6.0, macOS 13+, deps: swift-argument-parser, Yams
- Sources/mcs/ — main executable target
- Tests/MCSTests/ — test target
CLI.swift—@mainstruct,MCSVersion.current, subcommand registration (SyncCommandis the default subcommand)
Constants.swift— centralized string constants (file names, CLI paths, JSON keys, external packs, plugins)Environment.swift— paths, arch detection, brew path, claude-home cwd detection (isInsideClaudeHome(_:))CLIOutput.swift— ANSI colors, logging, prompts, multi-select, doctor summaryShellRunner.swift— Process execution wrapperSettings.swift— Codable model forsettings.jsonandsettings.local.json, deep-mergeBackup.swift— timestamped backups for mixed-ownership files (CLAUDE.local.md), backup discovery and deletionGitignoreManager.swift— global gitignore management, core entry listClaudeIntegration.swift—claude mcp add/remove(with scope support),claude plugin install/removeClaudePrerequisite.swift— Claude Code CLI availability check with optional Homebrew auto-installHomebrew.swift— brew detection, package install/uninstallFileHasher.swift— SHA-256 file and directory hashing via CryptoKit (used byPackTrustManagerandComponentExecutor)FileLock.swift— POSIXflock()process lock andLockedCommandprotocol for mutually exclusive CLI commandsLockfile.swift—mcs.lock.yamlmodel for pinning pack commitsPathContainment.swift— centralized path-boundary checks and relative-path utilities (symlink-safe containment, traversal prevention)PluginRef.swift— parsedname@repoplugin references with marketplace resolutionProjectDetector.swift— walk-up project root detection (.git/orCLAUDE.local.md)SettingsHasher.swift— deterministic SHA-256 hashing of per-pack settings key-value pairs for drift detectionProjectState.swift— per-project.claude/.mcs-projectJSON state (configured packs, per-packPackArtifactRecordwith ownership tracking, version)ProjectIndex.swift— cross-project index (~/.mcs/projects.yaml) mapping project paths to pack IDs for reference countingMCSError.swift— error types for the CLIMCSConfig.swift— user preferences (~/.mcs/config.yaml):update-check-packs,update-check-cli,telemetry,generate-lockfileUpdateChecker.swift— pack freshness checks (git ls-remote), CLI version checks (git ls-remote --tags), cooldown management
TechPack.swift— protocol for tech packs (components, templates, hooks, doctor checks, project configuration)Component.swift— ComponentDefinition with install actions, ComponentType enum, MCPServerConfig (with scope), CopyFileType (with project-scoped directories)PromptDefinition.swift— PromptDefinition, PromptType, PromptOption (prompt types used by TechPack protocol)TechPackRegistry.swift— registry of available packs (external only), filtering by installed stateDependencyResolver.swift— topological sort of component dependencies with cycle detection
ExternalPackManifest.swift— YAMLtechpack.yamlschema (Codable models for components, templates, hooks, doctor checks, configure scripts). Supports shorthand syntax (brew:,mcp:,plugin:,hook:,command:,skill:,agent:,settingsFile:,gitignore:,shell:) that inferstype+installActionfrom a single key.shellInteractive: truealongsideshell:allocates a PTY for commands needing terminal access (e.g.sudo)ExternalPackAdapter.swift— bridgesExternalPackManifestto theTechPackprotocolExternalPackLoader.swift— discovers and loads packs from~/.mcs/packs/(git) or absolute paths (local)PackFetcher.swift— Git clone/pull for pack repositoriesPackSourceResolver.swift— resolves user input into git URL or local path (URL schemes, filesystem, GitHub shorthand)PackRegistryFile.swift— YAML registry of installed external packs (~/.mcs/registry.yaml)PackTrustManager.swift— pack trust verificationPromptExecutor.swift— executes pack prompts (interactive value resolution during sync)ScriptRunner.swift— sandboxed script execution for pack scriptsExternalDoctorCheck.swift— factory for converting YAML doctor check definitions toDoctorCheckinstancesPackHeuristics.swift— heuristic validation checks formcs pack validate(empty pack, root source copy, missing files, unreferenced files, MCP dependency gaps, python module paths)
DoctorRunner.swift— 5-layer check orchestration with project-aware pack resolutionCoreDoctorChecks.swift— check structs (CommandCheck, MCPServerCheck, PluginCheck, HookCheck, GitignoreCheck, CommandFileCheck, FileExistsCheck, FileContentCheck, HookSettingsCheck, SettingsKeysCheck, SettingsDriftCheck, PackGitignoreCheck, ProjectIndexCheck)DerivedDoctorChecks.swift—deriveDoctorCheck()extension on ComponentDefinitionProjectDoctorChecks.swift— project-scoped checks (CLAUDE.local.md freshness, state file)SectionValidator.swift— validation of CLAUDE.local.md section markers
SyncCommand.swift— primary command (mcs sync), handles both project-scoped and global-scoped sync with--pack,--all,--dry-run,--customize,--global,--lock,--updateflags.guardClaudeHomeCwd()at the top ofperform()rejects/redirects runs from~/.claudeor$HOMEto prevent silent corruption of the global scopeDoctorCommand.swift— health checks with optional --fix and --pack filterCleanupCommand.swift— backup file management with --force flagPackCommand.swift—mcs pack add/remove/list/update/validatesubcommands; usesPackSourceResolverfor 3-tier input detection (URL schemes → filesystem paths → GitHub shorthand)ValidatePackCommand.swift—mcs pack validateread-only subcommand; structural validation viaExternalPackLoader.validate(at:)+ heuristic checks viaPackHeuristicsExportCommand.swift— export wizard: reads live configuration and generates a reusable tech pack directory; supports--global,--identifier,--non-interactive,--dry-runCheckUpdatesCommand.swift— lightweight update checker for packs (git ls-remote) and CLI version (git ls-remote --tags); respects config keys and 24-hour cooldownConfigCommand.swift—mcs config list/get/setfor managing user preferences;setimmediately syncs the SessionStart hook in~/.claude/settings.json
ConfigurationDiscovery.swift— reads live config sources (settings, MCP servers, hooks, skills, CLAUDE.md, gitignore), producesDiscoveredConfigurationmodelManifestBuilder.swift— converts selected artifacts into YAML using custom renderer (ordered metadata, section comments, proper quoting)PackWriter.swift— writes output directory with symlink resolution for copied files
Configurator.swift— unified multi-pack convergence engine parameterized bySyncStrategy(artifact tracking, settings composition, CLAUDE file writing, gitignore).unconfigurePack()handles removal for bothmcs sync(deselection) andmcs pack remove(federated across all affected scopes)ConfiguratorSupport.swift— shared utilities forConfiguratorandSyncStrategyimplementations (executor factory, gitignore setup, dry-run summary, settings composition helpers)SyncScope.swift— pure data struct capturing path-level differences between project and global scopesSyncStrategy.swift— protocol isolating scope-specific behavior (artifact installation, settings/CLAUDE composition, file removal)ProjectSyncStrategy.swift— project-scope strategy (settings.local.json, CLAUDE.local.md, repo name resolution)GlobalSyncStrategy.swift— global-scope strategy (settings.json preservation, brew/plugin ownership, MCP scope override to "user")ComponentExecutor.swift— dispatches install actions (brew, MCP servers, plugins, gitignore, project-scoped file copy/removal)CrossPackPromptResolver.swift— deduplicates shared prompt keys across multiple packs (groups by key, executes once for sharedinput/selectprompts)DestinationCollisionResolver.swift— auto-namespacescopyPackFiledestinations when multiple packs target the same(destination, fileType)pairPackInstaller.swift— auto-installs missing pack componentsPackUpdater.swift— shared fetch → validate → trust cycle for updating a single git pack (used byUpdatePackandLockfileOperations)ResourceRefCounter.swift— two-tier reference counting (global artifacts + project index manifests) for safe brew/plugin removalLockfileOperations.swift— reads/writesmcs.lock.yaml, checks out locked versions, updates lockfileSyncDeltaSummary.swift— computes add/remove/keep deltas between previous and selected pack sets and renders the review-changes summary shown before destructive sync operations
TemplateEngine.swift—__PLACEHOLDER__substitutionTemplateComposer.swift— section markers for composed files (<!-- mcs:begin/end -->), section parsing, user content preservation
SwiftFormat and SwiftLint enforce consistent code style. Both are installed via Homebrew.
# Format modified files (run before committing)
swiftformat <file-or-directory>
# Lint — check without modifying (CI runs these with --strict)
swiftformat --lint .
swiftlint
# Auto-fix lint issues
swiftlint --fix- SwiftFormat runs first, then SwiftLint — SwiftFormat owns formatting rules, SwiftLint owns semantic rules
- Config files:
.swiftformatand.swiftlint.ymlat project root - CI runs both in strict mode (warnings become errors) with GitHub Actions inline annotations
- SwiftLint excludes
Tests/— only Sources and Package.swift are linted - Never use
try?to silently discard errors — usedo/catchand surface the error (viaoutput.warn(), logging, or propagation).try?hides root causes and makes debugging impossible. The only acceptable use is when the absence of a result is the entire semantic meaning (e.g.,FileManager.fileExistsalternative)
- Test files mirror source:
FooTests.swifttestsFoo.swift - Run a single test class:
swift test --filter MCSTests.FooTests - Tests construct all state inline; no external fixtures or shared setup
- Important:
swift testoutput does not display in Claude Code's terminal. Redirect to a file and read it:swift test > .test-output/results.txt 2>&1then read.test-output/results.txt - Integration tests are mandatory for new features that touch components, settings composition, hook entries, or doctor checks — add cases to
LifecycleIntegrationTests.swift(sync → doctor lifecycle) orDoctorRunnerIntegrationTests.swift(doctor-specific flows) - Integration tests use
LifecycleTestBedfor sandbox setup — see existing tests for the pattern
- Never amend commits — always create new commits so the change history stays trackable
- Never force-push — use regular
git pushonly - Always run
swiftformatandswiftlinton changed files before committing — CI will reject PRs that fail lint. Runswiftformat <changed-files>thenswiftlintto catch issues locally
- Pure engine, zero bundled content:
mcsships no templates, hooks, settings, or skills — all features come from external packs users add viamcs pack add mcs syncis the primary command: per-project multi-select of registered packs, fully idempotent convergence (add/remove/update), per-project artifact placement.--globalflag handles global-scope install- Per-project artifacts: skills, hooks, commands, and
settings.local.jsongo to<project>/.claude/; only brew packages and plugins are global - MCP scope defaults to
local: per-user, per-project isolation viaclaude mcp add -s local(stored in~/.claude.jsonkeyed by project path) - Convergent sync:
ProjectStaterecords per-packPackArtifactRecord(MCP servers, files, template sections, hook commands, settings keys, settings hash, brew packages, plugins, gitignore entries, file hashes); re-running converges to desired state by diffing previous vs. selected packs.mcs pack removediscovers all scopes viaProjectIndexand runs the sameunconfigurePack()convergence for each - External pack protocol:
TechPackprotocol withExternalPackAdapterbridging YAML manifests (techpack.yaml) to the same install/doctor/sync flows - Section markers: composed files use
<!-- mcs:begin/end -->HTML comments to separate tool-managed content from user content - Settings composition: each pack's hook entries compose into
<project>/.claude/settings.local.jsonas individualHookGroupentries - Backup for mixed-ownership files: timestamped backup before modifying files with user content (CLAUDE.local.md); tool-managed files are not backed up since they can be regenerated
- Component-derived doctor checks:
ComponentDefinitionis the single source of truth —deriveDoctorCheck()auto-generates verification frominstallAction, supplementary checks handle extras - Project awareness: doctor detects project root (walk-up for
.git/), resolves packs from.claude/.mcs-projectbefore falling back to section marker inference, then to global manifest - Lockfile support (opt-in):
mcs.lock.yamlpins pack commits for reproducible builds. Generation is opt-in — enable withmcs config set generate-lockfile trueto write on every sync, or pass--updateto fetch latest and write once.--lockchecks out pinned commits from an existing lockfile. Tri-state semantics ongenerate-lockfile:truewrites,falseis silent (explicit opt-out),nil(never configured) surfaces a one-time drift warning if a stale lockfile exists — the upgrade nudge - Local packs:
mcs pack add /pathregisters a pack read in-place — no git clone, nomcs pack update, no directory deletion on remove. UsesisLocal: Bool?onPackEntry(backward-compatible) andcommitSHA: "local"sentinel. Trust verification is skipped since scripts change during development - GitHub shorthand:
mcs pack add user/repoexpands tohttps://github.com/user/repo.git. Filesystem paths are checked before shorthand regex to prevent ambiguity with relative paths likeorg/pack - Cross-project reference counting:
ProjectIndex(~/.mcs/projects.yaml) tracks which projects use which packs;ResourceRefCounterchecks all scopes before removing shared brew packages or plugins. Conservative by default — if state is unreadable, assume resource is still needed. MCP servers are project-independent (scoped via-s local) and skip ref counting - Conditional copyPackFile namespacing:
copyPackFiledestinations are installed flat by default. When two+ packs define the same(destination, fileType), theDestinationCollisionResolverauto-namespaces: subdirectory prefix (<pack-id>/) for hooks/commands/agents/generic, or directory name suffix (-<pack-id>) for skills (which require flat one-level directories). First pack keeps the clean name; subsequent packs get namespaced. Skill renames emit a warning