From 616b387f12ee6c86823be07b946458d4b0862a6e Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 27 May 2026 20:22:06 -0400 Subject: [PATCH] perf(#320): hoist By-Phase state table regex to module scope (#403) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Static regex literal `byPhaseTablePattern` was recompiled on every call to `updatePerformanceMetricsSection`; hoisted to module scope (compiled once; stateless /i used with .match → safe to share across calls). `phaseRowPattern` uses dynamic interpolation and stays in-function. `Fixes #320`. Co-authored-by: Claude Opus 4.7 (1M context) --- .changeset/320-hoist-state-table-regex.md | 5 +++ get-shit-done/bin/lib/state.cjs | 4 +- tests/state.test.cjs | 54 +++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 .changeset/320-hoist-state-table-regex.md diff --git a/.changeset/320-hoist-state-table-regex.md b/.changeset/320-hoist-state-table-regex.md new file mode 100644 index 000000000..935ede5fb --- /dev/null +++ b/.changeset/320-hoist-state-table-regex.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 320 +--- +Hoist static `byPhaseTablePattern` regex in `updatePerformanceMetricsSection` to module scope so it compiles once instead of on every call. (#320) diff --git a/get-shit-done/bin/lib/state.cjs b/get-shit-done/bin/lib/state.cjs index 9f6eafcf1..37b41b7eb 100644 --- a/get-shit-done/bin/lib/state.cjs +++ b/get-shit-done/bin/lib/state.cjs @@ -38,6 +38,9 @@ process.on('exit', () => { } }); +// Hoisted to module scope — compiled once, not per call (#320). Stateless (/i, used with .match). +const byPhaseTablePattern = /(\|\s*Phase\s*\|\s*Plans\s*\|\s*Total\s*\|\s*Avg\/Plan\s*\|[ \t]*\n\|(?:[- :\t]+\|)+[ \t]*\n)((?:[ \t]*\|[^\n]*\n)*)(?=\n|$)/i; + function cmdStateLoad(cwd, raw) { const config = loadConfig(cwd); const planDir = planningPaths(cwd).planning; @@ -1286,7 +1289,6 @@ function updatePerformanceMetricsSection(content, cwd, phaseNum, planCount, summ ); // Update By Phase table — upsert row for this phase - const byPhaseTablePattern = /(\|\s*Phase\s*\|\s*Plans\s*\|\s*Total\s*\|\s*Avg\/Plan\s*\|[ \t]*\n\|(?:[- :\t]+\|)+[ \t]*\n)((?:[ \t]*\|[^\n]*\n)*)(?=\n|$)/i; const byPhaseMatch = content.match(byPhaseTablePattern); if (byPhaseMatch) { let tableBody = byPhaseMatch[2].trim(); diff --git a/tests/state.test.cjs b/tests/state.test.cjs index 98aab11b8..d551ecadf 100644 --- a/tests/state.test.cjs +++ b/tests/state.test.cjs @@ -2025,6 +2025,60 @@ describe('updatePerformanceMetricsSection', () => { const phase5Rows = (afterSecond.match(/\|\s*5\s*\|/g) || []).length; assert.ok(phase5Rows <= 1, 'Phase 5 should appear at most once in By Phase table (no duplicates)'); }); + + test('byPhaseTablePattern behavior-lock (#320): By Phase table header preserved and phase row upserted after hoist to module scope', () => { + // Exercises the byPhaseTablePattern match path directly: header must be preserved, + // an existing phase row must be replaced (not duplicated), and a new phase row inserted. + const content = `# Project State + +**Current Phase:** 06 +**Status:** Executing Phase 6 + +## Performance Metrics + +**Velocity:** +- Total plans completed: 1 +- Average duration: 5 min +- Total execution time: 0.1 hours + +**By Phase:** + +| Phase | Plans | Total | Avg/Plan | +|-------|-------|-------|----------| +| 6 | 1 | 5 min | 5 min | + +## Accumulated Context +`; + const statePath = path.join(tmpDir, '.planning', 'STATE.md'); + fs.writeFileSync(statePath, content); + + const phaseDir = path.join(tmpDir, '.planning', 'phases', '06-lock'); + fs.mkdirSync(phaseDir, { recursive: true }); + fs.writeFileSync(path.join(phaseDir, '06-01-PLAN.md'), '# Plan\n'); + fs.writeFileSync(path.join(phaseDir, '06-02-PLAN.md'), '# Plan 2\n'); + fs.writeFileSync(path.join(phaseDir, '06-01-SUMMARY.md'), '# Summary\n'); + fs.writeFileSync(path.join(phaseDir, '06-02-SUMMARY.md'), '# Summary 2\n'); + + fs.writeFileSync( + path.join(tmpDir, '.planning', 'ROADMAP.md'), + `# Roadmap\n\n## Phase 6: Lock\n\n- [ ] Phase 6: Lock\n` + ); + + const result = runGsdTools('phase complete 6', tmpDir); + assert.ok(result.success, `phase complete failed: ${result.error}`); + + const stateAfter = fs.readFileSync(statePath, 'utf-8'); + + // Header must be preserved + assert.ok(stateAfter.includes('| Phase | Plans | Total | Avg/Plan |'), 'By Phase table header must be preserved'); + + // Phase 6 row must appear exactly once (upserted, not duplicated) + const phase6Rows = (stateAfter.match(/\|\s*6\s*\|/g) || []).length; + assert.strictEqual(phase6Rows, 1, 'Phase 6 row must appear exactly once in By Phase table (upsert, not append)'); + + // Total plans count updated correctly (1 pre-existing + 2 new summaries) + assert.ok(stateAfter.match(/Total plans completed:\s*3/), 'Total plans completed should be 3 after upsert'); + }); }); // ─────────────────────────────────────────────────────────────────────────────