From 850797eff11aaa2d1f751c09728c44a20a86d008 Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Wed, 1 Jul 2026 13:14:37 -0700 Subject: [PATCH 1/6] CI: validate that new GAP metadata id matches PR number Per CONTRIBUTING.md, each GAP's `id` field in metadata.yml must match the PR number that introduces it. This adds a CI check that enforces this rule for PRs that add a new metadata.yml file. This would have caught the issue in #4 where the id was set to 2 instead of 4. Co-Authored-By: Claude --- .github/workflows/validate.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index a3e921d..799bb4c 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -14,6 +14,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v6 @@ -26,3 +28,25 @@ jobs: - name: Validate GAP structure run: npm run test:structure + + - name: Validate new GAP id matches PR number + if: github.event_name == 'pull_request' + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + # Find metadata.yml files newly added in this PR + new_metadata_files=$(git diff --name-only --diff-filter=A origin/main...HEAD -- 'gaps/*/metadata.yml') + + if [ -z "$new_metadata_files" ]; then + echo "No new metadata.yml files added in this PR." + exit 0 + fi + + for file in $new_metadata_files; do + id=$(grep -m1 '^id:' "$file" | awk '{print $2}') + if [ "$id" != "$PR_NUMBER" ]; then + echo "::error file=${file}::metadata.yml 'id' field is ${id}, but must match the PR number (${PR_NUMBER}). See CONTRIBUTING.md." + exit 1 + fi + echo "✓ ${file}: id ${id} matches PR #${PR_NUMBER}" + done From f77f0741c643046960d5c078d4a7f1bbf9bd34bd Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Wed, 1 Jul 2026 14:29:09 -0700 Subject: [PATCH 2/6] CI: validate that new GAP metadata id matches PR number Adds a dedicated workflow and script that enforces new GAP metadata.yml files have their `id` field set to the PR number, per CONTRIBUTING.md. Only runs on pull requests that add a new metadata.yml. Would have caught the issue in #4 where id was set to 2 instead of 4. Co-Authored-By: Claude --- .github/workflows/validate-new-gap-number.yml | 30 +++++++++++ .github/workflows/validate.yml | 24 --------- scripts/validate-new-gap-number.js | 53 +++++++++++++++++++ 3 files changed, 83 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/validate-new-gap-number.yml create mode 100755 scripts/validate-new-gap-number.js diff --git a/.github/workflows/validate-new-gap-number.yml b/.github/workflows/validate-new-gap-number.yml new file mode 100644 index 0000000..6a0a198 --- /dev/null +++ b/.github/workflows/validate-new-gap-number.yml @@ -0,0 +1,30 @@ +name: Validate new GAP number + +on: + pull_request: + branches: [main] + +jobs: + validate-gap-number: + name: Validate new GAP id matches PR number + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "24" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Validate new GAP id matches PR number + run: node scripts/validate-new-gap-number.js + env: + PR_NUMBER: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 799bb4c..a3e921d 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -14,8 +14,6 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v6 - with: - fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v6 @@ -28,25 +26,3 @@ jobs: - name: Validate GAP structure run: npm run test:structure - - - name: Validate new GAP id matches PR number - if: github.event_name == 'pull_request' - env: - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - # Find metadata.yml files newly added in this PR - new_metadata_files=$(git diff --name-only --diff-filter=A origin/main...HEAD -- 'gaps/*/metadata.yml') - - if [ -z "$new_metadata_files" ]; then - echo "No new metadata.yml files added in this PR." - exit 0 - fi - - for file in $new_metadata_files; do - id=$(grep -m1 '^id:' "$file" | awk '{print $2}') - if [ "$id" != "$PR_NUMBER" ]; then - echo "::error file=${file}::metadata.yml 'id' field is ${id}, but must match the PR number (${PR_NUMBER}). See CONTRIBUTING.md." - exit 1 - fi - echo "✓ ${file}: id ${id} matches PR #${PR_NUMBER}" - done diff --git a/scripts/validate-new-gap-number.js b/scripts/validate-new-gap-number.js new file mode 100755 index 0000000..d3f26a7 --- /dev/null +++ b/scripts/validate-new-gap-number.js @@ -0,0 +1,53 @@ +#!/usr/bin/env node + +/** + * Validates that newly added metadata.yml files have an `id` field matching + * the PR number, per CONTRIBUTING.md. + * + * Usage: PR_NUMBER=123 node scripts/validate-new-gap-number.js + */ + +import { readFileSync } from "node:fs"; +import { execSync } from "node:child_process"; +import { parse as parseYaml } from "yaml"; + +const prNumber = process.env.PR_NUMBER; + +if (!prNumber) { + console.error("PR_NUMBER environment variable is required."); + process.exit(1); +} + +const newMetadataFiles = execSync( + "git diff --name-only --diff-filter=A origin/main...HEAD -- 'gaps/*/metadata.yml'", + { encoding: "utf8" }, +) + .trim() + .split("\n") + .filter(Boolean); + +if (newMetadataFiles.length === 0) { + console.log("No new metadata.yml files added in this PR."); + process.exit(0); +} + +let failed = false; + +for (const file of newMetadataFiles) { + const content = readFileSync(file, "utf8"); + const metadata = parseYaml(content); + const id = String(metadata.id); + + if (id !== prNumber) { + console.error( + `::error file=${file}::metadata.yml 'id' field is ${id}, but must match the PR number (${prNumber}). See CONTRIBUTING.md.`, + ); + failed = true; + } else { + console.log(`✓ ${file}: id ${id} matches PR #${prNumber}`); + } +} + +if (failed) { + process.exit(1); +} From 5c3e27f3faec5ae0cba5b7a62d66fc7d92e1adaf Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Wed, 1 Jul 2026 14:29:43 -0700 Subject: [PATCH 3/6] Also validate that the GAP directory name matches the PR number Co-Authored-By: Claude --- scripts/validate-new-gap-number.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/scripts/validate-new-gap-number.js b/scripts/validate-new-gap-number.js index d3f26a7..b39283a 100755 --- a/scripts/validate-new-gap-number.js +++ b/scripts/validate-new-gap-number.js @@ -1,14 +1,15 @@ #!/usr/bin/env node /** - * Validates that newly added metadata.yml files have an `id` field matching - * the PR number, per CONTRIBUTING.md. + * Validates that newly added GAP directories and their metadata.yml `id` field + * both match the PR number, per CONTRIBUTING.md. * * Usage: PR_NUMBER=123 node scripts/validate-new-gap-number.js */ import { readFileSync } from "node:fs"; import { execSync } from "node:child_process"; +import { dirname, basename } from "node:path"; import { parse as parseYaml } from "yaml"; const prNumber = process.env.PR_NUMBER; @@ -34,6 +35,18 @@ if (newMetadataFiles.length === 0) { let failed = false; for (const file of newMetadataFiles) { + const gapDir = basename(dirname(file)); + const expectedDir = `GAP-${prNumber}`; + + if (gapDir !== expectedDir) { + console.error( + `::error file=${file}::GAP directory is ${gapDir}, but must be ${expectedDir} to match the PR number. See CONTRIBUTING.md.`, + ); + failed = true; + } else { + console.log(`✓ ${file}: directory ${gapDir} matches PR #${prNumber}`); + } + const content = readFileSync(file, "utf8"); const metadata = parseYaml(content); const id = String(metadata.id); From ec2921baa0de5a7e05d3f7d60336d91142315a2c Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Wed, 1 Jul 2026 14:31:18 -0700 Subject: [PATCH 4/6] Use --pr-number argument instead of env var Co-Authored-By: Claude --- .github/workflows/validate-new-gap-number.yml | 4 +--- scripts/validate-new-gap-number.js | 14 +++++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/validate-new-gap-number.yml b/.github/workflows/validate-new-gap-number.yml index 6a0a198..c9bb73d 100644 --- a/.github/workflows/validate-new-gap-number.yml +++ b/.github/workflows/validate-new-gap-number.yml @@ -25,6 +25,4 @@ jobs: run: npm ci - name: Validate new GAP id matches PR number - run: node scripts/validate-new-gap-number.js - env: - PR_NUMBER: ${{ github.event.pull_request.number }} + run: node scripts/validate-new-gap-number.js --pr-number ${{ github.event.pull_request.number }} diff --git a/scripts/validate-new-gap-number.js b/scripts/validate-new-gap-number.js index b39283a..4543d5e 100755 --- a/scripts/validate-new-gap-number.js +++ b/scripts/validate-new-gap-number.js @@ -4,18 +4,26 @@ * Validates that newly added GAP directories and their metadata.yml `id` field * both match the PR number, per CONTRIBUTING.md. * - * Usage: PR_NUMBER=123 node scripts/validate-new-gap-number.js + * Usage: node scripts/validate-new-gap-number.js --pr-number 123 */ import { readFileSync } from "node:fs"; import { execSync } from "node:child_process"; import { dirname, basename } from "node:path"; +import { parseArgs } from "node:util"; import { parse as parseYaml } from "yaml"; -const prNumber = process.env.PR_NUMBER; +const { values } = parseArgs({ + options: { + "pr-number": { type: "string" }, + }, + strict: true, +}); + +const prNumber = values["pr-number"]; if (!prNumber) { - console.error("PR_NUMBER environment variable is required."); + console.error("Usage: node scripts/validate-new-gap-number.js --pr-number "); process.exit(1); } From d2f5029b0c7f8624a513597d8807bb7308b9bcb3 Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Wed, 1 Jul 2026 14:33:45 -0700 Subject: [PATCH 5/6] Remove manual --pr-number guard; strict parseArgs is sufficient Co-Authored-By: Claude --- scripts/validate-new-gap-number.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/validate-new-gap-number.js b/scripts/validate-new-gap-number.js index 4543d5e..331ba62 100755 --- a/scripts/validate-new-gap-number.js +++ b/scripts/validate-new-gap-number.js @@ -22,11 +22,6 @@ const { values } = parseArgs({ const prNumber = values["pr-number"]; -if (!prNumber) { - console.error("Usage: node scripts/validate-new-gap-number.js --pr-number "); - process.exit(1); -} - const newMetadataFiles = execSync( "git diff --name-only --diff-filter=A origin/main...HEAD -- 'gaps/*/metadata.yml'", { encoding: "utf8" }, From 6ed9fbc6dd8070319bb59dc5982c6f885b85fb5d Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Wed, 1 Jul 2026 14:36:34 -0700 Subject: [PATCH 6/6] Bump actions/checkout to v7 Co-Authored-By: Claude --- .github/workflows/validate-new-gap-number.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-new-gap-number.yml b/.github/workflows/validate-new-gap-number.yml index c9bb73d..1806e79 100644 --- a/.github/workflows/validate-new-gap-number.yml +++ b/.github/workflows/validate-new-gap-number.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0