Skip to content

ISSUE-70: Show component-level diff when mcs pack update updates a pack - #361

Merged
bguidolim merged 3 commits into
mainfrom
bruno/ISSUE-70-pack-update-diff
Jul 30, 2026
Merged

ISSUE-70: Show component-level diff when mcs pack update updates a pack#361
bguidolim merged 3 commits into
mainfrom
bruno/ISSUE-70-pack-update-diff

Conversation

@bguidolim

@bguidolim bguidolim commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #70.

mcs pack update reported only a commit SHA, so an update told you a pack had changed but never what changed. The only existing visibility was the trust prompt, which lists scripts, and only when trust is actually needed. Updates now print what moved — added, removed, and modified components, templates, prompts, doctor checks, and the configure script — where "modified" means either a rewritten declaration or an edited file that a declaration points at.

Changes

  • The same summary appears for mcs update; long lists cap at ten lines with a remainder count.
  • An update that touches only files no declaration references (README, CI config) prints no summary.
  • A pack whose previous manifest no longer decodes still updates — it just reports that no summary is available, rather than stranding the user on the broken revision.

Test plan

  • swift test passes locally
  • swiftformat --lint . and swiftlint pass without violations
  • Affected commands verified with a real pack (e.g. mcs sync, mcs doctor)

Not verified against a live pack: Environment resolves home through NSHomeDirectory(), which ignores a HOME override, so a sandboxed run reads the real ~/.mcs and updating for real would mutate the reviewer's own packs. To check by hand, push three commits to a scratch pack — one adding a component, one editing only a hook script body, one removing a component — then run mcs pack update <id> after each and expect a +, a ~ naming the script, and a - respectively.

- Snapshot the pack before the fetch resets the working tree — --depth 1 clones make the previous commit unreadable once reset --hard lands
- Diff every manifest surface by declaration and by referenced-file content, so a script-body edit is reported even when the YAML is untouched
- Move referencedPaths onto the manifest so the diff and ignore: validation share one definition of what a pack references

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an inline, component-level change summary to pack update flows so users can see what changed in an updated external pack (components/templates/prompts/doctor checks/configure script), not just the new commit SHA. It does this by snapshotting the pre-update manifest + referenced file hashes, computing a structured diff after fetching, and rendering that diff under mcs pack update and mcs update output.

Changes:

  • Introduces PackSnapshot + PackDiff to compute and render added/modified/removed entries (with truncation for long diffs).
  • Extends PackUpdater.UpdateResult.updated to carry an optional diff, and prints the diff in both mcs pack update and mcs update.
  • Centralizes “referenced pack file” path normalization via ExternalPackManifest.referencedPaths and adds Equatable conformance needed for diffing.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Tests/MCSTests/PackUpdaterTests.swift Adds integration-style tests asserting update results include expected diffs (added/removed/modified/unreferenced/no-diff-available).
Tests/MCSTests/PackDiffTests.swift Adds pure tests for diff construction and rendering behavior (ordering, paths, truncation).
Tests/MCSTests/ExternalPackManifestTests.swift Adds a “surface drift” tripwire to keep diffing + referenced-path traversal in sync with manifest properties.
Sources/mcs/TechPack/PromptDefinition.swift Adds Equatable to enable prompt diffing.
Sources/mcs/Sync/PackUpdater.swift Captures a pre-fetch snapshot and returns .updated(updatedEntry, diff:) with optional diff; currently emits a “no summary” message.
Sources/mcs/Sync/PackDiff.swift New diff/snapshot implementation and renderer for change summaries.
Sources/mcs/ExternalPack/PackHeuristics.swift Switches heuristic referenced-path use to manifest.referencedPaths and simplifies root-copy detection.
Sources/mcs/ExternalPack/ExternalPackManifest.swift Adds normalized referenced-path helpers and referencedPaths; adds Equatable to multiple manifest model types.
Sources/mcs/Commands/UpdateCommand.swift Prints per-pack diffs under mcs update success lines when available and non-empty.
Sources/mcs/Commands/PackCommand.swift Prints per-pack diffs under mcs pack update success lines when available and non-empty.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +168 to +172
diff = PackDiff.between(old: beforeSnapshot, new: after)
} else {
diff = nil
output.dimmed("\(entry.displayName): no change summary available")
}
Comment on lines +29 to +39
for path in manifest.referencedPaths {
let fileURL = packPath.appendingPathComponent(path)
do {
hashes[path] = try FileHasher.sha256(of: fileURL)
} catch {
// Absent or unreadable: leave the key out. Its absence on one side and presence
// on the other is exactly the "changed" signal we want, and a manifest that
// references a missing file is a pack-validation problem, not a diff problem.
continue
}
}
- GitignoreManager resolved the process home, so any sandboxed caller — including the test suite — read and wrote the real user's ~/.config/git/ignore
- Pass HOME to git config --global so --global resolution stays inside the sandbox too
- Seed the sandbox gitignore in the shared test fixture; without it every doctor run in a sandbox carried an unrelated "global gitignore not found" issue
…ate line

- Hash directory sources into a combined per-file digest; skills ship as directories, so edits inside them were invisible to the change summary
- Move the "no change summary available" notice to the callers, which print it after the success line instead of before
- Cover both directory-content change and directory-unchanged cases

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

Sources/mcs/Sync/PackDiff.swift:194

  • PackDiff currently diffs supplementaryDoctorChecks only by the YAML declaration (Equatable) and never consults file hashes, so content-only edits to a .shellScript doctor check’s script file (and its fixScript file) won’t show up as ~ modified changes. This contradicts the PR goal of reporting edits to referenced files, and it also won’t work until those doctor-check script paths are included in ExternalPackManifest.referencedPaths/PackSnapshot hashing.
        entries += diffKeyed(
            kind: .doctorCheck,
            old: old.manifest.supplementaryDoctorChecks ?? [],
            new: new.manifest.supplementaryDoctorChecks ?? [],
            key: \.name
        )

Sources/mcs/Sync/PackDiff.swift:73

  • The PackDiff doc comment lists only components/templates/doctor checks, but the implementation also diffs prompts and the configure script. This makes the documentation misleading for callers/users reading the source.
/// What changed in a pack between two revisions, at the granularity a pack author declares:
/// components, template sections, and supplementary doctor checks.

@bguidolim
bguidolim merged commit e761f94 into main Jul 30, 2026
5 checks passed
@bguidolim
bguidolim deleted the bruno/ISSUE-70-pack-update-diff branch July 30, 2026 23:20
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.

Show component-level diff when mcs pack update updates a pack

2 participants