refactor: adopt parseUnitId utility across all auto-* modules

Replace manual unitId.split("/") calls with the canonical parseUnitId()
utility from unit-id.ts. This eliminates ad-hoc array indexing patterns
(parts[0]!, parts[1], parts[2]) in favor of named destructuring
({ milestone, slice, task }), improving readability and consistency.

Files modified:
- auto-post-unit.ts (5 occurrences)
- auto-artifact-paths.ts (2 occurrences)
- auto-dashboard.ts (1 occurrence)
- auto-verification.ts (1 occurrence)
- auto-start.ts (1 occurrence)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lex Christopherson 2026-03-25 23:07:55 -06:00
parent ef5006e16d
commit 15030ce97d
5 changed files with 30 additions and 43 deletions

View file

@ -13,6 +13,7 @@ import {
buildSliceFileName,
buildTaskFileName,
} from "./paths.js";
import { parseUnitId } from "./unit-id.js";
import { join } from "node:path";
/**
@ -23,9 +24,7 @@ export function resolveExpectedArtifactPath(
unitId: string,
base: string,
): string | null {
const parts = unitId.split("/");
const mid = parts[0]!;
const sid = parts[1];
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
switch (unitType) {
case "discuss-milestone": {
const dir = resolveMilestonePath(base, mid);
@ -56,7 +55,6 @@ export function resolveExpectedArtifactPath(
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"))
@ -93,38 +91,35 @@ export function diagnoseExpectedArtifact(
unitId: string,
base: string,
): string | null {
const parts = unitId.split("/");
const mid = parts[0];
const sid = parts[1];
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
switch (unitType) {
case "discuss-milestone":
return `${relMilestoneFile(base, mid!, "CONTEXT")} (milestone context from discussion)`;
return `${relMilestoneFile(base, mid, "CONTEXT")} (milestone context from discussion)`;
case "research-milestone":
return `${relMilestoneFile(base, mid!, "RESEARCH")} (milestone research)`;
return `${relMilestoneFile(base, mid, "RESEARCH")} (milestone research)`;
case "plan-milestone":
return `${relMilestoneFile(base, mid!, "ROADMAP")} (milestone roadmap)`;
return `${relMilestoneFile(base, mid, "ROADMAP")} (milestone roadmap)`;
case "research-slice":
return `${relSliceFile(base, mid!, sid!, "RESEARCH")} (slice research)`;
return `${relSliceFile(base, mid, sid!, "RESEARCH")} (slice research)`;
case "plan-slice":
return `${relSliceFile(base, mid!, sid!, "PLAN")} (slice plan)`;
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`;
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`;
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")}`;
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)`;
return `${relSliceFile(base, mid, sid!, "ASSESSMENT")} (roadmap reassessment)`;
case "run-uat":
return `${relSliceFile(base, mid!, sid!, "UAT")} (UAT result)`;
return `${relSliceFile(base, mid, sid!, "UAT")} (UAT result)`;
case "validate-milestone":
return `${relMilestoneFile(base, mid!, "VALIDATION")} (milestone validation report)`;
return `${relMilestoneFile(base, mid, "VALIDATION")} (milestone validation report)`;
case "complete-milestone":
return `${relMilestoneFile(base, mid!, "SUMMARY")} (milestone summary)`;
return `${relMilestoneFile(base, mid, "SUMMARY")} (milestone summary)`;
default:
return null;
}

View file

@ -25,6 +25,7 @@ import { computeProgressScore } from "./progress-score.js";
import { getActiveWorktreeName } from "./worktree-command.js";
import { loadEffectiveGSDPreferences, getGlobalGSDPreferencesPath } from "./preferences.js";
import { resolveServiceTierIcon, getEffectiveServiceTier } from "./service-tier.js";
import { parseUnitId } from "./unit-id.js";
// ─── UAT Slice Extraction ─────────────────────────────────────────────────────
@ -33,8 +34,8 @@ import { resolveServiceTierIcon, getEffectiveServiceTier } from "./service-tier.
* Returns null if the format doesn't match.
*/
export function extractUatSliceId(unitId: string): string | null {
const parts = unitId.split("/");
if (parts.length >= 2 && parts[1]!.startsWith("S")) return parts[1]!;
const { slice } = parseUnitId(unitId);
if (slice?.startsWith("S")) return slice;
return null;
}

View file

@ -23,6 +23,7 @@ import {
buildTaskFileName,
} from "./paths.js";
import { invalidateAllCaches } from "./cache.js";
import { parseUnitId } from "./unit-id.js";
import { closeoutUnit, type CloseoutOptions } from "./auto-unit-closeout.js";
import {
autoCommitCurrentBranch,
@ -91,11 +92,10 @@ export function detectRogueFileWrites(
): RogueFileWrite[] {
if (!isDbAvailable()) return [];
const parts = unitId.split("/");
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
const rogues: RogueFileWrite[] = [];
if (unitType === "execute-task") {
const [mid, sid, tid] = parts;
if (!mid || !sid || !tid) return [];
const summaryPath = resolveTaskFile(basePath, mid, sid, tid, "SUMMARY");
@ -106,7 +106,6 @@ export function detectRogueFileWrites(
rogues.push({ path: summaryPath, unitType, unitId });
}
} else if (unitType === "complete-slice") {
const [mid, sid] = parts;
if (!mid || !sid) return [];
const summaryPath = resolveSliceFile(basePath, mid, sid, "SUMMARY");
@ -117,7 +116,6 @@ export function detectRogueFileWrites(
rogues.push({ path: summaryPath, unitType, unitId });
}
} else if (unitType === "plan-milestone") {
const [mid] = parts;
if (!mid) return [];
const roadmapPath = resolveMilestoneFile(basePath, mid, "ROADMAP");
@ -135,7 +133,6 @@ export function detectRogueFileWrites(
rogues.push({ path: roadmapPath, unitType, unitId });
}
} else if (unitType === "plan-slice" || unitType === "replan-slice") {
const [mid, sid] = parts;
if (!mid || !sid) return [];
const planPath = resolveSliceFile(basePath, mid, sid, "PLAN");
@ -159,7 +156,6 @@ export function detectRogueFileWrites(
rogues.push({ path: replanPath, unitType, unitId });
}
} else if (unitType === "reassess-roadmap") {
const [mid, sid] = parts;
if (!mid || !sid) return [];
const assessPath = resolveSliceFile(basePath, mid, sid, "ASSESSMENT");
@ -176,7 +172,6 @@ export function detectRogueFileWrites(
}
}
} else if (unitType === "plan-task") {
const [mid, sid, tid] = parts;
if (!mid || !sid || !tid) return [];
const taskPlanPath = resolveTaskFile(basePath, mid, sid, tid, "PLAN");
@ -249,8 +244,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
let taskContext: TaskCommitContext | undefined;
if (s.currentUnit.type === "execute-task") {
const parts = s.currentUnit.id.split("/");
const [mid, sid, tid] = parts;
const { milestone: mid, slice: sid, task: tid } = parseUnitId(s.currentUnit.id);
if (mid && sid && tid) {
const summaryPath = resolveTaskFile(s.basePath, mid, sid, tid, "SUMMARY");
if (summaryPath) {
@ -354,8 +348,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
// Reactive state cleanup on slice completion
if (s.currentUnit.type === "complete-slice") {
try {
const parts = s.currentUnit.id.split("/");
const [mid, sid] = parts;
const { milestone: mid, slice: sid } = parseUnitId(s.currentUnit.id);
if (mid && sid) {
const { clearReactiveState } = await import("./reactive-graph.js");
clearReactiveState(s.basePath, mid, sid);
@ -440,8 +433,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
// from DB data before giving up (e.g. research-slice produces PLAN from engine).
if (!triggerArtifactVerified) {
try {
const parts = s.currentUnit.id.split("/");
const [mid, sid] = parts;
const { milestone: mid, slice: sid } = parseUnitId(s.currentUnit.id);
if (mid && sid) {
const regenerated = regenerateIfMissing(s.basePath, mid, sid, "PLAN");
if (regenerated) {
@ -541,8 +533,7 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
// ── State reset: undo the completion so deriveState re-derives the unit ──
try {
const parts = trigger.unitId.split("/");
const [mid, sid, tid] = parts;
const { milestone: mid, slice: sid, task: tid } = parseUnitId(trigger.unitId);
// 1. Reset task status in DB and re-render plan checkboxes
if (mid && sid && tid) {

View file

@ -66,6 +66,7 @@ import {
isDebugEnabled,
getDebugLogPath,
} from "./debug-logger.js";
import { parseUnitId } from "./unit-id.js";
import type { AutoSession } from "./auto/session.js";
import {
existsSync,
@ -200,7 +201,7 @@ export async function bootstrapAutoSession(
);
return releaseLockAndReturn();
}
const recoveredMid = crashLock.unitId.split("/")[0];
const recoveredMid = parseUnitId(crashLock.unitId).milestone;
const milestoneAlreadyComplete = recoveredMid
? !!resolveMilestoneFile(base, recoveredMid, "SUMMARY")
: false;

View file

@ -12,6 +12,7 @@
import type { ExtensionContext, ExtensionAPI } from "@gsd/pi-coding-agent";
import { resolveSliceFile, resolveSlicePath } from "./paths.js";
import { parseUnitId } from "./unit-id.js";
import { isDbAvailable, getTask } from "./gsd-db.js";
import { loadEffectiveGSDPreferences } from "./preferences.js";
import {
@ -60,10 +61,9 @@ export async function runPostUnitVerification(
const prefs = effectivePrefs?.preferences;
// Read task plan verify field
const parts = s.currentUnit.id.split("/");
const { milestone: mid, slice: sid, task: tid } = parseUnitId(s.currentUnit.id);
let taskPlanVerify: string | undefined;
if (parts.length >= 3) {
const [mid, sid, tid] = parts;
if (mid && sid && tid) {
if (isDbAvailable()) {
taskPlanVerify = getTask(mid, sid, tid)?.verify;
}
@ -141,9 +141,8 @@ export async function runPostUnitVerification(
// Write verification evidence JSON
const attempt = s.verificationRetryCount.get(s.currentUnit.id) ?? 0;
if (parts.length >= 3) {
if (mid && sid && tid) {
try {
const [mid, sid, tid] = parts;
const sDir = resolveSlicePath(s.basePath, mid, sid);
if (sDir) {
const tasksDir = join(sDir, "tasks");