Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
31 changes: 31 additions & 0 deletions .github/workflows/test-scripts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Test Scripts

# Runs the agent-rules script regression tests (e.g. the scope-index heading
# contract between generate-agents.sh and validate-structure.sh, issue #55).
# Local job — no reusable workflow needed; the tests only require git, jq and
# bash, all preinstalled on the runner.

on:
push:
branches: [main]
paths:
- 'skills/agent-rules/scripts/**'
- 'skills/agent-rules/assets/**'
pull_request:
paths:
- 'skills/agent-rules/scripts/**'
- 'skills/agent-rules/assets/**'
workflow_dispatch:

permissions:
contents: read

jobs:
script-tests:
name: Script regression tests
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Run scope-index heading regression test
run: bash skills/agent-rules/scripts/tests/test-scope-index-heading.sh
85 changes: 85 additions & 0 deletions skills/agent-rules/scripts/tests/test-scope-index-heading.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# Regression test for the scope-index heading contract between
# generate-agents.sh and validate-structure.sh (issue #55).
#
# generate-agents.sh emits the root scope index under the heading
# "## Scoped AGENTS.md (MUST read when working in these directories)"
# while older output used the legacy heading
# "## Index of scoped AGENTS.md".
# validate-structure.sh must accept BOTH so a freshly generated root does not
# fail the skill's own structure validation, while a bloated root without any
# scope index still fails.
set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPTS_DIR="$(dirname "$SCRIPT_DIR")"
GENERATE="$SCRIPTS_DIR/generate-agents.sh"
VALIDATE="$SCRIPTS_DIR/validate-structure.sh"

NEW_HEADING='## Scoped AGENTS.md (MUST read when working in these directories)'
LEGACY_HEADING='## Index of scoped AGENTS.md'

WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT

fail() { echo "❌ FAIL: $1"; exit 1; }

Check warning on line 25 in skills/agent-rules/scripts/tests/test-scope-index-heading.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=netresearch_agent-rules-skill&issues=AZ8Htui4HYiRC2s7SIOs&open=AZ8Htui4HYiRC2s7SIOs&pullRequest=62

Check warning on line 25 in skills/agent-rules/scripts/tests/test-scope-index-heading.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=netresearch_agent-rules-skill&issues=AZ8Htui4HYiRC2s7SIOt&open=AZ8Htui4HYiRC2s7SIOt&pullRequest=62
pass() { echo "✅ PASS: $1"; }

Check warning on line 26 in skills/agent-rules/scripts/tests/test-scope-index-heading.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=netresearch_agent-rules-skill&issues=AZ8Htui4HYiRC2s7SIOv&open=AZ8Htui4HYiRC2s7SIOv&pullRequest=62

Check warning on line 26 in skills/agent-rules/scripts/tests/test-scope-index-heading.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=netresearch_agent-rules-skill&issues=AZ8Htui4HYiRC2s7SIOu&open=AZ8Htui4HYiRC2s7SIOu&pullRequest=62

# Build a minimal repo with a scoped directory so the generated root exceeds
# 50 lines and includes a populated scope index.
build_fixture() {

Check warning on line 30 in skills/agent-rules/scripts/tests/test-scope-index-heading.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=netresearch_agent-rules-skill&issues=AZ8Htui4HYiRC2s7SIOw&open=AZ8Htui4HYiRC2s7SIOw&pullRequest=62
local dir="$1"
rm -rf "$dir"
mkdir -p "$dir/src" "$dir/.github/workflows"
cat > "$dir/package.json" <<'JSON'
{ "name": "fixture", "version": "1.0.0", "scripts": { "test": "vitest", "build": "tsc" } }
JSON
echo "console.log('hi')" > "$dir/src/index.ts"
echo "name: ci" > "$dir/.github/workflows/ci.yml"
git -C "$dir" init -q
git -C "$dir" -c user.email=t@t.t -c user.name=t add -A
git -C "$dir" -c user.email=t@t.t -c user.name=t commit -qm init
}

# --- Test 1: generated root (new heading) passes validation -----------------
FX1="$WORK/new-heading"
build_fixture "$FX1"
bash "$GENERATE" "$FX1" --style=thin >/dev/null 2>&1 || fail "generate-agents.sh errored"

grep -qF "$NEW_HEADING" "$FX1/AGENTS.md" \
|| fail "generator no longer emits the expected heading: $NEW_HEADING"
lines=$(wc -l < "$FX1/AGENTS.md")
[ "$lines" -gt 50 ] || fail "generated root is only $lines lines; expected >50 to exercise the scope-index path"

Check failure on line 52 in skills/agent-rules/scripts/tests/test-scope-index-heading.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=netresearch_agent-rules-skill&issues=AZ8Htui4HYiRC2s7SIOx&open=AZ8Htui4HYiRC2s7SIOx&pullRequest=62

if bash "$VALIDATE" "$FX1" >/dev/null 2>&1; then
pass "generated root ($lines lines, new heading) validates"
else
fail "validate-structure.sh rejected a freshly generated root (#55 regression)"
fi

# --- Test 2: legacy heading still accepted (backward compatibility) ----------
FX2="$WORK/legacy-heading"
cp -r "$FX1" "$FX2"
# Rewrite only the heading line, keeping the rest of the generated root intact.
sed -i "s|^${NEW_HEADING}\$|${LEGACY_HEADING}|" "$FX2/AGENTS.md"
Comment thread
CybotTM marked this conversation as resolved.
Outdated
grep -qF "$LEGACY_HEADING" "$FX2/AGENTS.md" || fail "could not rewrite heading to legacy form"

if bash "$VALIDATE" "$FX2" >/dev/null 2>&1; then
pass "root with legacy heading still validates (backward compatible)"
else
fail "validate-structure.sh rejected the legacy scope-index heading"
fi

# --- Test 3: bloated root without any scope index still fails ----------------
FX3="$WORK/no-heading"
cp -r "$FX1" "$FX3"
# Drop the scope-index heading so the >50-line root has no index at all.
sed -i "/^${NEW_HEADING}\$/d" "$FX3/AGENTS.md"
Comment thread
CybotTM marked this conversation as resolved.
Outdated

if bash "$VALIDATE" "$FX3" >/dev/null 2>&1; then
fail "validate-structure.sh accepted a bloated root with no scope index"
else
pass "bloated root without a scope index is still rejected"
fi

echo "All scope-index heading regression tests passed."
12 changes: 9 additions & 3 deletions skills/agent-rules/scripts/validate-structure.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Scope-index heading regex (ERE). Matches both the current template heading
# "## Scoped AGENTS.md (MUST read ...)" and the legacy "## Index of scoped
# AGENTS.md" so validation stays in sync with generate-agents.sh output and
# remains backward-compatible with files produced by older versions.
SCOPE_INDEX_HEADING_RE='^## (Index of scoped|Scoped) AGENTS\.md'

# Default options
PROJECT_DIR=""
CHECK_FRESHNESS=false
Expand Down Expand Up @@ -119,7 +125,7 @@ check_root_is_thin() {
if [ "$line_count" -le 50 ]; then
success "Root is thin: $line_count lines"
return 0
elif grep -q "## Index of scoped AGENTS.md" "$file"; then
elif grep -qE "$SCOPE_INDEX_HEADING_RE" "$file"; then
success "Root has scope index (verbose style acceptable)"
return 0
else
Expand Down Expand Up @@ -191,7 +197,7 @@ check_scoped_sections() {
check_scope_links() {
local root_file="$1"

if ! grep -q "## Index of scoped AGENTS.md" "$root_file"; then
if ! grep -qE "$SCOPE_INDEX_HEADING_RE" "$root_file"; then
# No scope index (thin root without scopes) -- nothing to check. Set an
# explicit status so a subsequent record_check does not reuse the
# previous check's LAST_STATUS/LAST_DETAIL in --json mode.
Expand All @@ -201,7 +207,7 @@ check_scope_links() {

# Extract links from scope index
local links
links=$(sed -n '/## Index of scoped AGENTS.md/,/^##/p' "$root_file" | grep -o '\./[^)]*AGENTS.md' || true)
links=$(sed -nE "/$SCOPE_INDEX_HEADING_RE/,/^##/p" "$root_file" | grep -o '\./[^)]*AGENTS.md' || true)

if [ -z "$links" ]; then
# Empty scope index with AGENTS-GENERATED markers is valid (placeholder)
Expand Down
Loading