diff --git a/src/resources/extensions/gsd/auto-start.ts b/src/resources/extensions/gsd/auto-start.ts index 64571710e..48521820f 100644 --- a/src/resources/extensions/gsd/auto-start.ts +++ b/src/resources/extensions/gsd/auto-start.ts @@ -624,7 +624,7 @@ export async function bootstrapAutoSession( "starting", s.currentMilestoneId ?? "unknown", ); - writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown", 0); + writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown"); // Secrets collection gate const mid = state.activeMilestone!.id; diff --git a/src/resources/extensions/gsd/auto.ts b/src/resources/extensions/gsd/auto.ts index b701aaa05..062715bbd 100644 --- a/src/resources/extensions/gsd/auto.ts +++ b/src/resources/extensions/gsd/auto.ts @@ -322,7 +322,6 @@ export function getAutoDashboardData(): AutoDashboardData { ? (s.autoStartTime > 0 ? Date.now() - s.autoStartTime : 0) : 0, currentUnit: s.currentUnit ? { ...s.currentUnit } : null, - completedUnits: [], basePath: s.basePath, totalCost: totals?.cost ?? 0, totalTokens: totals?.tokens.total ?? 0, @@ -1169,7 +1168,6 @@ export async function startAuto( lockBase(), "resuming", s.currentMilestoneId ?? "unknown", - 0, ); logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress"); @@ -1391,7 +1389,6 @@ export async function dispatchHookUnit( lockBase(), hookUnitType, triggerUnitId, - 0, sessionFile, ); diff --git a/src/resources/extensions/gsd/auto/phases.ts b/src/resources/extensions/gsd/auto/phases.ts index e02861c65..0f408105f 100644 --- a/src/resources/extensions/gsd/auto/phases.ts +++ b/src/resources/extensions/gsd/auto/phases.ts @@ -29,6 +29,10 @@ import { MergeConflictError } from "../git-service.js"; import { join } from "node:path"; import { existsSync, cpSync } from "node:fs"; import { logWarning, logError } from "../workflow-logger.js"; +import { gsdRoot } from "../paths.js"; +import { atomicWriteSync } from "../atomic-write.js"; +import { verifyExpectedArtifact } from "../auto-recovery.js"; +import { writeUnitRuntimeRecord } from "../unit-runtime.js"; // ─── generateMilestoneReport ────────────────────────────────────────────────── @@ -275,11 +279,7 @@ export async function runPreDispatch( .map((m: { id: string }) => m.id); deps.pruneQueueOrder(s.basePath, pendingIds); - // Reset completed-units tracking for the new milestone — stale entries - // from the previous milestone cause the dispatch loop to skip units - // that haven't actually been completed in the new milestone's context. // Archive the old completed-units.json instead of wiping it (#2313). - s.completedUnits = []; try { const completedKeysPath = join(gsdRoot(s.basePath), "completed-units.json"); if (existsSync(completedKeysPath) && s.currentMilestoneId) { @@ -538,7 +538,7 @@ export async function runDispatch( if (loopState.stuckRecoveryAttempts === 0) { // Level 1: try verifying the artifact, then cache invalidation + retry loopState.stuckRecoveryAttempts++; - const artifactExists = deps.verifyExpectedArtifact( + const artifactExists = verifyExpectedArtifact( unitType, unitId, s.basePath, @@ -847,7 +847,7 @@ export async function runUnitPhase( const unitStartSeq = ic.nextSeq(); deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } }); deps.captureAvailableSkills(); - deps.writeUnitRuntimeRecord( + writeUnitRuntimeRecord( s.basePath, unitType, unitId, @@ -1116,7 +1116,7 @@ export async function runUnitPhase( const skipArtifactVerification = unitType.startsWith("hook/") || unitType === "custom-step"; const artifactVerified = skipArtifactVerification || - deps.verifyExpectedArtifact(unitType, unitId, s.basePath); + verifyExpectedArtifact(unitType, unitId, s.basePath); if (artifactVerified) { s.unitDispatchCount.delete(`${unitType}/${unitId}`); s.unitRecoveryCount.delete(`${unitType}/${unitId}`); diff --git a/src/resources/extensions/gsd/commands/context.ts b/src/resources/extensions/gsd/commands/context.ts index 07f237592..7bbaa5790 100644 --- a/src/resources/extensions/gsd/commands/context.ts +++ b/src/resources/extensions/gsd/commands/context.ts @@ -47,15 +47,10 @@ export async function guardRemoteSession( return false; } - const unitsMsg = remote.completedUnits != null - ? `${remote.completedUnits} units completed` - : ""; - const choice = await showNextAction(ctx, { title: `Auto-mode is running in another terminal (PID ${remote.pid})`, summary: [ `Currently executing: ${unitLabel}`, - ...(unitsMsg ? [unitsMsg] : []), ...(remote.startedAt ? [`Started: ${remote.startedAt}`] : []), ], actions: [ diff --git a/src/resources/extensions/gsd/commands/handlers/parallel.ts b/src/resources/extensions/gsd/commands/handlers/parallel.ts index a2acb5367..6b2d630ff 100644 --- a/src/resources/extensions/gsd/commands/handlers/parallel.ts +++ b/src/resources/extensions/gsd/commands/handlers/parallel.ts @@ -63,7 +63,7 @@ export async function handleParallelCommand(trimmed: string, _ctx: ExtensionComm } const lines = ["# Parallel Workers\n"]; for (const worker of workers) { - lines.push(`- **${worker.milestoneId}** (${worker.title}) — ${worker.state} — ${worker.completedUnits} units — $${worker.cost.toFixed(2)}`); + lines.push(`- **${worker.milestoneId}** (${worker.title}) — ${worker.state} — $${worker.cost.toFixed(2)}`); } const state = getOrchestratorState(); if (state) { diff --git a/src/resources/extensions/gsd/dashboard-overlay.ts b/src/resources/extensions/gsd/dashboard-overlay.ts index ed0e69a51..cf5d59db9 100644 --- a/src/resources/extensions/gsd/dashboard-overlay.ts +++ b/src/resources/extensions/gsd/dashboard-overlay.ts @@ -99,18 +99,11 @@ export class GSDDashboardOverlay { const currentUnit = dashData.currentUnit ? `${dashData.currentUnit.type}:${dashData.currentUnit.id}:${dashData.currentUnit.startedAt}` : "-"; - const lastCompleted = dashData.completedUnits.length > 0 - ? dashData.completedUnits[dashData.completedUnits.length - 1] - : null; - const completedKey = lastCompleted - ? `${dashData.completedUnits.length}:${lastCompleted.type}:${lastCompleted.id}:${lastCompleted.finishedAt}` - : "0"; return [ base, dashData.active ? "1" : "0", dashData.paused ? "1" : "0", currentUnit, - completedKey, ].join("|"); } @@ -458,49 +451,6 @@ export class GSDDashboardOverlay { lines.push(centered(th.fg("dim", "No active milestone."))); } - if (this.dashData.completedUnits.length > 0) { - lines.push(blank()); - lines.push(hr()); - lines.push(row(th.fg("text", th.bold("Completed")))); - lines.push(blank()); - - // Build ledger lookup for budget indicators (last entry wins for retries) - const ledgerLookup = new Map(); - const currentLedger = getLedger(); - if (currentLedger) { - for (const lu of currentLedger.units) { - ledgerLookup.set(`${lu.type}:${lu.id}`, lu); - } - } - - const recent = [...this.dashData.completedUnits].reverse().slice(0, 10); - for (const u of recent) { - // Budget indicators from ledger — use warning glyph for pressured units - const ledgerEntry = ledgerLookup.get(`${u.type}:${u.id}`); - const hadPressure = ledgerEntry?.continueHereFired === true; - const hadTruncation = (ledgerEntry?.truncationSections ?? 0) > 0; - const unitGlyph = hadPressure - ? th.fg(STATUS_COLOR.warning, STATUS_GLYPH.warning) - : th.fg(STATUS_COLOR.done, STATUS_GLYPH.done); - const left = ` ${unitGlyph} ${th.fg("muted", unitLabel(u.type))} ${th.fg("muted", u.id)}`; - - let budgetMarkers = ""; - if (hadTruncation) { - budgetMarkers += th.fg("warning", ` ▼${ledgerEntry!.truncationSections}`); - } - if (hadPressure) { - budgetMarkers += th.fg("error", " → wrap-up"); - } - - const right = th.fg("dim", formatDuration(u.finishedAt - u.startedAt)); - lines.push(row(joinColumns(`${left}${budgetMarkers}`, right, contentWidth))); - } - - if (this.dashData.completedUnits.length > 10) { - lines.push(row(th.fg("dim", ` ...and ${this.dashData.completedUnits.length - 10} more`))); - } - } - const ledger = getLedger(); if (ledger && ledger.units.length > 0) { const totals = getProjectTotals(ledger.units); diff --git a/src/resources/extensions/gsd/guided-flow.ts b/src/resources/extensions/gsd/guided-flow.ts index a0479b68d..c5e757052 100644 --- a/src/resources/extensions/gsd/guided-flow.ts +++ b/src/resources/extensions/gsd/guided-flow.ts @@ -910,8 +910,7 @@ export async function showSmartEntry( // when the user exits during init wizard or discuss phase before any // real auto-mode work begins. const isBootstrapCrash = crashLock.unitType === "starting" - && crashLock.unitId === "bootstrap" - && crashLock.completedUnits === 0; + && crashLock.unitId === "bootstrap"; if (!isBootstrapCrash) { const resume = await showNextAction(ctx, { diff --git a/src/resources/extensions/gsd/parallel-merge.ts b/src/resources/extensions/gsd/parallel-merge.ts index 835920a1f..74b526fdd 100644 --- a/src/resources/extensions/gsd/parallel-merge.ts +++ b/src/resources/extensions/gsd/parallel-merge.ts @@ -37,7 +37,7 @@ export function determineMergeOrder( workers: WorkerInfo[], order: MergeOrder = "sequential", ): string[] { - const completed = workers.filter(w => w.state === "stopped" && w.completedUnits > 0); + const completed = workers.filter(w => w.state === "stopped"); if (order === "by-completion") { return completed .sort((a, b) => a.startedAt - b.startedAt) // earliest first diff --git a/src/resources/extensions/gsd/workflow-manifest.ts b/src/resources/extensions/gsd/workflow-manifest.ts index 76db80a45..d88dda8e9 100644 --- a/src/resources/extensions/gsd/workflow-manifest.ts +++ b/src/resources/extensions/gsd/workflow-manifest.ts @@ -128,6 +128,7 @@ export function snapshotState(): StateManifest { inputs: JSON.parse((r["inputs"] as string) || "[]"), expected_output: JSON.parse((r["expected_output"] as string) || "[]"), observability_impact: (r["observability_impact"] as string) ?? "", + full_plan_md: (r["full_plan_md"] as string) ?? "", sequence: (r["sequence"] as number) ?? 0, }));