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
This commit is contained in:
Tom Boucher 2026-03-18 11:10:43 -04:00 committed by GitHub
parent 8b70fc03f6
commit 22f2f452b9
3 changed files with 23 additions and 9 deletions

View file

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

View file

@ -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<DoctorIssueCode>([
"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;

View file

@ -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<DoctorIssueCode>([
"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;
};