#!/usr/bin/env bash # Regression test for the two ways an ADR-0020 gate can be made to check NOTHING # while still exiting 0. Both were live defects, both were silent, and both sit # in the shared resolver block that all three scripts embed verbatim — so every # case below runs against all three. # # 1. THE FRONTMATTER BLOCKER. The frontmatter matcher used to be `^---\n`. A # UTF-8 BOM, a leading blank line, a trailing space after either marker, or # CRLF line endings all defeated it, and the miss was not reported: every # ADR-0020 check was skipped and the file passed. Measured at the time: a # 550-character description with a 1,000-word body exited 0 behind a BOM. # So this file asserts two complementary things — that each of those four # shapes is now TOLERATED (the findings actually fire), and that # frontmatter which genuinely cannot be parsed is a hard ERROR rather than # a quiet skip. A file that cannot be measured must never report green. # # 2. THE VALUELESS DESCRIPTION. `description:` with no value, followed by # another key, let a line regex's `\s*` cross the newline and capture the # NEXT key. The value then looked present (so "missing or empty" never # fired) and was empty once folded (so every ADR-0020 gate early-returned). # An agent file with one exited 0 with zero output through a BLOCKING # pre-push gate. All five spellings of "no value" are pinned here, plus the # three shapes where the value is present but is not TEXT — a list, a # mapping, a bool. Those used to be `str()`-coerced and then measured as a # Python repr, so `description: true` was the four-character "True" and # passed the 400-character gate. # # 3. THE INDENTED CLOSING MARKER. The mirror image of (1): content the pattern # was too LOOSE to reject. `\r?\n[ \t]*---` matched an indented `---` inside # a `>`-folded description, truncating the frontmatter mid-value — the # description gate then measured a fragment and the body gate measured the # discarded description text. # # Every needle names the specific branch or measurement the case is about. A # needle loose enough to match two branches is how the yaml-none fixture spent # its life asserting the wrong one: it emitted `---\n---\n`, which never matched # the frontmatter pattern at all, and passed on the bare word "frontmatter". # # Both fixtures carry an over-ceiling description AND an over-ceiling body on # purpose: asserting a non-zero exit alone would be satisfied by the "cannot # parse" error itself, so the tolerated shapes are asserted on the CONTENT of # the findings, not on the exit code. set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" HOOK="$REPO_ROOT/scripts/skill-size-check.sh" SKILL_VALIDATE="$REPO_ROOT/plugins/kyberforge/.apm/skills/skill-audit/scripts/validate.sh" AGENT_VALIDATE="$REPO_ROOT/plugins/kyberforge/.apm/skills/agent-audit/scripts/validate.sh" PASS=0 FAIL=0 pass() { echo " PASS: $1"; PASS=$((PASS + 1)); } fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); } TMPDIR_T="$(mktemp -d)" trap 'rm -rf "$TMPDIR_T"' EXIT DESC_CHARS=450 BODY_WORDS=1000 # write_fixture — one generator for both file shapes. # # Byte-level control is the point: BOM placement, line endings and trailing # whitespace are exactly what is under test, so the file is emitted in binary # mode rather than through a shell heredoc that would normalise them. write_fixture() { python3 - "$1" "$2" "$3" "$DESC_CHARS" "$BODY_WORDS" <<'PY' import sys kind, path, name, desc_chars, body_words = sys.argv[1:6] desc = 'x' * int(desc_chars) body = ' '.join(['word'] * int(body_words)) # The default, well-formed shape. Variants below mutate it. open_marker = '---' close_marker = '---' prefix = '' newline = '\n' fm_lines = ['name: ' + name, 'description: ' + desc] if kind == 'plain': pass elif kind == 'bom': prefix = '' elif kind == 'leading-blanks': prefix = '\n\n \n' elif kind == 'trailing-ws': open_marker = '--- ' close_marker = '---\t ' elif kind == 'crlf': newline = '\r\n' elif kind == 'no-close': close_marker = None elif kind == 'yaml-list': fm_lines = ['- one', '- two'] elif kind == 'yaml-string': fm_lines = ['just a bare scalar, not a mapping'] elif kind == 'yaml-none': # A comment-only block, NOT an empty one. `---\n---\n` does not match # FRONTMATTER_RE at all (the pattern needs a `\n` between the markers), so # it lands on the "no parseable frontmatter" branch and never reaches the # `data is None` -> "not a YAML mapping" branch this fixture is named for. # It passed anyway because the needle used to be the bare word # "frontmatter", which both messages contain. A comment is real frontmatter # text that yaml.safe_load() returns None for, which is the branch. fm_lines = ['# nothing but a comment'] elif kind == 'yaml-empty-block': # The shape the fixture above USED to have, kept as its own case so the # "no parseable frontmatter block" branch is covered on purpose rather than # by accident. fm_lines = [] elif kind == 'desc-folded-indented': # A `>`-folded description whose CONTENT contains an indented `---` line. # YAML block-scalar content must be indented deeper than its key, so this is # a value, not a document marker — but the closing pattern used to be # `\r?\n[ \t]*---`, which matched it, truncated the frontmatter mid-value # and silently reclassified the rest of the description as body. Both halves # of that are vacuous greens: the description gate measured a fragment, and # the body gate measured description text. # # The value is padded to exactly desc_chars AFTER folding, and the boundary # clause naming a target sits in the part the truncation used to discard. head = 'Use when doing the thing. ' tail = ' Do not use for improvements — use no-such-folded-target instead.' span = int(desc_chars) - len(head) - len(tail) - len(' --- ') if span < 2: raise SystemExit('desc_chars too small for the folded fixture') fm_lines = [ 'name: ' + name, 'description: >', ' ' + head + 'x' * (span // 2), ' ---', ' ' + 'x' * (span - span // 2) + tail, ] elif kind == 'desc-list': fm_lines = ['name: ' + name, 'description:', ' - one', ' - two'] elif kind == 'desc-mapping': fm_lines = ['name: ' + name, 'description:', ' text: a description'] elif kind == 'desc-bool': fm_lines = ['name: ' + name, 'description: true'] elif kind == 'yaml-malformed': fm_lines = ['name: ' + name, 'description: "unterminated', 'tabs:\t- a'] elif kind == 'desc-no-value': # The exact shape that exited 0 with zero output: a line regex's `\s*` # crosses the newline and captures `model: sonnet` as the description. fm_lines = ['name: ' + name, 'description:', 'model: sonnet'] elif kind == 'desc-null': fm_lines = ['name: ' + name, 'description: null'] elif kind == 'desc-single-quoted-empty': fm_lines = ['name: ' + name, "description: ''"] elif kind == 'desc-double-quoted-empty': fm_lines = ['name: ' + name, 'description: ""'] elif kind == 'desc-empty-fold': fm_lines = ['name: ' + name, 'description: >'] else: raise SystemExit('unknown fixture kind: %s' % kind) parts = [prefix, open_marker, newline] for line in fm_lines: parts.append(line) parts.append(newline) if close_marker is not None: parts.append(close_marker) parts.append(newline) parts.append(newline) parts.append(body) parts.append(newline) with open(path, 'wb') as fh: fh.write(''.join(parts).encode('utf-8')) PY } # Builds all three subjects for one fixture kind and echoes nothing; the paths # are fixed by convention so the probes below can find them. # # skill-audit takes a DIRECTORY (SKILL.md inside it, name matching the dir); # agent-audit takes a FILE inside an apm package. The hook takes the SKILL.md # directly, so it and skill-audit share one file. build_subjects() { local kind="$1" base="$TMPDIR_T/$1" rm -rf "$base" mkdir -p "$base/skill/my-skill" "$base/agent/.apm/agents" cat > "$base/agent/apm.yml" <<'EOF' name: test-package version: 0.1.0 type: skill EOF write_fixture "$kind" "$base/skill/my-skill/SKILL.md" my-skill write_fixture "$kind" "$base/agent/.apm/agents/my-agent.agent.md" my-agent } # probe_all