From a3394a30a144485db4d77bb954b81efb049cc8ab Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 14:29:20 +0000 Subject: [PATCH] fix: bind eleven more failure-token probes to their probe, and quote a spaced skill dir #3469 fixed five filtered probes whose `||` fallback fires spuriously under `set -o pipefail`, and stopped there. The same shape is still live at eleven more sites across six plugins, four of them introduced by #3468 -- the commit immediately before the one that diagnosed the bug class. The mechanism is unchanged. `guard >/dev/null 2>&1 && probe | cap || echo TOKEN` takes the pipeline's exit status. Without pipefail that is the cap's, always 0. With pipefail it is the last non-zero status, which two ordinary situations produce: a filter matching nothing, and the writer taking SIGPIPE when the cap closes the pipe. Either fires the failure token on a healthy probe. ai-slop:audit is the worst and was live, not latent. detect.sh --show-config emits 10 lines against a head -8 cap, writing one at a time, so head always closes the pipe mid-run: PIPESTATUS reads 141 0. The probe rendered the full correct config and then appended "detector unavailable" beneath it. It reproduced from any directory with no configuration at all. knowledge is the one with a consequence beyond a wrong string. video-digest states "STOP if the pre-computed context above shows MISSING for yt-dlp, ffmpeg, or ImageMagick. Cloud agents without the media toolchain fail closed." #3468 fixed that gate's fail-open defect and shipped this inversion in its place, so the gate would refuse to run on a machine that HAS the toolchain. Whether the token fires is a race between the tool writing its banner and the cap closing the pipe, so it scales with output size. Measured here over 30 runs per size against a synthetic tool on PATH, installed and working: 41 lines fired 29/30, 5 lines fired 19/30, 1 line fired 0/30. So ffmpeg fails on nearly every run, magick (5 lines) is a genuine race, and yt-dlp (1 line) is latent by shape only. An intermittent false MISSING is harder to diagnose than a consistent one; all three are fixed regardless. Also fixed here, same shape: course-digest's two probes, firecrawl:firecrawl (pre-existing, and the idiom the fleet copied), provenance:audit's two detector probes, docs-hygiene:audit-derivability -- whose capture-first form protects only the git call, leaving its awk-and-head data run exposed past 20 dirty files -- and code-tidying:tidy, where a bare third "unknown" line was appended to well-formed output. Two of the eleven are latent by shape, not observed failures, and the provenance changelog says so plainly rather than claiming a live fix: list-corpus.sh --show-config emits 7 lines against a head -10 cap, and check-stamps.sh is piped into tail -3, which drains its input and cannot raise SIGPIPE. Both were verified to behave identically before and after in three states under both settings. Separately, ${CLAUDE_SKILL_DIR} is now quoted in ai-slop:audit and provenance:audit. Installed under a path containing a space, the unquoted expansion made a working detector report itself unavailable. firecrawl:update already quoted it; these now match. Pre-existing, but these commits are what made the token reachable at all -- before them the same failure rendered empty. Quoting changes the literal command string, and Bash permission rules are globs over that literal string, so the unquoted script grants no longer match the quoted invocations. Three companion rules are added, one per quoted script: Bash("${CLAUDE_SKILL_DIR}/scripts/detect.sh":*) in ai-slop, and the list-corpus and check-stamps equivalents in provenance. The unquoted rules are kept because both skill bodies still instruct unquoted invocation elsewhere. Each pair names one script under the same ${CLAUDE_SKILL_DIR} anchor with the same :* argument scope, so nothing is authorized that the plugin could not already run. No other grant changed: every other rewritten command keeps its original leading token, which is why code-tidying:tidy takes the ||-inside-the-brace-group form rather than the brace-group-first one. The remedy elsewhere is the brace group #3469 established: the data pipeline sits in { ...; :; }, closed by a command that cannot fail, so the || is reachable only when the guard short-circuits. No new idiom, and no $ expansion introduced beyond the quoted ${CLAUDE_SKILL_DIR}; audit-derivability keeps the pre-existing $s capture, which is the part that legitimately drives its ||. Every site proven by execution in three states -- tool or script absent, output under the cap, output over the cap -- with and without pipefail. The five sites #3469 fixed were re-verified at 3,000 dirty files under both settings and are correct as shipped, as is claude-ops:observability, whose sed drains its input. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo --- plugins/ai-slop/.claude-plugin/plugin.json | 2 +- plugins/ai-slop/CHANGELOG.md | 47 ++++++++++++++++++ plugins/ai-slop/skills/audit/SKILL.md | 4 +- .../code-tidying/.claude-plugin/plugin.json | 2 +- plugins/code-tidying/CHANGELOG.md | 21 ++++++++ plugins/code-tidying/skills/tidy/SKILL.md | 2 +- .../docs-hygiene/.claude-plugin/plugin.json | 2 +- plugins/docs-hygiene/CHANGELOG.md | 23 +++++++++ .../skills/audit-derivability/SKILL.md | 2 +- plugins/firecrawl/.claude-plugin/plugin.json | 2 +- plugins/firecrawl/CHANGELOG.md | 28 +++++++++++ plugins/firecrawl/skills/firecrawl/SKILL.md | 2 +- plugins/knowledge/.claude-plugin/plugin.json | 2 +- plugins/knowledge/CHANGELOG.md | 49 +++++++++++++++++++ .../knowledge/skills/course-digest/SKILL.md | 4 +- .../knowledge/skills/video-digest/SKILL.md | 6 +-- plugins/provenance/.claude-plugin/plugin.json | 2 +- plugins/provenance/CHANGELOG.md | 35 +++++++++++++ plugins/provenance/skills/audit/SKILL.md | 6 +-- 19 files changed, 222 insertions(+), 19 deletions(-) diff --git a/plugins/ai-slop/.claude-plugin/plugin.json b/plugins/ai-slop/.claude-plugin/plugin.json index 93f817e50..a4bf25024 100644 --- a/plugins/ai-slop/.claude-plugin/plugin.json +++ b/plugins/ai-slop/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "ai-slop", - "version": "0.5.4", + "version": "0.5.5", "description": "Detects and removes AI-writing tells (slop) in checked-in markdown prose: em dashes, emoji formatting, AI vocabulary, negative parallelisms, chatbot phrases, filler, stacked hedging, citation artifacts, model-era phrases, and the rest of a catalog distilled from Wikipedia's Signs of AI writing plus a repo-owned, evidence-graded inventory of current-generation model vocabulary. Read-only audit by default with a deterministic detector plus a judgment rubric; an explicit fix action rewrites findings behind a semantic-diff guard. Findings conform to the detector-findings convention so the review fanout fix relay can consume them.", "author": { "name": "Melodic Software", diff --git a/plugins/ai-slop/CHANGELOG.md b/plugins/ai-slop/CHANGELOG.md index 32b3e808f..a59510299 100644 --- a/plugins/ai-slop/CHANGELOG.md +++ b/plugins/ai-slop/CHANGELOG.md @@ -1,5 +1,52 @@ # Changelog +## [0.5.5] + +### Fixed + +- **`audit`: the `detector unavailable` token fired on a healthy detector, from any directory, with + no configuration required.** 0.5.4 gave the effective-config probe a guard, + `detect.sh --show-config >/dev/null 2>&1 && detect.sh --show-config 2>/dev/null | head -8 || echo + "detector unavailable"`. Under `set -o pipefail` the `&&` list takes the pipeline's exit status, + and `detect.sh --show-config` emits 10 lines against a `head -8` cap while writing them one at a + time, so `head` closes the pipe mid-run every time and the script dies of SIGPIPE. Measured here: + `PIPESTATUS` is `141 0`. The probe rendered the full, correct eight-line config and then appended + `detector unavailable` under it. This was not latent and not a corner case: it was the observed + behaviour of the shipped line in this repository, and unlike 0.5.4's defect it does not render an + empty value, it asserts a failure that did not happen. + + Reproduced and fixed by execution in three states, each with and without `pipefail`. Skill + directory absent: both shapes render `detector unavailable` under both settings. A two-line + detector (under the cap): both shapes render the two lines, no token, under both settings. The + real detector (10 lines, over the cap): without `pipefail` both render eight lines and no token; + with `pipefail` the 0.5.4 shape renders nine lines, the ninth being `detector unavailable`, and + this one renders eight lines and no token. + + The filter pipeline now sits in a brace group closed by `:`, a command that cannot fail, so the + `||` is reachable only by the guard short-circuiting. This is the shape `docs-hygiene` 0.21.23 and + `code-tidying` 0.14.13 established; the fleet gains no new idiom. No `$` expansion is introduced, + so the composed pre-compute block stays verifiable to the worktree-isolation guard, and the + command still begins `${CLAUDE_SKILL_DIR}/scripts/detect.sh`, so the existing + `Bash(${CLAUDE_SKILL_DIR}/scripts/detect.sh:*)` grant still matches its leading token. + +- **`audit`: the effective-config probe broke on any install path containing a space.** + Pre-existing, not introduced by the fix above, but the fix is what makes it visible: the + expansion was unquoted, so an install under a path with a space in it (a Windows profile + directory named ``, a macOS `Application Support` tree) word-split into two + arguments, the script was never found, and the guard short-circuited. Before this release that + rendered an empty line; now it renders `detector unavailable` on a detector that is present and + working. Reproduced by copying the real `detect.sh` into a directory whose name contains a + space: unquoted renders `detector unavailable`, quoted renders the full config, and on a + space-free path both render it. Both the guard run and the data run are now quoted, matching the + form `firecrawl:update` already ships. + + Quoting changes the literal command string, and Bash permission rules are globs over that literal + string, so `Bash(${CLAUDE_SKILL_DIR}/scripts/detect.sh:*)` no longer matches the quoted + invocation. A companion rule `Bash("${CLAUDE_SKILL_DIR}/scripts/detect.sh":*)` is added for it. + The existing unquoted rule is kept, because the skill body's own instructions still invoke the + script unquoted. Both rules name the same script under the same `${CLAUDE_SKILL_DIR}` anchor and + carry the same `:*` argument scope, so this authorizes nothing the plugin could not already run. + ## [0.5.4] ### Fixed diff --git a/plugins/ai-slop/skills/audit/SKILL.md b/plugins/ai-slop/skills/audit/SKILL.md index b2fe31228..d2f56fda6 100644 --- a/plugins/ai-slop/skills/audit/SKILL.md +++ b/plugins/ai-slop/skills/audit/SKILL.md @@ -3,7 +3,7 @@ description: "Audit markdown prose for AI-writing tells (slop): em dashes (zero- argument-hint: "[audit|fix] [target]" user-invocable: true disable-model-invocation: false -allowed-tools: ["Bash(${CLAUDE_SKILL_DIR}/scripts/detect.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/emit-findings.sh:*)", "Bash(git:*)", "Bash(grep:*)", "Bash(head:*)", "Bash(wc:*)"] +allowed-tools: ["Bash(${CLAUDE_SKILL_DIR}/scripts/detect.sh:*)", "Bash(\"${CLAUDE_SKILL_DIR}/scripts/detect.sh\":*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/emit-findings.sh:*)", "Bash(git:*)", "Bash(grep:*)", "Bash(head:*)", "Bash(wc:*)"] shell: bash metadata: workflow-stage: anytime @@ -13,7 +13,7 @@ metadata: ## Pre-computed context Current branch: !`git branch --show-current 2>/dev/null || echo "unknown"` -Effective config: !`${CLAUDE_SKILL_DIR}/scripts/detect.sh --show-config >/dev/null 2>&1 && ${CLAUDE_SKILL_DIR}/scripts/detect.sh --show-config 2>/dev/null | head -8 || echo "detector unavailable"` +Effective config: !`"${CLAUDE_SKILL_DIR}/scripts/detect.sh" --show-config >/dev/null 2>&1 && { "${CLAUDE_SKILL_DIR}/scripts/detect.sh" --show-config 2>/dev/null | head -8; :; } || echo "detector unavailable"` ## Purpose diff --git a/plugins/code-tidying/.claude-plugin/plugin.json b/plugins/code-tidying/.claude-plugin/plugin.json index 27462466c..66403f0d3 100644 --- a/plugins/code-tidying/.claude-plugin/plugin.json +++ b/plugins/code-tidying/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "code-tidying", - "version": "0.14.13", + "version": "0.14.14", "description": "Code tidying and comment hygiene: /code-tidying:tidy proactively hunts a rotated, glob-scoped lane for Beck-style tidyings under a research-backed scope budget and ships one tight PR; /code-tidying:batch-simplify sweeps a time window, a branch, or an entire repository through grouped, dependency-ordered simplification waves with a never-drop deferred-items contract; /code-tidying:dissolve-comments enforces self-describing expressive code over a diff or target, widening to the branch diff and then the whole repository when the tree is clean — deletes zero-information comments, dissolves code-expressible ones into names and structure behind a tests gate (safe mode restricts applied edits to removals), and keeps only terse load-bearing comments code cannot express; /code-tidying:audit-comment-residue is a read-only classifier that flags history, plan, conversational, and ticket/PR residue in code comments for author-applied deletion; /code-tidying:audit-dead-code is a read-only whole-repo dead-code hunter running four labelled lanes of unequal confidence (knip for TS/JS, vulture for Python, gopls for Go, and a portable grep lane for shell and other symbol languages), adjudicating every candidate against dynamic-usage evidence into a dead, uncertain, or alive verdict. Project-specific tidy lanes are scaffolded into a tracked .claude/tidy-lanes/ config folder by a re-runnable setup skill.", "author": { "name": "Melodic Software", diff --git a/plugins/code-tidying/CHANGELOG.md b/plugins/code-tidying/CHANGELOG.md index 8f6060bdf..713cae362 100644 --- a/plugins/code-tidying/CHANGELOG.md +++ b/plugins/code-tidying/CHANGELOG.md @@ -3,6 +3,27 @@ All notable changes to the `code-tidying` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.14.14] + +### Fixed + +- **`tidy`: a stray third `unknown` line in the throttle probe under pipefail.** Cosmetic, not a + false failure claim. `open-pr-count.sh` exits 1 while printing well-formed output when `gh` cannot + reach the API, and the probe was + `open-pr-count.sh 2>/dev/null | grep -E '^(Open tidy|Throttle)' || echo "unknown"`. Without + `pipefail` the pipeline's status is `grep`'s, which is 0 when it matches, and the `||` stays shut. + With `pipefail` the script's 1 becomes the pipeline's status and `unknown` is appended beneath the + two well-formed lines, garbling a block that already carries its own `unknown` values. Reproduced + and fixed by execution: with the real script under `pipefail` the old shape renders three lines, + the third a bare `unknown`, and the new one renders two. + + The `||` moves inside a brace group on the `grep` end of the pipe, so it is driven by the filter's + status alone and the script's exit code no longer reaches it. The token still fires when the + script is missing entirely, which is the case it exists for, verified under both settings. This + form was chosen over the brace-group-first shape used elsewhere precisely to keep the leading + token unchanged: the command still begins `${CLAUDE_SKILL_DIR}/scripts/open-pr-count.sh`, so the + existing grant still matches it and no rule was added or widened. + ## [0.14.13] ### Fixed diff --git a/plugins/code-tidying/skills/tidy/SKILL.md b/plugins/code-tidying/skills/tidy/SKILL.md index 0b9094971..fe4a2a361 100644 --- a/plugins/code-tidying/skills/tidy/SKILL.md +++ b/plugins/code-tidying/skills/tidy/SKILL.md @@ -15,7 +15,7 @@ metadata: Current branch: !`git branch --show-current 2>/dev/null || echo "unknown"` Recent commits: !`git log --oneline -5 2>/dev/null || echo "no commits"` Working tree status (empty = clean): !`{ git status --porcelain 2>/dev/null || echo "(git status unavailable)"; } | head -20` -Open chore/tidy-* PRs: !`${CLAUDE_SKILL_DIR}/scripts/open-pr-count.sh 2>/dev/null | grep -E '^(Open tidy|Throttle)' || echo "unknown"` +Open chore/tidy-* PRs: !`${CLAUDE_SKILL_DIR}/scripts/open-pr-count.sh 2>/dev/null | { grep -E '^(Open tidy|Throttle)' || echo "unknown"; }; :` ## Variables diff --git a/plugins/docs-hygiene/.claude-plugin/plugin.json b/plugins/docs-hygiene/.claude-plugin/plugin.json index 3af70c841..fe25ce525 100644 --- a/plugins/docs-hygiene/.claude-plugin/plugin.json +++ b/plugins/docs-hygiene/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "docs-hygiene", - "version": "0.21.25", + "version": "0.21.26", "description": "Documentation-hygiene toolkit: compress (flavor-trim markdown with a semantic-diff safety net), audit-noise (classify markdown noise), extract-ssot (deduplicate repeated content into a single source of truth), audit-encapsulation (detect citations into skill-private surfaces), rename-references (sweep stale references after renames), audit-derivability (classify whether a whole document earns its existence \u2014 could a fresh agent re-derive it from the code?), audit-progressive-disclosure (grade instruction files against a load-tier model for split opportunities and hub/spoke disclosure defects), write-for-agents (authoring-time doctrine that fires while agent-consumed markdown is being written), and write-for-humans (the same moment for the other reader \u2014 end-user READMEs, RFCs, release notes and guides \u2014 resolving the consuming project's own style guide first).", "author": { "name": "Melodic Software", diff --git a/plugins/docs-hygiene/CHANGELOG.md b/plugins/docs-hygiene/CHANGELOG.md index 5e1b5f8e7..4feb861f9 100644 --- a/plugins/docs-hygiene/CHANGELOG.md +++ b/plugins/docs-hygiene/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog — docs-hygiene plugin +## [0.21.26] + +### Fixed + +- **`audit-derivability`: the sixth filtered probe, missed when the other five were fixed.** 0.21.23 + made five filtered-probe injections pipefail-proof and left this one, on the reading that its + capture-first shape was already safe. Only half of it is. The capture, + `s=$(git status --porcelain 2>/dev/null) && …`, does carry git's own exit status outside any + pipeline, so a genuinely unavailable `git` is still detected. But the data run that follows is + `printf … | awk … | head -20`, an ordinary pipeline, and `head` closing the pipe at the cap kills + `awk` with SIGPIPE. Under `set -o pipefail` that becomes the `&&` list's status and fires + `(status unavailable)` on a healthy git. + + Reachable at 21 or more dirty `.md` files. Reproduced on a repository with 3,000 of them: without + `pipefail` the probe renders 20 paths; with `pipefail` it renders 21 lines, the twenty-first + being `(status unavailable)` printed under a correct listing. After the fix it renders 20 paths + under both settings, and outside a git repository it still renders `(status unavailable)` under + both, which is the case the token exists for. + + Only the data pipeline moves into the brace group closed by `:`; the `$s` capture stays where it + is, because that is the part that legitimately drives the `||`. The five sites 0.21.23 fixed were + re-verified at 3,000 dirty files under both settings and are correct as shipped. + ## [0.21.25] ### Added diff --git a/plugins/docs-hygiene/skills/audit-derivability/SKILL.md b/plugins/docs-hygiene/skills/audit-derivability/SKILL.md index cef77e297..2158bebb5 100644 --- a/plugins/docs-hygiene/skills/audit-derivability/SKILL.md +++ b/plugins/docs-hygiene/skills/audit-derivability/SKILL.md @@ -12,7 +12,7 @@ metadata: ## Pre-computed context Current branch: !`git branch --show-current 2>/dev/null || echo "unknown"` -Uncommitted .md files (first 20; empty = none): !`s=$(git status --porcelain 2>/dev/null) && printf '%s\n' "$s" | awk '/\.md$/{p=substr($0,4); sub(/^.* -> /,"",p); print p}' | head -20 || echo "(status unavailable)"` +Uncommitted .md files (first 20; empty = none): !`s=$(git status --porcelain 2>/dev/null) && { printf '%s\n' "$s" | awk '/\.md$/{p=substr($0,4); sub(/^.* -> /,"",p); print p}' | head -20; :; } || echo "(status unavailable)"` ## Purpose diff --git a/plugins/firecrawl/.claude-plugin/plugin.json b/plugins/firecrawl/.claude-plugin/plugin.json index 2bb6abed2..698239a2c 100644 --- a/plugins/firecrawl/.claude-plugin/plugin.json +++ b/plugins/firecrawl/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "firecrawl", - "version": "0.5.6", + "version": "0.5.7", "description": "Web scraping, search, crawling, and file parsing through the firecrawl-cli binary with a write-to-disk-then-Read pattern that keeps large results out of context — a user-facing wrapper skill, a lazy-install setup skill, and a separate gated maintainer update skill tracking the upstream CLI and skill source.", "author": { "name": "Melodic Software", diff --git a/plugins/firecrawl/CHANGELOG.md b/plugins/firecrawl/CHANGELOG.md index 5ad077464..97fb0966d 100644 --- a/plugins/firecrawl/CHANGELOG.md +++ b/plugins/firecrawl/CHANGELOG.md @@ -3,6 +3,34 @@ All notable changes to the `firecrawl` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.5.7] + +### Fixed + +- **`firecrawl`: the `NOT INSTALLED` token could fire on an installed, working CLI.** The status + probe was + `command -v firecrawl >/dev/null 2>&1 && firecrawl --status 2>/dev/null | head -10 || echo "NOT + INSTALLED — run: npm install -g firecrawl-cli"`. Under `set -o pipefail` the `&&` list takes the + pipeline's status, and `head -10` closing the pipe kills `firecrawl` with SIGPIPE when + `--status` prints more than the cap, so the `||` fires on a healthy CLI. The rendered context + then shows ten lines of real status followed by an instruction to install a CLI that is already + installed, and the paragraph beneath it tells the reader to go get an API key. + + This site predates the guard sweep that introduced the same defect elsewhere: it is where the + guard-first shape was copied FROM, so it was never touched by that sweep and has carried the + exposure since it was written. Fixed in the same pass as the sites that copied it. + + Proven by execution in three states, each with and without `pipefail`, against a fake `firecrawl` + on `PATH`. Absent: both shapes render `NOT INSTALLED …` under both settings. Present, output under + the cap: both render the output, no token, under both settings. Present, output over the cap: + without `pipefail` both render ten lines and no token; with `pipefail` the old shape renders ten + lines **and** `NOT INSTALLED — run: npm install -g firecrawl-cli`, and this one renders the ten + lines alone. + + The data pipeline now sits in a brace group closed by `:`, the shape `docs-hygiene` 0.21.23 and + `code-tidying` 0.14.13 established. The command still begins `command -v firecrawl`, so the + existing `Bash(command -v firecrawl*)` grant still matches. No grant changed. + ## [0.5.6] ### Fixed diff --git a/plugins/firecrawl/skills/firecrawl/SKILL.md b/plugins/firecrawl/skills/firecrawl/SKILL.md index c3ba28571..b09d687f5 100644 --- a/plugins/firecrawl/skills/firecrawl/SKILL.md +++ b/plugins/firecrawl/skills/firecrawl/SKILL.md @@ -12,7 +12,7 @@ metadata: ## Pre-computed context -Status: !`command -v firecrawl >/dev/null 2>&1 && firecrawl --status 2>/dev/null | head -10 || echo "NOT INSTALLED — run: npm install -g firecrawl-cli"` +Status: !`command -v firecrawl >/dev/null 2>&1 && { firecrawl --status 2>/dev/null | head -10; :; } || echo "NOT INSTALLED — run: npm install -g firecrawl-cli"` The `firecrawl --status` line above includes auth state. If it shows unauthenticated (or the CLI is missing), the fix is: obtain a key from the dashboard and set `FIRECRAWL_API_KEY` as an OS user environment variable. diff --git a/plugins/knowledge/.claude-plugin/plugin.json b/plugins/knowledge/.claude-plugin/plugin.json index ffa2fec40..5b20fc502 100644 --- a/plugins/knowledge/.claude-plugin/plugin.json +++ b/plugins/knowledge/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "knowledge", - "version": "0.13.23", + "version": "0.13.24", "description": "Ingest external knowledge into durable, synthesized artifacts. Ships a book-distillation pipeline (PDF/EPUB into concept-organized, author-attributed skill reference files), a video-digest pipeline (watch a single public video from YouTube or X, formerly Twitter: transcript, link harvest, and repo-applicability synthesis), a course-digest pipeline (extract and synthesize online video courses \u2014 Dometrain, Teachable \u2014 into repo-applicable recommendations), a docpage-digest pipeline (single online documentation page into a verified knowledge slice with dual verification \u2014 one cross-vendor verifier \u2014 and an interview handoff), and a map-corpus pipeline (multi-resource corpus into a classified link map, deterministic node manifests, gate-verified relevance inventory, and an approved queue of docpage-digest runs), plus a re-runnable setup action; a configurable library directory governs where synthesized artifacts land in the consuming repo.", "author": { "name": "Melodic Software", diff --git a/plugins/knowledge/CHANGELOG.md b/plugins/knowledge/CHANGELOG.md index 9547951f3..b254b547b 100644 --- a/plugins/knowledge/CHANGELOG.md +++ b/plugins/knowledge/CHANGELOG.md @@ -4,6 +4,55 @@ All notable changes to the `knowledge` plugin are recorded here. The `version` i `.claude-plugin/plugin.json` is the delivery vehicle — a consumer receives a change only after that version increases. +## [0.13.24] + +### Fixed + +- **`video-digest` and `course-digest`: the prerequisites gate would refuse to run on a machine that + HAS the toolchain.** 0.13.23 fixed a gate that failed open and left one that fails closed for the + wrong reason. The five tool probes were + `command -v ffmpeg >/dev/null 2>&1 && ffmpeg -version 2>/dev/null | head -1 || echo "MISSING …"`. + Under `set -o pipefail` the `&&` list takes the pipeline's exit status, and `head -1` closes the + pipe while a multi-line version banner is still being written, so `ffmpeg` dies of SIGPIPE (exit + 141) and pipefail promotes that to the list's status. The `||` then fires on a working tool. The + rendered context shows the real version line and `MISSING — install ffmpeg` directly beneath it. + + That is not cosmetic. Both skills instruct the agent to STOP when the pre-computed context shows + `MISSING` for yt-dlp, ffmpeg, or ImageMagick. On a correctly provisioned machine the context now + shows `MISSING`, so the skill refuses to run against a toolchain that is installed and working. + 0.13.23 turned a gate that failed open into one that spuriously fails closed. The direction is + safer, the behaviour is still wrong. + + The five probes are not equally exposed, and the difference matters for diagnosis. Whether the + token fires is a race between the tool still writing its banner and `head -1` closing the pipe, + so it scales with how much the tool prints. Measured over 30 runs per size against a synthetic + tool on `PATH`, under `pipefail`, with the tool installed and working: + + | Tool output | 0.13.23 shape fires `MISSING` | This shape | + |---|---|---| + | 1 line (`yt-dlp --version`) | 0/30 | 0/30 | + | 5 lines (`magick -version`) | 19/30 | 0/30 | + | 41 lines (`ffmpeg -version`) | 29/30 | 0/30 | + + So `yt-dlp` is safe by output size alone, `ffmpeg` fails on nearly every run, and `magick` is a + genuine race that fails on roughly two runs in three. The intermittent one is the worst to live + with: a gate that stops the skill on most invocations and lets it through on the rest reads as + flakiness in the skill rather than as a defect in the probe. All five are fixed together, because + the shape is wrong regardless of how often it trips. + + Proven by execution in three states, each with and without `pipefail`. Tool absent: both shapes + render `MISSING …` under both settings. Tool present, single-line output: both shapes render the + version, no token, under both settings. Tool present, banner long enough to close the pipe: + without `pipefail` both render one version line and no token; with `pipefail` the 0.13.23 shape + renders the version line **and** `MISSING — install ffmpeg (watch action only)`, and this one + renders the version line alone. + + The version pipeline now sits in a brace group closed by `:`, a command that cannot fail, so the + `||` is reachable only when `command -v` short-circuits. This is the shape `docs-hygiene` 0.21.23 + and `code-tidying` 0.14.13 established. No `$` expansion is introduced, so the composed + pre-compute block stays verifiable to the worktree-isolation guard. Neither skill declares + `allowed-tools`, so no grant changed. + ## [0.13.23] ### Fixed diff --git a/plugins/knowledge/skills/course-digest/SKILL.md b/plugins/knowledge/skills/course-digest/SKILL.md index 1c68afb88..cfa36da4c 100644 --- a/plugins/knowledge/skills/course-digest/SKILL.md +++ b/plugins/knowledge/skills/course-digest/SKILL.md @@ -10,8 +10,8 @@ shell: bash course-extraction deps: !`node -e "const fs=require('fs'),path=require('path'),p=process.env.CLAUDE_PLUGIN_DATA;process.stdout.write(p&&fs.existsSync(path.join(p,'node_modules','@melodic','video-digestion'))?'installed':'MISSING - run setup-deps.mjs (see Prerequisites)')"` Playwright Chromium: !`node -e "const fs=require('fs'),path=require('path');const b=process.env.PLAYWRIGHT_BROWSERS_PATH||(process.env.CLAUDE_PLUGIN_DATA&&path.join(process.env.CLAUDE_PLUGIN_DATA,'ms-playwright'));const ok=b&&fs.existsSync(b)&&fs.readdirSync(b).some(n=>n.startsWith('chromium'));process.stdout.write(ok?'installed':'MISSING - run setup-deps.mjs (see Prerequisites)')"` -ffmpeg: !`command -v ffmpeg >/dev/null 2>&1 && ffmpeg -version 2>/dev/null | head -1 || echo "MISSING — install ffmpeg (see Prerequisites)"` -ImageMagick: !`command -v magick >/dev/null 2>&1 && magick -version 2>/dev/null | head -1 || echo "MISSING — install ImageMagick 7 (see Prerequisites)"` +ffmpeg: !`command -v ffmpeg >/dev/null 2>&1 && { ffmpeg -version 2>/dev/null | head -1; :; } || echo "MISSING — install ffmpeg (see Prerequisites)"` +ImageMagick: !`command -v magick >/dev/null 2>&1 && { magick -version 2>/dev/null | head -1; :; } || echo "MISSING — install ImageMagick 7 (see Prerequisites)"` # Course Digest diff --git a/plugins/knowledge/skills/video-digest/SKILL.md b/plugins/knowledge/skills/video-digest/SKILL.md index a5ea8012d..1407fa461 100644 --- a/plugins/knowledge/skills/video-digest/SKILL.md +++ b/plugins/knowledge/skills/video-digest/SKILL.md @@ -9,9 +9,9 @@ shell: bash ## Pre-computed context video-extraction deps: !`node -e "const fs=require('fs'),path=require('path'),p=process.env.CLAUDE_PLUGIN_DATA;process.stdout.write(p&&fs.existsSync(path.join(p,'node_modules','@melodic','video-digestion'))?'installed':'MISSING - run setup-deps.mjs (see Prerequisites)')"` -yt-dlp: !`command -v yt-dlp >/dev/null 2>&1 && yt-dlp --version 2>/dev/null | head -1 || echo "MISSING — install yt-dlp (see Prerequisites)"` -ffmpeg: !`command -v ffmpeg >/dev/null 2>&1 && ffmpeg -version 2>/dev/null | head -1 || echo "MISSING — install ffmpeg (watch action only)"` -ImageMagick: !`command -v magick >/dev/null 2>&1 && magick -version 2>/dev/null | head -1 || echo "MISSING — install ImageMagick 7 (watch action only)"` +yt-dlp: !`command -v yt-dlp >/dev/null 2>&1 && { yt-dlp --version 2>/dev/null | head -1; :; } || echo "MISSING — install yt-dlp (see Prerequisites)"` +ffmpeg: !`command -v ffmpeg >/dev/null 2>&1 && { ffmpeg -version 2>/dev/null | head -1; :; } || echo "MISSING — install ffmpeg (watch action only)"` +ImageMagick: !`command -v magick >/dev/null 2>&1 && { magick -version 2>/dev/null | head -1; :; } || echo "MISSING — install ImageMagick 7 (watch action only)"` # Video digest. YouTube and X diff --git a/plugins/provenance/.claude-plugin/plugin.json b/plugins/provenance/.claude-plugin/plugin.json index 0c44fc82f..57bceb421 100644 --- a/plugins/provenance/.claude-plugin/plugin.json +++ b/plugins/provenance/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "provenance", - "version": "0.4.0", + "version": "0.4.1", "description": "Finds prose in tracked markdown that restates content an external source owns (vendor docs, blogs, articles) without adequate attribution, confirms the source, and refactors the copy into a pointer, a citation, or a dated stamped record. Documentation provenance, not software supply chain. Nomination and judgment are LLM work; the scripts do only reasoning-free work (corpus scoping, breadcrumb extraction, stamp expiry, fingerprint compare of two concrete texts). Read-only audit by default; explicit fix and sweep actions apply dispositions behind a semantic-diff guard and live pointer verification. Findings conform to the detector-findings convention.", "author": { "name": "Melodic Software", diff --git a/plugins/provenance/CHANGELOG.md b/plugins/provenance/CHANGELOG.md index 6dd28269b..00f24634e 100644 --- a/plugins/provenance/CHANGELOG.md +++ b/plugins/provenance/CHANGELOG.md @@ -1,5 +1,40 @@ # Changelog +## [0.4.1] + +### Fixed + +- **`audit`: the two config probes broke on any install path containing a space.** Both expansions + were unquoted, so an install under a path with a space in it (a Windows profile directory named + ``, a macOS `Application Support` tree) word-split into two arguments, the script was + never found, and the guard short-circuited to `detector unavailable` on scripts that are present + and working. Reproduced by copying a detector into a directory whose name contains a space: + unquoted renders `detector unavailable`, quoted renders the config, and on a space-free path both + render it. Both the guard run and the data run are quoted now, matching the form + `firecrawl:update` already ships. + + Quoting changes the literal command string, and Bash permission rules are globs over that literal + string, so the existing `Bash(${CLAUDE_SKILL_DIR}/scripts/list-corpus.sh:*)` and + `Bash(${CLAUDE_SKILL_DIR}/scripts/check-stamps.sh:*)` rules no longer match the quoted + invocations. A companion quoted rule is added for each. The unquoted rules are kept, because the + audit flow's own steps 1 and 3 still invoke both scripts unquoted. Each pair names one script + under the same `${CLAUDE_SKILL_DIR}` anchor with the same `:*` argument scope, so this authorizes + nothing the plugin could not already run. + +### Changed + +- **Both config probes adopt the pipefail-proof filtered-probe shape, on shape rather than on an + observed failure.** `list-corpus.sh --show-config` emits 7 lines against a `head -10` cap, so + `head` never closes the pipe early, and `check-stamps.sh --show-config` is piped into `tail -3`, + which drains its input and cannot raise SIGPIPE at all. Both were verified by execution in three + states under both `pipefail` settings and neither showed any difference: **these were latent by + shape, not live defects, and nothing observable is fixed here.** The change is that a later + `--show-config` growing past ten lines would silently start asserting `detector unavailable` + under a correct detector, which is exactly what happened to `ai-slop:audit`. Confirmed by + substituting a 500-line detector: the old shape renders 10 lines plus the token under `pipefail`, + the new one renders 10 lines. The filter pipelines now sit in a brace group closed by `:`, the + shape `docs-hygiene` 0.21.23 and `code-tidying` 0.14.13 established. + ## [0.4.0] ### Changed diff --git a/plugins/provenance/skills/audit/SKILL.md b/plugins/provenance/skills/audit/SKILL.md index e5214bd85..ca2a9cca9 100644 --- a/plugins/provenance/skills/audit/SKILL.md +++ b/plugins/provenance/skills/audit/SKILL.md @@ -3,7 +3,7 @@ description: "Audit tracked markdown for prose restating content an external sou argument-hint: "[audit|fix|sweep] [target]" user-invocable: true disable-model-invocation: false -allowed-tools: ["Bash(${CLAUDE_SKILL_DIR}/scripts/list-corpus.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/extract-breadcrumbs.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/check-stamps.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/emit-findings.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/score-golden.sh:*)", "Bash(node ${CLAUDE_SKILL_DIR}/scripts/fingerprint.mjs:*)", "Bash(git:*)", "Bash(jq:*)", "Bash(grep:*)", "Bash(head:*)", "Bash(wc:*)"] +allowed-tools: ["Bash(${CLAUDE_SKILL_DIR}/scripts/list-corpus.sh:*)", "Bash(\"${CLAUDE_SKILL_DIR}/scripts/list-corpus.sh\":*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/extract-breadcrumbs.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/check-stamps.sh:*)", "Bash(\"${CLAUDE_SKILL_DIR}/scripts/check-stamps.sh\":*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/emit-findings.sh:*)", "Bash(${CLAUDE_SKILL_DIR}/scripts/score-golden.sh:*)", "Bash(node ${CLAUDE_SKILL_DIR}/scripts/fingerprint.mjs:*)", "Bash(git:*)", "Bash(jq:*)", "Bash(grep:*)", "Bash(head:*)", "Bash(wc:*)"] shell: bash metadata: workflow-stage: anytime @@ -13,8 +13,8 @@ metadata: ## Pre-computed context Current branch: !`git branch --show-current 2>/dev/null || echo "unknown"` -Effective config: !`${CLAUDE_SKILL_DIR}/scripts/list-corpus.sh --show-config >/dev/null 2>&1 && ${CLAUDE_SKILL_DIR}/scripts/list-corpus.sh --show-config 2>/dev/null | head -10 || echo "detector unavailable"` -Stamp config: !`${CLAUDE_SKILL_DIR}/scripts/check-stamps.sh --show-config >/dev/null 2>&1 && ${CLAUDE_SKILL_DIR}/scripts/check-stamps.sh --show-config 2>/dev/null | tail -3 || echo "detector unavailable"` +Effective config: !`"${CLAUDE_SKILL_DIR}/scripts/list-corpus.sh" --show-config >/dev/null 2>&1 && { "${CLAUDE_SKILL_DIR}/scripts/list-corpus.sh" --show-config 2>/dev/null | head -10; :; } || echo "detector unavailable"` +Stamp config: !`"${CLAUDE_SKILL_DIR}/scripts/check-stamps.sh" --show-config >/dev/null 2>&1 && { "${CLAUDE_SKILL_DIR}/scripts/check-stamps.sh" --show-config 2>/dev/null | tail -3; :; } || echo "detector unavailable"` ## Purpose