perf(health): merge four readdirSync passes into one in cmdValidateHealth (#1973)
cmdValidateHealth read the phases directory four separate times for checks 6 (naming), 7 (orphaned plans), 7b (validation artifacts), and 8 (roadmap cross-reference). Hoist the directory listing into a single readdirSync call with a shared Map of per-phase file lists. Reduces syscalls from ~3N+1 to N+1 where N is the number of phase directories. Closes #1973 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -655,52 +655,55 @@ function cmdValidateHealth(cwd, options, raw) {
|
|||||||
} catch { /* intentionally empty */ }
|
} catch { /* intentionally empty */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Check 6: Phase directory naming (NN-name format) ─────────────────────
|
// ─── Read phase directories once for checks 6, 7, 7b, and 8 (#1973) ──────
|
||||||
|
let phaseDirEntries = [];
|
||||||
|
const phaseDirFiles = new Map(); // phase dir name → file list
|
||||||
try {
|
try {
|
||||||
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
phaseDirEntries = fs.readdirSync(phasesDir, { withFileTypes: true }).filter(e => e.isDirectory());
|
||||||
for (const e of entries) {
|
for (const e of phaseDirEntries) {
|
||||||
if (e.isDirectory() && !e.name.match(/^\d{2}(?:\.\d+)*-[\w-]+$/)) {
|
try {
|
||||||
addIssue('warning', 'W005', `Phase directory "${e.name}" doesn't follow NN-name format`, 'Rename to match pattern (e.g., 01-setup)');
|
phaseDirFiles.set(e.name, fs.readdirSync(path.join(phasesDir, e.name)));
|
||||||
}
|
} catch { phaseDirFiles.set(e.name, []); }
|
||||||
}
|
}
|
||||||
} catch { /* intentionally empty */ }
|
} catch { /* intentionally empty */ }
|
||||||
|
|
||||||
|
// ─── Check 6: Phase directory naming (NN-name format) ─────────────────────
|
||||||
|
for (const e of phaseDirEntries) {
|
||||||
|
if (!e.name.match(/^\d{2}(?:\.\d+)*-[\w-]+$/)) {
|
||||||
|
addIssue('warning', 'W005', `Phase directory "${e.name}" doesn't follow NN-name format`, 'Rename to match pattern (e.g., 01-setup)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Check 7: Orphaned plans (PLAN without SUMMARY) ───────────────────────
|
// ─── Check 7: Orphaned plans (PLAN without SUMMARY) ───────────────────────
|
||||||
try {
|
for (const e of phaseDirEntries) {
|
||||||
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
const phaseFiles = phaseDirFiles.get(e.name) || [];
|
||||||
for (const e of entries) {
|
const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
|
||||||
if (!e.isDirectory()) continue;
|
const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
||||||
const phaseFiles = fs.readdirSync(path.join(phasesDir, e.name));
|
const summaryBases = new Set(summaries.map(s => s.replace('-SUMMARY.md', '').replace('SUMMARY.md', '')));
|
||||||
const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
|
|
||||||
const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
|
||||||
const summaryBases = new Set(summaries.map(s => s.replace('-SUMMARY.md', '').replace('SUMMARY.md', '')));
|
|
||||||
|
|
||||||
for (const plan of plans) {
|
for (const plan of plans) {
|
||||||
const planBase = plan.replace('-PLAN.md', '').replace('PLAN.md', '');
|
const planBase = plan.replace('-PLAN.md', '').replace('PLAN.md', '');
|
||||||
if (!summaryBases.has(planBase)) {
|
if (!summaryBases.has(planBase)) {
|
||||||
addIssue('info', 'I001', `${e.name}/${plan} has no SUMMARY.md`, 'May be in progress');
|
addIssue('info', 'I001', `${e.name}/${plan} has no SUMMARY.md`, 'May be in progress');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch { /* intentionally empty */ }
|
}
|
||||||
|
|
||||||
// ─── Check 7b: Nyquist VALIDATION.md consistency ────────────────────────
|
// ─── Check 7b: Nyquist VALIDATION.md consistency ────────────────────────
|
||||||
try {
|
for (const e of phaseDirEntries) {
|
||||||
const phaseEntries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
const phaseFiles = phaseDirFiles.get(e.name) || [];
|
||||||
for (const e of phaseEntries) {
|
const hasResearch = phaseFiles.some(f => f.endsWith('-RESEARCH.md'));
|
||||||
if (!e.isDirectory()) continue;
|
const hasValidation = phaseFiles.some(f => f.endsWith('-VALIDATION.md'));
|
||||||
const phaseFiles = fs.readdirSync(path.join(phasesDir, e.name));
|
if (hasResearch && !hasValidation) {
|
||||||
const hasResearch = phaseFiles.some(f => f.endsWith('-RESEARCH.md'));
|
const researchFile = phaseFiles.find(f => f.endsWith('-RESEARCH.md'));
|
||||||
const hasValidation = phaseFiles.some(f => f.endsWith('-VALIDATION.md'));
|
try {
|
||||||
if (hasResearch && !hasValidation) {
|
|
||||||
const researchFile = phaseFiles.find(f => f.endsWith('-RESEARCH.md'));
|
|
||||||
const researchContent = fs.readFileSync(path.join(phasesDir, e.name, researchFile), 'utf-8');
|
const researchContent = fs.readFileSync(path.join(phasesDir, e.name, researchFile), 'utf-8');
|
||||||
if (researchContent.includes('## Validation Architecture')) {
|
if (researchContent.includes('## Validation Architecture')) {
|
||||||
addIssue('warning', 'W009', `Phase ${e.name}: has Validation Architecture in RESEARCH.md but no VALIDATION.md`, 'Re-run /gsd-plan-phase with --research to regenerate');
|
addIssue('warning', 'W009', `Phase ${e.name}: has Validation Architecture in RESEARCH.md but no VALIDATION.md`, 'Re-run /gsd-plan-phase with --research to regenerate');
|
||||||
}
|
}
|
||||||
}
|
} catch { /* intentionally empty */ }
|
||||||
}
|
}
|
||||||
} catch { /* intentionally empty */ }
|
}
|
||||||
|
|
||||||
// ─── Check 7c: Agent installation (#1371) ──────────────────────────────────
|
// ─── Check 7c: Agent installation (#1371) ──────────────────────────────────
|
||||||
// Verify GSD agents are installed. Missing agents cause Task(subagent_type=...)
|
// Verify GSD agents are installed. Missing agents cause Task(subagent_type=...)
|
||||||
@@ -733,15 +736,10 @@ function cmdValidateHealth(cwd, options, raw) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const diskPhases = new Set();
|
const diskPhases = new Set();
|
||||||
try {
|
for (const e of phaseDirEntries) {
|
||||||
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
const dm = e.name.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
|
||||||
for (const e of entries) {
|
if (dm) diskPhases.add(dm[1]);
|
||||||
if (e.isDirectory()) {
|
}
|
||||||
const dm = e.name.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
|
|
||||||
if (dm) diskPhases.add(dm[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch { /* intentionally empty */ }
|
|
||||||
|
|
||||||
// Build a set of phases explicitly marked not-yet-started in the ROADMAP
|
// Build a set of phases explicitly marked not-yet-started in the ROADMAP
|
||||||
// summary list (- [ ] **Phase N:**). These phases are intentionally absent
|
// summary list (- [ ] **Phase N:**). These phases are intentionally absent
|
||||||
|
|||||||
Reference in New Issue
Block a user