From 76d4b317249335c222d832eb6d2b2cf367006576 Mon Sep 17 00:00:00 2001 From: "bootc-bot[bot]" <225049296+bootc-bot[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 22:42:05 +0000 Subject: [PATCH] org-history: Omit unset bot and repositoryFilter from snapshots workflowRuns records already drop unavailable properties via sparseProperties(), but the snapshot header still serialized `bot: null` and `repositoryFilter: null` whenever --bot or --repo was not passed. Build the header through sparseProperties() too so absent options are omitted rather than written as null. coverage.aic.total keeps its explicit null, since there it means "not every eligible run has a known value" and CI asserts the key. Closes: #118 Assisted-by: AI Co-Authored-By: Claude Opus 5.5 --- scripts/README.md | 3 ++- scripts/org-history.js | 8 ++++++-- tests/org-history.test.js | 10 +++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/scripts/README.md b/scripts/README.md index 7d16e41..24e7950 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -31,7 +31,8 @@ Unavailable properties on `workflowRuns` records, including component AIC values omitted rather than serialized as `null`. Pass `--repo REPO` to collect one non-archived, non-fork repository within `ORG` instead -of the full visible organization. The resulting snapshot records the repository filter. +of the full visible organization. The resulting snapshot records the repository filter as +`repositoryFilter`; like `bot`, it is omitted when the corresponding option is not given. It requires authenticated `gh` and `unzip`. The snapshot is intentionally written to `/tmp` by default, not committed, and reports partial repository/artifact coverage. diff --git a/scripts/org-history.js b/scripts/org-history.js index a3d4298..b0bfb13 100644 --- a/scripts/org-history.js +++ b/scripts/org-history.js @@ -558,13 +558,17 @@ function selectedRepositories(org, repositoryFilter) { return [repository]; } +function snapshotHeader(org, interval, bot, repositoryFilter) { + return sparseProperties({ schemaVersion: SCHEMA_VERSION, org, period: interval.period, bot, repositoryFilter, interval: { start: interval.start, end: interval.end } }); +} + function collect(org, interval, bot, repositoryFilter) { // These are global prerequisites: do not turn a missing CLI/authentication // problem into misleading per-repository coverage gaps. exec('gh', ['auth', 'status']); exec('unzip', ['-v']); const repositories = selectedRepositories(org, repositoryFilter); - const snapshot = { schemaVersion: SCHEMA_VERSION, org, period: interval.period, bot: bot || null, repositoryFilter: repositoryFilter || null, interval: { start: interval.start, end: interval.end }, repositories: [], items: [], workflowRuns: [], commentAic: [], coverage: { repositories: { included: repositories.length, failed: [] }, aic: { relevantRuns: 0, eligibleRuns: 0, runsWithValues: 0, artifactBackedRuns: 0, commentBackedRuns: 0, ineligibleRuns: 0, missingOrExpired: 0, total: null } }, errors: [] }; + const snapshot = { ...snapshotHeader(org, interval, bot, repositoryFilter), repositories: [], items: [], workflowRuns: [], commentAic: [], coverage: { repositories: { included: repositories.length, failed: [] }, aic: { relevantRuns: 0, eligibleRuns: 0, runsWithValues: 0, artifactBackedRuns: 0, commentBackedRuns: 0, ineligibleRuns: 0, missingOrExpired: 0, total: null } }, errors: [] }; for (const repo of repositories) { let repositoryAic; try { @@ -650,4 +654,4 @@ function main(argv) { } if (require.main === module) { try { main(process.argv.slice(2)); } catch (error) { console.error(`org-history: ${error.message}`); process.exitCode = 1; } } -module.exports = { aicLinkedItems, applyCommentAicFallback, artifactAicProperties, deduplicateCommentAic, historyFilename, normalizedPullRequests, parseCommentAic, parsePeriod, previousCompleteIsoWeek, summarizeAicCoverage, validateDetailedItems, workflowRunRecord }; +module.exports = { aicLinkedItems, applyCommentAicFallback, artifactAicProperties, deduplicateCommentAic, historyFilename, normalizedPullRequests, parseCommentAic, parsePeriod, previousCompleteIsoWeek, snapshotHeader, summarizeAicCoverage, validateDetailedItems, workflowRunRecord }; diff --git a/tests/org-history.test.js b/tests/org-history.test.js index c1efded..14a9a24 100644 --- a/tests/org-history.test.js +++ b/tests/org-history.test.js @@ -4,7 +4,7 @@ const assert = require('node:assert/strict'); const childProcess = require('node:child_process'); const path = require('node:path'); const test = require('node:test'); -const { aicLinkedItems, applyCommentAicFallback, artifactAicProperties, deduplicateCommentAic, historyFilename, normalizedPullRequests, parseCommentAic, parsePeriod, previousCompleteIsoWeek, summarizeAicCoverage, validateDetailedItems, workflowRunRecord } = require('../scripts/org-history.js'); +const { aicLinkedItems, applyCommentAicFallback, artifactAicProperties, deduplicateCommentAic, historyFilename, normalizedPullRequests, parseCommentAic, parsePeriod, previousCompleteIsoWeek, snapshotHeader, summarizeAicCoverage, validateDetailedItems, workflowRunRecord } = require('../scripts/org-history.js'); const script = path.join(__dirname, '..', 'scripts', 'org-history.js'); @@ -94,6 +94,14 @@ test('normalizes only same-repository workflow PR references', () => { assert.deepEqual(normalizedPullRequests({ pull_requests: [{ url: 'https://api.github.com/repos/org/repo/pulls/2' }, { url: 'https://api.github.com/repos/org/repo/pulls/1' }, { url: 'https://api.github.com/repos/org/repo/pulls/2' }, { url: 'https://api.github.com/repos/org/other/pulls/3' }] }, 'org', 'repo'), [{ number: 1, url: 'https://github.com/org/repo/pull/1' }, { number: 2, url: 'https://github.com/org/repo/pull/2' }]); }); +test('omits unset optional snapshot header properties', () => { + const interval = parsePeriod('2026-W38'); + const expected = { schemaVersion: 5, org: 'org', period: '2026-W38', interval: { start: interval.start, end: interval.end } }; + assert.deepEqual(snapshotHeader('org', interval, undefined, undefined), expected); + assert.deepEqual(Object.keys(snapshotHeader('org', interval, 'bot[bot]', 'repo')), ['schemaVersion', 'org', 'period', 'bot', 'repositoryFilter', 'interval']); + assert.deepEqual(snapshotHeader('org', interval, 'bot[bot]', 'repo'), { ...expected, bot: 'bot[bot]', repositoryFilter: 'repo' }); +}); + test('serializes sparse workflow-run AIC properties without losing zero values', () => { const record = workflowRunRecord({ id: 1, path: 'drafter.lock.yml', aic: 0, aicRecords: 0, aicAgent: 0,