Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/markdown-format/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "markdown-format",
"version": "0.4.1",
"version": "0.5.0",
"description": "Auto-format and lint Markdown on edit via markdownlint-cli2, using the consuming repo's own markdownlint config.",
"author": {
"name": "Melodic Software",
Expand Down
15 changes: 15 additions & 0 deletions plugins/markdown-format/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@
All notable changes to the `markdown-format` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.5.0]

### Added

- **`setup` skill on the uniform contract** (fleet conformance wave, dim 8 —
the fleet's first conforming exemplar). `check` verifies the hook's runtime
prerequisites read-only (Bash, `jq`, `markdownlint-cli2` resolution,
discovered markdownlint config + trust boundary, effective toggle);
`apply` re-checks and resolves — guidance for system tools and the native
toggle, and an explicitly requested `apply install-lint` as its only write
path: `markdownlint-cli2` added as a dev dependency via the repository's own
package manager (npm, pnpm, Yarn, or Bun, resolved from the repo's lockfile
and `packageManager` field).
Non-interactive when the action argument is supplied.

## [0.4.1]

### Changed
Expand Down
13 changes: 10 additions & 3 deletions plugins/markdown-format/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ imposes no rules of its own.
reported via `additionalContext`; they never reject the edit. Make a commit
hook or CI your hard gate.
- **Config from the consumer.** `markdownlint-cli2` discovers config
(`.markdownlint-cli2.jsonc`, `.markdownlint.json`, …) by walking up from the
repository root. The hook `cd`s to that root before linting so the right
cascade applies regardless of the session's working directory.
(`.markdownlint-cli2.jsonc`, `.markdownlint.json`, …) per edited file, from
the file's directory up through its parents — so a nested config governs its
subtree. The hook `cd`s to the repository root before linting so that
discovery caps at the root regardless of the session's working directory.

## Requirements

Expand Down Expand Up @@ -63,6 +64,12 @@ the advisory to appear again.
/plugin install markdown-format@melodic-software
```

Then verify the runtime prerequisites with `/markdown-format:setup check`;
`/markdown-format:setup apply` resolves anything the check reports with
guidance, and `/markdown-format:setup apply install-lint` additionally
authorizes installing `markdownlint-cli2` as a dev dependency using the
repository's own package manager.

## Configuration

The rules themselves are never configured here — the plugin's only rule source is
Expand Down
89 changes: 89 additions & 0 deletions plugins/markdown-format/skills/setup/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
name: setup
description: "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."
argument-hint: "check | apply [install-lint]"
user-invocable: true
disable-model-invocation: true
---

## Purpose

Thin check-centric setup per the uniform contract: `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.

1. **Bash version** — check against the hook's documented floor (README Requirements),
noting any features the hook degrades without (for example telemetry's Bash builtin).
2. **`jq`** — `command -v jq`. FAIL if absent: the hook then skips with a visible
once-per-session notice instead of formatting.
Comment on lines +30 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Verify that jq can execute before reporting it available

When jq resolves on PATH but cannot run (for example, a stale binary with a missing dynamic loader or an incompatible executable), this probe reports PASS. The hook uses the same command -v gate in hooks/markdown-format.sh:132-139, then relies on jq to parse file_path at lines 193-210; a failed parse makes the hook exit without formatting. Run a harmless jq execution/capability probe so setup does not report the prerequisite as configured in this case.

Useful? React with 👍 / 👎.

3. **`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.
4. **Consumer markdownlint config** — mirror the hook's config walk: it loads configs from
an edited file's directory up to the repo root, so nested configs apply to nested files.
Comment thread
kyle-sexton marked this conversation as resolved.
Search the whole tree (skip `node_modules`), report the root config the cascade
discovers (or INFO that none exists — tool defaults then apply), 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.
5. **Hook toggle** — report the effective `markdown_format_enabled` value:
`${user_config.markdown_format_enabled}` (unexpanded or empty means default `true`).
6. **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
Comment thread
kyle-sexton marked this conversation as resolved.
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`,
Comment thread
kyle-sexton marked this conversation as resolved.
`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: direct to `/plugin configure markdown-format` or
`claude plugin install markdown-format@<marketplace> --config markdown_format_enabled=true`;
this skill never writes user settings or `pluginConfigs`.
- no markdownlint config: offer to create a minimal `.markdownlint-cli2.jsonc` in the
repository root only when explicitly asked — the plugin imposes no rules of its own.

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.
Loading