perf(#320): hoist By-Phase state table regex to module scope (#403)

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) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-27 20:22:06 -04:00
committed by GitHub
parent b058c5861f
commit 616b387f12
3 changed files with 62 additions and 1 deletions

View File

@@ -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)

View File

@@ -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();

View File

@@ -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');
});
});
// ─────────────────────────────────────────────────────────────────────────────