Purpose
Thin check-centric setup per the uniform setup contract (docs/PLUGIN-PHILOSOPHY.md
"Setup is explicit and repeatable" in the marketplace repository): check inspects and
reports, apply resolves. This plugin owns no consumer-project configuration. Rules
come from the repository's own markdownlint config, and the only tunable is the native
userConfig toggle, so apply is guidance-and-verify, with exactly one write path:
the explicitly invoked apply install-lint dependency install described below.
Action routing: no argument or check runs the check; apply runs the check first, then
remediation; apply install-lint additionally authorizes the consumer-repo dependency
install described below. All are non-interactive. Never prompt when the action is given.
check (read-only)
The hook script (${CLAUDE_PLUGIN_ROOT}/hooks/markdown-format.sh) is the single source of
truth for what it requires and how it resolves things.
Read it first. Probe what it actually does, don't recite this file. Then run each probe via
Bash and report a PASS/FAIL/INFO table with one remediation line per FAIL. Do not modify anything.
When the plugin's toggle is disabled, every prerequisite absence downgrades from FAIL to
INFO. The hook exits through its enabled-gate before probing anything, so a deliberately
disabled plugin is not broken. Report the probes informationally and note that re-enabling
restores the FAIL semantics.
- Bash version. Check against the hook's documented floor (README Requirements),
noting any features the hook degrades without (for example telemetry's Bash builtin).
jq. command -v jq. FAIL if absent and the repository opted in per item 4: the
hook then skips with a visible once-per-session notice instead of formatting. Without
that opt-in the hook decides the opt-in first and emits nothing at all, so report jq's
absence as INFO there. The missing config, not jq, is why nothing happens.
markdownlint-cli2. Resolve it exactly the way the hook's resolution code does
(its sanctioned lookup paths, including its symlink/escape validation of a repo-local
shim). A binary or shim the hook would reject must not PASS here. Then confirm the
resolved tool actually executes. Run it with --version (a repo shim can resolve yet
still be broken: missing Node interpreter, dangling target); resolution without
successful execution is FAIL, with the execution error in the remediation line. FAIL
when nothing the hook would accept resolves.
- Consumer markdownlint config, the opt-in. This is what activates the hook, not a
style detail. Mirror its walk: from an edited file's directory up to the repo root, so
nested configs apply to nested files and the opt-in is per-path (a root config covers
the tree; a
docs/ config covers only docs/). Where that walk finds nothing the hook
exits silently: no --fix, no findings, and no notice of any kind, not even a missing
prerequisite. Search the whole tree (skip node_modules), report the root config the
cascade discovers, list nested configs with their directory scope, and surface the
README's configuration trust boundary for every config the hook's own risk collection
(collect_risky_configs) would flag. For a path no config governs, report the hook as
INFO, inactive, not PASS: nothing is broken, the repository simply never opted in,
and that is the whole reason formatting is not happening. Name the remediation in the
same line rather than leaving the reader to infer it. Never report markdownlint's own
default rules as the fallback. An unconfigured repo gets no rules, not the defaults.
- Path scope, the repository's
.gitignore. INFO: the hook leaves a gitignored
file alone, neither rewriting nor reporting on it, because a path the repository
excludes is not part of the reviewable artifact. List the ignored Markdown the
repository carries as out of scope with
git ls-files --others --ignored --exclude-standard -- '*.md' '*.mdc',
and note that a tracked file is never treated
as ignored even when a pattern matches it. Report the effective
${user_config.markdown_format_lint_gitignored} value (unexpanded or empty means
default false, i.e. gitignored files are skipped); true restores linting there.
- Hook toggle. Report the effective
markdown_format_enabled value:
${user_config.markdown_format_enabled} (unexpanded or empty means default true).
- Hook registration. INFO: confirm the plugin is enabled for this project
(
/plugin → Installed) rather than parsing settings files.
apply (idempotent)
Run check, then for each FAIL offer the resolution. Never install anything without the
consumer's explicit go-ahead in the invocation. apply install-lint adds
markdownlint-cli2 as a dev dependency in the consumer repository using the
repository's own package manager, resolved in order: lockfile (pnpm-lock.yaml →
pnpm add -D, yarn.lock → yarn add -D, bun.lock/bun.lockb → bun add -d,
package-lock.json or npm-shrinkwrap.json → npm install --save-dev), then the
package.json "packageManager" field when no lockfile exists, then npm only when
neither signal is present. With no package.json, an ambiguous multi-lockfile state, or a lockfile that
contradicts packageManager, stop with manager-specific guidance instead of guessing.
Never introduce a competing lockfile. The change is stated before running. For a Yarn repository, don't infer the linker. Ask
the repo's own Yarn: run yarn config get nodeLinker in the repo. pnp (Berry's default
when unset) → skip the install and give guidance, because Plug'n'Play generates a loader file,
not the node_modules/.bin shim the hook resolves; install markdownlint-cli2 on
PATH or switch the linker. node-modules/pnpm, or Yarn Classic (which has no such
setting and always materializes node_modules) → install. The
verify-after-remediation rule below is the backstop when an install still yields no
usable shim. After ANY remediation, re-run the
relevant check probe and report its actual result. Never claim resolved on the
install command's exit code alone. For everything else apply only points:
- missing
jq / Bash: platform install instructions from the README Requirements section;
this skill never installs system packages.
- toggle off: 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 markdown-format@<marketplace>
any time, or headless
claude plugin install markdown-format@<marketplace> -s <scope> --config markdown_format_enabled=true
(repeatable per key) — against an already-installed plugin it prints already installed and
still writes the value. Do not uninstall to reconfigure: that drops the plugin's entire
stored pluginConfigs entry, resetting every option in the README's Options reference to its
manifest default. -s defaults to user; pass the scope claude plugin list reports, and run
from that project's directory for a project/local scope, or the write lands at a scope that
does not load. This skill never writes user settings or pluginConfigs. Afterwards rerun
check in a fresh session — the rendered ${user_config.*} and the hook's
CLAUDE_PLUGIN_OPTION_* are fixed at session start, so a same-session check still reports
the OLD value; report the observed effective value, never an unobserved change.
- no markdownlint config: this is why the hook does nothing here, so lead with it rather
than leaving it as a footnote under the passing prerequisites. Then offer to create a
minimal
.markdownlint-cli2.jsonc in the repository root only when explicitly asked.
The plugin imposes no rules of its own, and which rules a repository adopts is its own
decision, never this skill's.
Re-running apply after everything passes changes nothing and reports "already configured".
What this skill does NOT do
- Run the formatter. Editing any
.md file exercises the hook end-to-end. The only
execution check performs is the harmless --version liveness probe of the resolved
linter; it never lints, fixes, or touches repository content.
- Write the plugin cache, Claude Code user settings, or
pluginConfigs.
- Download anything during
check; network use happens only in an explicitly
requested apply install-lint inside the consumer repository.
1---2name: setup-483description: Verify the markdown-format hook's runtime prerequisites and configuration for this repository. Use when: 'set up markdown-format', 'configure markdown-format', 'is markdown-format working', formatting silently isn't happening, or the hook reported a missing prerequisite. Actions: check (read-only verification, default) | apply (resolve what check found). Re-runnable and safe.4---56## Purpose78Thin check-centric setup per the uniform setup contract (`docs/PLUGIN-PHILOSOPHY.md`9"Setup is explicit and repeatable" in the marketplace repository): `check` inspects and10reports, `apply` resolves. This plugin owns no consumer-project configuration. Rules11come from the repository's own markdownlint config, and the only tunable is the native12`userConfig` toggle, so `apply` is guidance-and-verify, with exactly one write path:13the explicitly invoked `apply install-lint` dependency install described below.1415Action routing: no argument or `check` runs the check; `apply` runs the check first, then16remediation; `apply install-lint` additionally authorizes the consumer-repo dependency17install described below. All are non-interactive. Never prompt when the action is given.1819## `check` (read-only)2021The hook script (`${CLAUDE_PLUGIN_ROOT}/hooks/markdown-format.sh`) is the single source of22truth for what it requires and how it resolves things.2324**Read it first.** Probe what it actually does, don't recite this file. Then run each probe via25Bash and report a PASS/FAIL/INFO table with one remediation line per FAIL. Do not modify anything.2627When the plugin's toggle is disabled, every prerequisite absence downgrades from FAIL to28INFO. The hook exits through its enabled-gate before probing anything, so a deliberately29disabled plugin is not broken. Report the probes informationally and note that re-enabling30restores the FAIL semantics.31321. **Bash version.** Check against the hook's documented floor (README Requirements),33 noting any features the hook degrades without (for example telemetry's Bash builtin).342. **`jq`.** `command -v jq`. FAIL if absent *and* the repository opted in per item 4: the35 hook then skips with a visible once-per-session notice instead of formatting. Without36 that opt-in the hook decides the opt-in first and emits nothing at all, so report jq's37 absence as INFO there. The missing config, not jq, is why nothing happens.383. **`markdownlint-cli2`.** Resolve it exactly the way the hook's resolution code does39 (its sanctioned lookup paths, including its symlink/escape validation of a repo-local40 shim). A binary or shim the hook would reject must not PASS here. Then confirm the41 resolved tool actually executes. Run it with `--version` (a repo shim can resolve yet42 still be broken: missing Node interpreter, dangling target); resolution without43 successful execution is FAIL, with the execution error in the remediation line. FAIL44 when nothing the hook would accept resolves.454. **Consumer markdownlint config, the opt-in.** This is what activates the hook, not a46 style detail. Mirror its walk: from an edited file's directory up to the repo root, so47 nested configs apply to nested files and the opt-in is per-path (a root config covers48 the tree; a `docs/` config covers only `docs/`). Where that walk finds nothing the hook49 exits silently: no `--fix`, no findings, and no notice of any kind, not even a missing50 prerequisite. Search the whole tree (skip `node_modules`), report the root config the51 cascade discovers, list nested configs with their directory scope, and surface the52 README's configuration trust boundary for every config the hook's own risk collection53 (`collect_risky_configs`) would flag. For a path no config governs, report the hook as54 **INFO, inactive, not PASS**: nothing is broken, the repository simply never opted in,55 and that is the whole reason formatting is not happening. Name the remediation in the56 same line rather than leaving the reader to infer it. Never report markdownlint's own57 default rules as the fallback. An unconfigured repo gets no rules, not the defaults.585. **Path scope, the repository's `.gitignore`.** INFO: the hook leaves a gitignored59 file alone, neither rewriting nor reporting on it, because a path the repository60 excludes is not part of the reviewable artifact. List the ignored Markdown the61 repository carries as out of scope with62 `git ls-files --others --ignored --exclude-standard -- '*.md' '*.mdc'`,63 and note that a tracked file is never treated64 as ignored even when a pattern matches it. Report the effective65 `${user_config.markdown_format_lint_gitignored}` value (unexpanded or empty means66 default `false`, i.e. gitignored files are skipped); `true` restores linting there.676. **Hook toggle.** Report the effective `markdown_format_enabled` value:68 `${user_config.markdown_format_enabled}` (unexpanded or empty means default `true`).697. **Hook registration.** INFO: confirm the plugin is enabled for this project70 (`/plugin` → Installed) rather than parsing settings files.7172## `apply` (idempotent)7374Run `check`, then for each FAIL offer the resolution. Never install anything without the75consumer's explicit go-ahead in the invocation. `apply install-lint` adds76`markdownlint-cli2` as a dev dependency in the consumer repository **using the77repository's own package manager**, resolved in order: lockfile (`pnpm-lock.yaml` →78`pnpm add -D`, `yarn.lock` → `yarn add -D`, `bun.lock`/`bun.lockb` → `bun add -d`,79`package-lock.json` or `npm-shrinkwrap.json` → `npm install --save-dev`), then the80`package.json` `"packageManager"` field when no lockfile exists, then npm only when81neither signal is present. With no `package.json`, an ambiguous multi-lockfile state, or a lockfile that82contradicts `packageManager`, stop with manager-specific guidance instead of guessing.83Never introduce a competing lockfile. The change is stated before running. For a Yarn repository, don't infer the linker. Ask84the repo's own Yarn: run `yarn config get nodeLinker` in the repo. `pnp` (Berry's default85when unset) → skip the install and give guidance, because Plug'n'Play generates a loader file,86not the `node_modules/.bin` shim the hook resolves; install `markdownlint-cli2` on87`PATH` or switch the linker. `node-modules`/`pnpm`, or Yarn Classic (which has no such88setting and always materializes `node_modules`) → install. The89verify-after-remediation rule below is the backstop when an install still yields no90usable shim. After ANY remediation, re-run the91relevant `check` probe and report its actual result. Never claim resolved on the92install command's exit code alone. For everything else `apply` only points:9394- missing `jq` / Bash: platform install instructions from the README Requirements section;95 this skill never installs system packages.96- toggle off: reconfigure through Claude Code's native flow, per the marketplace's97 plugin-reconfiguration convention98 (<https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/plugin-reconfiguration/README.md>,99 which owns the verified-version record): interactive `/plugin configure markdown-format@<marketplace>`100 any time, or headless101 `claude plugin install markdown-format@<marketplace> -s <scope> --config markdown_format_enabled=true`102 (repeatable per key) — against an already-installed plugin it prints `already installed` and103 still writes the value. Do **not** uninstall to reconfigure: that drops the plugin's entire104 stored `pluginConfigs` entry, resetting every option in the README's Options reference to its105 manifest default. `-s` defaults to `user`; pass the scope `claude plugin list` reports, and run106 from that project's directory for a `project`/`local` scope, or the write lands at a scope that107 does not load. This skill never writes user settings or `pluginConfigs`. Afterwards rerun108 `check` in a **fresh session** — the rendered `${user_config.*}` and the hook's109 `CLAUDE_PLUGIN_OPTION_*` are fixed at session start, so a same-session `check` still reports110 the OLD value; report the observed effective value, never an unobserved change.111- no markdownlint config: this is why the hook does nothing here, so lead with it rather112 than leaving it as a footnote under the passing prerequisites. Then offer to create a113 minimal `.markdownlint-cli2.jsonc` in the repository root only when explicitly asked.114 The plugin imposes no rules of its own, and which rules a repository adopts is its own115 decision, never this skill's.116117Re-running `apply` after everything passes changes nothing and reports "already configured".118119## What this skill does NOT do120121- Run the formatter. Editing any `.md` file exercises the hook end-to-end. The only122 execution `check` performs is the harmless `--version` liveness probe of the resolved123 linter; it never lints, fixes, or touches repository content.124- Write the plugin cache, Claude Code user settings, or `pluginConfigs`.125- Download anything during `check`; network use happens only in an explicitly126 requested `apply install-lint` inside the consumer repository.