From 5575872d4b359c984ccce04bcc3a5eb7adea45d0 Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Fri, 6 Mar 2026 10:10:45 +0100 Subject: [PATCH 1/2] Move file path derivation for copyPackFile into SyncStrategy - Add `fileRelativePath(destination:fileType:)` to SyncStrategy protocol - Implement in GlobalSyncStrategy and ProjectSyncStrategy with scope-specific path logic - Remove `deriveFileRelativePath` from Configurator, eliminating isGlobalScope branch --- Sources/mcs/Install/Configurator.swift | 22 +------------------ Sources/mcs/Install/GlobalSyncStrategy.swift | 18 ++++++++++----- Sources/mcs/Install/ProjectSyncStrategy.swift | 11 ++++++++++ Sources/mcs/Install/SyncStrategy.swift | 6 +++++ 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/Sources/mcs/Install/Configurator.swift b/Sources/mcs/Install/Configurator.swift index 6c15c5a..86db972 100644 --- a/Sources/mcs/Install/Configurator.swift +++ b/Sources/mcs/Install/Configurator.swift @@ -452,7 +452,7 @@ struct Configurator { } case let .copyPackFile(_, destination, fileType): - let relativePath = deriveFileRelativePath( + let relativePath = strategy.fileRelativePath( destination: destination, fileType: fileType ) if removeFileArtifactItem(relativePath: relativePath) { @@ -519,26 +519,6 @@ struct Configurator { } } - /// Derive the relative file path that `installArtifacts` would have recorded for a `copyPackFile` component. - private func deriveFileRelativePath(destination: String, fileType: CopyFileType) -> String { - if scope.isGlobalScope { - let baseDir = fileType.baseDirectory(in: environment) - let destURL = baseDir.appendingPathComponent(destination) - return PathContainment.relativePath( - of: destURL.path, - within: environment.claudeDirectory.path - ) - } else { - let projectPath = scope.targetPath.deletingLastPathComponent() - let baseDir = fileType.projectBaseDirectory(projectPath: projectPath) - let destURL = baseDir.appendingPathComponent(destination) - return PathContainment.relativePath( - of: destURL.path, - within: projectPath.path - ) - } - } - /// Resolve all template/placeholder values upfront (single pass). /// /// Resolves built-in values (e.g. REPO_NAME), shared cross-pack prompts, diff --git a/Sources/mcs/Install/GlobalSyncStrategy.swift b/Sources/mcs/Install/GlobalSyncStrategy.swift index 01127e3..b41f336 100644 --- a/Sources/mcs/Install/GlobalSyncStrategy.swift +++ b/Sources/mcs/Install/GlobalSyncStrategy.swift @@ -102,12 +102,7 @@ struct GlobalSyncStrategy: SyncStrategy { resolvedValues: resolvedValues ) if result.success { - let baseDir = fileType.baseDirectory(in: environment) - let destURL = baseDir.appendingPathComponent(destination) - let relativePath = PathContainment.relativePath( - of: destURL.path, - within: environment.claudeDirectory.path - ) + let relativePath = fileRelativePath(destination: destination, fileType: fileType) artifacts.files.append(relativePath) artifacts.fileHashes.merge(result.hashes) { _, new in new } if component.type == .hookFile, @@ -301,6 +296,17 @@ struct GlobalSyncStrategy: SyncStrategy { return Set(allContributions.map(\.sectionIdentifier)) } + // MARK: - File Path Derivation + + func fileRelativePath(destination: String, fileType: CopyFileType) -> String { + let baseDir = fileType.baseDirectory(in: environment) + let destURL = baseDir.appendingPathComponent(destination) + return PathContainment.relativePath( + of: destURL.path, + within: environment.claudeDirectory.path + ) + } + // MARK: - File Removal func removeFileArtifact(relativePath: String, output: CLIOutput) -> Bool { diff --git a/Sources/mcs/Install/ProjectSyncStrategy.swift b/Sources/mcs/Install/ProjectSyncStrategy.swift index 844b0c2..13b4127 100644 --- a/Sources/mcs/Install/ProjectSyncStrategy.swift +++ b/Sources/mcs/Install/ProjectSyncStrategy.swift @@ -210,6 +210,17 @@ struct ProjectSyncStrategy: SyncStrategy { return Set(allContributions.map(\.sectionIdentifier)) } + // MARK: - File Path Derivation + + func fileRelativePath(destination: String, fileType: CopyFileType) -> String { + let baseDir = fileType.projectBaseDirectory(projectPath: projectPath) + let destURL = baseDir.appendingPathComponent(destination) + return PathContainment.relativePath( + of: destURL.path, + within: projectPath.path + ) + } + // MARK: - File Removal func removeFileArtifact(relativePath: String, output: CLIOutput) -> Bool { diff --git a/Sources/mcs/Install/SyncStrategy.swift b/Sources/mcs/Install/SyncStrategy.swift index fd16b7d..4eca0ef 100644 --- a/Sources/mcs/Install/SyncStrategy.swift +++ b/Sources/mcs/Install/SyncStrategy.swift @@ -69,6 +69,12 @@ protocol SyncStrategy { output: CLIOutput ) throws -> Set? + /// Derive the relative file path that artifact tracking records for a `copyPackFile` component. + /// + /// Global scope computes relative to `~/.claude/`. + /// Project scope computes relative to the project root. + func fileRelativePath(destination: String, fileType: CopyFileType) -> String + /// Remove a file artifact during pack unconfiguration. /// /// Project scope uses `ComponentExecutor.removeProjectFile`. From fdaa924bc2dd8955853e3666ff75f1e8d8428cef Mon Sep 17 00:00:00 2001 From: Bruno Guidolim Date: Fri, 6 Mar 2026 10:15:29 +0100 Subject: [PATCH 2/2] Use existing destinationURL helper in GlobalSyncStrategy.fileRelativePath - Replace manual baseDirectory + appendingPathComponent with CopyFileType.destinationURL(in:destination:) --- Sources/mcs/Install/GlobalSyncStrategy.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/mcs/Install/GlobalSyncStrategy.swift b/Sources/mcs/Install/GlobalSyncStrategy.swift index b41f336..36af97f 100644 --- a/Sources/mcs/Install/GlobalSyncStrategy.swift +++ b/Sources/mcs/Install/GlobalSyncStrategy.swift @@ -299,8 +299,7 @@ struct GlobalSyncStrategy: SyncStrategy { // MARK: - File Path Derivation func fileRelativePath(destination: String, fileType: CopyFileType) -> String { - let baseDir = fileType.baseDirectory(in: environment) - let destURL = baseDir.appendingPathComponent(destination) + let destURL = fileType.destinationURL(in: environment, destination: destination) return PathContainment.relativePath( of: destURL.path, within: environment.claudeDirectory.path