From 61cf3b556b61a7ea51741a1fcdb765bb3a19272d Mon Sep 17 00:00:00 2001 From: Lex Christopherson Date: Wed, 25 Mar 2026 22:43:05 -0600 Subject: [PATCH] refactor: deduplicate artifact path functions into single module Remove duplicate resolveExpectedArtifactPath() and diagnoseExpectedArtifact() from auto-recovery.ts, making auto-artifact-paths.ts the single source of truth. auto-recovery.ts re-exports both functions for backward compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/auto-recovery.ts | 127 +----------------- .../extensions/gsd/tests/replan-slice.test.ts | 3 +- .../gsd/tests/validate-milestone.test.ts | 3 +- 3 files changed, 11 insertions(+), 122 deletions(-) diff --git a/src/resources/extensions/gsd/auto-recovery.ts b/src/resources/extensions/gsd/auto-recovery.ts index a03b5887a..b489138d7 100644 --- a/src/resources/extensions/gsd/auto-recovery.ts +++ b/src/resources/extensions/gsd/auto-recovery.ts @@ -24,18 +24,13 @@ import { nativeResetHard, } from "./native-git-bridge.js"; import { - resolveMilestonePath, resolveSlicePath, resolveSliceFile, resolveTasksDir, resolveTaskFiles, relMilestoneFile, relSliceFile, - relSlicePath, - relTaskFile, - buildMilestoneFileName, buildSliceFileName, - buildTaskFileName, resolveMilestoneFile, clearPathCache, resolveGsdRootFile, @@ -49,82 +44,16 @@ import { } from "node:fs"; import { execFileSync } from "node:child_process"; import { dirname, join } from "node:path"; +import { + resolveExpectedArtifactPath, + diagnoseExpectedArtifact, +} from "./auto-artifact-paths.js"; + +// Re-export so existing consumers of auto-recovery.ts keep working. +export { resolveExpectedArtifactPath, diagnoseExpectedArtifact }; // ─── Artifact Resolution & Verification ─────────────────────────────────────── -/** - * Resolve the expected artifact for a unit to an absolute path. - */ -export function resolveExpectedArtifactPath( - unitType: string, - unitId: string, - base: string, -): string | null { - const parts = unitId.split("/"); - const mid = parts[0]!; - const sid = parts[1]; - switch (unitType) { - case "discuss-milestone": { - const dir = resolveMilestonePath(base, mid); - return dir ? join(dir, buildMilestoneFileName(mid, "CONTEXT")) : null; - } - case "research-milestone": { - const dir = resolveMilestonePath(base, mid); - return dir ? join(dir, buildMilestoneFileName(mid, "RESEARCH")) : null; - } - case "plan-milestone": { - const dir = resolveMilestonePath(base, mid); - return dir ? join(dir, buildMilestoneFileName(mid, "ROADMAP")) : null; - } - case "research-slice": { - const dir = resolveSlicePath(base, mid, sid!); - return dir ? join(dir, buildSliceFileName(sid!, "RESEARCH")) : null; - } - case "plan-slice": { - const dir = resolveSlicePath(base, mid, sid!); - return dir ? join(dir, buildSliceFileName(sid!, "PLAN")) : null; - } - case "reassess-roadmap": { - const dir = resolveSlicePath(base, mid, sid!); - return dir ? join(dir, buildSliceFileName(sid!, "ASSESSMENT")) : null; - } - case "run-uat": { - const dir = resolveSlicePath(base, mid, sid!); - return dir ? join(dir, buildSliceFileName(sid!, "UAT")) : null; - } - case "execute-task": { - const tid = parts[2]; - const dir = resolveSlicePath(base, mid, sid!); - return dir && tid - ? join(dir, "tasks", buildTaskFileName(tid, "SUMMARY")) - : null; - } - case "complete-slice": { - const dir = resolveSlicePath(base, mid, sid!); - return dir ? join(dir, buildSliceFileName(sid!, "SUMMARY")) : null; - } - case "validate-milestone": { - const dir = resolveMilestonePath(base, mid); - return dir ? join(dir, buildMilestoneFileName(mid, "VALIDATION")) : null; - } - case "complete-milestone": { - const dir = resolveMilestonePath(base, mid); - return dir ? join(dir, buildMilestoneFileName(mid, "SUMMARY")) : null; - } - case "replan-slice": { - const dir = resolveSlicePath(base, mid, sid!); - return dir ? join(dir, buildSliceFileName(sid!, "REPLAN")) : null; - } - case "rewrite-docs": - return null; - case "reactive-execute": - // Reactive execute produces multiple task summaries — verified separately - return null; - default: - return null; - } -} - /** * Check whether a milestone produced implementation artifacts (non-`.gsd/` files) * in the git history. Uses `git log --name-only` to inspect all commits on the @@ -471,48 +400,6 @@ export function writeBlockerPlaceholder( return diagnoseExpectedArtifact(unitType, unitId, base); } -export function diagnoseExpectedArtifact( - unitType: string, - unitId: string, - base: string, -): string | null { - const parts = unitId.split("/"); - const mid = parts[0]; - const sid = parts[1]; - switch (unitType) { - case "discuss-milestone": - return `${relMilestoneFile(base, mid!, "CONTEXT")} (milestone context from discussion)`; - case "research-milestone": - return `${relMilestoneFile(base, mid!, "RESEARCH")} (milestone research)`; - case "plan-milestone": - return `${relMilestoneFile(base, mid!, "ROADMAP")} (milestone roadmap)`; - case "research-slice": - return `${relSliceFile(base, mid!, sid!, "RESEARCH")} (slice research)`; - case "plan-slice": - return `${relSliceFile(base, mid!, sid!, "PLAN")} (slice plan)`; - case "execute-task": { - const tid = parts[2]; - return `Task ${tid} marked [x] in ${relSliceFile(base, mid!, sid!, "PLAN")} + summary written`; - } - case "complete-slice": - return `Slice ${sid} marked [x] in ${relMilestoneFile(base, mid!, "ROADMAP")} + summary + UAT written`; - case "replan-slice": - return `${relSliceFile(base, mid!, sid!, "REPLAN")} + updated ${relSliceFile(base, mid!, sid!, "PLAN")}`; - case "rewrite-docs": - return "Active overrides resolved in .gsd/OVERRIDES.md + plan documents updated"; - case "reassess-roadmap": - return `${relSliceFile(base, mid!, sid!, "ASSESSMENT")} (roadmap reassessment)`; - case "run-uat": - return `${relSliceFile(base, mid!, sid!, "UAT")} (UAT result)`; - case "validate-milestone": - return `${relMilestoneFile(base, mid!, "VALIDATION")} (milestone validation report)`; - case "complete-milestone": - return `${relMilestoneFile(base, mid!, "SUMMARY")} (milestone summary)`; - default: - return null; - } -} - // ─── Merge State Reconciliation ─────────────────────────────────────────────── /** diff --git a/src/resources/extensions/gsd/tests/replan-slice.test.ts b/src/resources/extensions/gsd/tests/replan-slice.test.ts index 35c89eaba..f7804dd8d 100644 --- a/src/resources/extensions/gsd/tests/replan-slice.test.ts +++ b/src/resources/extensions/gsd/tests/replan-slice.test.ts @@ -503,7 +503,8 @@ console.log('\n=== doctor: no blocker → no blocker_discovered_no_replan issue // Artifact Resolution: resolveExpectedArtifactPath for replan-slice (#858) // ═══════════════════════════════════════════════════════════════════════════ -import { resolveExpectedArtifactPath, verifyExpectedArtifact } from '../auto-recovery.ts'; +import { resolveExpectedArtifactPath } from '../auto-artifact-paths.ts'; +import { verifyExpectedArtifact } from '../auto-recovery.ts'; describe('replan-slice', () => { diff --git a/src/resources/extensions/gsd/tests/validate-milestone.test.ts b/src/resources/extensions/gsd/tests/validate-milestone.test.ts index 47372c1ea..79fe1c35f 100644 --- a/src/resources/extensions/gsd/tests/validate-milestone.test.ts +++ b/src/resources/extensions/gsd/tests/validate-milestone.test.ts @@ -6,7 +6,8 @@ import { tmpdir } from "node:os"; import { randomUUID } from "node:crypto"; import { deriveState, isValidationTerminal } from "../state.ts"; -import { resolveExpectedArtifactPath, verifyExpectedArtifact, diagnoseExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.ts"; +import { resolveExpectedArtifactPath, diagnoseExpectedArtifact } from "../auto-artifact-paths.ts"; +import { verifyExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.ts"; import { resolveDispatch, type DispatchContext } from "../auto-dispatch.ts"; import type { GSDState } from "../types.ts"; import { clearPathCache } from "../paths.ts";