Files
Conor Bronsdon 6e425f2e5d check-pattern-count: assert CLAUDE.md's category-count copy on every push (#111)
* check-pattern-count: assert CLAUDE.md's copy of the category count on every push

CLAUDE.md's don't-restate-it sentence quotes the README bullet, and its copy
is tracked only by .ssot.yaml, which runs on release and weekly cron - so a
same-repo edit could sit stale for up to a week (how #95's 46-vs-61 lived
unnoticed). The sed pattern mirrors .ssot.yaml's regex for the copy, so the
two guards cannot read different sentences.

Controls: clean tree passes; 61->62 mutation fails with the drift message;
a reworded sentence fails with its own message pointing at both guards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKwz2XfwaMD6uR5ABmVaKq

* count guard: actually trigger on CLAUDE.md edits, survive a missing file, take the first match

All three findings from the adversarial review of #111:
- HIGH: plugin-skill-sync.yml's paths filter omitted CLAUDE.md, so the
  guard never ran on the edits it exists to catch (PR #106: zero check
  runs). Added to both triggers.
- MEDIUM: under set -euo pipefail a missing CLAUDE.md killed the script
  with sed's raw error and exit 2 before the friendly branch. Explicit -f
  check with its own message and exit 1.
- LOW: greedy sed took the LAST match on a line where a first-match regex
  engine takes the first; extraction is now grep -o | head -n1 (file-order
  first match) and the mirror-claim comment states the semantics honestly.

Controls, exit codes checked unpiped: clean 0; mutation 1; reworded 1;
missing file 1; same-line double-mention resolves to the first match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EKwz2XfwaMD6uR5ABmVaKq

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-04 20:30:25 -07:00

59 lines
1.9 KiB
YAML

name: plugin-skill-sync
on:
pull_request:
paths:
- "SKILL.md"
- "README.md"
# CLAUDE.md carries a checked copy of the pattern count; without this
# path the count guard never runs on a CLAUDE.md-only edit (PR #106
# had zero check runs - review finding on #111).
- "CLAUDE.md"
- "plugins/**"
- ".claude-plugin/**"
- "cursor-rules/**"
- "scripts/**"
push:
branches: [main]
paths:
- "SKILL.md"
- "README.md"
# CLAUDE.md carries a checked copy of the pattern count; without this
# path the count guard never runs on a CLAUDE.md-only edit (PR #106
# had zero check runs - review finding on #111).
- "CLAUDE.md"
- "plugins/**"
- ".claude-plugin/**"
- "cursor-rules/**"
- "scripts/**"
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Validate manifest JSON
run: |
python3 -m json.tool .claude-plugin/marketplace.json > /dev/null
python3 -m json.tool plugins/avoid-ai-writing/.claude-plugin/plugin.json > /dev/null
- name: Check README pattern count matches SKILL.md
run: bash scripts/check-pattern-count.sh
- name: Regenerate bundled skill + check version
run: bash scripts/sync-plugin-skill.sh
- name: Regenerate cursor rule
run: bash scripts/sync-cursor-rules.sh
- name: Fail if a generated copy drifted from SKILL.md
run: |
if ! git diff --quiet; then
echo "::error::A generated copy (plugins/ bundled skill or cursor-rules/avoid-ai-writing.mdc) is out of sync with the root SKILL.md."
echo "Run 'bash scripts/sync-plugin-skill.sh && bash scripts/sync-cursor-rules.sh' and commit the result."
git --no-pager diff --stat
exit 1
fi
echo "generated copies are in sync"