Purpose
Inspect and customize /machine-health:audit on this host per the uniform setup contract
(docs/PLUGIN-PHILOSOPHY.md "Setup is explicit and repeatable" in the marketplace repository):
check reads the effective configuration and reports, apply writes it. The machine-local
surface is the catalog overlay at <StateBase>/catalog/checks.local.jsonc and the remediation
approvals at <StateBase>/state/approvals.json. Configuration here is machine-local by design.
A workstation's check tuning does not belong in any repository. Idempotent: re-running reads the
existing files and offers updates rather than overwriting blind.
Action routing: no argument or check runs the check; apply runs the check first, then applies
the requested changes. When apply is given complete write arguments (disable=, deprecate=, demote=,
approve=) it applies them non-interactively; with no arguments in an interactive session it runs
the full interview below. Custom-check registration is inherently interactive (it authors a script)
and always interviews.
Resolving the state root
<StateBase> is ${CLAUDE_PLUGIN_DATA}, the per-plugin data directory that survives plugin
updates. Create it if missing.
There is no hardcoded fallback path, and inventing one is a defect rather than a safety net. The
directory under ~/.claude/plugins/data/ is named for the plugin's install identity
(<name>-<marketplace>, or <name>-inline for a --plugin-dir session), not for the plugin. Any
literal path written here therefore resolves to a different directory than the one this plugin
actually reads and writes. The overlay and approvals land in one place while the audit's state
and logs live in another, each half looking complete to whoever wrote it, and the operator's
disabled checks silently stop taking effect.
So when ${CLAUDE_PLUGIN_DATA} renders as the literal unexpanded token, stop at step 1 of
check: report that the skill is running outside plugin context and cannot resolve its state
root, run no further probe, and write nothing. Continuing would be worse than stopping. With the
root unresolved, an absent overlay and an unreadable one are the same observation, so every verdict
below would assert more than the evidence supports. apply refuses outright for the same reason:
there is no root to write to. (This differs from /machine-health:audit, which passes a state root
to an orchestrator that has its own documented fallback ladder; this skill reads and writes the
overlay directly and has no such rung.)
Split-state-root report. Older installs may hold a stray root at the literal
~/.claude/plugins/data/machine-health; check reports a split when it finds one: probe that exact
legacy path and any machine-health-* sibling of the resolved <StateBase>, and for each that
exists and is not <StateBase>, name it and list what it holds. Only <StateBase> is read.
Consolidating is the operator's move. Moving or deleting the stray root is a decision about their
data, and this skill neither relocates nor removes files.
check (read-only)
The shipped catalog (${CLAUDE_PLUGIN_ROOT}/skills/audit/catalog/checks.jsonc) is the single
source of truth for what checks exist and their defaults. Read it first, then read the existing
overlay, approvals, and <StateBase>/TODO.md when present. Report a PASS/FAIL/INFO table with one
suggested-action line per FAIL; modify nothing. The plugin ships a working zero-config default (the whole
shipped catalog, no remediations approved), so an absent overlay or absent approvals file is INFO
(default in effect), never FAIL.
- State root. INFO: the resolved
<StateBase>path, whether it exists yet, and any split root found. FAIL and stop here when the token did not expand (see above). The remaining probes do not run. - Catalog overlay (
<StateBase>/catalog/checks.local.jsonc). INFO when absent (the shipped catalog applies unchanged). When present, validate against${CLAUDE_PLUGIN_ROOT}/skills/audit/catalog/schemas/checks.schema.json: a registered custom check is a full#/$defs/CheckEntryand must satisfy it outright, while a partial override of a shipped check carriesidplus only the fields it changes. Check those field names and value types againstCheckEntrywithout applying itsrequiredlist (merge behavior:${CLAUDE_PLUGIN_ROOT}/skills/audit/reference/shared/catalog-overlay.md). Confirm every entry targets a real check: a shipped check id, or a custom check whosescriptpath resolves under<StateBase>. FAIL a malformed entry, an entry targeting an unknown check id, or a custom entry whosescriptfile is missing; report which checks the overlay patches (disabled, deprecated, demoted) and any custom checks it registers. - Remediation approvals (
<StateBase>/state/approvals.json). INFO when absent (no remediation is approved, the safe default; a bare audit mutates nothing). When present: validate against${CLAUDE_PLUGIN_ROOT}/skills/audit/catalog/schemas/approvals.schema.json(shape and examples in${CLAUDE_PLUGIN_ROOT}/skills/audit/reference/shared/approvals.md) and confirm each approval names a real remediation (restart-stopped-service,clear-temp-files). FAIL a malformed file or an approval for an unknown remediation; otherwise report which remediations are approved. - Pending proposals (
<StateBase>/TODO.md). INFO: count proposals (deprecation, cadence demotion, new check) awaiting a decision from a prior audit; the suggested-action line is to runapplyto walk them. - Report directory. INFO: the effective location. The
report_dirplugin option when set, else$env:USERPROFILE\Documents\MachineHealth.
apply (idempotent)
Run check first. Then, if the invocation carries write arguments, apply each non-interactively;
otherwise run the interview. After any write, re-read the target file and confirm the entry landed.
Never report success on the write alone. Only write entries that differ from the shipped catalog, so
the overlay stays minimal.
Non-interactive write paths (named in the argument-hint; each targets a shipped check or remediation, so no interview is needed):
disable=<check-id>. Write"enabled": falsefor that check to the overlay.deprecate=<check-id>. Write"deprecated": trueplus adeprecation_reason(from areason=<text>argument when supplied, else a short default) to the overlay.demote=<check-id>. Write"cadence": "monthly"for that check to the overlay.approve=<remediation-id>. Write the approval to<StateBase>/state/approvals.jsonper${CLAUDE_PLUGIN_ROOT}/skills/audit/reference/shared/approvals.md. A suppliedapprove=argument IS the explicit user decision; never approve a remediation not named in the arguments or the interview.
Reject an argument targeting an unknown check id or remediation with the same message check would
give, rather than writing a dangling entry.
Interview (no write arguments AND an interactive session), one decision at a time:
- Read current state first and present the
checksummary: checks shipped, checks patched by the overlay, approvals granted, proposals awaiting a decision. - Walk pending proposals. For each
<StateBase>/TODO.mdproposal, present it with a recommendation, apply the user's decision to the overlay, and mark the proposal resolved inTODO.md. - Interview catalog changes against the merged view: disable (
"enabled": false), retire ("deprecated": true+deprecation_reason), or demote to monthly ("cadence": "monthly"). - Register custom checks when the user wants one (always interactive, it authors a script):
write the script to
<StateBase>/scripts/windows/checks/Test-<Thing>.ps1(single JSON object per${CLAUDE_PLUGIN_ROOT}/skills/audit/reference/shared/output-schema.md,-Humanmode included), then add a full schema-valid overlay entry withscriptset toscripts/windows/checks/Test-<Thing>.ps1. Merge and resolution semantics:${CLAUDE_PLUGIN_ROOT}/skills/audit/reference/shared/catalog-overlay.md. - Offer remediation approvals. The two shipped remediations (
restart-stopped-service,clear-temp-files) default to not approved. Present each with its risk posture from${CLAUDE_PLUGIN_ROOT}/skills/audit/reference/windows/remediation-policy.md; on an explicit yes, write the approval per${CLAUDE_PLUGIN_ROOT}/skills/audit/reference/shared/approvals.md. Never enable a remediation the user did not explicitly approve. - Confirm the report directory. Show where reports land (the
report_dirplugin option when set, else$env:USERPROFILE\Documents\MachineHealth). The option is stored in plugin config, not the overlay; reconfigure through Claude Code's native flow, per the marketplace's plugin-reconfiguration convention (https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/plugin-reconfiguration/README.md, which owns the verified-version record): interactive/plugin configure machine-health@<marketplace>any time, or headlessclaude plugin install machine-health@<marketplace> -s <scope> --config report_dir=<path>(repeatable per key) — against an already-installed plugin it printsalready installedand still writes the value. Do not uninstall to reconfigure: that drops the plugin's entire storedpluginConfigsentry, resetting every option in the README's Options reference to its manifest default.-sdefaults touser; pass the scopeclaude plugin listreports, and run from that project's directory for aproject/localscope, or the write lands at a scope that does not load. This skill never writes user settings orpluginConfigs. Afterwards reruncheckin a fresh session — the rendered${user_config.*}and each hook'sCLAUDE_PLUGIN_OPTION_*are fixed at session start, so a same-sessioncheckstill reports the OLD value; report the observed effective value, never an unobserved change.
Re-running apply after everything is already set changes nothing and reports "already configured".
Output
An updated <StateBase>/catalog/checks.local.jsonc (and approvals.json when approvals changed),
plus a one-paragraph summary of what changed and how to re-run this setup to reconfigure. check
alone reports the effective configuration and changes nothing.
What this skill does NOT do
- Run the audit. That is
/machine-health:audit. - Edit the shipped catalog or anything inside the plugin install directory. A plugin update
replaces it; machine-local changes live only under
<StateBase>. - Approve remediations silently. Every approval is an explicit user decision (an
approve=argument or an interview yes). - Write the plugin cache, Claude Code user settings, or
pluginConfigs.