From 28517f7b6d2e2216209f17aae06b54e1de209389 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Tue, 7 Apr 2026 14:24:58 -0700 Subject: [PATCH] perf(roadmap): hoist readdirSync out of phase loop in analyze command (#1899) cmdRoadmapAnalyze called fs.readdirSync(phasesDir) inside the per-phase while loop, causing O(N^2) directory reads for N phases. At 50 phases this produced 100 redundant syscalls; at 100 phases, 200. Move the directory listing before the loop and build a lookup array that is reused for each phase match. This reduces the pattern from O(N^2) to O(N) directory reads. Co-authored-by: Claude Opus 4.6 --- get-shit-done/bin/lib/roadmap.cjs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/get-shit-done/bin/lib/roadmap.cjs b/get-shit-done/bin/lib/roadmap.cjs index f6b3d1c28..638f24897 100644 --- a/get-shit-done/bin/lib/roadmap.cjs +++ b/get-shit-done/bin/lib/roadmap.cjs @@ -129,6 +129,15 @@ function cmdRoadmapAnalyze(cwd, raw) { const phases = []; let match; + // Build phase directory lookup once (O(1) readdir instead of O(N) per phase) + const _phaseDirNames = (() => { + try { + return fs.readdirSync(phasesDir, { withFileTypes: true }) + .filter(e => e.isDirectory()) + .map(e => e.name); + } catch { return []; } + })(); + while ((match = phasePattern.exec(content)) !== null) { const phaseNum = match[1]; const phaseName = match[2].replace(/\(INSERTED\)/i, '').trim(); @@ -155,9 +164,7 @@ function cmdRoadmapAnalyze(cwd, raw) { let hasResearch = false; try { - const entries = fs.readdirSync(phasesDir, { withFileTypes: true }); - const dirs = entries.filter(e => e.isDirectory()).map(e => e.name); - const dirMatch = dirs.find(d => phaseTokenMatches(d, normalized)); + const dirMatch = _phaseDirNames.find(d => phaseTokenMatches(d, normalized)); if (dirMatch) { const phaseFiles = fs.readdirSync(path.join(phasesDir, dirMatch));