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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,12 @@ genuinely carry no suite are recorded, with the CI lane that does cover them, in
[`scripts/affected-tests-no-suite.txt`](scripts/affected-tests-no-suite.txt);
`--allow-unmapped` is the escape hatch for everything else. That list is for
prose and manifests, never for code: a source file with no coverage is supposed
to fail here.
to fail here. A **deletion** is the one exception: a changed path that no longer
exists and that nothing claims is reported as a visible `deleted:` note instead
of the error, because there is no content left to cover. A deletion that a
surviving suite still names (a co-located test left behind, a suite that
references the dead path) keeps selecting those suites, which are exactly what
fails loudly if the deletion broke something.

The runner is deliberately sequential: parallelising it measured sublinear
(the suites are spawn-bound), and several guardrails suites assert wall-clock
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
"markdownlint-cli2": "0.23.2"
},
"allowScripts": {
"@anthropic-ai/claude-code@2.1.209": true
"@anthropic-ai/claude-code@2.1.246": true
}
}
2 changes: 1 addition & 1 deletion plugins/claude-config/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "claude-config",
"version": "0.40.19",
"version": "0.40.21",
"description": "Nine configuration-health skills (plus setup) for a repo's Claude Code configuration: audit (settings.json / .mcp.json / hooks / plugins / permissions drift), audit-automation-gaps (evidence-gated verdicts on automation gaps), audit-permission-grants (allow-rule / allowed-tools grants for auto-mode durability and portability), audit-permission-state (the permission rules actually in effect \u2014 every settings scope merged with per-rule provenance, what auto mode drops on entry, config written where nothing reads it, and which managed intents are enforced versus loosenable), draft-auto-mode-rules (interview and draft a paste-ready autoMode classifier block; prints only, never writes), audit-instructions (locally-owned instruction surfaces vs current model capability \u2014 proposes removals/rewrites of instructions the model no longer needs, and detects cross-surface instruction conflicts), audit-prompting-postures (the additive lane \u2014 posture guidance the prompting guide says a component's purpose needs but the component does not carry), audit-pass (one coordinated, ordered, resumable pass over a named target \u2014 three-scope inventory, run-time-derived exclusion set, stable finding identity, suppression memory, resume, one human gate \u2014 delegating every check to the plugin that owns it), and unhobble (the empirical bare-baseline experiment: reversibly strip a repo's standing instructions, log real stumbles against the current model, re-add only what evidence earns).",
"author": {
"name": "Melodic Software",
Expand Down
22 changes: 22 additions & 0 deletions plugins/claude-config/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,28 @@
All notable changes to the `claude-config` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.40.21]

### Changed

- **`audit-permission-grants`: `permission-rule-check.test.sh` renumbers a colliding block of case
labels.** The regression block after Case 11 reused labels 8b through 8g that an earlier block
already carried, so two different cases shared each label. The later block is now Cases 12
through 13b, grouped by the issue each case pins. Comment-only: no assertion, variable, or
fixture changed, and the suite passes unchanged at 139 checks.

## [0.40.20]

### Changed

- **Two documentation-accuracy fixes and one dead-guard removal in skill
scripts.** `audit-pass/scripts/run-state.sh`'s header usage block regains the
`--plugin-data` and `[--epoch <n>]` arguments its own `usage()` already
documents (comment-only; verified against the negative-test sed targets);
`audit-instructions/scripts/conflict-scan.test.sh` drops a prerequisite guard
for `grep`, a tool neither the suite nor the script under test invokes (the
awk guard stays; skip-discipline gates re-run clean).

## [0.40.19]

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@ assert_not_contains() {
esac
}

if ! command -v grep >/dev/null 2>&1; then
echo "SKIP: grep not installed" >&2
exit 0
fi
if ! command -v awk >/dev/null 2>&1; then
echo "SKIP: awk not installed" >&2
exit 0
Expand Down
6 changes: 3 additions & 3 deletions plugins/claude-config/skills/audit-pass/scripts/run-state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@
#
# Usage:
# run-state.sh paths --plugin-data <dir> --run-id <id> [--root <path>]
# run-state.sh lease acquire --run-dir <dir> --run-id <id> [--stale-after <s>]
# [--skew-grace <s>] [--epoch <n>]
# run-state.sh lease acquire --run-dir <dir> --run-id <id> --plugin-data <dir>
# [--stale-after <s>] [--skew-grace <s>] [--epoch <n>]
# run-state.sh lease heartbeat --run-dir <dir>
# run-state.sh lease release --run-dir <dir>
# run-state.sh lease classify --run-dir <dir>
# run-state.sh partial append --run-dir <dir> --record <json-line>
# run-state.sh partial append --run-dir <dir> --record <json-line> [--epoch <n>]
#
# `--plugin-data` is required because `${CLAUDE_PLUGIN_DATA}` is NOT in the Bash
# tool's environment (plugins reference: the three placeholders are exported to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ assert_contains "refusal names the sanctioned variable as the fix" \
-u CLAUDE_PROJECT_DIR GIT_CEILING_DIRECTORIES="$TEST_TMPDIR" bash "$SCRIPT" 2>&1)" \
"PERMISSION_HYGIENE_SCAN_ROOT"

# --- Case 8b: #2282 — full-rule reporting, `//` is NOT exempt, P1 pinned npm view
# --- Case 12: #2282 — full-rule reporting, `//` is NOT exempt, P1 pinned npm view
#
# `//` is the ABSOLUTE anchor, not a portable one. permissions.md's own table row is
# `//path` = "Absolute path from filesystem root", with `Read(//Users/<name>/secrets/**)`
Expand All @@ -582,15 +582,15 @@ assert_contains "P2 reports the full offending Bash rule" "$OUT_2282" "Bash(${PO
assert_not_contains "fully-pinned npm view rule is not flagged as P1" "$OUT_2282" "npm view ctx7 version"
assert_eq "both machine-path rules flagged, npm view not" "2" "$(run "$D8B" --count)"

# --- Case 8c: the genuinely portable anchors stay exempt ----------------------
# --- Case 12b: the genuinely portable anchors stay exempt ---------------------
# The distinction the fix turns on: `~/` and `${CLAUDE_PROJECT_DIR}/` supply the
# user/project segment at resolution time; `//` does not.
D8C="$TEST_TMPDIR/issue-2282-portable"
mkdir -p "$D8C/.claude"
jq -n '{permissions:{allow:["Read(~/Documents/*.pdf)","Bash(${CLAUDE_PROJECT_DIR}/scripts/x.sh:*)"]}}' >"$D8C/.claude/settings.json"
assert_eq "portable anchors produce no P2 finding" "0" "$(run "$D8C" --count)"

# --- Case 8d: P2 reach is the open tool grammar, not five hardcoded names ------
# --- Case 12c: P2 reach is the open tool grammar, not five hardcoded names -----
# A hardcoded machine path leaks a username whatever tool the rule names. An
# enumerated (Read|Edit|Write|Bash|PowerShell) list silently stopped flagging these;
# `Agent` in particular is indefensible, since this script has a dedicated
Expand All @@ -604,15 +604,15 @@ assert_contains "P2 sees Glob rules" "$OUT_TOOLS" "Glob(${GLOB_MP})"
assert_contains "P2 sees NotebookEdit rules" "$OUT_TOOLS" "NotebookEdit(${NB_MP})"
assert_contains "P2 sees MCP tool rules" "$OUT_TOOLS" "mcp__srv__tool(${MCP_MP})"

# --- Case 8e: a `//` prefix does not launder a path later in the same rule -----
# --- Case 12d: a `//` prefix does not launder a path later in the same rule ----
# Regression guard for a substring carve-out (`$m == *"(//"*`) that passed any rule
# whose payload merely began with `//`, leaving the rest unexamined.
D8E="$TEST_TMPDIR/issue-2282-traversal"
mkdir -p "$D8E/.claude"
jq -n --arg l "Read(${LAUNDER_MP})" '{permissions:{allow:[$l]}}' >"$D8E/.claude/settings.json"
assert_eq "a // prefix does not exempt a user home later in the rule" "1" "$(run "$D8E" --count)"

# --- Case 8f: #2397 A12 — tilde-user Bash paths leak a username ----------------
# --- Case 13: #2397 A12 — tilde-user Bash paths leak a username ----------------
D8F="$TEST_TMPDIR/issue-2397-tilde-user"
mkdir -p "$D8F/.claude"
jq -n '{permissions:{allow:["Bash(~kyle/scripts/x.sh:*)"]}}' >"$D8F/.claude/settings.json"
Expand All @@ -627,7 +627,7 @@ jq -n '{permissions:{allow:["Bash(curl https://example.com/~alice/index.html)"]}
>"$D8F_URL/.claude/settings.json"
assert_eq "URL user-directory segment is not flagged as tilde-user path" "0" "$(run "$D8F_URL" --count)"

# --- Case 8g: #2397 A7b — inert substitution tokens in allowed-tools ----------
# --- Case 13b: #2397 A7b — inert substitution tokens in allowed-tools ---------
D8G="$TEST_TMPDIR/issue-2397-inert"
mkdir -p "$D8G/.claude/skills/demo"
cat >"$D8G/.claude/skills/demo/SKILL.md" <<'EOF'
Expand Down
2 changes: 1 addition & 1 deletion plugins/claude-memory/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "claude-memory",
"version": "0.11.8",
"version": "0.11.9",
"description": "Keeps a repo's Claude Code memory layer healthy and under your control, against criteria derived from official Claude Code documentation. The audit skill checks the instruction/memory layer (CLAUDE.md, CLAUDE.local.md, .claude/rules/, auto-memory) with a deterministic script-backed spine plus judgment-tier checks. The stateless skill inspects, disables, and (confirm-gated) purges Claude-written auto memory across all settings scopes.",
"author": {
"name": "Melodic Software",
Expand Down
10 changes: 10 additions & 0 deletions plugins/claude-memory/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
All notable changes to the `claude-memory` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.11.9]

### Changed

- **`audit/scripts/memory-dir-stats.test.sh` fixture style normalized.** The
one fixture block that embedded literal newlines inside printf quotes (a
#2042 leftover) now uses the `\n`-escape form every other fixture in the
file uses; the produced fixture bytes are sha256-identical and all 64
checks pass.

## [0.11.8]

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,13 +184,9 @@ assert_eq "a heavy one-line runaway counts every line" "$(raw_lines)" "$(run "$H
# as octal escapes so the file stays ASCII and no tool can re-encode the fixture.
WIDE=$(for ((i = 0; i < 600; i++)); do printf '\303\251'; done)
{
printf -- '---
'
printf 'Note: %s
' "$WIDE"
printf -- '---
body
'
printf -- '---\n'
printf 'Note: %s\n' "$WIDE"
printf -- '---\nbody\n'
} >"$M3/MEMORY.md"
assert_eq "a multibyte heavy line is bounded by weight in bytes" "$(raw_bytes)" "$(run "$H3" --memory-bytes)"

Expand Down
2 changes: 1 addition & 1 deletion plugins/claude-ops/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "claude-ops",
"version": "0.38.20",
"version": "0.38.21",
"description": "Claude Code operations toolkit. Twelve skills: audit-skill-visibility (audit whether each installed skill is actually VISIBLE to the model, and diagnose why most of a fleet never gets used \u2014 a skill is invisible when its description is dropped by Claude Code's skill-listing context budget, which drops descriptions least-invoked-first so an unused skill loses the keywords that would let it be matched, from skills genuinely not wanted, from skills the run cannot observe at all; computes whether the listing overflows from documented settings, and withholds every cold verdict the data cannot support rather than reporting absence of data as absence of use), inventory (read-only enumeration of the complete invocable surface \u2014 every built-in CLI command with aliases and hidden/gated status, every bundled skill, and every component of every installed plugin across all marketplaces; reads the shipped binary because upstream publishes no built-in command list, and carries an integrity verdict so a drifted build reports counts as floors rather than silently short totals), audit-install-state (read-only audit of the machine-scope ~/.claude installation directory and ~/.claude.json \u2014 full inventory split into an authored surface and rolled-up bulk trees, product-managed retention vs genuinely unmanaged state, filename-scheme resolution before any process-liveness check, and deliberate/mid-experiment detection; reports, never deletes), audit-performance (read-only slowness-diagnostic capture run at the moment the machine or a session feels slow: CLI version, retention-sweep health including the silent unparsable-settings pause, a timed census walk of the install tree as a sweep-cost proxy, active-session and plugin-fleet counts, a process census, and the fan-out layer, which covers a load-labelled no-op spawn baseline, every hook that will fire bucketed per-tool-call versus per-turn with its invocation shape, the configured statusline, subagent concurrency and spawn-depth ceilings against documented defaults, whether running sessions predate the settings file they are judged by, and orphan attribution by parent liveness rather than age; read against a bundled known-performance-issues reference that also records the causes tested and cleared; separates the four documented suspects of accumulated state, version regression, component bloat, and per-spawn fan-out cost, and routes remediation out; reports, never mutates, and never executes a discovered hook or statusline command), audit-native-overlap (map native Claude Code surfaces \u2014 built-in CLI commands, bundled skills, plugin-backed built-ins, session-provided skills \u2014 against the current repo's plugin skills and agents, so a custom component never silently duplicates what Claude Code itself ships; bare invocation is a read-only overlap report carrying the extraction's integrity floors and a shared-listing-budget exposure section, verdicts are human-gated in a committed store rendered into a generated registry whose every row carries an observable recheck trigger, and only an explicit apply step bakes presence-gated native references into descriptions and Boundary sections), observability (read locally captured telemetry \u2014 OTEL store, collector, hook-event JSONL, ccusage \u2014 with trend reports and store pruning), known-issues (search known Claude product GitHub bugs, check service health, maintain a persistent tracked-issue registry), changelog (ingest Claude Code changelog entries and integrate them into the current repo), plugins (bring a machine's plugin fleet current on demand \u2014 marketplace refresh, effective-scope updates including in-repo project/local installs, new-plugin install per policy, scope-divergence detection and explicit convergence), morning-brief (read-only gh-based operator morning view \u2014 queue-label counts, merge-ready PRs, parked decisions with their RECOMMENDED lines, and loop-lane telemetry freshness), lanes (start/restart/stop/status loop lanes as named background Claude Code sessions seeded from canonical prompt files, with per-lane model/effort, a repo-pull + marketplace-refresh launch step, and a consume-restarts action \u2014 an OS-schedulable reader that relaunches stopped lanes whose telemetry carries a restart_request), and a re-runnable setup action that settles where the known-issues registry lives. Plus a family of eight advisory *-audit hooks (API errors, config changes, instruction loads, permission denials, pre-compaction, skill usage, tool failures, and unsurfaced hook failures \u2014 the last also warns the user via systemMessage, since a hook that fails to launch enforces nothing and Claude Code surfaces the failure to nobody) that emit the shared hook-telemetry envelope, and a reference sink that maps envelopes into the hook-events.jsonl the observability skill reads.",
"author": {
"name": "Melodic Software",
Expand Down
19 changes: 19 additions & 0 deletions plugins/claude-ops/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
All notable changes to the `claude-ops` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.38.21]

### Changed

- **One fewer spawn per Stop turn in `hook-failure-audit.sh`.** The hook now
short-circuits before its summary jq spawn when the grep pre-filter matched
nothing (the empty case previously produced `[]` and the same silent exit 0).
Adversarial payload probes byte-identical across all paths; suite 88/88.
(An analogous jq_fields consolidation in the two skill-usage hooks was
attempted, refuted by differential testing on pathological payloads, and
reverted — recorded in the sweep report rather than shipped.)
- **restart-consumer.sh declares five formerly implicit globals `local` to
`process_lane`** (no post-return reader exists; full caller-graph and trap
audit) and drops two `${lock_rc:-0}` defaults dominated by an unconditional
assignment. `morning-brief.sh` declares its `read -ra` scratch array local;
two misplaced test assertions moved to the section whose fixture they read.
- **install_state.py** renames the unused `os.walk` dirnames slot to
`_dirnames`, matching the file's own convention.

## [0.38.20]

### Changed
Expand Down
8 changes: 6 additions & 2 deletions plugins/claude-ops/hooks/hook-failure-audit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ read_window() {
fi
}

# grep is a cheap pre-filter only; the structural jq selection decides.
# grep is a cheap pre-filter only; the structural jq selection decides. The
# no-match common case exits on the pre-filter's emptiness, before paying for
# the jq spawn (an empty stream produced the same silent exit via "[]").
# `fromjson?` skips unparsable lines instead of aborting the stream.
# Identity is the REGISTRATION, not the matcher name: several plugins register
# on the same event+matcher (multiple PreToolUse:Bash guards exist in this very
Expand Down Expand Up @@ -130,7 +132,9 @@ read_window() {
# in a narrower shape. The per-class counts below keep every class present in a
# group visible, and the message flags are computed from those counts, never
# from a single collapsed value.
SUMMARY=$(read_window | grep -F '"hook_non_blocking_error"' |
RECORDS=$(read_window | grep -F '"hook_non_blocking_error"')
[[ -n "$RECORDS" ]] || exit 0
SUMMARY=$(printf '%s' "$RECORDS" |
jq -cRs '[
split("\n")[] | fromjson?
| select(.type? == "attachment") | .attachment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,7 @@ def walk_tree(
exclude = exclude or set()
rows: list[FileRow] = []
errors: list[dict] = []
for dirpath, dirnames, filenames in os.walk(
for dirpath, _dirnames, filenames in os.walk(
root, followlinks=False, onerror=errors.append
):
for name in filenames:
Expand Down
7 changes: 4 additions & 3 deletions plugins/claude-ops/skills/lanes/scripts/restart-consumer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,8 @@ lane_selected() {

process_lane() {
local idx="$1" now="$2"
local lane repo issue marker bodies body state request reason used n i found=0
local lane repo issue marker instance bodies body state request req reason used n i found=0
local comment_marker sibling_request sibling_instance
lane="$(lane_field "$idx" name)"
[[ -n "$lane" ]] || return 0
lane_selected "$lane" || return 0
Expand Down Expand Up @@ -1271,11 +1272,11 @@ main() {
acquire_lock "$now" || lock_rc=$?
# 2 = the lock store itself is unusable. Exiting 0 here would let an
# unattended schedule log healthy ticks forever while processing nothing.
if ((${lock_rc:-0} == 2)); then
if ((lock_rc == 2)); then
err "restart-consumer: the lock store is unusable — not a held lock; failing rather than reporting a skipped tick"
exit 4
fi
if ((${lock_rc:-0} != 0)); then
if ((lock_rc != 0)); then
warn "another restart-consumer run holds the lock ($LOCK_DIR) — skipping this tick"
info "restart-consumer: $ACTION on ${TARGET_REPO:-<fixtures>} at $(iso_utc "$now")"
info "| lane | decision | detail |"
Expand Down
Loading
Loading