Skip to content

[#251] Add check-updates command, config system, and HookEvent enum#273

Merged
bguidolim merged 20 commits intomainfrom
bruno/251-check-updates-config
Mar 23, 2026
Merged

[#251] Add check-updates command, config system, and HookEvent enum#273
bguidolim merged 20 commits intomainfrom
bruno/251-check-updates-config

Conversation

@bguidolim
Copy link
Copy Markdown
Collaborator

@bguidolim bguidolim commented Mar 22, 2026

Summary

Users sync once and work for days/weeks with no visibility into pack or CLI staleness. This adds a lightweight mcs check-updates command, a mcs config preferences system, and makes hook event names type-safe with an enum. Closes #251, closes #269.

Changes

  • mcs check-updates: Non-interactive command that checks pack freshness (git ls-remote) and CLI version (git ls-remote --tags on the mcs repo). Supports --hook (SessionStart mode), --json (Codable DTO output), and context-aware pack filtering (global + current project packs only)
  • --hook flag: Identifies the command as running inside a Claude Code SessionStart hook — respects 7-day cooldown, respects config keys, manages cache, and outputs structured JSON (additionalContext) with a strong directive instruction for Claude. Without --hook, checks always run (user-invoked behavior)
  • JSON result cache: Single ~/.mcs/update-check.json stores timestamp + results. Hook mode serves cached results every session start (no network), refreshes when stale (7 days). User-invoked commands always refresh the cache. mcs pack update invalidates the cache (with warning on failure). CLI version cache self-invalidates on upgrade
  • mcs config list/get/set: Extensible user preferences at ~/.mcs/config.yaml. Two keys: update-check-packs and update-check-cli. set immediately manages the SessionStart hook in ~/.claude/settings.json
  • First-run prompt: Interactive mcs sync prompts once whether to enable update notifications, then immediately syncs the hook
  • Dual notification: mcs sync and mcs doctor always check for updates (no cooldown, ignore config) since they are user-initiated. They also refresh the cache for the hook
  • Constants.HookEvent enum: Replaces stringly-typed hook event names with CaseIterable/Codable enum for compile-time safety. Type-safe overloads on Settings.addHookEntry/removeHookEntry accept HookEvent directly. ManifestError.invalidHookEvent removed — validation now at decode time
  • VersionCompare.isNewer: Extracted from UpdateChecker into VersionCompare to eliminate duplicate comparison logic
  • MCSConfig.load(output:): Distinguishes missing (silent) from corrupt (warns via optional CLIOutput parameter)
  • ConfigurationDiscovery: Warns on unknown hook events during export instead of silently dropping them
  • Doctor --fix UX: Summary now shows before the fix confirmation prompt. "0 fixed" hidden from summary; post-fix line shows count
  • Global sync settings.json: Tracks whether hook stripping changed anything; saves only when content was added or removed (not unconditionally)
  • UpdateChecker as single source of truth: Hook parameters, cache management, check helpers (checkAndPrint), hook sync (syncHook), and output formatting centralized as static methods

Test plan

  • swift test passes locally (865 tests, 66 new) — clean build verified
  • swiftformat --lint . and swiftlint pass (0 violations across 67 source files)
  • mcs check-updates verified with real packs
  • mcs check-updates --json produces valid JSON via Codable DTO
  • mcs check-updates --hook outputs structured JSON with additionalContext
  • mcs config list/get/set verified
  • mcs doctor --fix shows summary before fix prompt
  • Cache: mcs pack update deletes cache, next hook re-checks
  • Integration tests: project hook injection (enable, disable, convergence)
  • Integration tests: global hook injection (inject, converge, remove)
  • Settings.removeHookEntry fully tested (5 tests)
  • Hook lifecycle: addHook/removeHook/syncHook round-trip tested (7 tests)
  • performCheck: hook-mode cached, user-mode fresh, cache save (3 tests)
Checklist for engine changes
  • Docs updated (CLAUDE.md, docs/cli.md, README.md)
  • Integration tests in LifecycleIntegrationTests (project scope) and GlobalConfiguratorTests (global scope)
  • try? violations addressed — do/catch with output.warn() where CLIOutput available; cache ops use do/catch with non-fatal fallback

- Add `mcs check-updates` (pack freshness via git ls-remote, CLI version via git tags) with 7-day cooldown, --force, --json, and context-aware pack filtering
- Add `mcs config list/get/set` for user preferences (update-check-packs, update-check-cli) with immediate SessionStart hook management in settings.json
- Replace stringly-typed hook events with `Constants.HookEvent` enum (CaseIterable, Codable) for compile-time safety (#269)
- --hook identifies automated SessionStart context (respects cooldown, records timestamp, respects config)
- Default (no flag) is user-invoked: always checks, never records timestamp
- sync/doctor update checks no longer consume the hook's cooldown window
- Prepends "The following mcs updates are available. Please inform the user:" so Claude proactively relays update notifications
- Single ~/.mcs/update-check.json stores timestamp + results
- Hook mode: serves cached results when fresh, does network check when stale
- User-invoked checks always do network + update cache (so hook benefits)
- mcs pack update invalidates cache so next hook re-checks
- CLI version cache self-invalidates when user upgrades (version mismatch)
- Hook stdout is injected into Claude's context; ANSI codes would appear as raw escape sequences
- User-invoked mode keeps colored output for terminal readability
- Output additionalContext via hookSpecificOutput JSON (documented Claude Code API)
- Plain stdout was implicit; structured format is the official hook output contract
- Call syncHook after first-run config save so the hook is installed immediately
- Default nil config values to false in hook mode (match documented defaults)
- Eliminate double loadCache() in the hook fast-path (single disk read)
- Reference CodingKeys.rawValue in knownKeys instead of duplicating string literals
- Fix line length lint violation in buildContextString
- Add VersionCompare.isNewer(candidate:than:) as the single comparison method
- Remove UpdateChecker.isNewer (was duplicating VersionCompare)
- Simplify parseLatestTag to use VersionCompare.isNewer instead of inline tuple comparison
- Replace try? with do/catch in checkAndPrint and CheckUpdatesCommand (CLAUDE.md rule)
- Add plain-text fallback for hook JSON serialization failure
- Remove dead isCacheStale() method (logic inlined in performCheck)
- Fix doctor summary: always shows fixed=0, add post-fix summary line
- Update cache tests to test loadCache directly
- 5 tests for Settings.removeHookEntry (match, no match, cleanup, nil hooks)
- 7 tests for UpdateChecker hook lifecycle (addHook, removeHook, idempotency, round-trip, syncHook add/remove)
- Add Settings.addHookEntry/removeHookEntry overloads accepting HookEvent (type safety extends to settings layer)
- Replace manual JSONSerialization in printJSON with Codable JSONOutput struct
- Add 3 integration tests: hook injection on enable, omission on disable, convergence on re-sync
Critical:
- MCSConfig.load: distinguish missing (silent) from corrupt (warn via output param)
- saveCache: replace try? with do/catch
- relevantPackIDs: use fileExists + do/catch instead of try?
- invalidateCache: return success/failure, warn in PackCommand on failure

Important:
- ConfigurationDiscovery: warn on unknown hook events during export
- Global sync: always save settings.json (fixes hook not being stripped when disabled)
- Add 3 global sync integration tests (inject, converge, remove)
- Add 3 performCheck tests (hook cached, user fresh, cache save)

Suggestions:
- Rename printHumanReadable to printResult (outputs JSON in hook mode)
- Fix removeHookEntry doc comment accuracy
- Track whether hook stripping actually removed entries (group count comparison)
- Save when hasContent (packs contributed) OR strippedContent (hooks were removed)
- Avoids unnecessary file rewrites when syncing with zero-content packs
- Clarify cache description in docs/cli.md (serves cached OR does fresh check, not both)
- Add CaseIterable to MCSConfig.CodingKeys for exhaustive key validation
- Replace hardcoded knownKeys test with Set equality against CodingKeys.allCases
@bguidolim bguidolim merged commit bdc730c into main Mar 23, 2026
4 checks passed
@bguidolim bguidolim deleted the bruno/251-check-updates-config branch March 23, 2026 09:34
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.

Make hook event names type-safe with an enum Notify users when tech packs have available updates

1 participant