From 22f2f452b99ef3557b4d78e6bf742b6486d18277 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 18 Mar 2026 11:10:43 -0400 Subject: [PATCH] fix: exclude completion-transition errors from health escalation at task level (#1157) When the last task in a slice completes, the doctor detects expected completion-transition issues (missing slice summary, unchecked roadmap) that will be resolved by the upcoming complete-slice dispatch. These were being counted as real errors in the proactive health tracker, inflating consecutiveErrorUnits and potentially triggering misleading heal escalation or verification-failure warnings. Changes: - Export COMPLETION_TRANSITION_CODES from doctor-types.ts (was local to doctor.ts) - doctor.ts uses the shared constant instead of its local copy - auto-post-unit.ts filters out completion-transition codes from the error count and health snapshot when fixLevel is 'task' Existing doctor-fixlevel tests confirm the doctor still detects and reports (but does not fix) these issues at task level. Fixes #1155 --- src/resources/extensions/gsd/auto-post-unit.ts | 11 ++++++++--- src/resources/extensions/gsd/doctor-types.ts | 13 +++++++++++++ src/resources/extensions/gsd/doctor.ts | 8 ++------ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/resources/extensions/gsd/auto-post-unit.ts b/src/resources/extensions/gsd/auto-post-unit.ts index 12444750e..ab6e5e9a6 100644 --- a/src/resources/extensions/gsd/auto-post-unit.ts +++ b/src/resources/extensions/gsd/auto-post-unit.ts @@ -35,6 +35,7 @@ import { import { writeUnitRuntimeRecord, clearUnitRuntimeRecord } from "./unit-runtime.js"; import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences } from "./preferences.js"; import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js"; +import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js"; import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js"; import { syncStateToProjectRoot } from "./auto-worktree-sync.js"; import { resetRewriteCircuitBreaker } from "./auto-dispatch.js"; @@ -154,13 +155,17 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info"); } - // Proactive health tracking - const summary = summarizeDoctorIssues(report.issues); + // Proactive health tracking — exclude completion-transition codes at task level + // since they are expected after the last task and resolved by complete-slice + const issuesForHealth = effectiveFixLevel === "task" + ? report.issues.filter(i => !COMPLETION_TRANSITION_CODES.has(i.code)) + : report.issues; + const summary = summarizeDoctorIssues(issuesForHealth); 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 = issuesForHealth .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-types.ts b/src/resources/extensions/gsd/doctor-types.ts index 1e76fc266..ae580c553 100644 --- a/src/resources/extensions/gsd/doctor-types.ts +++ b/src/resources/extensions/gsd/doctor-types.ts @@ -32,6 +32,19 @@ export type DoctorIssueCode = | "gitignore_missing_patterns" | "unresolvable_dependency"; +/** + * Issue codes that represent expected completion-transition states. + * These are detected by the doctor but should NOT be auto-fixed at task level — + * they are resolved by the complete-slice/complete-milestone dispatch units. + * Consumers (e.g. auto-post-unit health tracking) should exclude these from + * error counts when running at task fixLevel to avoid false escalation. + */ +export const COMPLETION_TRANSITION_CODES = new Set([ + "all_tasks_done_missing_slice_summary", + "all_tasks_done_missing_slice_uat", + "all_tasks_done_roadmap_not_checked", +]); + export interface DoctorIssue { severity: DoctorSeverity; code: DoctorIssueCode; diff --git a/src/resources/extensions/gsd/doctor.ts b/src/resources/extensions/gsd/doctor.ts index c83c6e51e..9fd0e0055 100644 --- a/src/resources/extensions/gsd/doctor.ts +++ b/src/resources/extensions/gsd/doctor.ts @@ -8,6 +8,7 @@ import { invalidateAllCaches } from "./cache.js"; import { loadEffectiveGSDPreferences, type GSDPreferences } from "./preferences.js"; import type { DoctorIssue, DoctorIssueCode } from "./doctor-types.js"; +import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js"; import { checkGitHealth, checkRuntimeHealth } from "./doctor-checks.js"; // ── Re-exports ───────────────────────────────────────────────────────────── @@ -356,16 +357,11 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean; // dispatch lifecycle (complete-slice, complete-milestone units), not to // mechanical post-hook bookkeeping. When fixLevel is "task", these are // detected and reported but never auto-fixed. - const completionTransitionCodes = new Set([ - "all_tasks_done_missing_slice_summary", - "all_tasks_done_missing_slice_uat", - "all_tasks_done_roadmap_not_checked", - ]); /** Whether a given issue code should be auto-fixed at the current fixLevel. */ const shouldFix = (code: DoctorIssueCode): boolean => { if (!fix) return false; - if (fixLevel === "task" && completionTransitionCodes.has(code)) return false; + if (fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code)) return false; return true; };