From 973c111b3569668c1f633095cdfb88612619d0c1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Jan 2026 10:03:21 +0000 Subject: [PATCH 1/5] feat: Add spec coverage reporting to PRs Add a new CI job that posts JMAP spec coverage as a PR comment: - Create .github/scripts/post-coverage.js for posting comments - Add Spec Coverage job that runs on PRs with submodule checkout - Capture coverage:spec --by-type output and post via github-script https://claude.ai/code/session_01BKenThf76JjbsGoqPWybft --- .github/scripts/post-coverage.js | 31 +++++++++++++++++++++++ .github/workflows/ci.yml | 42 ++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 .github/scripts/post-coverage.js diff --git a/.github/scripts/post-coverage.js b/.github/scripts/post-coverage.js new file mode 100644 index 0000000..59d44e4 --- /dev/null +++ b/.github/scripts/post-coverage.js @@ -0,0 +1,31 @@ +/** + * Posts JMAP spec coverage report as a PR comment. + * + * @param {object} params + * @param {import('@actions/github/lib/utils').GitHub} params.github + * @param {import('@actions/github').context} params.context + */ +module.exports = async ({ github, context }) => { + const report = process.env.COVERAGE_REPORT; + + if (!report) { + console.log('No coverage report found in COVERAGE_REPORT env var'); + return; + } + + const body = `## JMAP Spec Coverage + +\`\`\` +${report} +\`\`\` +`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body + }); + + console.log('Coverage comment posted successfully'); +}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60e4a3b..5cc9323 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -129,3 +129,45 @@ jobs: - name: Stop test server if: always() run: pnpm test:server:stop + + spec-coverage: + name: Spec Coverage + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - uses: pnpm/action-setup@v4 + with: + version: 10.17.0 + + - uses: actions/setup-node@v4 + with: + node-version: "24" + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run spec tests + run: pnpm test:spec + + - name: Generate coverage report + id: coverage + run: | + { + echo 'report<> "$GITHUB_OUTPUT" + + - name: Post coverage comment + uses: actions/github-script@v7 + env: + COVERAGE_REPORT: ${{ steps.coverage.outputs.report }} + with: + script: | + const script = require('./.github/scripts/post-coverage.js') + await script({ github, context }) From b86a8e1179548e480df907cec0765114f0c6146f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Jan 2026 10:31:10 +0000 Subject: [PATCH 2/5] fix: Add PR comment permissions and fix context handling - Add pull-requests: write permission to spec-coverage job - Use context.payload.pull_request.number for PR number - Use unique heredoc delimiter to avoid output conflicts https://claude.ai/code/session_01BKenThf76JjbsGoqPWybft --- .github/scripts/post-coverage.js | 8 +++++++- .github/workflows/ci.yml | 6 ++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/scripts/post-coverage.js b/.github/scripts/post-coverage.js index 59d44e4..f738c8e 100644 --- a/.github/scripts/post-coverage.js +++ b/.github/scripts/post-coverage.js @@ -20,10 +20,16 @@ ${report} \`\`\` `; + const prNumber = context.payload.pull_request?.number; + if (!prNumber) { + console.log('Not a pull request context, skipping comment'); + return; + } + await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, - issue_number: context.issue.number, + issue_number: prNumber, body }); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5cc9323..6f54a25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,6 +134,8 @@ jobs: name: Spec Coverage runs-on: ubuntu-latest if: github.event_name == 'pull_request' + permissions: + pull-requests: write steps: - uses: actions/checkout@v4 with: @@ -158,9 +160,9 @@ jobs: id: coverage run: | { - echo 'report<> "$GITHUB_OUTPUT" - name: Post coverage comment From 488cc119b22a849fd1cdc887c14696cdb0e04b01 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Jan 2026 10:34:35 +0000 Subject: [PATCH 3/5] fix: Rename post-coverage script to .cjs for CommonJS compatibility Package.json has "type": "module" so .js files are treated as ES modules. Rename to .cjs to allow require() from github-script action. https://claude.ai/code/session_01BKenThf76JjbsGoqPWybft --- .github/scripts/{post-coverage.js => post-coverage.cjs} | 0 .github/workflows/ci.yml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename .github/scripts/{post-coverage.js => post-coverage.cjs} (100%) diff --git a/.github/scripts/post-coverage.js b/.github/scripts/post-coverage.cjs similarity index 100% rename from .github/scripts/post-coverage.js rename to .github/scripts/post-coverage.cjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f54a25..d825944 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -171,5 +171,5 @@ jobs: COVERAGE_REPORT: ${{ steps.coverage.outputs.report }} with: script: | - const script = require('./.github/scripts/post-coverage.js') + const script = require('./.github/scripts/post-coverage.cjs') await script({ github, context }) From 0736dd419f11e08176d5434782df03e5a703a812 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Jan 2026 10:39:24 +0000 Subject: [PATCH 4/5] feat: Add markdown output format for spec coverage - Add --markdown flag to spec-coverage.ts for GitHub-flavored tables - Update CI workflow to use --markdown instead of --by-type - Remove code block wrapper since markdown renders natively https://claude.ai/code/session_01BKenThf76JjbsGoqPWybft --- .github/scripts/post-coverage.cjs | 2 -- .github/workflows/ci.yml | 2 +- scripts/spec-coverage.ts | 35 +++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/.github/scripts/post-coverage.cjs b/.github/scripts/post-coverage.cjs index f738c8e..e41f2fd 100644 --- a/.github/scripts/post-coverage.cjs +++ b/.github/scripts/post-coverage.cjs @@ -15,9 +15,7 @@ module.exports = async ({ github, context }) => { const body = `## JMAP Spec Coverage -\`\`\` ${report} -\`\`\` `; const prNumber = context.payload.pull_request?.number; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d825944..8bd7fa8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -161,7 +161,7 @@ jobs: run: | { echo 'report<> "$GITHUB_OUTPUT" diff --git a/scripts/spec-coverage.ts b/scripts/spec-coverage.ts index b728fe0..f8f678d 100644 --- a/scripts/spec-coverage.ts +++ b/scripts/spec-coverage.ts @@ -21,6 +21,7 @@ import { interface CliArgs { json: boolean byType: boolean + markdown: boolean help: boolean } @@ -29,6 +30,7 @@ function parseArgs(): CliArgs { return { json: args.includes('--json'), byType: args.includes('--by-type'), + markdown: args.includes('--markdown'), help: args.includes('--help') || args.includes('-h') } } @@ -45,12 +47,14 @@ Usage: Options: --json Output as JSON --by-type Group coverage by object type + --markdown Output as GitHub-flavored markdown table --help, -h Show this help message Examples: pnpm coverage:spec # Show coverage table pnpm coverage:spec --by-type # Show coverage grouped by type pnpm coverage:spec --json # Output as JSON for CI/scripts + pnpm coverage:spec --markdown # Output markdown for PR comments `) } @@ -145,6 +149,32 @@ function printJson() { console.log(JSON.stringify(result, null, 2)) } +function printMarkdown() { + const byType = getCompletenessByType() + const completeness = getCompleteness() + + // Header with overall progress + console.log(`**Overall: ${completeness.implemented}/${completeness.total} methods (${completeness.percentage.toFixed(1)}%)**`) + console.log('') + + // Table header + console.log('| Object Type | Coverage | Methods |') + console.log('|-------------|----------|---------|') + + // Table rows by type + for (const [type, data] of Object.entries(byType).sort((a, b) => a[0].localeCompare(b[0]))) { + const methodList = data.methods + .map(m => { + const name = m.name.split('/')[1] + return m.implemented ? `✅ ${name}` : `⬚ ${name}` + }) + .join(', ') + + const coverage = `${data.implemented}/${data.total} (${data.percentage.toFixed(0)}%)` + console.log(`| ${type} | ${coverage} | ${methodList} |`) + } +} + function main() { const args = parseArgs() @@ -158,6 +188,11 @@ function main() { return } + if (args.markdown) { + printMarkdown() + return + } + if (args.byType) { printByType() return From fc1fce0c11401f2afffc36559df570c7b0cdbd21 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Jan 2026 11:10:44 +0000 Subject: [PATCH 5/5] refactor: Split markdown coverage into tables per object type Each object type now has its own table with methods in separate rows. Format: Method | Status columns for cleaner PR comments. https://claude.ai/code/session_01BKenThf76JjbsGoqPWybft --- scripts/spec-coverage.ts | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/scripts/spec-coverage.ts b/scripts/spec-coverage.ts index f8f678d..c2620a3 100644 --- a/scripts/spec-coverage.ts +++ b/scripts/spec-coverage.ts @@ -157,21 +157,20 @@ function printMarkdown() { console.log(`**Overall: ${completeness.implemented}/${completeness.total} methods (${completeness.percentage.toFixed(1)}%)**`) console.log('') - // Table header - console.log('| Object Type | Coverage | Methods |') - console.log('|-------------|----------|---------|') - - // Table rows by type + // Table for each object type for (const [type, data] of Object.entries(byType).sort((a, b) => a[0].localeCompare(b[0]))) { - const methodList = data.methods - .map(m => { - const name = m.name.split('/')[1] - return m.implemented ? `✅ ${name}` : `⬚ ${name}` - }) - .join(', ') - - const coverage = `${data.implemented}/${data.total} (${data.percentage.toFixed(0)}%)` - console.log(`| ${type} | ${coverage} | ${methodList} |`) + console.log(`### ${type} (${data.implemented}/${data.total})`) + console.log('') + console.log('| Method | Status |') + console.log('|--------|--------|') + + for (const method of data.methods) { + const name = method.name.split('/')[1] + const status = method.implemented ? '✅' : '⬚' + console.log(`| ${name} | ${status} |`) + } + + console.log('') } }