From bb6a90de904dda9a39670e4123b634b194e0e573 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:37:36 -0400 Subject: [PATCH 01/11] feat(markdown-format): add setup skill on the uniform contract (0.5.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First tranche of wave #314 (epic #313, dim 8): the fleet's first conforming setup exemplar. check verifies the hook's runtime prerequisites read-only (Bash version, jq, markdownlint-cli2 via the hook's two sanctioned resolution paths, discovered markdownlint config plus the trust boundary, effective toggle); apply re-checks and resolves — pure guidance for system tools and the native userConfig toggle, with an explicitly requested consumer-repo npm install --save-dev markdownlint-cli2 as its only write path. Non-interactive when the action argument is supplied; never writes the plugin cache, user settings, or pluginConfigs. Contract-gate extension (check/apply structural rule) is deferred to the wave's final tranche: it would fail the 19 not-yet-migrated shape-B setups today. ## Related - Part of #314 - Part of #313 Co-Authored-By: Claude Fable 5 --- .../.claude-plugin/plugin.json | 2 +- plugins/markdown-format/CHANGELOG.md | 13 ++++ plugins/markdown-format/README.md | 3 + plugins/markdown-format/skills/setup/SKILL.md | 63 +++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 plugins/markdown-format/skills/setup/SKILL.md diff --git a/plugins/markdown-format/.claude-plugin/plugin.json b/plugins/markdown-format/.claude-plugin/plugin.json index 78efab998..b4a8bb2f0 100644 --- a/plugins/markdown-format/.claude-plugin/plugin.json +++ b/plugins/markdown-format/.claude-plugin/plugin.json @@ -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", diff --git a/plugins/markdown-format/CHANGELOG.md b/plugins/markdown-format/CHANGELOG.md index 202213115..13cd1676a 100644 --- a/plugins/markdown-format/CHANGELOG.md +++ b/plugins/markdown-format/CHANGELOG.md @@ -3,6 +3,19 @@ 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 consumer-repo + `npm install --save-dev markdownlint-cli2` as its only write path. + Non-interactive when the action argument is supplied. + ## [0.4.1] ### Changed diff --git a/plugins/markdown-format/README.md b/plugins/markdown-format/README.md index 28d3fd04b..1c16e07c1 100644 --- a/plugins/markdown-format/README.md +++ b/plugins/markdown-format/README.md @@ -63,6 +63,9 @@ 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. + ## Configuration The rules themselves are never configured here — the plugin's only rule source is diff --git a/plugins/markdown-format/skills/setup/SKILL.md b/plugins/markdown-format/skills/setup/SKILL.md new file mode 100644 index 000000000..04112cc05 --- /dev/null +++ b/plugins/markdown-format/skills/setup/SKILL.md @@ -0,0 +1,63 @@ +--- +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" +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, never a repository write. + +Action routing: no argument or `check` runs the check; `apply` runs the check first, then +remediation. Both are non-interactive — never prompt when the action is given. + +## `check` (read-only) + +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** — `bash --version | head -1`. Requires 3.2+; note that telemetry needs + 5.0+ (`EPOCHREALTIME`) and formatting still runs without it. +2. **`jq`** — `command -v jq`. FAIL if absent: the hook then skips with a visible + once-per-session notice instead of formatting. +3. **`markdownlint-cli2`** — resolvable as `command -v markdownlint-cli2` OR + `/node_modules/.bin/markdownlint-cli2` (the hook's two sanctioned resolution + paths; it never falls back to `npx`). FAIL if neither resolves. +4. **Consumer markdownlint config** — from the repository root, report which config file + `markdownlint-cli2` will discover (`.markdownlint-cli2.jsonc`, `.markdownlint.json`, …) + or INFO that none exists (the tool's defaults then apply). If the discovered config is + executable (`.cjs`/`.mjs`), surface the README's configuration trust boundary. +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 (e.g. `apply install-lint` may run +`npm install --save-dev markdownlint-cli2` in the consumer repository when a +`package.json` exists; that is a consumer-repo dependency change and is stated before +running). 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@ --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. +- Write the plugin cache, Claude Code user settings, or `pluginConfigs`. +- Download or execute tools during `check`; network use happens only in an explicitly + requested `apply install-lint` inside the consumer repository. From 5d9afdc504d8fcb976a6c60f45e29549de8c7262 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:44:43 -0400 Subject: [PATCH 02/11] fix(markdown-format): setup respects repo package manager + nested configs Codex review on #342: apply install-lint now detects the repository's package manager from its lockfile (pnpm/yarn/bun/npm) and stops with guidance on ambiguity instead of hardcoding npm; check scans the whole tree for markdownlint configs since the hook loads the file-to-root cascade, so nested executable configs now surface in the trust-boundary report instead of a falsely reassuring root-only scan. Co-Authored-By: Claude Fable 5 --- plugins/markdown-format/skills/setup/SKILL.md | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/plugins/markdown-format/skills/setup/SKILL.md b/plugins/markdown-format/skills/setup/SKILL.md index 04112cc05..720d0f584 100644 --- a/plugins/markdown-format/skills/setup/SKILL.md +++ b/plugins/markdown-format/skills/setup/SKILL.md @@ -28,10 +28,14 @@ Do not modify anything. 3. **`markdownlint-cli2`** — resolvable as `command -v markdownlint-cli2` OR `/node_modules/.bin/markdownlint-cli2` (the hook's two sanctioned resolution paths; it never falls back to `npx`). FAIL if neither resolves. -4. **Consumer markdownlint config** — from the repository root, report which config file - `markdownlint-cli2` will discover (`.markdownlint-cli2.jsonc`, `.markdownlint.json`, …) - or INFO that none exists (the tool's defaults then apply). If the discovered config is - executable (`.cjs`/`.mjs`), surface the README's configuration trust boundary. +4. **Consumer markdownlint config** — search the whole repository tree, not just the root: + the hook loads every config on the walk from an edited file's directory up to the repo + root, so `docs/.markdownlint.cjs` applies to `docs/foo.md` even when the root has none. + Find all `.markdownlint*` / `.markdownlint-cli2.*` files at any depth (skip + `node_modules`), report the root config the default cascade discovers (or INFO that none + exists — the tool's defaults then apply), list nested configs with their directory scope, + and surface the README's configuration trust boundary for every executable + (`.cjs`/`.mjs`) config found anywhere in the tree. 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 @@ -40,10 +44,14 @@ Do not modify anything. ## `apply` (idempotent) Run `check`, then for each FAIL offer the resolution — never install anything without the -consumer's explicit go-ahead in the invocation (e.g. `apply install-lint` may run -`npm install --save-dev markdownlint-cli2` in the consumer repository when a -`package.json` exists; that is a consumer-repo dependency change and is stated before -running). For everything else `apply` only points: +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**, detected from its lockfile: `pnpm-lock.yaml` → +`pnpm add -D`, `yarn.lock` → `yarn add -D`, `bun.lock`/`bun.lockb` → `bun add -d`, +`package-lock.json` or none → `npm install --save-dev`. With no `package.json`, or an +ambiguous multi-lockfile state, stop with manager-specific guidance instead of guessing — +never introduce a competing lockfile. The change is stated before running. For everything +else `apply` only points: - missing `jq` / Bash: platform install instructions from the README Requirements section; this skill never installs system packages. From d0efc44db41966128e66a7ef15f2bf48063065da Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:49:16 -0400 Subject: [PATCH 03/11] fix(markdown-format): setup matches hook risk criteria + packageManager field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex round 2 on #342: check's trust-boundary scan now mirrors the hook's collect_risky_configs exactly — declarative .markdownlint-cli2.* files declaring customRules/markdownItPlugins/outputFormatters are risky, not just .cjs/.mjs. Package-manager resolution gains the package.json "packageManager" rung between lockfile and the npm default, and a lockfile-vs-packageManager contradiction stops with guidance. Co-Authored-By: Claude Fable 5 --- plugins/markdown-format/skills/setup/SKILL.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/markdown-format/skills/setup/SKILL.md b/plugins/markdown-format/skills/setup/SKILL.md index 720d0f584..1ee352845 100644 --- a/plugins/markdown-format/skills/setup/SKILL.md +++ b/plugins/markdown-format/skills/setup/SKILL.md @@ -34,8 +34,10 @@ Do not modify anything. Find all `.markdownlint*` / `.markdownlint-cli2.*` files at any depth (skip `node_modules`), report the root config the default cascade discovers (or INFO that none exists — the tool's defaults then apply), list nested configs with their directory scope, - and surface the README's configuration trust boundary for every executable - (`.cjs`/`.mjs`) config found anywhere in the tree. + and surface the README's configuration trust boundary using the hook's own risk criteria: + any executable (`.cjs`/`.mjs`) config anywhere in the tree, AND any declarative + `.markdownlint-cli2.*` file declaring `customRules`, `markdownItPlugins`, or + `outputFormatters` — those keys load modules just like executable config. 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 @@ -46,10 +48,12 @@ Do not modify anything. 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**, detected from its lockfile: `pnpm-lock.yaml` → +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 none → `npm install --save-dev`. With no `package.json`, or an -ambiguous multi-lockfile state, stop with manager-specific guidance instead of guessing — +`package-lock.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 everything else `apply` only points: From 482ca750b65422e93df28f888a98c7b5a7ef4bd5 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:56:55 -0400 Subject: [PATCH 04/11] fix(markdown-format): setup validates shim like the hook, handles Yarn PnP Codex round 3 on #342: check now applies the hook's shim validation (resolve symlinks, reject targets escaping the repo's node_modules) so a shim the hook rejects cannot PASS setup; apply routes Yarn Berry/PnP repos to guidance instead of an install that yields no node_modules/.bin shim, and every remediation re-runs its check probe before claiming resolved. Co-Authored-By: Claude Fable 5 --- plugins/markdown-format/skills/setup/SKILL.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/markdown-format/skills/setup/SKILL.md b/plugins/markdown-format/skills/setup/SKILL.md index 1ee352845..356ff4b75 100644 --- a/plugins/markdown-format/skills/setup/SKILL.md +++ b/plugins/markdown-format/skills/setup/SKILL.md @@ -27,7 +27,10 @@ Do not modify anything. once-per-session notice instead of formatting. 3. **`markdownlint-cli2`** — resolvable as `command -v markdownlint-cli2` OR `/node_modules/.bin/markdownlint-cli2` (the hook's two sanctioned resolution - paths; it never falls back to `npx`). FAIL if neither resolves. + paths; it never falls back to `npx`). Validate the repo shim the way the hook does: + resolve symlinks and FAIL (with the reason) when the physical target escapes the + repository's `node_modules` tree — a shim the hook will reject must not PASS here. + FAIL if neither path resolves. 4. **Consumer markdownlint config** — search the whole repository tree, not just the root: the hook loads every config on the walk from an edited file's directory up to the repo root, so `docs/.markdownlint.cjs` applies to `docs/foo.md` even when the root has none. @@ -54,8 +57,13 @@ repository's own package manager**, resolved in order: lockfile (`pnpm-lock.yaml `"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 everything -else `apply` only points: +never introduce a competing lockfile. The change is stated before running. Yarn Berry/PnP +repositories (a `.pnp.cjs`, or `yarn.lock` without `nodeLinker: node-modules` in +`.yarnrc.yml`) get guidance instead of an install: PnP produces no +`node_modules/.bin` shim, so the hook cannot resolve a PnP-installed binary — install +`markdownlint-cli2` on `PATH` or switch the linker. 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. From 02c555c372a2dc0824134b16e98bf845eaaaf71c Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:01:19 -0400 Subject: [PATCH 05/11] refactor(markdown-format): setup derives hook behavior from hook source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pointer-over-copy: check no longer hand-copies the hook's resolution paths and risk criteria — it reads the hook script as the single source of truth and mirrors what it actually does, killing the drift class the last two review rounds exposed. Yarn linker semantics corrected against the yarnpkg nodeLinker docs (pnp default; node-modules and pnpm are the linkers that materialize node_modules). Co-Authored-By: Claude Fable 5 --- plugins/markdown-format/skills/setup/SKILL.md | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/plugins/markdown-format/skills/setup/SKILL.md b/plugins/markdown-format/skills/setup/SKILL.md index 356ff4b75..64b48b36c 100644 --- a/plugins/markdown-format/skills/setup/SKILL.md +++ b/plugins/markdown-format/skills/setup/SKILL.md @@ -18,29 +18,25 @@ remediation. Both are non-interactive — never prompt when the action is given. ## `check` (read-only) -Run each probe via Bash and report a PASS/FAIL/INFO table with one remediation line per FAIL. -Do not modify anything. +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** — `bash --version | head -1`. Requires 3.2+; note that telemetry needs - 5.0+ (`EPOCHREALTIME`) and formatting still runs without it. +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. -3. **`markdownlint-cli2`** — resolvable as `command -v markdownlint-cli2` OR - `/node_modules/.bin/markdownlint-cli2` (the hook's two sanctioned resolution - paths; it never falls back to `npx`). Validate the repo shim the way the hook does: - resolve symlinks and FAIL (with the reason) when the physical target escapes the - repository's `node_modules` tree — a shim the hook will reject must not PASS here. - FAIL if neither path resolves. -4. **Consumer markdownlint config** — search the whole repository tree, not just the root: - the hook loads every config on the walk from an edited file's directory up to the repo - root, so `docs/.markdownlint.cjs` applies to `docs/foo.md` even when the root has none. - Find all `.markdownlint*` / `.markdownlint-cli2.*` files at any depth (skip - `node_modules`), report the root config the default cascade discovers (or INFO that none - exists — the tool's defaults then apply), list nested configs with their directory scope, - and surface the README's configuration trust boundary using the hook's own risk criteria: - any executable (`.cjs`/`.mjs`) config anywhere in the tree, AND any declarative - `.markdownlint-cli2.*` file declaring `customRules`, `markdownItPlugins`, or - `outputFormatters` — those keys load modules just like executable config. +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. 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. + 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 @@ -58,10 +54,11 @@ repository's own package manager**, resolved in order: lockfile (`pnpm-lock.yaml 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. Yarn Berry/PnP -repositories (a `.pnp.cjs`, or `yarn.lock` without `nodeLinker: node-modules` in -`.yarnrc.yml`) get guidance instead of an install: PnP produces no -`node_modules/.bin` shim, so the hook cannot resolve a PnP-installed binary — install -`markdownlint-cli2` on `PATH` or switch the linker. After ANY remediation, re-run the +repositories (a `.pnp.cjs`, or `yarn.lock` whose `.yarnrc.yml` `nodeLinker` is `pnp` or +unset — PnP is Berry's default; only `node-modules` and `pnpm` materialize +`node_modules`) get guidance instead of an install: PnP generates a loader file, not the +`node_modules/.bin` shim the hook resolves — install `markdownlint-cli2` on `PATH` or +switch the linker. 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: From 114d4e5d28654968cc456cd8ce5cecc0741a47ff Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:02:09 -0400 Subject: [PATCH 06/11] fix(markdown-format): setup installs for Yarn Classic, exposes install-lint Codex round 4 on #342: PnP guidance now triggers only on positive PnP evidence (.pnp.cjs/.pnp.loader.mjs or nodeLinker: pnp) so Yarn Classic and materializing-linker Berry repos install normally, with the verify-after-remediation rule as the backstop; the install-lint action is exposed in the argument-hint and routing instead of being reachable only from prose. Co-Authored-By: Claude Fable 5 --- plugins/markdown-format/skills/setup/SKILL.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/plugins/markdown-format/skills/setup/SKILL.md b/plugins/markdown-format/skills/setup/SKILL.md index 64b48b36c..71876a128 100644 --- a/plugins/markdown-format/skills/setup/SKILL.md +++ b/plugins/markdown-format/skills/setup/SKILL.md @@ -1,7 +1,7 @@ --- 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" +argument-hint: "check | apply [install-lint]" user-invocable: true disable-model-invocation: true --- @@ -14,7 +14,8 @@ repository's own markdownlint config, and the only tunable is the native `userCo toggle — so `apply` is guidance-and-verify, never a repository write. Action routing: no argument or `check` runs the check; `apply` runs the check first, then -remediation. Both are non-interactive — never prompt when the action is given. +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) @@ -53,12 +54,13 @@ repository's own package manager**, resolved in order: lockfile (`pnpm-lock.yaml `"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. Yarn Berry/PnP -repositories (a `.pnp.cjs`, or `yarn.lock` whose `.yarnrc.yml` `nodeLinker` is `pnp` or -unset — PnP is Berry's default; only `node-modules` and `pnpm` materialize -`node_modules`) get guidance instead of an install: PnP generates a loader file, not the -`node_modules/.bin` shim the hook resolves — install `markdownlint-cli2` on `PATH` or -switch the linker. After ANY remediation, re-run the +never introduce a competing lockfile. The change is stated before running. When the repository shows Plug'n'Play evidence (a +`.pnp.cjs`/`.pnp.loader.mjs`, or `.yarnrc.yml` with `nodeLinker: pnp`), skip the install +and give guidance — PnP generates a loader file, not the `node_modules/.bin` shim the +hook resolves; install `markdownlint-cli2` on `PATH` or switch the linker. Otherwise +install (Yarn Classic and Berry with a materializing linker both qualify) — 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: From 98c809eeafab82128277e79abe28b05bd0c3d597 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:06:59 -0400 Subject: [PATCH 07/11] fix(markdown-format): setup queries yarn config get nodeLinker before installing Codex round 5 on #342: Berry defaults nodeLinker to pnp when unset, so marker-file inference misses a fresh Berry checkout. The install path now asks the repository's own Yarn (yarn config get nodeLinker) and routes pnp to guidance; node-modules/pnpm and Yarn Classic install. Co-Authored-By: Claude Fable 5 --- plugins/markdown-format/skills/setup/SKILL.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/markdown-format/skills/setup/SKILL.md b/plugins/markdown-format/skills/setup/SKILL.md index 71876a128..311bc2448 100644 --- a/plugins/markdown-format/skills/setup/SKILL.md +++ b/plugins/markdown-format/skills/setup/SKILL.md @@ -54,11 +54,12 @@ repository's own package manager**, resolved in order: lockfile (`pnpm-lock.yaml `"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. When the repository shows Plug'n'Play evidence (a -`.pnp.cjs`/`.pnp.loader.mjs`, or `.yarnrc.yml` with `nodeLinker: pnp`), skip the install -and give guidance — PnP generates a loader file, not the `node_modules/.bin` shim the -hook resolves; install `markdownlint-cli2` on `PATH` or switch the linker. Otherwise -install (Yarn Classic and Berry with a materializing linker both qualify) — the +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 PnP 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 From 8fd0127497a83ca98220f668eb252be7d8f53cae Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:12:32 -0400 Subject: [PATCH 08/11] fix(markdown-format): carve install-lint out of setup's no-write guarantee Codex round 6 on #342: the purpose-level "never a repository write" rule contradicted the explicitly authorized apply install-lint path; the guarantee now names that single write-path exception. Co-Authored-By: Claude Fable 5 --- plugins/markdown-format/skills/setup/SKILL.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/markdown-format/skills/setup/SKILL.md b/plugins/markdown-format/skills/setup/SKILL.md index 311bc2448..0c89cf93c 100644 --- a/plugins/markdown-format/skills/setup/SKILL.md +++ b/plugins/markdown-format/skills/setup/SKILL.md @@ -11,7 +11,8 @@ disable-model-invocation: true 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, never a repository write. +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 From d39f19a45a99791d64b40de75391498564846c8f Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:53:39 -0400 Subject: [PATCH 09/11] fix(markdown-format): spell Plug'n'Play in full for the typos gate Bare "PnP" trips the spell-checker's word splitter (Pn -> On); the full spelling is cleaner prose and needs no allowlist change to the standards-managed typos config. Co-Authored-By: Claude Fable 5 --- plugins/markdown-format/skills/setup/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/markdown-format/skills/setup/SKILL.md b/plugins/markdown-format/skills/setup/SKILL.md index 0c89cf93c..aa11edc9d 100644 --- a/plugins/markdown-format/skills/setup/SKILL.md +++ b/plugins/markdown-format/skills/setup/SKILL.md @@ -57,7 +57,7 @@ present. With no `package.json`, an ambiguous multi-lockfile state, or a lockfil 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 PnP generates a loader file, +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 From 52a1bb71bbb6d7f36cb62a424530d48b26c0210c Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 18 Jul 2026 17:09:17 -0400 Subject: [PATCH 10/11] =?UTF-8?q?fix(markdown-format):=20round-7=20review?= =?UTF-8?q?=20=E2=80=94=20docs=20match=20the=20skill,=20shrinkwrap=20rung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README documents apply install-lint explicitly and states the per-file config cascade accurately (nested configs govern their subtree; discovery caps at the repo root the hook cds to — per upstream markdownlint-cli2 docs); CHANGELOG describes the package-manager-aware install instead of hardcoding npm; npm-shrinkwrap.json now counts as an npm lockfile signal (and as a party to multi-lockfile ambiguity). Co-Authored-By: Claude Fable 5 --- plugins/markdown-format/CHANGELOG.md | 6 ++++-- plugins/markdown-format/README.md | 12 ++++++++---- plugins/markdown-format/skills/setup/SKILL.md | 6 +++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/plugins/markdown-format/CHANGELOG.md b/plugins/markdown-format/CHANGELOG.md index 13cd1676a..b1d4ab373 100644 --- a/plugins/markdown-format/CHANGELOG.md +++ b/plugins/markdown-format/CHANGELOG.md @@ -12,8 +12,10 @@ All notable changes to the `markdown-format` plugin are documented here. Format 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 consumer-repo - `npm install --save-dev markdownlint-cli2` as its only write path. + 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] diff --git a/plugins/markdown-format/README.md b/plugins/markdown-format/README.md index 1c16e07c1..3ca7f3714 100644 --- a/plugins/markdown-format/README.md +++ b/plugins/markdown-format/README.md @@ -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 @@ -64,7 +65,10 @@ the advisory to appear again. ``` Then verify the runtime prerequisites with `/markdown-format:setup check`; -`/markdown-format:setup apply` resolves anything the check reports. +`/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 diff --git a/plugins/markdown-format/skills/setup/SKILL.md b/plugins/markdown-format/skills/setup/SKILL.md index aa11edc9d..74a6c7d15 100644 --- a/plugins/markdown-format/skills/setup/SKILL.md +++ b/plugins/markdown-format/skills/setup/SKILL.md @@ -51,9 +51,9 @@ 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` → `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 +`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 From 435af2ecb7651aaca9ccd16578918fa0deb855b8 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 18 Jul 2026 17:10:51 -0400 Subject: [PATCH 11/11] fix(markdown-format): setup check adds a --version liveness probe Codex round 8 on #342: a repo shim can resolve yet be broken (missing Node interpreter, dangling target), so resolution-only PASS was misleading. check now executes the resolved linter with --version and fails with the execution error when it cannot run; the does-NOT-do section scopes its no-execution claim to that single liveness probe. Co-Authored-By: Claude Fable 5 --- plugins/markdown-format/skills/setup/SKILL.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/markdown-format/skills/setup/SKILL.md b/plugins/markdown-format/skills/setup/SKILL.md index 74a6c7d15..251a8dcd8 100644 --- a/plugins/markdown-format/skills/setup/SKILL.md +++ b/plugins/markdown-format/skills/setup/SKILL.md @@ -31,8 +31,11 @@ PASS/FAIL/INFO table with one remediation line per FAIL. Do not modify anything. once-per-session notice instead of formatting. 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. FAIL when nothing the - hook would accept resolves. + 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. Search the whole tree (skip `node_modules`), report the root config the cascade @@ -78,7 +81,9 @@ Re-running `apply` after everything passes changes nothing and reports "already ## What this skill does NOT do -- Run the formatter — editing any `.md` file exercises the hook end-to-end. +- 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 or execute tools during `check`; network use happens only in an explicitly +- Download anything during `check`; network use happens only in an explicitly requested `apply install-lint` inside the consumer repository.