Skip to content

#338: Filter non-material upstream commits from update notifications#343

Merged
bguidolim merged 6 commits intomainfrom
bruno/338-update-check-noise-filter
Apr 27, 2026
Merged

#338: Filter non-material upstream commits from update notifications#343
bguidolim merged 6 commits intomainfrom
bruno/338-update-check-noise-filter

Conversation

@bguidolim
Copy link
Copy Markdown
Collaborator

@bguidolim bguidolim commented Apr 24, 2026

Summary

Upstream pack repos accumulate README, LICENSE, CI, and .github/ commits that never affect what mcs sync installs. Every one of these fires the SessionStart hook's "pack update available" banner, training users to ignore it. This PR filters those commits out so the notification only fires on commits that actually change the install surface — closes the first half of #338.

Changes

  • After git ls-remote detects a new SHA, the update check now runs a shallow fetch + name-only diff in the local pack clone and classifies the changed-path list. If every path is deny-listed infrastructure (README, LICENSE, CHANGELOG, CONTRIBUTING, .gitignore, .editorconfig, .dockerignore, package/lock files, Makefile, Dockerfile, .git/, .github/, .gitlab/, .vscode/, node_modules/, __pycache__, .build/), the notification is suppressed and the local pack-registry baseline advances to the new remote SHA so the same commits don't re-trigger on future cooldown windows. Diff output is trimmed with .whitespacesAndNewlines so trailing \r from CRLF-configured git doesn't bypass the deny-list.
  • techpack.yaml changes are always material, regardless of what else is in the diff — a manifest edit can swap hook scripts, MCP server commands, or the install surface entirely, so silent suppression would be a supply-chain risk.
  • Filter failures (offline, fetch error, diff error, missing clone) surface the notification unfiltered — the filter can only suppress, never manufacture silence.
  • Argument-injection guard: entry.ref is validated via isValidGitRef before any git invocation. A corrupted registry ref (e.g. starting with -) is skipped without invoking git.
  • Concurrency: the post-loop registry write goes through a non-blocking withFileLock(at: ~/.mcs/lock). The SessionStart hook is not a LockedCommand (it stays non-blocking by design); without the lock it could race mcs pack add/update and clobber a just-added entry. On contention the advance is skipped and the next check retries.
  • Annotated-tag correctness: parseRemoteSHA walks every ls-remote line and prefers the peeled ^{} commit when present, so the registry stores the commit SHA (not the tag-object SHA). Without this, the registry would desync from the checkout's rev-parse HEAD and trigger PackUpdater's "disk ahead of registry" recovery path.
  • Phase 1 of the Update checker flags every commit; filter out noise files (README, CI, LICENSE) to reduce false positives #338 noise filter. An author-supplied ignore: field on techpack.yaml and a validate-time remediation hint ship in follow-up PRs.

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)

Manual end-to-end:

  1. mcs pack add <a test pack>; note the commitSHA in ~/.mcs/registry.yaml.
  2. Push a README-only commit upstream; delete ~/.mcs/update-check.json to bust the cache; run mcs doctor → expect no "pack update available" notification, and expect commitSHA in ~/.mcs/registry.yaml to advance to the new upstream SHA.
  3. Push a commit that changes a file the pack actually installs (e.g. a hook script); bust the cache again; run mcs doctor → expect the notification.
  4. Push a commit touching techpack.yaml only; bust the cache; run mcs doctor → expect the notification.
  5. Disconnect from the network, repeat step 3 → expect the notification to surface regardless (never-hide).
Checklist for engine changes
  • Docs updated if behavior changed (CLAUDE.md, docs/, techpack.yaml schema in ExternalPackManifest.swift)

- Shallow git fetch + name-only diff after ls-remote detects a new SHA; when every changed path is deny-listed infrastructure (README, LICENSE, CHANGELOG, .github/, node_modules/, .build/, Makefile), the notification is suppressed and the registry baseline advances so the same commits don't re-trigger on future cooldown windows.
- techpack.yaml changes always surface unfiltered — manifest edits can swap hook scripts and MCP commands, so suppressing them would be a supply-chain risk.
- Fetch or diff failure surfaces the notification unfiltered (never-hide invariant); pipeline covered by pure classifier + mock-shell orchestrator + end-to-end PackRegistryFile write-back tests.
…y, doc-rot

- Collapse PackCheckOutcome+SHAAdvance into a private enum (.emit / .advance) so the unreachable "both set" state can no longer produce a stuck-update bug; UpstreamChange.unknown gains an UnknownReason payload (missingClone/fetchFailed/diffFailed) for telemetry; classifyDiffPaths uses .filter; never-hide invariant comment now correctly scopes to classifyUpstreamChange (the earlier ls-remote/parse-error guard is documented as a separate "couldn't determine if there's a change at all" path).
- MockShellRunner is now thread-safe (NSLock around runResults / runCalls / shellResults) and gains runResultsByFirstArg for argument-keyed dispatch — required for parallel tests where DispatchQueue.concurrentPerform interleaves calls non-deterministically; preparePackDir lifted to TestHelpers; new tests cover multi-pack parallel classification, custom entry.ref propagation, missingClone path, local-pack filter at checkPackUpdates boundary, registry-write failure contract (suppression sticks but baseline doesn't advance), and cross-invocation persistence; existing tests pin exact fetch/diff arguments and working-directory.
- applyRegistryAdvances catch now emits to stderr under MCS_DEBUG so silent registry-write failures aren't invisible during development; trim issue-#338 references from doc-comments (UpstreamChange, checkPackUpdates, withCommitSHA, infrastructureFilesForUpdateCheck) and drop the codebase-convention narration on UpdateChecker.shell.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Reduces spurious “pack update available” notifications by adding a post-SHA-change noise filter that suppresses infra-only upstream commits while preserving supply-chain visibility for techpack.yaml changes.

Changes:

  • Add an UpdateChecker noise filter using shallow git fetch + git diff --name-only, suppressing updates when only deny-listed infra paths change.
  • Advance ~/.mcs/registry.yaml’s commitSHA on suppressed changes so the same infra-only commits don’t re-trigger on subsequent cooldown windows.
  • Add targeted test coverage and update docs to describe the new behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Sources/mcs/Core/UpdateChecker.swift Implements diff-path classification, orchestrated fetch/diff, and registry baseline advancement.
Sources/mcs/ExternalPack/PackHeuristics.swift Exposes ignoredDirectories and introduces infrastructureFilesForUpdateCheck (excluding techpack.yaml).
Sources/mcs/ExternalPack/PackRegistryFile.swift Adds withCommitSHA(_:) helper for advancing registry baseline without moving checkout.
Tests/MCSTests/UpdateCheckerTests.swift Adds unit + orchestrator tests for classification and baseline advancement behavior.
Tests/MCSTests/TestHelpers.swift Makes MockShellRunner thread-safe and adds preparePackDir helper for git-working-dir tests.
Tests/MCSTests/PackHeuristicsTests.swift Verifies update-check infra set excludes techpack.yaml and checks ignored dir set.
docs/cli.md Documents the noise filter, manifest invariant, and never-hide-on-error behavior.
docs/architecture.md Adds UpdateChecker architecture notes including noise-filter semantics and caching.

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

Comment thread Sources/mcs/Core/UpdateChecker.swift Outdated
Comment thread Sources/mcs/Core/UpdateChecker.swift Outdated
Comment thread Sources/mcs/Core/UpdateChecker.swift Outdated
- Mirror PackFetcher.update's nil-ref pattern: when entry.ref == nil, fetch without a positional ref and diff against origin/HEAD instead of passing HEAD as a remote ref (more reliable across git servers, matches the rest of the codebase).
- Validate entry.ref via the new module-level isValidGitRef predicate (extracted from PackFetcher.validateRef) before invoking ls-remote / fetch / diff — registry-corrupted refs starting with `-` would otherwise be interpreted as git options. Invalid refs silently skip the pack, consistent with ls-remote network-failure behavior. parseRemoteSHA now prefers the peeled `^{}` line for annotated tags so the registry SHA stays in sync with rev-parse HEAD.
- Centralize the MCS_DEBUG env check on Environment.isDebugMode; route preparePackDir through Environment.packsDirectory; trim verbose MockShellRunner doc-comments; drop a duplicate inline comment.
Copy link
Copy Markdown

Copilot AI left a comment

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 reduces noisy “pack update available” notifications by distinguishing material upstream pack changes from infrastructure-only commits, and advancing the registry baseline when the change is non-material so the same commits don’t re-trigger.

Changes:

  • Add an UpdateChecker “noise filter” that runs a shallow git fetch + git diff --name-only and suppresses notifications when only deny-listed infra paths changed (while treating techpack.yaml as always material).
  • Advance ~/.mcs/registry.yaml commitSHA on suppressed (infra-only) updates so they don’t re-trigger.
  • Add/update unit tests and documentation describing the new behavior, plus small supporting utilities (ref validation predicate, PackHeuristics set split, test helper locking).

Reviewed changes

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

Show a summary per file
File Description
docs/cli.md Documents update-check noise filter behavior and invariants.
docs/architecture.md Adds UpdateChecker architecture notes covering the new noise filter.
Tests/MCSTests/UpdateCheckerTests.swift Adds comprehensive tests for diff-path classification, orchestrator behavior, registry advancement, and SHA parsing.
Tests/MCSTests/TestHelpers.swift Makes MockShellRunner safe for concurrent use and adds a helper to prepare pack directories.
Tests/MCSTests/PackHeuristicsTests.swift Tests the update-check infra set excludes techpack.yaml and exposes ignored dirs.
Sources/mcs/ExternalPack/PackRegistryFile.swift Adds withCommitSHA helper to update the registry baseline without moving the checkout.
Sources/mcs/ExternalPack/PackHeuristics.swift Exposes ignored directories and introduces infrastructureFilesForUpdateCheck (excluding techpack.yaml).
Sources/mcs/ExternalPack/PackFetcher.swift Refactors ref validation into a reusable predicate (isValidGitRef).
Sources/mcs/Core/UpdateChecker.swift Implements noise filtering, ref validation for git invocations, baseline advancement writes, and improved ls-remote SHA parsing for annotated tags.
Sources/mcs/Core/Environment.swift Adds Environment.isDebugMode gate for developer-facing diagnostics.

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

Comment thread Sources/mcs/Core/UpdateChecker.swift Outdated
Comment thread Sources/mcs/Core/UpdateChecker.swift Outdated
Comment thread Sources/mcs/ExternalPack/PackFetcher.swift
- Wrap applyRegistryAdvances in withFileLock; lock contention skips silently and the next check retries (closes data-loss race vs. concurrent mcs pack add/update)
- Trim diff paths with .whitespacesAndNewlines so trailing \r from core.autocrlf=true CRLF output doesn't bypass the deny-list; regression test added
- Make PackEntry fields var so withCommitSHA copies-and-mutates instead of re-listing every stored property; surface invalid-ref skip via MCS_DEBUG; soften isValidGitRef doc-comment
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements an UpdateChecker “noise filter” so upstream pack commits that only touch infrastructure files (README/CI/etc.) don’t trigger “pack update available” notifications, while ensuring techpack.yaml changes always surface for supply-chain safety.

Changes:

  • Adds git fetch --depth 1 + git diff --name-only-based classification to suppress infra-only upstream commits and (when suppressed) advance registry.yaml’s baseline SHA.
  • Hardens ref handling via shared isValidGitRef validation and improves ls-remote parsing for annotated tags (prefer peeled ^{} commit SHA).
  • Adds comprehensive Swift Testing coverage and updates docs to describe the new behavior.

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
docs/cli.md Documents the update-check noise filter behavior and invariants (never-hide on failure, techpack.yaml always material).
docs/architecture.md Adds architecture-level description of UpdateChecker’s new noise filter and caching behavior.
Sources/mcs/Core/UpdateChecker.swift Core implementation: diff-path classifier, fetch/diff orchestration, registry baseline advancement under lock, peeled-SHA parsing.
Sources/mcs/Core/Environment.swift Adds Environment.isDebugMode gate for developer-facing stderr diagnostics.
Sources/mcs/ExternalPack/PackHeuristics.swift Exposes ignore-dir list and adds infrastructureFilesForUpdateCheck that excludes techpack.yaml.
Sources/mcs/ExternalPack/PackRegistryFile.swift Makes PackEntry mutable to support copy-and-mutate helper withCommitSHA.
Sources/mcs/ExternalPack/PackFetcher.swift Refactors ref validation to reuse the new shared isValidGitRef predicate.
Tests/MCSTests/UpdateCheckerTests.swift Adds unit + orchestration tests for noise classification, ref validation, failure fallthrough, and peeled tag SHA parsing.
Tests/MCSTests/TestHelpers.swift Makes MockShellRunner concurrency-safe (lock + keyed dispatch) and adds helper to prepare pack clone dirs in tests.
Tests/MCSTests/PackHeuristicsTests.swift Adds tests asserting update-check infra set excludes techpack.yaml and retains expected infra entries.

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

Comment thread Sources/mcs/Core/UpdateChecker.swift Outdated
Comment thread Sources/mcs/Core/UpdateChecker.swift
`resolvedPath` only validates the path shape — if the user `rm -rf`'d the clone, current code spawns git with a non-existent cwd and returns `.fetchFailed`. Add a `fileExists` check so the case classifies as `.missingClone` (its intended meaning), avoiding the doomed subprocess. Notification still surfaces — both reasons map to `.emit` at the call site.
Switch the parallel writes to UnsafeMutableBufferPointer so each thread touches a single element address with no Array COW/refcount machinery in the hot path. nonisolated(unsafe) is still needed (UnsafeMutableBufferPointer is not Sendable) but the data-race surface area is now obviously a flat slab of memory.
@bguidolim bguidolim merged commit 2c989f5 into main Apr 27, 2026
4 checks passed
@bguidolim bguidolim deleted the bruno/338-update-check-noise-filter branch April 27, 2026 08:42
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.

2 participants