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
2 changes: 1 addition & 1 deletion plugins/ai-slop/.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": "ai-slop",
"version": "0.3.7",
"version": "0.3.8",
"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, and the rest of a catalog distilled from Wikipedia's Signs of AI writing. 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",
Expand Down
23 changes: 23 additions & 0 deletions plugins/ai-slop/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changelog

## [0.3.8]

- **Directory targets silently fell back to an untracked-inclusive filesystem
walk when git and the shell spelled the same checkout differently.**
`detect.sh` built its expansion prefix from `git rev-parse --show-toplevel`
and its filter from `pwd`. On Git Bash those disagree (`D:/repo` vs
`/d/repo`), so no prefixed candidate survived the filter, `grep` exited
non-zero, and `|| find` ran in place of the tracked-files listing it was
meant to back up. The walk returns untracked and ignored markdown, so a
directory target audited files the checkout does not track and said
nothing about it. Expansion now runs `git ls-files` with `-C <dir>`,
which is already restricted to that directory's subtree and answers in
paths relative to it. The caller's own spelling of the directory is the
only anchor. The branch is chosen up front from `--is-inside-work-tree`
rather than from an empty pipeline, so a filesystem walk is only ever
the answer for a directory genuinely outside a checkout; inside one, a
listing that fails reports on stderr instead of degrading into a
different set of files. A Windows drive-root target such as `C:/` keeps
its trailing slash: stripping it produced `C:`, which Windows treats as
drive-relative (the cwd on that drive), so `git -C` / `find` can scan
the wrong tree or nothing. Ordinary directory targets still lose one
trailing slash.

## [0.3.7]

- **A branch name beginning with a YAML indicator silently dropped every finding it emitted.**
Expand Down
64 changes: 58 additions & 6 deletions plugins/ai-slop/skills/audit/scripts/detect.sh
Original file line number Diff line number Diff line change
Expand Up @@ -259,17 +259,69 @@ fi
# Directory targets expand to the markdown beneath them (tracked files when the
# directory is inside a git checkout, a filesystem walk otherwise). Without
# this a directory fails the scan loop's -f test and is skipped silently.
#
# ONE anchor, the caller's own spelling of the directory. A directory has
# several spellings on Git Bash: git answers `C:/Users/...` for the same
# checkout a shell reaches as `/tmp/...`. The earlier expansion built its
# prefix from `git rev-parse --show-toplevel` and its filter from `pwd`, so on
# any host where those disagree no candidate survived the filter and the walk
# below silently replaced the tracked-files listing it was meant to back up.
# Running `ls-files` with `-C <dir>` needs neither: it is already
# restricted to that directory's subtree and answers in paths relative to it,
# so `<dir>` is the only anchor and cannot disagree with itself.
#
# The branch is chosen up front from `--is-inside-work-tree`, never from an
# empty pipeline, so a walk is only ever the answer for a directory that is
# genuinely outside a checkout. Inside one, a listing that fails says so on
# stderr rather than degrading into a different set of files.
#
# Strip one trailing slash, except when that would turn a Windows drive root
# (`C:/`) into a drive-relative path (`C:`). Windows then treats the target as
# "cwd on that drive", so git -C / find can scan the wrong tree or nothing.
# Unix root `/` is the same class: stripping leaves empty, so the original
# spelling is kept. `C:\` is unchanged because this strip only removes `/`.
normalize_dir_target() {
local dir="${1%/}"
if [[ -z "$dir" || "$dir" == [A-Za-z]: ]]; then
printf '%s\n' "$1"
return 0
fi
printf '%s\n' "$dir"
}

expand_dir_target() {
local dir inside listing status
dir="$(normalize_dir_target "$1")"

inside="$(git -C "$dir" rev-parse --is-inside-work-tree 2>/dev/null || true)"
if [[ "$inside" != "true" ]]; then
find "$dir" -name '*.md' -type f 2>/dev/null
return 0
fi

listing="$(git -C "$dir" ls-files '*.md')"
status=$?
if [[ "$status" -ne 0 ]]; then
echo "detect.sh: git ls-files failed under $dir (exit $status); that directory expanded to nothing" >&2
return 0
fi

while IFS= read -r rel; do
[[ -n "$rel" ]] || continue
if [[ "$dir" == */ || "$dir" == *\\ ]]; then
printf '%s%s\n' "$dir" "$rel"
else
printf '%s/%s\n' "$dir" "$rel"
fi
done <<<"$listing"
}

EXPANDED=()
for t in ${TARGETS[@]+"${TARGETS[@]}"}; do
if [[ -d "$t" ]]; then
while IFS= read -r line; do
[[ -n "$line" ]] && EXPANDED+=("$line")
done < <(
git -C "$t" ls-files --full-name '*.md' 2>/dev/null |
sed "s|^|$(git -C "$t" rev-parse --show-toplevel 2>/dev/null)/|" |
grep -F "$(cd "$t" && pwd)/" ||
find "$t" -name '*.md' -type f 2>/dev/null
)
done < <(expand_dir_target "$t")
else
EXPANDED+=("$t")
fi
Expand Down
52 changes: 52 additions & 0 deletions plugins/ai-slop/skills/audit/scripts/detect.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ assert_not_contains() {
*) pass "$1" ;;
esac
}
assert_eq() {
if [[ "$2" == "$3" ]]; then pass "$1"; else fail "$1" "$3" "$2"; fi
}

EM=$'\xe2\x80\x94'
CHECKMARK=$'\xe2\x9c\x85'
Expand Down Expand Up @@ -404,6 +407,55 @@ out="$(bash "$DETECT" "$GITDIR/docs" 2>&1)"
assert_contains "dir target in git repo: tracked file scanned via ls-files" "$out" "tracked.md"
assert_contains "dir target in git repo: only the tracked file counts" "$out" "1 files scanned"

# Path agreement. One directory has several SPELLINGS on Git Bash: git answers
# the Windows form of the same checkout a shell reaches as "/tmp/...". An
# expansion that derives its prefix from one source and its filter from another
# drops every candidate wherever the two disagree, and the walk that backs it up
# returns untracked markdown in place of the tracked listing. The
# assertion is that the SPELLING of the target cannot change the answer. On a
# host where the two forms already agree these two cases restate the one above,
# which is the point: the invariant holds everywhere, and it is only observable
# here.
GITSPELL="$(git -C "$GITDIR/docs" rev-parse --show-toplevel)/docs"
PWDSPELL="$(cd "$GITDIR/docs" && pwd)"

out="$(bash "$DETECT" "$GITSPELL" 2>&1)"
assert_contains "dir target, git spelling: only the tracked file counts" "$out" "1 files scanned"

out="$(bash "$DETECT" "$PWDSPELL" 2>&1)"
assert_contains "dir target, shell spelling: only the tracked file counts" "$out" "1 files scanned"

# A trailing slash is the same directory and must expand identically; the
# expansion builds paths by concatenation, so an unnormalized target would emit
# a doubled separator and scan nothing.
out="$(bash "$DETECT" "$GITDIR/docs/" 2>&1)"
assert_contains "dir target with a trailing slash: only the tracked file counts" "$out" "1 files scanned"

# Drive-root slash preservation is a string contract, not a host contract: the
# suite does not need Windows. Source the production helper so this case cannot
# drift from the function expand_dir_target actually calls.
# shellcheck disable=SC1090
source <(sed -n '/^normalize_dir_target()/,/^}/p' "$DETECT")
assert_eq "ordinary trailing slash is stripped" "$(normalize_dir_target "docs/")" "docs"
assert_eq "nested trailing slash is stripped" "$(normalize_dir_target "C:/tmp/")" "C:/tmp"
assert_eq "unix root keeps its slash" "$(normalize_dir_target "/")" "/"
assert_eq "windows drive root keeps its slash" "$(normalize_dir_target "C:/")" "C:/"
assert_eq "lowercase windows drive root keeps its slash" "$(normalize_dir_target "d:/")" "d:/"
assert_eq "windows drive-root backslash is unchanged" "$(normalize_dir_target "C:\\")" "C:\\"
assert_eq "already-drive-relative spelling is left alone" "$(normalize_dir_target "C:")" "C:"
assert_eq "ordinary path without a slash is unchanged" "$(normalize_dir_target "docs")" "docs"

# The walk is reserved for a directory genuinely outside a checkout. A directory
# INSIDE one that holds only untracked markdown expands to nothing rather than
# falling through to a filesystem walk, which is what the documented
# tracked-files-only contract means.
mkdir -p "$GITDIR/untrackedonly"
cat >"$GITDIR/untrackedonly/loose.md" <<EOF
A loose em dash ${EM} here.
EOF
out="$(bash "$DETECT" "$GITDIR/untrackedonly" 2>&1)"
assert_contains "dir target in git repo, no tracked markdown: expands to nothing" "$out" "0 files scanned"

# --- Excerpt truncation at the byte boundary --------------------------------------

# 79 ASCII bytes then an em dash: a byte cut at 80 would keep only the first
Expand Down