Created
June 16, 2026 12:47
-
-
Save magyargergo/9e65ebd4e8975538d46191259dd95254 to your computer and use it in GitHub Desktop.
GitNexus issue #2225 repro script — wiki __wiki__ idle eviction
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env node | |
| /** | |
| * Reproduction harness for GitHub issue #2225: | |
| * Error: LadybugDB not initialized for repo "__wiki__". Call initLbug first. | |
| * | |
| * Usage (from repo root): | |
| * node gitnexus/scripts/repro-wiki-2225.mjs [scenario] | |
| * | |
| * Scenarios: | |
| * baseline — query without init (expected failure, confirms error text) | |
| * no-init-mock — WikiGenerator with initWikiDb mocked out (simulates broken init) | |
| * pool-fill — fill LRU pool then run wiki init+query | |
| * branch-slot — meta at flat path but lbug only under branches/<slug>/ | |
| * cli-chinese — full `gitnexus wiki --lang chinese` with stub LLM | |
| * all — run every scenario | |
| */ | |
| import fs from 'fs/promises'; | |
| import path from 'path'; | |
| import os from 'os'; | |
| import { execSync, spawnSync } from 'child_process'; | |
| import { fileURLToPath } from 'url'; | |
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); | |
| const ROOT = path.resolve(__dirname, '../..'); | |
| const GITNEXUS = path.join(ROOT, 'gitnexus'); | |
| const DIST = path.join(GITNEXUS, 'dist'); | |
| const WORKSPACE_LBUG = path.join(ROOT, '.gitnexus/lbug'); | |
| const TARGET_ERR = 'LadybugDB not initialized for repo "__wiki__"'; | |
| async function importDist(subpath) { | |
| return import(path.join(DIST, subpath)); | |
| } | |
| function header(name) { | |
| console.log(`\n${'='.repeat(60)}\n SCENARIO: ${name}\n${'='.repeat(60)}`); | |
| } | |
| function result(name, reproduced, detail) { | |
| const tag = reproduced ? 'REPRODUCED' : 'NOT REPRODUCED'; | |
| console.log(`\n[${tag}] ${name}`); | |
| if (detail) console.log(` ${detail}`); | |
| return reproduced; | |
| } | |
| async function scenarioBaseline() { | |
| header('baseline — executeQuery without initWikiDb'); | |
| const { executeQuery } = await importDist('core/lbug/pool-adapter.js'); | |
| try { | |
| await executeQuery('__wiki__', 'RETURN 1'); | |
| return result('baseline', false, 'Query succeeded unexpectedly'); | |
| } catch (e) { | |
| const msg = e.message || String(e); | |
| const hit = msg.includes(TARGET_ERR); | |
| return result('baseline', hit, hit ? msg : `Different error: ${msg}`); | |
| } | |
| } | |
| async function scenarioNoInitMock() { | |
| header('no-init-mock — graph query without initWikiDb (same as baseline)'); | |
| const { getFilesWithExports } = await importDist('core/wiki/graph-queries.js'); | |
| try { | |
| await getFilesWithExports(); | |
| return result('no-init-mock', false, 'Query succeeded unexpectedly'); | |
| } catch (e) { | |
| const msg = e.message || String(e); | |
| const hit = msg.includes(TARGET_ERR); | |
| return result('no-init-mock', hit, msg); | |
| } | |
| } | |
| async function scenarioIdleEviction() { | |
| header('idle-eviction — vitest fake timers (see wiki-2225-idle-eviction.test.ts)'); | |
| const run = spawnSync( | |
| 'npx', | |
| ['vitest', 'run', 'test/unit/wiki-2225-idle-eviction.test.ts'], | |
| { cwd: GITNEXUS, encoding: 'utf-8', timeout: 120_000 }, | |
| ); | |
| const out = `${run.stdout}\n${run.stderr}`; | |
| const hit = run.status === 0 && out.includes('1 passed'); | |
| console.log(out.slice(-1500)); | |
| return result( | |
| 'idle-eviction', | |
| hit, | |
| hit ? 'Vitest repro passed — idle eviction triggers exact error' : `exit=${run.status}`, | |
| ); | |
| } | |
| async function scenarioPoolFill() { | |
| header('pool-fill — LRU pool at capacity during wiki init'); | |
| const pool = await importDist('core/lbug/pool-adapter.js'); | |
| const { initWikiDb, getFilesWithExports, closeWikiDb } = await importDist( | |
| 'core/wiki/graph-queries.js', | |
| ); | |
| const opened = []; | |
| try { | |
| for (let i = 0; i < 5; i++) { | |
| const id = `repro-fill-${i}`; | |
| await pool.initLbug(id, WORKSPACE_LBUG); | |
| opened.push(id); | |
| } | |
| await initWikiDb(WORKSPACE_LBUG); | |
| const rows = await getFilesWithExports(); | |
| await closeWikiDb(); | |
| return result('pool-fill', false, `Query OK after pool fill (${rows.length} files)`); | |
| } catch (e) { | |
| const msg = e.message || String(e); | |
| const hit = msg.includes(TARGET_ERR); | |
| return result('pool-fill', hit, msg); | |
| } finally { | |
| for (const id of opened) { | |
| try { | |
| await pool.closeLbug(id); | |
| } catch {} | |
| } | |
| try { | |
| await closeWikiDb(); | |
| } catch {} | |
| } | |
| } | |
| async function scenarioBranchSlot() { | |
| header('branch-slot — flat meta.json but lbug only under branches/<slug>/'); | |
| const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'wiki2225-branch-')); | |
| const storage = path.join(tmp, '.gitnexus'); | |
| const branchName = 'feature/test'; | |
| const { branchSlug } = await importDist('storage/branch-index.js'); | |
| const slug = branchSlug(branchName); | |
| const branchDir = path.join(storage, 'branches', slug); | |
| await fs.mkdir(branchDir, { recursive: true }); | |
| await fs.copyFile(WORKSPACE_LBUG, path.join(branchDir, 'lbug')); | |
| await fs.writeFile( | |
| path.join(storage, 'meta.json'), | |
| JSON.stringify( | |
| { | |
| repoPath: tmp, | |
| lastCommit: 'abc', | |
| indexedAt: new Date().toISOString(), | |
| branch: 'main', | |
| }, | |
| null, | |
| 2, | |
| ), | |
| ); | |
| const { initWikiDb, getFilesWithExports, closeWikiDb } = await importDist( | |
| 'core/wiki/graph-queries.js', | |
| ); | |
| const flatLbug = path.join(storage, 'lbug'); | |
| try { | |
| await initWikiDb(flatLbug); | |
| await getFilesWithExports(); | |
| return result('branch-slot', false, 'Unexpected success with missing flat lbug'); | |
| } catch (e) { | |
| const msg = e.message || String(e); | |
| if (msg.includes(TARGET_ERR)) { | |
| return result('branch-slot', true, msg); | |
| } | |
| return result('branch-slot', false, `Different error (expected): ${msg}`); | |
| } finally { | |
| try { | |
| await closeWikiDb(); | |
| } catch {} | |
| } | |
| } | |
| async function scenarioCliChinese() { | |
| header('cli-chinese — gitnexus wiki --lang chinese (needs LLM; often fails on auth/network)'); | |
| const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'wiki2225-cli-')); | |
| spawnSync('git', ['init'], { cwd: tmp, stdio: 'ignore' }); | |
| spawnSync('git', ['config', 'user.email', 'repro@test.local'], { cwd: tmp, stdio: 'ignore' }); | |
| spawnSync('git', ['config', 'user.name', 'repro'], { cwd: tmp, stdio: 'ignore' }); | |
| await fs.writeFile(path.join(tmp, 'README.md'), '# repro\n'); | |
| spawnSync('git', ['add', '.'], { cwd: tmp, stdio: 'ignore' }); | |
| spawnSync('git', ['commit', '-m', 'init'], { cwd: tmp, stdio: 'ignore' }); | |
| const commit = execSync('git rev-parse HEAD', { cwd: tmp }).toString().trim(); | |
| const storage = path.join(tmp, '.gitnexus'); | |
| await fs.mkdir(storage, { recursive: true }); | |
| await fs.copyFile(WORKSPACE_LBUG, path.join(storage, 'lbug')); | |
| await fs.writeFile( | |
| path.join(storage, 'meta.json'), | |
| JSON.stringify( | |
| { | |
| repoPath: tmp, | |
| lastCommit: commit, | |
| indexedAt: new Date().toISOString(), | |
| }, | |
| null, | |
| 2, | |
| ), | |
| ); | |
| const cli = path.join(DIST, 'cli/index.js'); | |
| const env = { | |
| ...process.env, | |
| GITNEXUS_VERBOSE: '1', | |
| OPENAI_API_KEY: 'sk-test-repro', | |
| }; | |
| const run = spawnSync( | |
| 'node', | |
| [ | |
| cli, | |
| 'wiki', | |
| tmp, | |
| '--lang', | |
| 'chinese', | |
| '--provider', | |
| 'openai', | |
| '--model', | |
| 'test', | |
| '--base-url', | |
| 'http://127.0.0.1:65503/v1', | |
| '--api-key', | |
| 'sk-test', | |
| '--force', | |
| '--review', | |
| ], | |
| { cwd: ROOT, env, encoding: 'utf-8', timeout: 30_000, input: 'n\n' }, | |
| ); | |
| const out = `${run.stdout}\n${run.stderr}`; | |
| console.log(out.slice(-2000)); | |
| const hit = out.includes(TARGET_ERR); | |
| return result('cli-chinese', hit, hit ? 'CLI emitted target error' : `exit=${run.status}`); | |
| } | |
| const scenarios = { | |
| baseline: scenarioBaseline, | |
| 'no-init-mock': scenarioNoInitMock, | |
| 'idle-eviction': scenarioIdleEviction, | |
| 'pool-fill': scenarioPoolFill, | |
| 'branch-slot': scenarioBranchSlot, | |
| 'cli-chinese': scenarioCliChinese, | |
| }; | |
| const chosen = process.argv[2] || 'all'; | |
| const toRun = | |
| chosen === 'all' ? Object.entries(scenarios) : [[chosen, scenarios[chosen]]].filter(([, fn]) => fn); | |
| if (toRun.length === 0) { | |
| console.error(`Unknown scenario: ${chosen}`); | |
| console.error(`Available: ${Object.keys(scenarios).join(', ')}, all`); | |
| process.exit(2); | |
| } | |
| console.log(`GitNexus issue #2225 reproduction harness`); | |
| console.log(`Target error substring: ${TARGET_ERR}`); | |
| console.log(`Using lbug: ${WORKSPACE_LBUG}`); | |
| const summary = []; | |
| for (const [name, fn] of toRun) { | |
| try { | |
| summary.push({ name, reproduced: await fn() }); | |
| } catch (e) { | |
| console.error(`Scenario ${name} crashed:`, e); | |
| summary.push({ name, reproduced: false, crash: e.message }); | |
| } | |
| } | |
| console.log(`\n${'='.repeat(60)}\n SUMMARY\n${'='.repeat(60)}`); | |
| for (const row of summary) { | |
| console.log(` ${row.reproduced ? 'YES' : 'no '} — ${row.name}${row.crash ? ` (crashed: ${row.crash})` : ''}`); | |
| } | |
| process.exit(summary.some((r) => r.reproduced) ? 0 : 1); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment