Summary
When a pack is configured both globally and in the current project, mcs doctor checks it exactly once — under the project scope — and never verifies its global artifacts at all.
DoctorRunner.resolveCheckScopes builds the second scope as:
let globalOnlyIDs = globallyConfiguredPackIDs.subtracting(projectPackIDs)
The deduplication is deliberate (PR #175, so a pack in both scopes is not checked twice), but it drops the global half rather than merging it. The surviving project scope carries effectiveProjectRoot set and the project's exclusions, so nothing in the run ever looks at:
~/.claude/skills|hooks|commands|agents/<destination> — the global copies of the pack's files
- the
bash ~/.claude/hooks/... hook entry in the global settings.json (a different command string from the project entry, per Constants.HookCommand)
- the pack's section in
~/.claude/CLAUDE.md
- settings keys and value hashes recorded on the global
PackArtifactRecord
Why it is easy to miss
FileExistsCheck carries a fallbackPath to the global location, so a project-scoped file check reports .pass when only the global copy exists. Combined with the scope subtraction, doctor cannot distinguish "installed in the project" from "installed globally" for copyPackFile artifacts, and reports a clean run either way.
Symptom
Silent under-reporting. A globally-installed hook script that was deleted, a global CLAUDE.md section that drifted, or a global settings entry that was hand-edited goes unreported for any pack that also happens to be configured in the current project. Running mcs doctor --global, or running outside a project, does check them — so the gap only appears from inside a project that shares a pack with the global scope.
Proposed fix
Give a both-scopes pack a second CheckScope with effectiveProjectRoot: nil, the global scope's excludedComponentIDs, and its artifactsByPack record, rather than subtracting it out. Two things need attention:
- Check names would collide in the output (the same pack contributing two entries per component), so the scope
label needs to reach the rendered name or the section grouping, otherwise a reader cannot tell which copy failed.
resolveCheckScopes currently returns at most two scopes and callers assume the project one is unique; adding a third changes that shape.
Worth doing alongside #371's ScopeDuplicationCheck, which already loads both scopes' state and could share the resolution.
Acceptance criteria
Summary
When a pack is configured both globally and in the current project,
mcs doctorchecks it exactly once — under the project scope — and never verifies its global artifacts at all.DoctorRunner.resolveCheckScopesbuilds the second scope as:The deduplication is deliberate (PR #175, so a pack in both scopes is not checked twice), but it drops the global half rather than merging it. The surviving project scope carries
effectiveProjectRootset and the project's exclusions, so nothing in the run ever looks at:~/.claude/skills|hooks|commands|agents/<destination>— the global copies of the pack's filesbash ~/.claude/hooks/...hook entry in the globalsettings.json(a different command string from the project entry, perConstants.HookCommand)~/.claude/CLAUDE.mdPackArtifactRecordWhy it is easy to miss
FileExistsCheckcarries afallbackPathto the global location, so a project-scoped file check reports.passwhen only the global copy exists. Combined with the scope subtraction, doctor cannot distinguish "installed in the project" from "installed globally" forcopyPackFileartifacts, and reports a clean run either way.Symptom
Silent under-reporting. A globally-installed hook script that was deleted, a global
CLAUDE.mdsection that drifted, or a global settings entry that was hand-edited goes unreported for any pack that also happens to be configured in the current project. Runningmcs doctor --global, or running outside a project, does check them — so the gap only appears from inside a project that shares a pack with the global scope.Proposed fix
Give a both-scopes pack a second
CheckScopewitheffectiveProjectRoot: nil, the global scope'sexcludedComponentIDs, and itsartifactsByPackrecord, rather than subtracting it out. Two things need attention:labelneeds to reach the rendered name or the section grouping, otherwise a reader cannot tell which copy failed.resolveCheckScopescurrently returns at most two scopes and callers assume the project one is unique; adding a third changes that shape.Worth doing alongside #371's
ScopeDuplicationCheck, which already loads both scopes' state and could share the resolution.Acceptance criteria
~/.claude/hooks/<script>is reported bymcs doctorrun inside the project