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
4 changes: 4 additions & 0 deletions generated/benchmarks/INCREMENTAL-BENCHMARKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ Import resolution: native batch vs JS fallback throughput.
| Per-import (JS) | 0ms |
| Speedup ratio | 1.2x |

<!-- NOTES_START -->
**Note (3.9.5):** No build/rebuild metrics for this release (both engines null) — only import resolution data was collected. Both the WASM and native workers reached the 1-file rebuild phase and then hung past the benchmark's 10-minute per-engine timeout (see `scripts/lib/fork-engine.ts`), so each was killed (`SIGKILL`) before returning results. Import resolution is unaffected because it runs in the parent process and doesn't depend on the full build. 3.9.5 is consequently absent from the top-level version-history comparison table since there are no build-time figures to compare against prior releases. The workflow run is [here](https://github.com/optave/ops-codegraph-tool/actions/runs/24863501577); the root cause will be investigated and the numbers backfilled in a follow-up if possible.
<!-- NOTES_END -->

<!-- INCREMENTAL_BENCHMARK_DATA
[
{
Expand Down
11 changes: 9 additions & 2 deletions scripts/update-incremental-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ if (arg) {
const entry = JSON.parse(jsonText);

// ── Paths ────────────────────────────────────────────────────────────────
const reportPath = path.join(root, 'generated', 'benchmarks', 'INCREMENTAL-BENCHMARKS.md');
const reportPath =
process.env.CODEGRAPH_INCREMENTAL_REPORT_PATH ??
path.join(root, 'generated', 'benchmarks', 'INCREMENTAL-BENCHMARKS.md');

// ── Load existing history ────────────────────────────────────────────────
// ── Load existing history + manual NOTES block ───────────────────────────
let history = [];
let notesBlock = '';
if (fs.existsSync(reportPath)) {
const content = fs.readFileSync(reportPath, 'utf8');
const match = content.match(/<!--\s*INCREMENTAL_BENCHMARK_DATA\s*([\s\S]*?)\s*-->/);
Expand All @@ -41,6 +44,8 @@ if (fs.existsSync(reportPath)) {
/* start fresh if corrupt */
}
}
const notesMatch = content.match(/<!--\s*NOTES_START\s*-->[\s\S]*?<!--\s*NOTES_END\s*-->/);
if (notesMatch) notesBlock = notesMatch[0];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Only first NOTES block is silently preserved

.match() returns the first regex match, so if a second <!-- NOTES_START --> ... <!-- NOTES_END --> block is ever added (e.g. to annotate a different anomalous release), it would be silently dropped the next time the script runs. This mirrors a real data-loss scenario: the PR itself was created specifically because a block was silently dropped once before. Consider using .matchAll() and concatenating all matched blocks, or at least documenting the single-block constraint as a comment near this code.

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d1623f6 — switched to matchAll() and join all matched blocks with blank lines, so any number of NOTES_START/NOTES_END blocks survive regeneration. Added a regression test covering two distinct NOTES blocks (both delimiter pairs preserved, both body texts present after roundtrip).

}

// Add new entry — dev entries are rolling, releases replace dev
Expand Down Expand Up @@ -155,6 +160,8 @@ if (r.nativeBatchMs != null && r.jsFallbackMs > 0) {
}
md += '\n';

if (notesBlock) md += `${notesBlock}\n\n`;

md += `<!-- INCREMENTAL_BENCHMARK_DATA\n${JSON.stringify(history, null, 2)}\n-->\n`;

fs.mkdirSync(path.dirname(reportPath), { recursive: true });
Expand Down
105 changes: 105 additions & 0 deletions tests/unit/update-incremental-report.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, '..', '..');
const scriptPath = path.join(repoRoot, 'scripts', 'update-incremental-report.ts');

// --experimental-strip-types works in Node 22.6+ through current 24.x; the
// renamed --strip-types was added then removed again across 24.x minor
// versions, so prefer the experimental name for compatibility.
const stripFlag = '--experimental-strip-types';

const SAMPLE_ENTRY = {
version: '9.9.9',
date: '2026-05-14',
files: 100,
wasm: { fullBuildMs: 1000, noopRebuildMs: 10, oneFileRebuildMs: 50 },
native: { fullBuildMs: 500, noopRebuildMs: 5, oneFileRebuildMs: 25 },
resolve: {
imports: 200,
nativeBatchMs: 2,
jsFallbackMs: 4,
perImportNativeMs: 0,
perImportJsMs: 0,
},
};

let tmpDir: string;
let reportPath: string;
let entryPath: string;

beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-incr-report-'));
reportPath = path.join(tmpDir, 'INCREMENTAL-BENCHMARKS.md');
entryPath = path.join(tmpDir, 'entry.json');
fs.writeFileSync(entryPath, JSON.stringify(SAMPLE_ENTRY));
});

afterEach(() => {
if (tmpDir) fs.rmSync(tmpDir, { recursive: true, force: true });
});

function runScript() {
execFileSync('node', [stripFlag, scriptPath, entryPath], {
env: { ...process.env, CODEGRAPH_INCREMENTAL_REPORT_PATH: reportPath },
stdio: 'pipe',
});
}

describe('update-incremental-report script', () => {
it('preserves a manual NOTES_START/NOTES_END block across regeneration', () => {
const NOTES = `<!-- NOTES_START -->
**Note (9.9.8):** Workers hung past the 10-minute timeout and were SIGKILL'd.
<!-- NOTES_END -->`;
const initial = `# Codegraph Incremental Build Benchmarks

${NOTES}

<!-- INCREMENTAL_BENCHMARK_DATA
[
{
"version": "9.9.8",
"date": "2026-05-01",
"files": 99,
"wasm": null,
"native": null,
"resolve": { "imports": 100, "nativeBatchMs": 1, "jsFallbackMs": 2, "perImportNativeMs": 0, "perImportJsMs": 0 }
}
]
-->
`;
fs.writeFileSync(reportPath, initial);

runScript();

const out = fs.readFileSync(reportPath, 'utf8');
expect(out).toContain('<!-- NOTES_START -->');
expect(out).toContain('<!-- NOTES_END -->');
expect(out).toContain("Workers hung past the 10-minute timeout and were SIGKILL'd");
// Notes should appear before the data comment, after the latest summary
expect(out.indexOf('<!-- NOTES_START -->')).toBeLessThan(
out.indexOf('<!-- INCREMENTAL_BENCHMARK_DATA'),
);
});

it('does not invent a NOTES block when none was present', () => {
const initial = `# Codegraph Incremental Build Benchmarks

<!-- INCREMENTAL_BENCHMARK_DATA
[]
-->
`;
fs.writeFileSync(reportPath, initial);

runScript();

const out = fs.readFileSync(reportPath, 'utf8');
expect(out).not.toContain('NOTES_START');
expect(out).not.toContain('NOTES_END');
});
});
Loading