From 614a0d5efa8e38fff151f784e2d05807d0fc271a Mon Sep 17 00:00:00 2001 From: Defame1297 Date: Wed, 16 Sep 2026 15:27:17 +0000 Subject: [PATCH] fix(gates): read leading-whitespace frontmatter in check-skill-version-bump read_version required --- at byte 0 while skill-size-check accepts leading blank lines, so a file one gate passed the other reported as unversioned, and an unversioned merge-base side let an unbumped change through. Match FRONTMATTER_RE, add case 39, and describe the main-tip check and fail-closed cases in the hook entry. Co-Authored-By: Claude Opus 5 (1M context) --- .pre-commit-config.yaml | 13 ++++++++----- scripts/check-skill-version-bump.sh | 6 ++++-- tests/test-skill-version-bump.sh | 21 +++++++++++++++++++++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dbdfa77..4d784e5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -189,16 +189,19 @@ repos: - id: check-skill-version-bump name: Check changed skills bump metadata.version - description: On every push, fail if a skill directory changed (tests/ excluded) since the merge-base with main without its SKILL.md metadata.version rising (ADR-0022) + description: On every push, fail if a skill directory changed (tests/ excluded) since the merge-base with main without its SKILL.md metadata.version rising above both that merge-base's and main's tip's (ADR-0022) entry: bash scripts/check-skill-version-bump.sh language: system stages: [pre-push] pass_filenames: false always_run: true - # Baseline is the merge-base with origin/main (falling back to main), - # not the remote branch tip: readers install from main. Fails closed - # when no main ref resolves. Merges through Gitea's merge button run no - # local hook, so they bypass this. + # "Changed" is measured from the merge-base with origin/main (falling + # back to main), not the remote branch tip: readers install from main. + # The version must also beat main's tip, so two branches making the same + # bump cannot both land. Fails closed when no main ref resolves, when + # there is no merge-base, or when only local main resolves and already + # contains the pushed commit. Merges through Gitea's merge button run no + # local hook, so they bypass this. See docs/spec/gates.md. - id: validate-marketplace name: Validate marketplace manifest diff --git a/scripts/check-skill-version-bump.sh b/scripts/check-skill-version-bump.sh index 28307c2..8490427 100755 --- a/scripts/check-skill-version-bump.sh +++ b/scripts/check-skill-version-bump.sh @@ -175,12 +175,14 @@ done < "$CHANGED_FILE" # python3 crashing, PyYAML failing to import — is a non-zero exit with no OK / # INVALID line, which the caller reports as a read failure, never as a missing # version. Bytes are decoded explicitly so the caller's locale cannot turn a -# non-ASCII SKILL.md into a crash; `\s*` before each `\n` absorbs CRLF. +# non-ASCII SKILL.md into a crash; `[ \t\r]*` before each `\n` absorbs CRLF. +# Whitespace before the opening `---` is accepted, matching FRONTMATTER_RE in +# factory-audit's lib-boundary-resolver.sh, which skill-size-check applies. read_version() { python3 -c ' import re, sys, yaml text = sys.stdin.buffer.read().decode("utf-8-sig", errors="replace") -m = re.match(r"---[ \t\r]*\n(.*?)\n---[ \t\r]*(\n|\Z)", text, re.S) +m = re.match(r"[ \t\r\n]*---[ \t\r]*\n(.*?)\n---[ \t\r]*(\n|\Z)", text, re.S) data = None if m: try: diff --git a/tests/test-skill-version-bump.sh b/tests/test-skill-version-bump.sh index 3573ff0..4e71f9a 100755 --- a/tests/test-skill-version-bump.sh +++ b/tests/test-skill-version-bump.sh @@ -567,6 +567,27 @@ else fi expect_pass "a skill replaced by a symlink is exempt as deleted" "$F" +echo "" +echo "--- 39. leading blank lines before the frontmatter are parsed ---" +# skill-size-check's FRONTMATTER_RE accepts whitespace before the opening +# `---`; this gate must read the same shape. Otherwise the pushed side reports +# "missing" for a file the commit hook accepted, and a baseline shaped this way +# reads as "no valid version", which lets any version pass. +# blank_lead : prefix that skill's SKILL.md with blank lines. +blank_lead() { + local f="$1/plugins/demo/.apm/skills/$2/SKILL.md" + { printf '\n \t\n'; cat "$f"; } > "$f.tmp" && mv "$f.tmp" "$f" +} +F="$(make_fixture)" +(cd "$F" && git checkout -q main) +blank_lead "$F" alpha; commit "$F" "alpha leading blank" +(cd "$F" && git checkout -q feature && git merge -q main) +write_skill "$F" demo alpha 'version: "1.0.0"' "lead body"; blank_lead "$F" alpha; commit "$F" +expect_fail "unbumped skill with leading blank lines is held to its baseline version" \ + "alpha: 1\.0\.0 -> 1\.0\.0 \(not above merge-base\)" "$F" +write_skill "$F" demo alpha 'version: "1.0.1"' "lead body 2"; blank_lead "$F" alpha; commit "$F" +expect_pass "bumped skill with leading blank lines passes" "$F" + echo "" echo "Results: $PASS passed, $FAIL failed" [[ $FAIL -eq 0 ]]