Skip to content

Fix convergence gaps in sync engine#234

Merged
bguidolim merged 9 commits intomainfrom
feature/fix-customize-component-removal
Mar 5, 2026
Merged

Fix convergence gaps in sync engine#234
bguidolim merged 9 commits intomainfrom
feature/fix-customize-component-removal

Conversation

@bguidolim
Copy link
Collaborator

Summary

Fixes multiple convergence gaps where the sync engine doesn't properly clean up stale artifacts. All gaps share a root pattern: installArtifacts() rebuilds artifact records from scratch without diffing against previous records, so removed/renamed artifacts are silently dropped from tracking without cleanup.

Stacked on #233 (template dependencies)

Changes

Fix 1: Stale artifact reconciliation (Gaps 1A, 2A)

  • New reconcileStaleArtifacts() diffs previous vs current PackArtifactRecord after installArtifacts(), removing orphaned MCP servers, files, gitignore entries, brew packages, and plugins
  • Catches both component removal/rename and MCP scope changes (MCPServerRef hashes on name+scope)

Fix 2: Stale settings key cleanup (Gaps 4A, 4B)

  • Added previousSettingsKeys parameter to composeSettings protocol method
  • Global scope: strips previous keys with Settings.removeKeys() before recomposing, passes dropKeys to Settings.save() to prevent Layer 3 re-injection
  • Project scope: passes dropKeys to prevent Layer 3 re-injection of stale extraJSON keys from existing file

Fix 3: Stale template section removal (Gap 5A)

  • New reconcileTemplateSections() compares previously-tracked sections against what was written this run
  • Removes orphaned sections from the physical CLAUDE file using TemplateComposer.removeSection()

Fix 0: Component exclusion cleanup

  • New removeNewlyExcludedComponentArtifacts() handles --customize deselection of individual components
  • Compares previous vs current exclusion sets, removes artifacts for newly-excluded components

Refactoring

  • Extracted resolveAllValues(), preloadTemplates(), installAndReconcileArtifacts(), saveStateAndUpdateIndex() from configure() for readability
  • configure() reads as a clear table of contents of the 12-phase convergence flow

Test plan

  • swift build compiles cleanly
  • swiftlint --strict and swiftformat --lint pass on all modified files
  • swift test — 650/654 pass (4 pre-existing PackFetcher failures from 1Password SSH agent)
  • New ConfiguratorExcludedComponentsTests (8 tests): MCP removal, file removal, first-run safety, re-inclusion, template dependency filtering
  • New StaleArtifactReconciliationTests (3 tests): stale file removal, stale settings cleanup, stale template section removal
  • Manual: modify a local pack to remove a component → mcs sync → verify old artifact is gone
  • Manual: mcs sync --global --customize → deselect → verify cleanup

Base automatically changed from feature/158-template-dependencies to main March 5, 2026 17:39
- Add stale artifact reconciliation after installArtifacts (orphaned MCP servers, files, gitignore, brew, plugins)
- Strip stale settings keys on re-sync to prevent Layer 3 re-injection of removed extraJSON/enabledPlugins
- Remove stale template sections from CLAUDE file when pack updates drop sections
- Clean up artifacts for newly excluded components via --customize
- Refactor configure() into focused helper methods (resolveAllValues, preloadTemplates, installAndReconcileArtifacts, saveStateAndUpdateIndex)
@bguidolim bguidolim force-pushed the feature/fix-customize-component-removal branch from 08d341c to d6c6f8b Compare March 5, 2026 17:43
Doctor's globalScope() hardcoded excludedComponentIDs to empty set,
causing excluded components to show as errors instead of skipped.
Also fix --pack flag path which had the same issue.
- Surface project state errors as warnings instead of silently swallowing
- Fix mangled docstring on resolveAllValues (had reconcileStaleArtifacts' doc prepended)
- Add docstring to reconcileStaleArtifacts at its actual location
- Remove unnecessary local aliases in DoctorRunner.resolveCheckScopes
- Hoist duplicate ResourceRefCounter out of per-type branches
- Add Constants.MCPScope.user/local and replace bare "user" strings
- reconcileStaleArtifacts: use inout to re-add failed removals for retry
- reconcileTemplateSections: gate artifact record update on successful file write
- Fix inaccurate docstrings (multi-phase, CLAUDE file, strategy-specific behavior)
- Add comment explaining .shellCommand/.settingsMerge skip in exclusion handler
- Add stale MCP server reconciliation test
- Add ClaudeCLI protocol and conform ClaudeIntegration to it; inject via ComponentExecutor and Configurator
- Create MockClaudeCLI in tests that records method calls, inject into all makeConfigurator helpers
- Update MCP tests to verify removal calls via mock instead of relying on real CLI side effects
- Make Configurator.claudeCLI non-optional with explicit init; resolve default at construction time
- Remove `let claude = claudeCLI` aliases in ComponentExecutor, use property directly
- Simplify test helpers to pass claudeCLI at init instead of two-step mutation
- Add warnings for failed removals in removeNewlyExcludedComponentArtifacts (MCP, file, brew, plugin)
- Reconcile fileHashes in reconcileStaleArtifacts: remove on success, preserve previous hash on failure
- Fix stale docstrings: "12-phase" → "multi-phase", ".mcs-project" → "scope's state file"
- Add `isAvailable` to ClaudeCLI protocol; ClaudeIntegration checks shell, MockClaudeCLI returns true
- Replace `shell.commandExists` guards in ComponentExecutor with `claudeCLI.isAvailable`
- Fixes CI failures where MCP tests failed because `claude` CLI is not installed
@bguidolim bguidolim merged commit f11e620 into main Mar 5, 2026
4 checks passed
@bguidolim bguidolim deleted the feature/fix-customize-component-removal branch March 5, 2026 19:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant