Skip to content

feat(claude-ops): surface missing fleet prerequisites without installing (#4240) - #5096

Merged
kyle-sexton merged 11 commits into
mainfrom
cursor/4240-fleet-prereq-check-37e9
Sep 29, 2026
Merged

kyle-sexton merged 11 commits into
mainfrom
cursor/4240-fleet-prereq-check-37e9

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Closes #4240

Summary

Missing external tools now surface to the main session and are reported, never installed. This follows the operator decision on #4240: a claude-ops:prerequisites skill backed by a prerequisites.json manifest per plugin, with every probe reading that file.

Fix

  • prerequisites.json at the root of biome-format, go-format, markdown-format, context7, and playwright declares each tool: name, optional local_bin, check, and install.
  • claude-ops:prerequisites (check-prerequisites.sh) reads the manifest of each enabled plugin and prints a present/missing table. It does not install, download, or run npx.
  • The session-start hooks/probe-prerequisite.sh in biome-format, go-format, and markdown-format is one shared script. It takes every tool name, local_bin, check command, and install line from its plugin's prerequisites.json; no binary name or install text is hardcoded.
  • hook::notice_once takes an optional prerequisite class: the notice latches once per session instead of per agent, and a renewal keeps the install route. Callers that omit the class, including every guardrails, context-guard, and rate-limit-guard hook, behave exactly as before. The shared lib/hook-utils.sh is synced to all 17 copies.
  • The per-edit format hooks share the probe's notice key, so a session sees one notice, not two.
  • Per-plugin check skills for the three format plugins are read-only and never run apply.
  • Every touched plugin is bumped one patch above main with a CHANGELOG entry.

Verification

  • scripts/check-prerequisite-probes.test.sh (new, 18 cases). It fails when a probe differs from the shared manifest reader, contains an install command, omits a declared tool's name, check, or install text from its notice while the tool is missing, or prints anything when every declared tool is present.
  • check-prerequisites.test.sh 6/0, lib/hook-utils.test.sh 582/0, sync-hook-utils.sh --check, go-format 63/0, biome-format 54/0.
  • markdown-format (175/2) and guardrails run-guards.test.sh (309/2) fail the same two cases each on a clean origin/main checkout on this machine, so those failures are not from this diff.
  • Guard plugins: the only code change is the synced hook-utils.sh notice path. No guard hook passes the prerequisite class, so their allow and deny decisions are unchanged from main.
  • validate-plugins.sh, check-changelog-parity.sh --check --check-order, check-changed-skills.sh origin/main, validate-plugin-contracts.mjs, catalog and cheat-sheet --check, skill-count claims, skill leaf names, purged em dashes, and shellcheck all pass.

Related

🤖 Generated with Claude Code

https://claude.ai/code/session_01WejvGhkHUSWPRim2caW2Wh

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

PR body contract — issue linkage

This PR body conforms to the issue-linkage contract. Nothing to do.

@cursor
cursor Bot force-pushed the cursor/4240-fleet-prereq-check-37e9 branch 3 times, most recently from c693470 to 09f2ea7 Compare September 28, 2026 12:28
Prerequisite notices latch once per session and keep the install route.
markdown-format, biome-format, and go-format probe at session start and
name a model-invocable check skill. claude-ops:prerequisites reads each
plugin's prerequisites.json and prints which tools are missing.

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
@cursor
cursor Bot force-pushed the cursor/4240-fleet-prereq-check-37e9 branch from 09f2ea7 to 5a15299 Compare September 28, 2026 12:38
kyle-sexton and others added 3 commits September 28, 2026 11:39
# Conflicts:
#	plugins/claude-ops/.claude-plugin/plugin.json
#	plugins/claude-ops/CHANGELOG.md
Hoist each probe's early exit above the hook-utils source, run the three
SessionStart probes in exec form, let the format-hook launch gates admit
that probe row, add evals for the four new skills, register the new
check leaf owners, count thirteen claude-ops skills in the README, and
replace A && B || C test lines shellcheck flags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PT4esxbdC7eQiwQxy35Mie
Resolve conflicts in claude-ops (identical-bump collision at 0.63.25,
renumbered to 0.63.26; description conflict resolved by keeping the
PR's content unchanged, since main's description text matched the
merge base) and source-control (identical-bump collision at 0.62.9,
renumbered to 0.62.10). The other 17 touched plugins' version bumps
were already strictly above main's and merged clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PT4esxbdC7eQiwQxy35Mie
kyle-sexton and others added 3 commits September 28, 2026 21:47
Re-bump every touched plugin one patch above main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WejvGhkHUSWPRim2caW2Wh
…eement

The session-start probes in biome-format, go-format, and markdown-format are
one shared script that takes every tool name, local_bin, check, and install
line from the plugin's prerequisites.json instead of hardcoding them.
scripts/check-prerequisite-probes.test.sh fails when a probe drifts from the
shared reader, carries an install command, or omits a declared tool's name,
check, or install text. markdown-format's edit hook now latches on the same
notice key as its probe. Restore the claude-ops description the re-bump
dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WejvGhkHUSWPRim2caW2Wh
Re-bump every touched plugin one patch above main and keep the claude-ops
description's prerequisites entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WejvGhkHUSWPRim2caW2Wh
@kyle-sexton
kyle-sexton marked this pull request as ready for review September 29, 2026 02:00
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-29T02:04:50.562756Z 6c144df Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c144dfdbc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/claude-ops/skills/prerequisites/scripts/check-prerequisites.sh Outdated
Comment thread plugins/claude-ops/skills/prerequisites/scripts/check-prerequisites.sh Outdated
Comment thread plugins/claude-ops/skills/prerequisites/SKILL.md Outdated
Re-bump every touched plugin one patch above main. State the prerequisite
notice cadence (once per session, renewal keeps the install route) in the
hook-observability convention and the biome-format and go-format READMEs,
beside main's once-per-session-and-agent wording for other skip notices.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WejvGhkHUSWPRim2caW2Wh
@claude

claude Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 2m 45s —— View job


Security review — /review:security-review

  • Gather context (PR diff, changed files)
  • Run /review:security-review criteria over the diff
  • Report findings

No security issues found in this PR's diff.

What I reviewed: the new check-prerequisites.sh fleet-prerequisite reader (plugins/claude-ops/skills/prerequisites/scripts/check-prerequisites.sh), the shared probe-prerequisite.sh now templated from each plugin's prerequisites.json (e.g. plugins/go-format/hooks/probe-prerequisite.sh), and the hook::notice_once/hook::emit_skip_notice changes synced to all 17 hook-utils.sh copies (e.g. lib/hook-utils.sh#L193-L218).

Notes:

  • field() in probe-prerequisite.sh extracts check/install text from prerequisites.json via regex and only ever interpolates it into a notice string (hook::emit_skip_notice ... "Install: $(field install "$entry")"); that substitution happens once and the result is never re-evaluated as shell, so embedded shell metacharacters in a manifest value can't achieve command injection. prerequisites.json is also a repo-committed, plugin-author-controlled file, not runtime/attacker input, so it isn't a trust-boundary crossing either way.
  • check-prerequisites.sh's embedded Python only reads local ~/.claude config/state (settings.json, installed_plugins.json, prerequisites.json) with json.load and does presence checks via shutil.which — no subprocess/eval/os.system calls, so there's no injection surface from plugin or config content.
  • Confirmed the guardrails/rate-limit-guard/context-guard hook-utils.sh diffs are the synced notice-path change only (plugins/guardrails/hooks/hook-utils.sh) — no guard hook in this diff passes the new prerequisite class to hook::notice_once, so allow/deny decisions are unaffected, matching the PR description.
  • No new network calls, credential handling, or install/download execution were introduced; every check/probe path is read-only as documented, and apply-style install skills stay gated behind explicit user request in the check SKILL.md files.

Out of scope for this lane (functional, not security — already flagged by the Codex review and better suited to /review:code-review): check-prerequisites.sh's enabled-plugin discovery only reads user/machine-scope settings.json/settings.local.json under CLAUDE_CONFIG_DIR, so project/local-scoped plugin enablement isn't merged in, and an empty resolved fleet falls through to the repo-wide plugins/*/prerequisites.json scan — both can produce a misleading present/missing table, but neither is an exploitable vulnerability.

@claude

claude Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 3m 17s —— View job


Code review — /review:code-review

  • Gather context (PR diff, files, existing Codex feedback)
  • Invoke /review:code-review skill per instructions
  • Post inline comments for anchorable findings
  • Update summary comment with overview

Reviewed the changed files per the code-review lane's criteria (correctness + CLAUDE.md/AGENTS.md alignment, scoped to files changed in this PR). This PR's core surface is: the new claude-ops:prerequisites skill and its check-prerequisites.sh fleet reader, the shared probe-prerequisite.sh SessionStart probe (synced identically into biome-format, go-format, markdown-format), the prerequisites.json manifests, and the hook::notice_once prerequisite class added to lib/hook-utils.sh (synced to all 17 copies).

3 findings posted as inline comments:

  1. plugins/claude-ops/skills/prerequisites/scripts/check-prerequisites.sh:37 — the no-args discovery path reads enabledPlugins only from $CLAUDE_CONFIG_DIR (~/.claude)'s settings.json/settings.local.json, never from the current repo's .claude/settings.json / settings.local.json. A plugin enabled only at project/local scope is invisible to this checker, so the table can report the fleet as healthy while a project-enabled plugin's prerequisite is actually missing. plugins/claude-ops/skills/plugins/scripts/fleet-state.sh (~lines 773-804) already merges user+project+local scopes; this script should do the same. check-prerequisites.test.sh only covers the explicit --plugin-root path, so this gap is untested.
  2. plugins/claude-ops/skills/prerequisites/scripts/check-prerequisites.sh:79 — an empty ROOTS array is treated identically whether the settings/installed_plugins.json read succeeded with zero enabled plugins or genuinely failed. Both fall through to scanning this repo's own plugins/*/prerequisites.json, so running the checker from a fresh claude-code-plugins checkout with nothing enabled reports all 5 in-repo manifests as "the fleet" — the opposite of "does this host have what the enabled plugins need?" The read-succeeded-but-empty case needs to be distinguished from read-failed before falling back.
  3. plugins/claude-ops/skills/prerequisites/SKILL.md:29 (low severity) — the ## Next bullet hard-codes /markdown-format:check as the outcome for "a tool is missing," which is wrong for 4 of the 5 declared tools. Not actively harmful since line 32 immediately corrects it ("Run the named check"), but worth generalizing the bullet.

Findings 1 and 2 substantively overlap with the existing Codex review comments on this PR — I verified both independently against fleet-state.sh's scope-merge logic and by tracing the empty-vs-absent-state branch, and both hold up as real, concretely-triggerable bugs (I did not just restate Codex's text).

Everything else checked out: the shared hook::notice_once "prerequisite" class in lib/hook-utils.sh correctly scopes the latch to session-only (not per-agent) while leaving every non-prerequisite caller's behavior unchanged (verified by reading the full function and its new test cases in lib/hook-utils.test.sh:1300-1339); the per-edit format hooks (biome-format.sh:124, go-format.sh:140, markdown-format.sh:689) and their SessionStart probes key on the identical <plugin>-<tool> string, so the "one notice, not two" claim checks out; probe-prerequisite.sh is byte-identical across all three format plugins (confirmed via the SHA in the file listing) and contains no install/download command; and the prerequisites.json manifests for context7/playwright are plain declarative data with no logic to review.

Author-claimed and not independently re-run by me (no test/build tooling available in this session): the test counts and pass/fail figures in the PR's Verification section, sync-hook-utils.sh --check, and the various validate-*/check-* gate scripts.

Comment thread plugins/claude-ops/skills/prerequisites/scripts/check-prerequisites.sh Outdated
Comment thread plugins/claude-ops/skills/prerequisites/SKILL.md Outdated
kyle-sexton and others added 2 commits September 28, 2026 22:23
Register hooks/probe-prerequisite.sh in the cross-plugin source registry with
check-prerequisite-probes.test.sh as its drift check, and drop the GNU \b and
the piped early-exit grep from that test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WejvGhkHUSWPRim2caW2Wh
Re-bump claude-ops and source-control one patch above main and keep the
claude-ops description's prerequisites entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WejvGhkHUSWPRim2caW2Wh
@claude

claude Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 47s —— View job


Code review — /review:code-review

  • Gather context (PR diff, files, existing Codex/Claude feedback)
  • Invoke /review:code-review skill per instructions
  • Post inline comments for anchorable findings
  • Update summary comment with overview

Reviewed the full diff (origin/main...HEAD, 94 files, +1424/-163) at head 1d32bf0. The only change since the prior review pass (base 253ef75/6c144df) is 0449898 — registering the synced probe-prerequisite.sh in scripts/cross-plugin-source-registry.txt and portability-hardening two grep patterns in scripts/check-prerequisite-probes.test.sh. That commit is correct: it uses an associative-array membership check instead of the GNU-only \b/piped-grep -q pattern, and adds the registry entry so hooks/probe-prerequisite.sh (byte-identical across biome-format, go-format, markdown-format) has a drift check.

The substantive surface — check-prerequisites.sh, the shared probe, the hook::notice_once "prerequisite" class in lib/hook-utils.sh, and the prerequisites.json manifests — is unchanged from the previously-reviewed commit, so the 3 findings from that pass still hold and are posted as inline comments on the current HEAD:

  1. check-prerequisites.sh:42 — no-args discovery reads enabledPlugins only from $CLAUDE_CONFIG_DIR, never from the current repo's project/local .claude/settings*.json. A project-only-enabled plugin's prerequisite is silently invisible to the table.
  2. check-prerequisites.sh:89 — a successfully-read, legitimately-empty enabled fleet is indistinguishable from "state absent" and falls back to scanning this repo's own plugins/*/prerequisites.json, reporting an unrelated 5-tool "fleet" from a fresh checkout with nothing enabled.
  3. SKILL.md:29 (low severity) — ## Next hard-codes /markdown-format:check as the missing-tool outcome, wrong for 4 of 5 declared tools; corrected two lines later but misleading as the headline bullet.

Findings 1 and 2 are P2 correctness bugs on the no-args discovery path, which check-prerequisites.test.sh doesn't cover (it only exercises --plugin-root). Both were independently raised by Codex and a prior claude review pass on commit 253ef75/6c144df and remain unaddressed on this HEAD.

Everything else checked out on this pass: lib/hook-utils.sh's new HOOK_NOTICE_KEEP_BODY/session_only path for the prerequisite class only changes behavior when a caller passes that class (verified no guard/rate-limit/context-guard hook does); probe-prerequisite.sh correctly derives every name/local_bin/check/install string from prerequisites.json with no hardcoded binary or install command; and the per-edit hook / SessionStart probe notice keys match (e.g. biome-format-biome in both biome-format.sh:124 and probe-prerequisite.sh:52), so the "one notice, not two" claim holds.

@claude

claude Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 1s —— View job


Security review — /review:security-review

  • Gather context (PR diff, changed files)
  • Run /review:security-review criteria over the diff
  • Report findings

No security issues found in this PR's diff.

What changed since the last security review on this head: the only new commits since the prior /review:security-review pass (at 253ef75) are 0449898 (registers hooks/probe-prerequisite.sh in the cross-plugin source registry and swaps a GNU-\b grep for a POSIX-compatible pattern in check-prerequisite-probes.test.sh) and the merge commit 1d32bf0. I confirmed the security-relevant surface — plugins/claude-ops/skills/prerequisites/scripts/check-prerequisites.sh, the three probe-prerequisite.sh copies (biome-format, go-format, markdown-format), and lib/hook-utils.sh — is byte-identical to what was already reviewed with no findings; the new commit touches only a test script and a registry doc entry, neither of which executes against untrusted input or handles credentials/tokens.

New commit reviewed on its own merits:

  • scripts/check-prerequisite-probes.test.sh: the \b-word-boundary grep was replaced with [[:space:]]|$ and the piped printf … | grep -qxF early-exit was replaced with an associative-array membership check (IS_DECLARED[$base]). Both are portability/style fixes to test-harness code that only inspects this repo's own prerequisites.json manifests and local $PATH binaries — no injection surface, no untrusted input, no privilege boundary.
  • scripts/cross-plugin-source-registry.txt: adds a comment line documenting that hooks/probe-prerequisite.sh is a synced file guarded by the new drift test. Pure documentation.

This reaffirms the prior pass's conclusions: check-prerequisites.sh's embedded Python only does read-only json.load/shutil.which checks against local ~/.claude state (no subprocess/eval/os.system), probe-prerequisite.sh's field() extraction only interpolates prerequisites.json values into a notice string (never re-evaluated as shell), and no guard hook (guardrails/rate-limit-guard/context-guard) passes the new prerequisite notice class, so their allow/deny decisions are unchanged.

(Functional/correctness observations — project/local plugin-scope discovery gap and the empty-vs-absent ROOTS fallback — are already tracked via Codex's and the prior code-review pass's inline comments; out of scope for this security lane.)

Comment thread plugins/claude-ops/skills/prerequisites/scripts/check-prerequisites.sh Outdated
Comment thread plugins/claude-ops/skills/prerequisites/scripts/check-prerequisites.sh Outdated
Comment thread plugins/claude-ops/skills/prerequisites/SKILL.md Outdated
…mpty fleet

check-prerequisites.sh merges enabledPlugins from user, project, and local
settings (later scope wins), and falls back to scanning the repository only
when no settings or install state exists, so a read state with nothing
enabled prints an empty table. The skill's Next line points at the row's own
check skill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WejvGhkHUSWPRim2caW2Wh
@claude

claude Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 53s —— View job


Security review — /review:security-review

  • Gather context (PR diff, changed files)
  • Run /review:security-review criteria over the diff
  • Report findings

No security issues found in this PR's diff.

What changed since the last security review on this branch: the only new commit is b44b45e (fix(claude-ops): read project-scope enablement; no repo scan for an empty fleet), which addresses the two P2 correctness findings raised by Codex and prior claude review passes (project/local-scope plugin discovery, and distinguishing "state read but empty" from "state absent"). It touches only plugins/claude-ops/skills/prerequisites/scripts/check-prerequisites.sh, its test, and SKILL.md prose — no other file changed.

Reviewed on its own merits:

  • The embedded Python (lines 36–83) now also reads $CLAUDE_PROJECT_DIR/.claude/settings.json / settings.local.json in addition to $CLAUDE_CONFIG_DIR's, and merges enabledPlugins across scopes. All four paths are opened with plain json.load — no subprocess/eval/os.system — so this is a read-only widening of which trusted-looking file paths get parsed, not a new execution surface.
  • The set of enabled keys can now be influenced by a project-scope .claude/settings.local.json inside a checked-out repo, but the actual installPath used to locate each plugin's prerequisites.json still comes only from $CLAUDE_CONFIG_DIR/plugins/installed_plugins.json (line 62), which is user/machine-scope, not repo-controlled. So a hostile repo's settings.local.json can at most toggle which already-installed plugin's prerequisites get checked — it can't point the script at an attacker-chosen path or inject a new check/install string, since those still come from that installed plugin's own prerequisites.json on disk. No new injection or path-traversal surface.
  • project (from $CLAUDE_PROJECT_DIR or git rev-parse --show-toplevel) is passed to Python only as an argv string consumed via os.path.join/os.path.isfile — never interpolated into a shell command or eval'd. The STATE_READ sentinel line is parsed back in bash with a literal == string comparison (line 90), not eval, so no injection there either.
  • This mirrors the existing scope-merge trust model already used by plugins/claude-ops/skills/plugins/scripts/fleet-state.sh for the equivalent "effective fleet" computation, so it doesn't introduce a new trust boundary — it fixes an inconsistency with it.
  • The new test cases in check-prerequisites.test.sh (project-local enablement, empty-fleet fallback) only exercise local fixture directories under $WORK; no network calls, credentials, or execution of untrusted content.

This reaffirms the conclusions of the prior security-review passes on this PR (253ef75/1d32bf0): check-prerequisites.sh remains read-only, probe-prerequisite.sh's field() extraction only interpolates manifest text into a notice string, and no guard hook passes the prerequisite notice class.

(The correctness fix itself — and whether it fully resolves the Codex/code-review P2 findings — is /review:code-review's lane, not this one.)

@claude

claude Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 2m 54s —— View job


Code review — /review:code-review

  • Gather context (PR diff, files, existing Codex/Claude feedback)
  • Invoke /review:code-review skill per instructions
  • Post inline comments for anchorable findings
  • Update summary comment with overview

The only change since the last review pass on this lane (base 1d32bf0) is b44b45e, which touches exactly check-prerequisites.sh, check-prerequisites.test.sh, and SKILL.md in plugins/claude-ops/skills/prerequisites/. I traced the new logic by hand (line by line, including the embedded Python) rather than restating the author's own test-count claims, since I could not execute check-prerequisites.test.sh in this sandbox (script execution was denied by the permission gate). I did not find any new correctness issues, and the three findings raised by Codex and prior claude review passes on the earlier commit are each resolved:

  1. Project/local-scope enablement now merges in. check-prerequisites.sh:38-95 now reads enabledPlugins from $CLAUDE_CONFIG_DIR/settings.json, settings.local.json, and $CLAUDE_PROJECT_DIR/.claude/settings.json/settings.local.json (falling back to git rev-parse --show-toplevel when CLAUDE_PROJECT_DIR is unset), applying each scope's true/false in that order so a later scope's value wins per key. I traced the new test case ("project-local enablement is read") by hand: a plugin disabled in user settings.json but enabled in the project's settings.local.json correctly resolves to enabled and its installed_plugins.json record is picked up.
  2. Empty-but-read state no longer triggers the repo-scan fallback. The Python block now emits a separate STATE_READ marker whenever any settings file parsed successfully or installed_plugins.json exists (lines 60-68), and the repo-scan/error branches (lines 99, 109) now gate on STATE_READ == 0, not just an empty ROOTS array. I traced the new "empty enabled fleet exits 0" test case: with settings read successfully and zero plugins enabled, the script now prints an empty table (missing=0 present=0, exit 0) instead of scanning this repo's own plugins/*/prerequisites.json.
  3. SKILL.md's ## Next bullet is now generic. SKILL.md:29 reads "A tool is missing: the check skill that row names" instead of hard-coding /markdown-format:check.

One thing worth noting but not blocking: the merge order comment says "User, then project, then local" (line 46), but the actual scope list also includes the user-level settings.local.json between user and project scopes — a scope fleet-state.sh's merge doesn't model at all. That's pre-existing behavior carried over from before this PR (not introduced by b44b45e), and it's arguably more complete than fleet-state.sh, so I'm not raising it as a finding.

No inline comments to post — no new anchorable findings on this head.

Author-claimed and not independently re-run by me in this session (no ability to execute scripts): the specific test pass counts in the PR description, sync-hook-utils.sh --check, and the various validate-*/check-* gate scripts.

@kyle-sexton
kyle-sexton merged commit 030bb3d into main Sep 29, 2026
19 checks passed
@kyle-sexton
kyle-sexton deleted the cursor/4240-fleet-prereq-check-37e9 branch September 29, 2026 02:38
kyle-sexton added a commit that referenced this pull request Sep 29, 2026
…t:check

The setup skill's pre-computed jq row only runs when setup itself loads, so
under check it is literal command text. The check skill now runs the probe
itself, setup tells a reader to do the same when the row carries no result,
and a new eval requires a jq FAIL row when jq is absent.

Refs #5096, #4566

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EugXnFddtpHcY5gTuyEirB
kyle-sexton added a commit that referenced this pull request Sep 29, 2026
…pped (#5245)

Refs: #4240
Refs: #5096
Refs: #4612

## Summary

The 0.8.5 CHANGELOG entry for `powershell-format` said format hooks
probe at session start and that `/claude-ops:prerequisites` reads a
`prerequisites.json` from this plugin. Neither exists:
`hooks/hooks.json` has only PostToolUse rows, and the plugin ships no
`prerequisites.json` or `probe-prerequisite.sh`. The omission is
deliberate (README Requirements, `skills/setup/SKILL.md`): `jq` is the
only prerequisite the hook script probes, and pwsh, PSScriptAnalyzer and
the settings file stay quiet not-applicable.

Two cross-group requests also landed here: every hook row runs through
`node hooks/exec-bash.mjs`, so Node.js is now a declared prerequisite;
and the hook budget figures predate that launcher.

## Fix

- Rewrote the single 0.8.5 bullet to state what shipped here:
`hooks/hook-utils.sh` was resynced, `hook::notice_once` gained an
optional `prerequisite` class, no hook in this plugin passes it, and
there is no probe or `prerequisites.json`.
- Bumped the plugin to 0.8.6 and added a 0.8.6 `Fixed` entry noting the
correction. It links #5286, which tracks adding the probe and manifest
to the remaining binary-probing format plugins.
- README Requirements now lists Node.js on `PATH` (Claude Code's native
binary neither ships nor uses Node, so without `node` the hooks do not
launch); the setup `check` gains a `node` probe run through Bash, FAIL
when absent, next to `jq`.
- README hook budget table carries a dated note that its 0.7.45 figures
predate the node launcher and that each fire now adds one `node`
process.
- Extended the 0.8.6 CHANGELOG entry for the above, no second version
bump.
- No probe or `prerequisites.json` was added (waits on the #4240 Q1
owner answer); `hook-utils.sh` and `exec-bash.mjs` are untouched.

## Verification

- `bash scripts/check-changelog-parity.sh --check --check-order`: pass
- `bash scripts/validate-plugins.sh`: all manifests and the catalog
validated
- `bash scripts/check-purged-em-dashes.sh`: no em dashes
- `bash scripts/check-cross-plugin-source-drift.sh`: exit 0
- `bash scripts/check-prerequisite-probes.test.sh`: 18 cases, 0 failed
- `bash plugins/powershell-format/hooks/powershell-format.test.sh`:
PASS=91 FAIL=0

## Related

- Audit finding `plugin-powershell-format` (changelog-integrity, medium)
in `.work/audit/REPORT.md`; originating change #4240, resync in #5096,
README note #4612.
- No owned issues. Cross-group requests applied: hook-launcher (F26
budget note and node declaration; the launcher code stays with #5309,
which also bumps this plugin to 0.8.6, so whichever merges second needs
a version and CHANGELOG reconcile) and biome-format (link #5286, no
probe or `prerequisites.json` until #4240 Q1 is answered).
- Follow-up for the other binary-probing plugins: #5286.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01EugXnFddtpHcY5gTuyEirB

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Sep 29, 2026
… docs (#5266)

Refs: #3574
Refs: #4784
Refs: #4671
Refs: #5096
Refs: #3686
Refs: #4240
Refs: #3356
Refs: #5057
Refs: #4581

## Summary

Audit fixes for `plugins/go-format` (REPORT.md finding
`plugin-go-format` and the 3c row for
`hooks/probe-prerequisite.sh:43-52`). Closes no issue: #3574 is owned by
core-docs and waits on an owner decision.

- The SessionStart prerequisite probe ran even with
`go_format_enabled=false`, so the disabled plugin still printed a
`goimports was not found` notice.
- README, setup skill, hook header comments and evals still described
the hook as unconditional, which predates the gitignore exemption
(#4784).
- The setup skill's toggle-off step carried stale scope advice that
contradicts the reconfiguration convention.

## Fix

- `hooks/hooks.json`: the SessionStart row passes
`--run-if-unset-or-true GO_FORMAT_ENABLED` to the launcher, the same
shape as `typos-format`. `probe-prerequisite.sh`, `hook-utils.sh` and
`exec-bash.mjs` are untouched (the first two are byte-pinned to the
shared copy).
- `hooks/go-format.test.sh`: hook-wiring selectors match the new args;
new behavioral cases assert no notice with the switch off and the notice
with it unset or `true`.
- `README.md`, `skills/setup/SKILL.md`, `hooks/go-format.sh` and
`go-format.test.sh` header comments: "no consumer-config opt-in gate"
replaces "unconditional"; the gitignored-file skip and
`go_format_lint_gitignored` are documented; the setup `check` action
reports the option's effective value.
- `skills/setup/SKILL.md` toggle-off: prints the convention's short form
(`-s user`, never uninstall to reconfigure, next-session observation,
read output not exit code) and cites the convention.
- `skills/setup/evals/evals.json`: new eval for the gitignore option;
eval 5 aligned with the short form.
- `plugin.json` 0.4.5 to 0.4.6 with a CHANGELOG entry. The `plugin.json`
description ("Runs unconditionally (no consumer-config gate)") stays
accurate; changing it would force a `docs/catalog.md` regeneration
outside this change's scope.

- Cross-group requests applied: the setup toggle-off step keeps `-s
user` per the convention on main, and the setup `check` action keeps
`node` a FAIL behind the kill switch. README Requirements and the setup
`check` action declare Node.js (hook-launcher request). CHANGELOG
entries 0.3.62, 0.3.63, 0.4.2 and 0.4.3 read "Shared launcher/library
sync; no change to this plugin's behavior" and 0.4.1 drops the
shell-form sentence; these released-entry edits are declared in the
0.4.6 entry and are not folded or renumbered.

## Verification

- `bash plugins/go-format/hooks/go-format.test.sh`: PASS=66 FAIL=0
- `bash scripts/validate-plugins.sh`: all manifests and catalog
validated
- `bash scripts/check-changelog-parity.sh --check --check-order`: pass
- `bash scripts/check-prerequisite-probes.test.sh`,
`check-hook-exec-form.sh`, `check-hook-userconfig-argv.sh`,
`check-hook-wiring-liveness.sh`, `check-killswitch-hoist.sh`,
`check-cross-plugin-source-drift.sh`, `check-hooks-description.sh`,
`check-purged-em-dashes.sh`: exit 0
- `bash scripts/check-changed-skills.sh origin/main`: setup skill PASS,
0 errors
- `python3 scripts/sync-plugin-options-docs.py --check`, `node
scripts/generate-catalog.mjs --check`: exit 0

## Related

Audit findings: `plugin-go-format` (correctness, docs-coherence,
convention); 3c row `probe-prerequisite.sh:43-52`; 3d finding #3574
(owned by core-docs, not changed here). Related issues: #4784, #4671,
#5096, #3686, #4240, #3356, #5057, #4581.

Applied from other groups: hook-launcher F43, F44 and the node
declaration; the scope wording request was not applied (see below).
Skipped: biome-format request to gate the SessionStart row, already done
in this PR (`hooks.json` passes `--run-if-unset-or-true
GO_FORMAT_ENABLED`, with disabled, unset and `true` probe tests). The
setup toggle-off step follows caveat 2 as it reads on main (`-s user`);
the conventions group (#5313) owns changing it, after which the setup
skills can follow.

Cross-group requests:
- core-docs: include go-format's setup rationale (`SKILL.md:69`) as
evidence in the #3574 decision packet; optionally reword the
`plugin.json` description together with `docs/catalog.md` if the owner
wants the gitignore exemption named.
- conventions: `scripts/sync-plugin-options-docs.py:126` emits "pass the
scope `claude plugin list` reports", contradicting the reconfiguration
convention's caveat 2; fix the template and regenerate every README's
generated options block (go-format's generated block changes in that
run).
- biome-format and markdown-format carry the same probe-gate defect;
their own groups own the fix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01EugXnFddtpHcY5gTuyEirB

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Sep 29, 2026
…t-ssot, write-for-agents and the changelog (#5296)

Refs: #3574
Refs: #4784
Refs: #4671
Refs: #5096
Refs: #3686
Refs: #4240
Refs: #3356
Refs: #5057
Refs: #4581

## Summary

Audit fixes for `plugins/go-format` (REPORT.md finding
`plugin-go-format` and the 3c row for
`hooks/probe-prerequisite.sh:43-52`). Closes no issue: #3574 is owned by
core-docs and waits on an owner decision.

- The SessionStart prerequisite probe ran even with
`go_format_enabled=false`, so the disabled plugin still printed a
`goimports was not found` notice.
- README, setup skill, hook header comments and evals still described
the hook as unconditional, which predates the gitignore exemption
(#4784).
- The setup skill's toggle-off step carried stale scope advice that
contradicts the reconfiguration convention.

## Fix

- `hooks/hooks.json`: the SessionStart row passes
`--run-if-unset-or-true GO_FORMAT_ENABLED` to the launcher, the same
shape as `typos-format`. `probe-prerequisite.sh`, `hook-utils.sh` and
`exec-bash.mjs` are untouched (the first two are byte-pinned to the
shared copy).
- `hooks/go-format.test.sh`: hook-wiring selectors match the new args;
new behavioral cases assert no notice with the switch off and the notice
with it unset or `true`.
- `README.md`, `skills/setup/SKILL.md`, `hooks/go-format.sh` and
`go-format.test.sh` header comments: "no consumer-config opt-in gate"
replaces "unconditional"; the gitignored-file skip and
`go_format_lint_gitignored` are documented; the setup `check` action
reports the option's effective value.
- `skills/setup/SKILL.md` toggle-off: prints the convention's short form
(`-s user`, never uninstall to reconfigure, next-session observation,
read output not exit code) and cites the convention.
- `skills/setup/evals/evals.json`: new eval for the gitignore option;
eval 5 aligned with the short form.
- `plugin.json` 0.4.5 to 0.4.6 with a CHANGELOG entry. The `plugin.json`
description ("Runs unconditionally (no consumer-config gate)") stays
accurate; changing it would force a `docs/catalog.md` regeneration
outside this change's scope.

- Cross-group requests applied: the setup toggle-off step keeps `-s
user` per the convention on main, and the setup `check` action keeps
`node` a FAIL behind the kill switch. README Requirements and the setup
`check` action declare Node.js (hook-launcher request). CHANGELOG
entries 0.3.62, 0.3.63, 0.4.2 and 0.4.3 read "Shared launcher/library
sync; no change to this plugin's behavior" and 0.4.1 drops the
shell-form sentence; these released-entry edits are declared in the
0.4.6 entry and are not folded or renumbered.

## Verification

- `bash plugins/go-format/hooks/go-format.test.sh`: PASS=66 FAIL=0
- `bash scripts/validate-plugins.sh`: all manifests and catalog
validated
- `bash scripts/check-changelog-parity.sh --check --check-order`: pass
- `bash scripts/check-prerequisite-probes.test.sh`,
`check-hook-exec-form.sh`, `check-hook-userconfig-argv.sh`,
`check-hook-wiring-liveness.sh`, `check-killswitch-hoist.sh`,
`check-cross-plugin-source-drift.sh`, `check-hooks-description.sh`,
`check-purged-em-dashes.sh`: exit 0
- `bash scripts/check-changed-skills.sh origin/main`: setup skill PASS,
0 errors
- `python3 scripts/sync-plugin-options-docs.py --check`, `node
scripts/generate-catalog.mjs --check`: exit 0

## Related

Audit findings: `plugin-go-format` (correctness, docs-coherence,
convention); 3c row `probe-prerequisite.sh:43-52`; 3d finding #3574
(owned by core-docs, not changed here). Related issues: #4784, #4671,
#5096, #3686, #4240, #3356, #5057, #4581.

Applied from other groups: hook-launcher F43, F44 and the node
declaration; the scope wording request was not applied (see below).
Skipped: biome-format request to gate the SessionStart row, already done
in this PR (`hooks.json` passes `--run-if-unset-or-true
GO_FORMAT_ENABLED`, with disabled, unset and `true` probe tests). The
setup toggle-off step follows caveat 2 as it reads on main (`-s user`);
the conventions group (#5313) owns changing it, after which the setup
skills can follow.

Cross-group requests:
- core-docs: include go-format's setup rationale (`SKILL.md:69`) as
evidence in the #3574 decision packet; optionally reword the
`plugin.json` description together with `docs/catalog.md` if the owner
wants the gitignore exemption named.
- conventions: `scripts/sync-plugin-options-docs.py:126` emits "pass the
scope `claude plugin list` reports", contradicting the reconfiguration
convention's caveat 2; fix the template and regenerate every README's
generated options block (go-format's generated block changes in that
run).
- biome-format and markdown-format carry the same probe-gate defect;
their own groups own the fix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01EugXnFddtpHcY5gTuyEirB

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Sep 29, 2026
… and correct notice docs (#5275)

Closes #4265

Refs #4240
Refs #5096

## Summary

Audit fixes for the `markdown-format` plugin (0.11.79). The SessionStart
prerequisite probe ignored the `markdown_format_enabled` kill switch,
the missing-`markdownlint-cli2` notice and the README described a
once-per-session latch that the code no longer has, no test covered the
8-fire renewal sequence, the setup skill told the reader to copy a scope
from `claude plugin list`, and the check skill relied on a pre-computed
`jq` row that never runs under it.

## Fix

- `hooks/hooks.json`: the SessionStart row passes
`--run-if-unset-or-true MARKDOWN_FORMAT_ENABLED` to the launcher, the
same shape `typos-format` uses. `probe-prerequisite.sh` and
`exec-bash.mjs` are untouched.
- `hooks/markdown-format.sh`: the notice says it is shown on the first
skip and renewed every eighth (`there is no skip latch` kept); comments
corrected, including that `rewrite-guard.sh` is sourced only for the
gitignore helper.
- `hooks/markdown-format.test.sh`: new `SessionStart probe` section (row
args, kill switch silences the probe, notice when unset) and an 8-fire
renewal test.
- `README.md`: notice paragraphs split by class, the session-start probe
and `/markdown-format:check` documented, and why `jq` is absent from
`prerequisites.json`. The probe's lack of a markdownlint config check is
stated factually; no owner decision is taken.
- `skills/setup`: the reconfiguration command passes `-s user` and cites
the plugin-reconfiguration convention, which says the same, instead of
telling the reader to copy a scope from `claude plugin list`; notice
wording corrected; a Node.js row added to `check`; evals updated.
- `skills/check`: runs its own `jq` probe via Bash; eval 4 added, eval 3
extended.
- `README.md`: Requirements declare Node.js on `PATH` (every hook row
launches through `hooks/exec-bash.mjs`); the hook budget and cost
figures are annotated as predating the launcher and the SessionStart
probe, with the probe's k stated (1, not measured).
- `CHANGELOG.md`: 0.11.66 and 0.11.67 gain their `### Fixed`/`###
Changed` headings; 0.11.74 no longer says the hook rows are unchanged.
- Version 0.11.79 with a CHANGELOG entry.

## Verification

- `bash plugins/markdown-format/hooks/markdown-format.test.sh`: PASS=182
FAIL=0 SKIPPED=0
- `bash scripts/check-prerequisite-probes.test.sh`: 18 cases, 0 failed
- `bash scripts/check-changelog-parity.sh --check --check-order`: pass
- `bash scripts/validate-plugins.sh`: all manifests and the catalog
validated
- `python3 scripts/sync-plugin-options-docs.py --check`: up to date
- Per-task checks (hook exec form, userconfig argv, wiring liveness,
kill-switch hoist, shell portability, hook-utils sync, skill precompute
compose, skill portability, leaf names, eval JSON, skill-quality check
and validate-evals) passed on the task commits.
- `git grep docs.claude.com -- plugins/markdown-format` hits only
historical CHANGELOG entries.

## Related

- Audit findings: `.work/audit/REPORT.md`, issues #4265 (closed here)
and #4240 (session-start probe opt-in, owner decision stays open).
- Cross-group requests:
- scripts: reword the `latches once per session` string in
`lib/hook-utils.sh` and sync; fix the `claude plugin list` scope advice
in the `sync-plugin-options-docs.py` template and regenerate READMEs;
optionally lift `hook::gitignored_out_of_scope` into the shared lib.
- biome-format (owner of #4240): the SessionStart probe never consults a
per-repo opt-in; include markdown-format in the #4240 decision.
- conventions: sweep sibling setup skills for the `claude plugin list`
scope advice; give cluster-sync changelog entries a fixed line.
  - typos-format: reword `typos-format.sh:156` notice string.
- Requests received from other groups, applied here: hook-launcher
(Node.js requirement and `check` row, budget figure annotations,
CHANGELOG headings and 0.11.74 wording) and conventions (the setup skill
and its evals prescribe `-s user` and do not copy a scope from `claude
plugin list`; this matches the convention text on main). The
biome-format request (gate the SessionStart row with
`--run-if-unset-or-true MARKDOWN_FORMAT_ENABLED`, add disabled and unset
probe cases, leave the `no config, no run, no notice` comment and
`probe-prerequisite.sh` alone until #4240 Q1 is answered) was already
met by the first commits on this branch, so nothing changed for it.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01EugXnFddtpHcY5gTuyEirB

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Sep 30, 2026
…aining format plugins (#5468)

Closes #5286

## Summary

bash-format, ruff-format, typos-format, actionlint and powershell-format
probed binaries with no manifest, so `/claude-ops:prerequisites`
reported `missing=0` on a host lacking their tools. Each now declares
its prerequisites, probes at SessionStart and ships a `check` skill.

## Fix

- Each plugin ships `prerequisites.json`, a SessionStart probe, and
`skills/check`; its hook notice names the check skill.
- bash-format (`shfmt`, `shellcheck`) and ruff-format (`ruff`) move
their missing-tool notices to the `prerequisite` class: once per
session, shared by all agents, install route kept on renewal. The `jq`
notice stays once per session and agent.
- In every probed plugin whose hook has a missing-binary notice (all but
powershell-format, whose hook exits quietly without `pwsh`), the
SessionStart probe and the PostToolUse notice share one latch key, so
the probe's notice counts as the first and the first PostToolUse notice
stays silent until the renewal. actionlint's hook latched on
`actionlint-missing` while its probe used `actionlint-actionlint`; the
hook now uses the probe's key, and a suite case pins it.
- `scripts/check-prerequisite-probes.test.sh` binds each notice text to
its manifest and covers the new plugins.
- `docs/formatter-path-probes.md`, the skill cheat sheet and the
leaf-name registry list the new probes and skills.
- claude-ops `prerequisites` Next section points at every formatter and
linter check skill, `/actionlint:check` included.
- The `hooks.json` description of each of the five plugins names the
SessionStart probe.
- ruff-format docs: the probe checks the working directory plus seven
ancestors for `.venv/bin/ruff`, and say that a Windows host whose only
ruff is `.venv/Scripts/ruff.exe` still gets the probe notice (the
manifest holds one `local_bin` and the probe script is shared
byte-for-byte by every probed plugin, so a second path is a
shared-schema change left out of this PR).
- powershell-format docs: a machine without `pwsh` is INFO in `check`,
and the probe notice appears on it once per session while the plugin is
enabled (owner decision Q1 A on #4240); the setup skill and its evals no
longer call it not-applicable by design.
- Versions: actionlint 0.11.0, bash-format 0.9.0, powershell-format
0.9.0, ruff-format 0.8.0, typos-format 0.8.0, claude-ops 0.71.3, each
with a CHANGELOG entry. The bash-format, ruff-format, typos-format and
actionlint entries record the latch change.

## Verification

- Merged origin/main; the conflicts were the claude-ops and typos-format
versions and CHANGELOGs (main ships claude-ops 0.71.2 and typos-format
0.7.9), resolved by re-bumping to claude-ops 0.71.3 and typos-format
0.8.0 above main's entries.
- `scripts/check-changelog-parity.sh --check`, `--check-order`,
`--check-bump origin/main` and `--check-preserved origin/main`: pass.
- `scripts/validate-plugins.sh`: all manifests and the catalog
validated.
- `scripts/sync-hook-utils.sh --check`, `scripts/sync-exec-bash.sh
--check`, `scripts/check-changed-skills.sh origin/main`,
`scripts/generate-cheatsheet.mjs --check`, `check-evals-quality.sh` on
the two edited evals files: pass.
- Hook suites for actionlint (60 cases), bash-format (69), ruff-format
(76), typos-format (205), powershell-format (96): 0 failed. The new
actionlint case fails against the old `actionlint-missing` key.
- `scripts/check-prerequisite-probes.test.sh`: 51 cases, 0 failed. It
runs in CI (`ci.yml`).
- `check-prerequisites.test.sh` (claude-ops): 30 cases, 0 failed.
- The 0.x.5 CHANGELOG entries on main already say no probe ships (for
example `plugins/bash-format/CHANGELOG.md` 0.8.5 and
`plugins/ruff-format/CHANGELOG.md` 0.7.5). That correction satisfies
AC4; the probes now exist.

## Related

Refs #4240, #5096. PR 5377 covers playwright, biome and markdown
separately.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hooks: missing external tools are not surfaced to the user; add a model-invocable fleet-wide prerequisites check (no auto-install)

2 participants