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) <noreply@anthropic.com>
This commit is contained in:
Lex Christopherson 2026-03-25 22:43:05 -06:00
parent ef5006e16d
commit 61cf3b556b
3 changed files with 11 additions and 122 deletions

View file

@ -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 ───────────────────────────────────────────────
/**

View file

@ -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', () => {

View file

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