From fb7b484d1056af09483652a31f5d32be533cdecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Fri, 20 Mar 2026 10:47:49 -0600 Subject: [PATCH] fix: filter cross-milestone errors from health tracker escalation (#1621) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs fixed: 1. recordHealthSnapshot counted ALL doctor issues including cross-milestone stale errors, inflating consecutiveErrorUnits past the escalation threshold from unfixable errors in other milestones. Now filters report.issues to only the current milestone before summarizing for health tracking. 2. matchesScope used unitId.startsWith(scope) without a delimiter, so scope "M004/S01" would false-match "M004/S010". Removed the redundant delimiter-less startsWith branch — exact match and slash-delimited startsWith are sufficient. Closes #1579 Co-authored-by: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/auto-post-unit.ts | 13 ++++++++++--- src/resources/extensions/gsd/doctor.ts | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/resources/extensions/gsd/auto-post-unit.ts b/src/resources/extensions/gsd/auto-post-unit.ts index a7c1ee682..ca6760011 100644 --- a/src/resources/extensions/gsd/auto-post-unit.ts +++ b/src/resources/extensions/gsd/auto-post-unit.ts @@ -172,13 +172,20 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info"); } - // Proactive health tracking - const summary = summarizeDoctorIssues(report.issues); + // Proactive health tracking — filter to current milestone to avoid + // cross-milestone stale errors inflating the escalation counter + const currentMilestoneId = s.currentUnit.id.split("/")[0]; + const milestoneIssues = currentMilestoneId + ? report.issues.filter(i => + i.unitId === currentMilestoneId || + i.unitId.startsWith(`${currentMilestoneId}/`)) + : report.issues; + const summary = summarizeDoctorIssues(milestoneIssues); recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length); // Check if we should escalate to LLM-assisted heal if (summary.errors > 0) { - const unresolvedErrors = report.issues + const unresolvedErrors = milestoneIssues .filter(i => i.severity === "error" && !i.fixable) .map(i => ({ code: i.code, message: i.message, unitId: i.unitId })); const escalation = checkHealEscalation(summary.errors, unresolvedErrors); diff --git a/src/resources/extensions/gsd/doctor.ts b/src/resources/extensions/gsd/doctor.ts index 475e1f92e..88112f79a 100644 --- a/src/resources/extensions/gsd/doctor.ts +++ b/src/resources/extensions/gsd/doctor.ts @@ -297,7 +297,7 @@ async function markSliceUndoneInRoadmap(basePath: string, milestoneId: string, s function matchesScope(unitId: string, scope?: string): boolean { if (!scope) return true; - return unitId === scope || unitId.startsWith(`${scope}/`) || unitId.startsWith(`${scope}`); + return unitId === scope || unitId.startsWith(`${scope}/`); } function auditRequirements(content: string | null): DoctorIssue[] {