Skip to content

feat: add claude-config-audit plugin#62

Merged
kyle-sexton merged 4 commits into
mainfrom
feat/publish-claude-config-audit
Jul 11, 2026
Merged

feat: add claude-config-audit plugin#62
kyle-sexton merged 4 commits into
mainfrom
feat/publish-claude-config-audit

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Closes melodic-software/medley#1287 (EPIC melodic-software/medley#1273).

What

New claude-config-audit plugin bundling three configuration-audit skills as one cohesive capability:

Skill Question it answers
/claude-config-audit:settings-audit Are the config FILES (settings.json, settings.local.json, .mcp.json, hooks, plugins, permissions) correct against upstream truth? Includes live plugin-drift detection (ORPHAN/NEW/RENAME) with an asymmetric auto-fix policy
/claude-config-audit:memory-health Is the instruction/memory layer (CLAUDE.md, rules, auto-memory) healthy against official-doc criteria? Deterministic script-backed spine + judgment-tier checks
/claude-config-audit:automation-deep-dive Is the configured automation SET the right set? Eight evidence-required quality gates; default verdict REJECT

Marketplace entry: category: configuration, tags maintenance + per-component. Explicit semver 0.1.0 in plugin.json only.

Decoupling (playbook per-plugin gate)

  • Scripts resolve the consumer project root as git-toplevel → CLAUDE_PROJECT_DIR → cwd; never the plugin install dir. All bundled assets referenced via ${CLAUDE_PLUGIN_ROOT}; no ../ reach-outs (grep-verified).
  • State (memory-health audit reports) persists under ${CLAUDE_PLUGIN_DATA} — contributor-local by design (reports cover personal auto-memory).
  • Sibling-skill refs inside the plugin stay bare; adjacent capabilities (research skill, work-item tracker, issue-tracking skill) degrade gracefully to inline guidance.
  • Source-repo-specific content removed or re-seamed: the required-permissions list ships only the cross-repo security floor with a documented consumer-rules extension seam; repo-convention memory checks route through the consumer's own CLAUDE.md/rules (REPO check-IDs); known-issues.md carries broadly-applicable upstream issues only.
  • No userConfig — the only variability that surfaced routes through consumer context (no speculative knobs).

Gate evidence (speed-adjusted per issue)

  • claude plugin validate --strict ./plugins/claude-config-auditValidation passed; claude plugin validate --strict . (catalog manifest) → Validation passed.
  • claude plugin details always-on token cost: ~577 tok (automation-deep-dive ~230, settings-audit ~180, memory-health ~170; on-invoke ~3.9k / ~3.5k / ~1.5k).
  • --plugin-dir smoke test in a clean non-source fixture repo (headless claude -p): skill loaded under the claude-config-audit: namespace, check-structure.sh executed from the plugin root against the fixture's .claude/settings.json, Category B baseline correctly reported 1/17 patterns present. The smoke test caught (and this PR fixes) a real plugin-form failure: ! pre-computed-context blocks failed permission preflight in a clean repo — preambles removed, facts collected in the workflow body instead.
  • Contract tests: 6 self-contained *.test.sh suites (92 checks) all pass locally; shellcheck clean against the repo .shellcheckrc; markdownlint + typos clean.
  • Full baseline/parity apparatus not required per the issue (non-destructive skill content; source-repo originals remain until the cutover issue).

Security review (playbook acceptance)

  • No hooks, no MCP servers, no agents, no userConfig secrets. Surface 1/2/3: N/A.
  • Cache isolation (surface 4): verified — no ../ reach-outs, no absolute/machine paths.
  • Data egress (surface 5): read-only fetches only — official docs pages (settings/permissions/hooks/env-vars) and each registered marketplace's marketplace.json from github.com/ghraw; a failed fetch degrades to SKIP. No telemetry.
  • Provenance (surface 6): first-party (Melodic Software), MIT.
  • Only mutation surface: fix-plugin-drift.sh --yes edits the consumer's settings.json behind explicit opt-in + per-item approval, atomic jq write with post-edit validation (contract-tested).

Note: the advisor consultation step was unavailable this session (tool errored); gates above stand on direct evidence.


Note

Low Risk
Greenfield plugin content only; the sole write path is opt-in settings.json edits via fix-plugin-drift.sh --yes. No hooks, MCP servers, or secrets in the plugin manifest.

Overview
Adds the claude-config-audit plugin (0.1.0) to the marketplace and README catalog — three report-first skills for keeping a consumer repo’s Claude Code setup healthy.

settings-audit audits settings.json, .mcp.json, hooks, permissions, MCP, plugins, and env against official docs and a bundled checklist. It uses check-structure.sh for secret-safe settings.local.json stats, check-plugin-drift.sh / fix-plugin-drift.sh for live marketplace drift (ORPHAN/NEW/RENAME) with asymmetric auto-fix (never drop user-enabled orphans). --fix is gated on per-finding approval.

memory-health runs a doc-derived criteria checklist on CLAUDE.md, rules, and auto-memory, with script-backed checks (resolve-memory-dir.sh, index integrity, orphan always-loaded rules). Reports go to ${CLAUDE_PLUGIN_DATA}, not the repo; optional fix / update / report actions.

automation-deep-dive inventories automation, proposes gap candidates, and scores them through eight evidence gates (default REJECT); optional --implement after approval.

Ships reference docs, phase checklists, skill evals, and six self-contained *.test.sh suites for the bash helpers. Scripts resolve the consumer git root, not the plugin install path.

Reviewed by Cursor Bugbot for commit aba9ba1. Bugbot is set up for automated code reviews on this repo. Configure here.

Bundle three configuration-audit skills as one cohesive plugin:
settings-audit (config files vs upstream truth, with live plugin-drift
detection), memory-health (CLAUDE.md / rules / auto-memory against
official-doc criteria), and automation-deep-dive (evidence-gated
verdicts on automation gaps). All three default to report-only;
mutations require explicit opt-in plus per-item approval.

Decoupled from the source repo per the migration playbook: scripts
resolve the consumer project root (git toplevel, then
CLAUDE_PROJECT_DIR, then cwd), state persists under
CLAUDE_PLUGIN_DATA, sibling-skill references stay bare, adjacent
capabilities degrade gracefully to inline guidance, and the
required-permissions baseline carries only the cross-repo security
floor with a consumer-rules extension seam.
@cursor

cursor Bot commented Jul 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_eaf26048-abac-4e5c-930d-b0bcd14a427f)

@cursor cursor 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.

Stale comment

Risk: medium. Not approving because Cursor Bugbot skipped (usage limit reached) and the required automated review did not complete successfully. Human review is needed; no reviewers were assigned because no other repository contributors are available besides the PR author.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

…onfig-audit

# Conflicts:
#	.claude-plugin/marketplace.json
#	README.md
@cursor

cursor Bot commented Jul 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_0388364b-3c47-4391-9ef2-e9dc827e13d2)

@claude

claude Bot commented Jul 11, 2026

Copy link
Copy Markdown

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


PR Review: feat: add claude-config-audit plugin

Tasks

  • Gather context and understand the request
  • Review plugin manifest and marketplace entry
  • Review shell scripts for correctness and security
  • Review skill files (SKILL.md) for alignment with guidelines
  • Review test files for coverage
  • Check CLAUDE.md compliance (repo-agnostic, plugin-form-safe, etc.)
  • Compile and post final review findings

Summary

Solid plugin overall. The structure, safety contracts, and decoupling story are well-executed. Three findings worth addressing before merge — one medium-risk jq filter injection, one low-risk regex metacharacter bug, and one trap-clobber edge case — plus a design note on the baseline permissions list.


Findings

1. jq filter injection in fix-plugin-drift.sh — Medium

Files: plugins/claude-config-audit/skills/settings-audit/scripts/fix-plugin-drift.sh L221–234

Plugin names from the upstream marketplace.json are interpolated directly into a jq filter string via shell string concatenation:

remove_args=$(echo "$auto_remove" | jq -R . | jq -s 'map("del(.enabledPlugins[\"" + . + "\"])") | join(" | ")' -r)
jq_filter="$jq_filter | $remove_args"

The jq -R . step JSON-encodes each line as a string, but the outer jq -s map(...) then embeds those strings raw into a jq program fragment. A plugin named a"]) | halt_error | path(["b in a compromised marketplace JSON would produce the filter del(.enabledPlugins["a"]) | halt_error | path(["b"]). The error check at L234 (if ! jq "$jq_filter" ...) provides the main guard — a failed or erroring filter leaves $SETTINGS unchanged, and the post-edit JSON validity check at L239 also blocks corrupt writes. But the injection surface exists: a crafted name producing valid jq that also yields valid JSON output could silently reshape the settings file.

Recommended fix — use --arg parameterization to keep plugin names out of the filter program:

# Build a keys-list approach via --argjson instead of filter concatenation
remove_json=$(echo "$auto_remove" | jq -R . | jq -sc '.')
add_json=$(echo "$auto_add" | jq -R . | jq -sc '.')
jq --argjson rm "$remove_json" --argjson add "$add_json" '
  reduce $rm[] as $k (.; del(.enabledPlugins[$k])) |
  reduce $add[] as $k (.; .enabledPlugins[$k] = false)
' "$SETTINGS" > "$TMP_SETTINGS"

This approach also eliminates the multiline filter accumulation entirely.

Fix this →


2. Unescaped regex metacharacters in orphan-rule-check.sh — Low

File: plugins/claude-config-audit/skills/memory-health/scripts/orphan-rule-check.sh L74

local_refs=$(git grep -l -F -- "$base" -- ':(exclude).work/' 2>/dev/null | grep -vc "^${file}$" || true)

$file is a relative path such as .claude/rules/orphan.md. The . characters are unescaped BRE metacharacters (match any char), so grep -vc "^.claude/rules/orphan.md$" would also match xclaude/rules/orphanXmd. Extremely unlikely to fire in practice, but correctible with one character:

local_refs=$(git grep -l -F -- "$base" -- ':(exclude).work/' 2>/dev/null | grep -vcF "^${file}$" || true)

Or use grep -cv -- "^$(printf '%s' "$file" | sed 's/[.[\*^$]/\\&/g')\$".

Fix this →


3. Second trap clobbers first when --input is provided — Low

File: plugins/claude-config-audit/skills/settings-audit/scripts/fix-plugin-drift.sh L103–108 and L217

When --input <path> is provided, TMP_JSON is never set (remains ""). At L217, the apply path sets a new trap 'rm -f "$TMP_JSON" "$TMP_SETTINGS"' EXIT which replaces any earlier trap — and rm -f "" behavior for an empty string is implementation-defined (POSIX leaves it unspecified; some BSD implementations emit a diagnostic even with -f). The cleaner pattern:

trap '[[ -n "${TMP_JSON:-}" ]] && rm -f "$TMP_JSON"; rm -f "${TMP_SETTINGS:-}"' EXIT

4. Category B baseline may generate false positives for non-push repos — Design Note

File: plugins/claude-config-audit/skills/settings-audit/reference/required-permissions.md

The baseline lists Bash(git push *) and Bash(git push) as required ask patterns in every consumer's settings.json. A read-only analysis repo, a documentation repo, or any project where push is performed only through a separate CI step would be flagged as missing a pattern they deliberately omit. There is currently no documented exclusion mechanism in the baseline file itself (the consumer-extension seam is additive, not subtractive).

Suggestion: Add a paragraph to required-permissions.md noting when the baseline can be narrowed — e.g., repos without write access to the tracked remote can document an exemption in their own rules file, which Category B should check for before flagging absence. This isn't a blocking issue (the skill already reads the consumer's own rules), but the baseline file itself is silent on exemptions.


What's well done

  • Atomic write + validation in fix-plugin-drift.sh: write to temp → jq empty check → mv is the right pattern. If jq fails or produces invalid JSON, $SETTINGS is untouched.
  • Network-tolerant drift detection: SKIP on fetch failure rather than a hard exit is the correct choice for a marketplace where some entries may be unreachable.
  • Secret safety for settings.local.json: check-structure.sh emits only key counts and never file values; the evals include an explicit test (eval ci: onboard to the CI platform (ci-workflows + standards) #4) verifying the skill doesn't try to Read it.
  • Consumer project root resolution: git rev-parse --show-toplevelCLAUDE_PROJECT_DIRcwd chain is correct and consistent across all three scripts.
  • Asymmetric orphan fix policy (false-orphan auto-removable, true-orphan manual-review) is both logically correct and explicitly contract-tested (test case 4 in fix-plugin-drift.test.sh).
  • Isolated test fixture repos in orphan-rule-check.test.sh unset GIT_DIR/GIT_INDEX_FILE/GIT_WORK_TREE/GIT_COMMON_DIR — correct approach that prevents git env leakage into fixture commits.
  • Plugin data in ${CLAUDE_PLUGIN_DATA}: memory-health audit reports are contributor-local by design, correctly documented in the skill.
  • CLAUDE.md compliance: repo-agnostic path resolution, ${CLAUDE_PLUGIN_ROOT} references, no hardcoded paths, no userConfig secrets, explicit semver — all clean.

@cursor cursor 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.

Stale comment

Risk: medium. Not approving because Cursor Bugbot skipped (usage limit reached), so the required automated review did not complete successfully. Human review is needed; no reviewers were assigned because no eligible repository contributors are available besides the PR author.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

Parameterize the fix-plugin-drift jq edit via --argjson reduce so
upstream plugin names are data, never filter code (with a hostile-name
regression test); guard the EXIT trap against an unset temp path; make
the orphan-rule self-exclusion grep a fixed-string whole-line match;
document when the permission baseline may be narrowed with a
consumer-rules exemption.
…onfig-audit

# Conflicts:
#	.claude-plugin/marketplace.json
#	README.md
@cursor

cursor Bot commented Jul 11, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_99281b71-1d4d-433d-a69e-fe32c16eb6f8)

@claude

claude Bot commented Jul 11, 2026

Copy link
Copy Markdown

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


Tasks

  • Review fix commit for prior findings (jq injection, regex bug, trap clobber)
  • Read key changed files end-to-end
  • Check for new issues across full plugin
  • Compile and post final review

Follow-up Review: feat: add claude-config-audit plugin

The fix: address review findings commit (6fc31a2) correctly addresses all four findings from the prior review. No new issues found in the broader plugin.


Prior Findings — Status

1. jq filter injection in fix-plugin-drift.shFIXED

The old string-concatenation approach has been replaced with --argjson parameterization. Plugin names are now converted to a JSON array via jq -nR '[inputs | select(. != "")]' and consumed by reduce in the filter body — they're data, never code:

remove_json=$(jq -nR '[inputs | select(. != "")]' <<<"$auto_remove")
add_json=$(jq -nR '[inputs | select(. != "")]' <<<"$auto_add")

jq --argjson rm "$remove_json" --argjson add "$add_json" '
  reduce $rm[] as $k (.; del(.enabledPlugins[$k])) |
  reduce $add[] as $k (.; .enabledPlugins[$k] = false)
' "$SETTINGS" >"$TMP_SETTINGS"

The regression test (case 7 in fix-plugin-drift.test.sh) injects the exact hostile name from the original finding (a"]) | halt_error | path(["b) and asserts both that the entry is removed literally and that unrelated entries are untouched. Coverage is airtight.

2. Unescaped regex metacharacters in orphan-rule-check.shFIXED

Line 75 changed to grep -vcxF -- "$file":

local_refs=$(git grep -l -F -- "$base" -- ':(exclude).work/' 2>/dev/null | grep -vcxF -- "$file" || true)

-x anchors to the whole line, -F treats $file as a fixed string — dots in the path no longer act as BRE wildcards. Correct and minimal.

3. Second trap clobbers first — FIXED

Line 217 now uses a single consolidated trap:

trap '[[ -n "${TMP_JSON:-}" ]] && rm -f "$TMP_JSON"; rm -f "${TMP_SETTINGS:-}"' EXIT

When --input is provided (TMP_JSON=""), the guard prevents an rm -f "" attempt. When TMP_JSON is set from mktemp, it's cleaned up. TMP_SETTINGS is always cleaned up.

4. Baseline documentation — ADDRESSED

required-permissions.md now has a "Narrowing the baseline" section explaining when a pattern can be legitimately absent: document the exemption in consumer rules files, and Category B checks for it before flagging.


Broader Review — No New Issues

Read check-plugin-drift.sh, check-structure.sh, inventory.sh, memory-index-refs-check.sh, resolve-memory-dir.sh, and the plugin manifest. All are clean:

  • check-plugin-drift.sh: network-fetch tolerates failures correctly (SKIP path); jq aggregation uses --argjson/--arg throughout, no string interpolation of untrusted data.
  • check-structure.sh: emits only key counts; never echoes env values or secret field contents. set -uo pipefail + early jq validity check before any output. ✓
  • inventory.sh: find calls use fixed args from within the script only; set -u guards unset vars. ✓
  • resolve-memory-dir.sh: cygpath fallback is correct; empty-git-common-dir edge case handled gracefully by the for cand in ... loop and the fallback on the last line. ✓
  • plugin.json: correct semver 0.1.0, $schema present, MIT license, author block. ✓

Plugin is ready to merge.

@cursor cursor 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.

Risk: medium. Not approving because Cursor Bugbot skipped, so the required automated review did not complete successfully. Human review is needed; no reviewers were assigned because no eligible repository contributors are available besides the PR author.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@kyle-sexton
kyle-sexton merged commit fb788eb into main Jul 11, 2026
22 checks passed
@kyle-sexton
kyle-sexton deleted the feat/publish-claude-config-audit branch July 11, 2026 12:09
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.

1 participant