diff --git a/src/resources/extensions/gsd/auto-dashboard.ts b/src/resources/extensions/gsd/auto-dashboard.ts index b385fa051..b4ddbc78a 100644 --- a/src/resources/extensions/gsd/auto-dashboard.ts +++ b/src/resources/extensions/gsd/auto-dashboard.ts @@ -285,8 +285,9 @@ export function updateSliceProgressCache(base: string, mid: string, activeSid?: taskDetails = dbTasks.map(t => ({ id: t.id, title: t.title, done: t.status === "complete" || t.status === "done" })); } } - } catch { + } catch (err) { // Non-fatal — just omit task count + process.stderr.write(`gsd [auto-dashboard]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -297,8 +298,9 @@ export function updateSliceProgressCache(base: string, mid: string, activeSid?: activeSliceTasks, taskDetails, }; - } catch { + } catch (err) { // Non-fatal — widget just won't show progress bar + process.stderr.write(`gsd [auto-dashboard]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -332,8 +334,9 @@ function refreshLastCommit(basePath: string): void { }; } lastCommitFetchedAt = Date.now(); - } catch { + } catch (err) { // Non-fatal — just skip last commit display + process.stderr.write(`gsd [auto-dashboard]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -376,7 +379,9 @@ function ensureWidgetModeLoaded(): void { if (saved && WIDGET_MODES.includes(saved as WidgetMode)) { widgetMode = saved as WidgetMode; } - } catch { /* non-fatal — use default */ } + } catch (err) { /* non-fatal — use default */ + process.stderr.write(`gsd [auto-dashboard]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); + } } /** Persist widget mode to global preferences YAML. */ @@ -395,7 +400,9 @@ function persistWidgetMode(mode: WidgetMode): void { content = content.trimEnd() + "\n" + line + "\n"; } writeFileSync(prefsPath, content, "utf-8"); - } catch { /* non-fatal — mode still set in memory */ } + } catch (err) { /* non-fatal — mode still set in memory */ + process.stderr.write(`gsd [auto-dashboard]: file write failed: ${err instanceof Error ? err.message : String(err)}\n`); + } } /** Cycle to the next widget mode. Returns the new mode. */ @@ -458,7 +465,9 @@ export function updateProgressWidget( // Cache git branch at widget creation time (not per render) let cachedBranch: string | null = null; - try { cachedBranch = getCurrentBranch(accessors.getBasePath()); } catch { /* not in git repo */ } + try { cachedBranch = getCurrentBranch(accessors.getBasePath()); } catch (err) { /* not in git repo */ + process.stderr.write(`gsd [auto-dashboard]: git branch detection failed: ${err instanceof Error ? err.message : String(err)}\n`); + } // Cache short pwd (last 2 path segments only) + worktree/branch info let widgetPwd: string; @@ -519,7 +528,9 @@ export function updateProgressWidget( } refreshRtkLabel(); cachedLines = undefined; - } catch { /* non-fatal */ } + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-dashboard]: DB status update failed: ${err instanceof Error ? err.message : String(err)}\n`); + } }, 15_000); return { @@ -878,3 +889,4 @@ function padToWidth(s: string, colWidth: number): string { if (vis >= colWidth) return truncateToWidth(s, colWidth, "…"); return s + " ".repeat(colWidth - vis); } + diff --git a/src/resources/extensions/gsd/auto-dispatch.ts b/src/resources/extensions/gsd/auto-dispatch.ts index 839ba5fb1..925a50bdb 100644 --- a/src/resources/extensions/gsd/auto-dispatch.ts +++ b/src/resources/extensions/gsd/auto-dispatch.ts @@ -712,7 +712,9 @@ export const DISPATCH_RULES: DispatchRule[] = [ } } } - } catch { /* fall through — don't block on DB errors */ } + } catch (err) { /* fall through — don't block on DB errors */ + process.stderr.write(`gsd [auto-dispatch]: lock cleanup failed: ${err instanceof Error ? err.message : String(err)}\n`); + } return { action: "dispatch", @@ -754,8 +756,9 @@ export async function resolveDispatch( try { const registry = getRegistry(); return await registry.evaluateDispatch(ctx); - } catch { + } catch (err) { // Registry not initialized — fall back to inline loop + process.stderr.write(`gsd [auto-dispatch]: dispatch failed: ${err instanceof Error ? err.message : String(err)}\n`); } for (const rule of DISPATCH_RULES) { @@ -779,3 +782,4 @@ export async function resolveDispatch( export function getDispatchRuleNames(): string[] { return DISPATCH_RULES.map((r) => r.name); } + diff --git a/src/resources/extensions/gsd/auto-post-unit.ts b/src/resources/extensions/gsd/auto-post-unit.ts index b3c0ce946..17cb19e37 100644 --- a/src/resources/extensions/gsd/auto-post-unit.ts +++ b/src/resources/extensions/gsd/auto-post-unit.ts @@ -279,8 +279,9 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV try { const { getTaskIssueNumberForCommit } = await import("../github-sync/sync.js"); ghIssueNumber = getTaskIssueNumberForCommit(s.basePath, mid, sid, tid) ?? undefined; - } catch { + } catch (err) { // GitHub sync not available — skip + process.stderr.write(`gsd [auto-post-unit]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } taskContext = { @@ -732,3 +733,4 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<" return "continue"; } + diff --git a/src/resources/extensions/gsd/auto-prompts.ts b/src/resources/extensions/gsd/auto-prompts.ts index 33000a526..cf3fd5682 100644 --- a/src/resources/extensions/gsd/auto-prompts.ts +++ b/src/resources/extensions/gsd/auto-prompts.ts @@ -198,7 +198,9 @@ export async function inlineDependencySummaries( } // If slice not found in DB, fall through to file-based parsing } - } catch { /* fall through */ } + } catch (err) { /* fall through */ + process.stderr.write(`gsd [auto-prompts]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); + } // If DB didn't provide depends, fall back to roadmap parsing if (!depends) { @@ -276,8 +278,9 @@ export async function inlineDecisionsFromDb( return `### Decisions\nSource: \`.gsd/DECISIONS.md\`\n\n${formatted}`; } } - } catch { + } catch (err) { // DB not available — fall through to filesystem + process.stderr.write(`gsd [auto-prompts]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } return inlineGsdRootFile(base, "decisions.md", "Decisions"); } @@ -303,8 +306,9 @@ export async function inlineRequirementsFromDb( return `### Requirements\nSource: \`.gsd/REQUIREMENTS.md\`\n\n${formatted}`; } } - } catch { + } catch (err) { // DB not available — fall through to filesystem + process.stderr.write(`gsd [auto-prompts]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } return inlineGsdRootFile(base, "requirements.md", "Requirements"); } @@ -325,8 +329,9 @@ export async function inlineProjectFromDb( return `### Project\nSource: \`.gsd/PROJECT.md\`\n\n${content}`; } } - } catch { + } catch (err) { // DB not available — fall through to filesystem + process.stderr.write(`gsd [auto-prompts]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } return inlineGsdRootFile(base, "project.md", "Project"); } @@ -486,8 +491,9 @@ export function buildSkillActivationBlock(params: { for (const skillName of taskPlan.frontmatter.skills_used) { matched.add(normalizeSkillReference(skillName)); } - } catch { + } catch (err) { // Non-fatal — malformed task plan should not break prompt construction + process.stderr.write(`gsd [auto-prompts]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -736,7 +742,9 @@ export async function checkNeedsReassessment( return { sliceId: lastCompleted }; } } - } catch { /* fall through */ } + } catch (err) { /* fall through */ + process.stderr.write(`gsd [auto-prompts]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); + } // File-based fallback using roadmap checkboxes const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP"); @@ -802,7 +810,9 @@ export async function checkNeedsRunUat( return { sliceId: sid, uatType }; } } - } catch { /* fall through */ } + } catch (err) { /* fall through */ + process.stderr.write(`gsd [auto-prompts]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); + } // File-based fallback using roadmap checkboxes if (!prefs?.uat_dispatch) return null; @@ -1312,7 +1322,9 @@ export async function buildCompleteMilestonePrompt( if (isDbAvailable()) { sliceIds = getMilestoneSlices(mid).map(s => s.id); } - } catch { /* fall through */ } + } catch (err) { /* fall through */ + process.stderr.write(`gsd [auto-prompts]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); + } // File-based fallback: parse roadmap for slice IDs when DB has no data if (sliceIds.length === 0 && roadmapPath) { const roadmapContent = await loadFile(roadmapPath); @@ -1393,7 +1405,9 @@ export async function buildValidateMilestonePrompt( } } } - } catch { /* fall through */ } + } catch (err) { /* fall through */ + process.stderr.write(`gsd [auto-prompts]: git push failed: ${err instanceof Error ? err.message : String(err)}\n`); + } // Inline all slice summaries and UAT results let valSliceIds: string[] = []; @@ -1402,7 +1416,9 @@ export async function buildValidateMilestonePrompt( if (isDbAvailable()) { valSliceIds = getMilestoneSlices(mid).map(s => s.id); } - } catch { /* fall through */ } + } catch (err) { /* fall through */ + process.stderr.write(`gsd [auto-prompts]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); + } // File-based fallback: parse roadmap for slice IDs when DB has no data if (valSliceIds.length === 0 && roadmapPath) { const roadmapContent = await loadFile(roadmapPath); @@ -1541,8 +1557,9 @@ export async function buildReplanSlicePrompt( `- **${c.id}**: "${c.text}" — ${c.rationale ?? "no rationale"}` ).join("\n"); } - } catch { + } catch (err) { // Non-fatal — captures module may not be available + process.stderr.write(`gsd [auto-prompts]: capture count failed: ${err instanceof Error ? err.message : String(err)}\n`); } return loadPrompt("replan-slice", { @@ -1642,8 +1659,9 @@ export async function buildReassessRoadmapPrompt( `- **${c.id}**: "${c.text}" — ${c.rationale ?? "deferred during triage"}` ).join("\n"); } - } catch { + } catch (err) { // Non-fatal — captures module may not be available + process.stderr.write(`gsd [auto-prompts]: capture count failed: ${err instanceof Error ? err.message : String(err)}\n`); } const reassessCommitInstruction = "Do not commit — .gsd/ planning docs are managed externally and not tracked in git."; @@ -1859,7 +1877,9 @@ export async function buildRewriteDocsPrompt( .filter(t => t.status !== "complete" && t.status !== "done") .map(t => ({ id: t.id })); } - } catch { /* fall through */ } + } catch (err) { /* fall through */ + process.stderr.write(`gsd [auto-prompts]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); + } if (!incompleteTasks) { // DB unavailable — no task data to inline @@ -1911,3 +1931,4 @@ export async function buildRewriteDocsPrompt( overridesPath: relGsdRootFile("OVERRIDES"), }); } + diff --git a/src/resources/extensions/gsd/auto-recovery.ts b/src/resources/extensions/gsd/auto-recovery.ts index 691deef1d..5877d3674 100644 --- a/src/resources/extensions/gsd/auto-recovery.ts +++ b/src/resources/extensions/gsd/auto-recovery.ts @@ -109,8 +109,9 @@ function detectMainBranch(basePath: string): string { encoding: "utf-8", }); if (result.trim()) return "main"; - } catch { + } catch (err) { // main doesn't exist + process.stderr.write(`gsd [auto-recovery]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } try { const result = execFileSync("git", ["rev-parse", "--verify", "master"], { @@ -119,8 +120,9 @@ function detectMainBranch(basePath: string): string { encoding: "utf-8", }); if (result.trim()) return "master"; - } catch { + } catch (err) { // master doesn't exist either + process.stderr.write(`gsd [auto-recovery]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } return "main"; // default fallback } @@ -144,8 +146,9 @@ function getChangedFilesSinceBranch(basePath: string, targetBranch: string): str ).trim(); return result ? result.split("\n").filter(Boolean) : []; } - } catch { + } catch (err) { // merge-base failed — fall back + process.stderr.write(`gsd [auto-recovery]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } // Fallback: check last 20 commits @@ -246,8 +249,9 @@ export function verifyExpectedArtifact( for (const gid of gateIds) { if (pendingIds.has(gid)) return false; } - } catch { + } catch (err) { // DB unavailable — treat as verified to avoid blocking + process.stderr.write(`gsd [auto-recovery]: dispatch failed: ${err instanceof Error ? err.message : String(err)}\n`); } return true; } @@ -335,8 +339,9 @@ export function verifyExpectedArtifact( } } } - } catch { + } catch (err) { // Parse failure — don't block; slice plan may have non-standard format + process.stderr.write(`gsd [auto-recovery]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } } } @@ -418,7 +423,9 @@ export function writeBlockerPlaceholder( if (unitType === "execute-task" && isDbAvailable()) { const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId); if (mid && sid && tid) { - try { updateTaskStatus(mid, sid, tid, "complete", new Date().toISOString()); } catch { /* non-fatal */ } + try { updateTaskStatus(mid, sid, tid, "complete", new Date().toISOString()); } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-recovery]: DB status update failed: ${err instanceof Error ? err.message : String(err)}\n`); + } } } @@ -439,20 +446,23 @@ function abortAndResetMerge( if (hasMergeHead) { try { nativeMergeAbort(basePath); - } catch { + } catch (err) { /* best-effort */ + process.stderr.write(`gsd [auto-recovery]: git merge-abort failed: ${err instanceof Error ? err.message : String(err)}\n`); } } else if (squashMsgPath) { try { unlinkSync(squashMsgPath); - } catch { + } catch (err) { /* best-effort */ + process.stderr.write(`gsd [auto-recovery]: file unlink failed: ${err instanceof Error ? err.message : String(err)}\n`); } } try { nativeResetHard(basePath); - } catch { + } catch (err) { /* best-effort */ + process.stderr.write(`gsd [auto-recovery]: git reset failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -592,3 +602,4 @@ export function buildLoopRemediationSteps( } return null; } + diff --git a/src/resources/extensions/gsd/auto-start.ts b/src/resources/extensions/gsd/auto-start.ts index 926313891..d9c4c94d8 100644 --- a/src/resources/extensions/gsd/auto-start.ts +++ b/src/resources/extensions/gsd/auto-start.ts @@ -112,8 +112,9 @@ async function openProjectDbIfPresent(basePath: string): Promise { try { const { openDatabase } = await import("./gsd-db.js"); openDatabase(gsdDbPath); - } catch { + } catch (err) { /* non-fatal — DB lifecycle block below will retry */ + process.stderr.write(`gsd [auto-start]: DB open failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -213,8 +214,9 @@ export async function bootstrapAutoSession( try { nativeAddAll(base); nativeCommit(base, "chore: init gsd"); - } catch { + } catch (err) { /* nothing to commit */ + process.stderr.write(`gsd [auto-start]: mkdir failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -724,8 +726,9 @@ export async function bootstrapAutoSession( } } } - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-start]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } return true; @@ -735,3 +738,4 @@ export async function bootstrapAutoSession( throw err; } } + diff --git a/src/resources/extensions/gsd/auto-timers.ts b/src/resources/extensions/gsd/auto-timers.ts index afa3af98b..ab34fa052 100644 --- a/src/resources/extensions/gsd/auto-timers.ts +++ b/src/resources/extensions/gsd/auto-timers.ts @@ -99,8 +99,9 @@ export function startUnitSupervision(sctx: SupervisionContext): void { } } } - } catch { + } catch (err) { // Non-fatal — fall through with no estimate + process.stderr.write(`gsd [auto-timers]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } } const estimateMinutes = taskEstimate ? parseEstimateMinutes(taskEstimate) : null; @@ -219,7 +220,9 @@ export function startUnitSupervision(sctx: SupervisionContext): void { resolveAgentEndCancelled({ message: `Idle watchdog error: ${message}`, category: "idle", isTransient: true }); try { ctx.ui.notify(`Idle watchdog error: ${message}`, "warning"); - } catch { /* best effort */ } + } catch (err) { /* best effort */ + process.stderr.write(`gsd [auto-timers]: notification failed: ${err instanceof Error ? err.message : String(err)}\n`); + } } }, 15000); @@ -253,7 +256,9 @@ export function startUnitSupervision(sctx: SupervisionContext): void { resolveAgentEndCancelled({ message: `Hard timeout error: ${message}`, category: "timeout", isTransient: true }); try { ctx.ui.notify(`Hard timeout error: ${message}`, "warning"); - } catch { /* best effort */ } + } catch (err) { /* best effort */ + process.stderr.write(`gsd [auto-timers]: notification failed: ${err instanceof Error ? err.message : String(err)}\n`); + } } }, hardTimeoutMs); @@ -311,3 +316,4 @@ export function startUnitSupervision(sctx: SupervisionContext): void { } }, 15_000); } + diff --git a/src/resources/extensions/gsd/auto-unit-closeout.ts b/src/resources/extensions/gsd/auto-unit-closeout.ts index 8d5bf4f94..40c02bf01 100644 --- a/src/resources/extensions/gsd/auto-unit-closeout.ts +++ b/src/resources/extensions/gsd/auto-unit-closeout.ts @@ -41,8 +41,11 @@ export async function closeoutUnit( if (process.env.GSD_DEBUG) console.error(`[gsd] memory extraction failed for ${unitType}/${unitId}:`, err); }); } - } catch { /* non-fatal */ } + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-unit-closeout]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); + } } return activityFile ?? undefined; } + diff --git a/src/resources/extensions/gsd/auto-worktree.ts b/src/resources/extensions/gsd/auto-worktree.ts index 92cb389c8..66346a886 100644 --- a/src/resources/extensions/gsd/auto-worktree.ts +++ b/src/resources/extensions/gsd/auto-worktree.ts @@ -153,16 +153,19 @@ function forceOverwriteAssessmentsWithVerdict( // Source has a verdict — force-copy into worktree mkdirSync(dstSliceDir, { recursive: true }); safeCopy(srcFile, join(dstSliceDir, fileEntry.name), { force: true }); - } catch { + } catch (err) { /* non-fatal per file */ + process.stderr.write(`gsd [auto-worktree]: mkdir failed: ${err instanceof Error ? err.message : String(err)}\n`); } } - } catch { + } catch (err) { /* non-fatal per slice */ + process.stderr.write(`gsd [auto-worktree]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } } - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -182,8 +185,9 @@ function clearProjectRootStateFiles(basePath: string, milestoneId: string): void for (const file of transientFiles) { try { unlinkSync(file); - } catch { + } catch (err) { /* non-fatal — file may not exist */ + process.stderr.write(`gsd [auto-worktree]: file unlink failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -211,14 +215,16 @@ function clearProjectRootStateFiles(basePath: string, milestoneId: string): void for (const f of untrackedOutput.split("\n").filter(Boolean)) { try { unlinkSync(join(basePath, f)); - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: file unlink failed: ${err instanceof Error ? err.message : String(err)}\n`); } } } } - } catch { + } catch (err) { /* non-fatal — git command may fail if not in repo */ + process.stderr.write(`gsd [auto-worktree]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } } } @@ -313,8 +319,9 @@ export function syncProjectRootToWorktree( unlinkSync(wtDb); } } - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: file unlink failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -480,13 +487,15 @@ export function cleanStaleRuntimeUnits( try { unlinkSync(join(runtimeUnitsDir, file)); cleaned++; - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: file unlink failed: ${err instanceof Error ? err.message : String(err)}\n`); } } } - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } return cleaned; } @@ -528,8 +537,9 @@ export function syncGsdStateToWorktree( try { cpSync(src, dst); synced.push(f); - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: file copy failed: ${err instanceof Error ? err.message : String(err)}\n`); } } } @@ -548,8 +558,9 @@ export function syncGsdStateToWorktree( try { cpSync(src, dst); synced.push(file); - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: file copy failed: ${err instanceof Error ? err.message : String(err)}\n`); } break; } @@ -578,8 +589,9 @@ export function syncGsdStateToWorktree( try { cpSync(srcDir, dstDir, { recursive: true }); synced.push(`milestones/${mid}/`); - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: file copy failed: ${err instanceof Error ? err.message : String(err)}\n`); } } else { // Milestone directory exists but may be missing files (stale snapshot). @@ -598,8 +610,9 @@ export function syncGsdStateToWorktree( cpSync(srcFile, dstFile); synced.push(`milestones/${mid}/${f}`); } - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: file copy failed: ${err instanceof Error ? err.message : String(err)}\n`); } } } @@ -611,8 +624,9 @@ export function syncGsdStateToWorktree( try { cpSync(srcSlicesDir, dstSlicesDir, { recursive: true }); synced.push(`milestones/${mid}/slices/`); - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: file copy failed: ${err instanceof Error ? err.message : String(err)}\n`); } } else if (existsSync(srcSlicesDir) && existsSync(dstSlicesDir)) { // Both exist — sync missing slice directories @@ -628,19 +642,22 @@ export function syncGsdStateToWorktree( try { cpSync(srcSlice, dstSlice, { recursive: true }); synced.push(`milestones/${mid}/slices/${sid}/`); - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: file copy failed: ${err instanceof Error ? err.message : String(err)}\n`); } } } } - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } } } - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -692,8 +709,9 @@ export function syncWorktreeStateBack( try { reconcileWorktreeDb(mainDb, wtLocalDb); synced.push("gsd.db (pre-upgrade reconcile)"); - } catch { + } catch (err) { // Non-fatal — file sync below is the fallback + process.stderr.write(`gsd [auto-worktree]: DB reconciliation failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -710,8 +728,9 @@ export function syncWorktreeStateBack( try { cpSync(src, dst, { force: true }); synced.push(f); - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: file copy failed: ${err instanceof Error ? err.message : String(err)}\n`); } } } @@ -731,8 +750,9 @@ export function syncWorktreeStateBack( for (const mid of wtMilestones) { syncMilestoneDir(wtGsd, mainGsd, mid, synced); } - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } return { synced }; @@ -756,12 +776,14 @@ function syncDirFiles( try { cpSync(join(srcDir, entry.name), join(dstDir, entry.name), { force: true }); synced.push(`${prefix}${entry.name}`); - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: file copy failed: ${err instanceof Error ? err.message : String(err)}\n`); } } - } catch { + } catch (err) { /* non-fatal — srcDir may not be readable */ + process.stderr.write(`gsd [auto-worktree]: git push failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -804,8 +826,9 @@ function syncMilestoneDir( syncDirFiles(wtTasksDir, mainTasksDir, isMd, synced, `milestones/${mid}/slices/${sid}/tasks/`); } } - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: mkdir failed: ${err instanceof Error ? err.message : String(err)}\n`); } } // ─── Worktree Post-Create Hook (#597) ──────────────────────────────────────── @@ -837,7 +860,9 @@ export function runWorktreePostCreateHook( return `Worktree post-create hook not found: ${resolved}`; } if (process.platform === "win32") { - try { resolved = realpathSync.native(resolved); } catch { /* keep original */ } + try { resolved = realpathSync.native(resolved); } catch (err) { /* keep original */ + process.stderr.write(`gsd [auto-worktree]: realpath failed: ${err instanceof Error ? err.message : String(err)}\n`); + } } try { @@ -921,8 +946,9 @@ function reconcilePlanCheckboxes( results.push(full); } } - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: git push failed: ${err instanceof Error ? err.message : String(err)}\n`); } return results; } @@ -972,8 +998,9 @@ function reconcilePlanCheckboxes( if (changed) { try { atomicWriteSync(dstFile, updated, "utf-8"); - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: file write failed: ${err instanceof Error ? err.message : String(err)}\n`); } } } @@ -1158,8 +1185,9 @@ export function teardownAutoWorktree( // Attempt a direct filesystem removal as a fallback try { rmSync(wtDir, { recursive: true, force: true }); - } catch { + } catch (err) { // Non-fatal — the warning above tells the user how to clean up + process.stderr.write(`gsd [auto-worktree]: file removal failed: ${err instanceof Error ? err.message : String(err)}\n`); } } } @@ -1359,8 +1387,9 @@ export function mergeMilestoneToMain( if (!isSamePath(worktreeDbPath, mainDbPath)) { reconcileWorktreeDb(mainDbPath, worktreeDbPath); } - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto-worktree]: DB reconciliation failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -1515,9 +1544,10 @@ export function mergeMilestoneToMain( ); stashed = true; } - } catch { + } catch (err) { // Stash failure is non-fatal — proceed without stash and let the merge // report the dirty tree if it fails. + process.stderr.write(`gsd [auto-worktree]: git stash failed: ${err instanceof Error ? err.message : String(err)}\n`); } // 7a. Shelter queued milestone directories before the squash merge (#2505). @@ -1538,9 +1568,13 @@ export function mergeMilestoneToMain( try { mkdirSync(milestonesDir, { recursive: true }); cpSync(join(shelterDir, dirName), join(milestonesDir, dirName), { recursive: true, force: true }); - } catch { /* best-effort */ } + } catch (err) { /* best-effort */ + process.stderr.write(`gsd [auto-worktree]: file copy failed: ${err instanceof Error ? err.message : String(err)}\n`); + } + } + try { rmSync(shelterDir, { recursive: true, force: true }); } catch (err) { /* best-effort */ + process.stderr.write(`gsd [auto-worktree]: shelter cleanup failed: ${err instanceof Error ? err.message : String(err)}\n`); } - try { rmSync(shelterDir, { recursive: true, force: true }); } catch { /* best-effort */ } }; try { @@ -1557,13 +1591,15 @@ export function mergeMilestoneToMain( cpSync(srcDir, dstDir, { recursive: true, force: true }); rmSync(srcDir, { recursive: true, force: true }); shelteredDirs.push(entry.name); - } catch { + } catch (err) { // Non-fatal — if shelter fails, the merge may still succeed + process.stderr.write(`gsd [auto-worktree]: file copy failed: ${err instanceof Error ? err.message : String(err)}\n`); } } } - } catch { + } catch (err) { // Non-fatal — proceed with merge; untracked files may block it + process.stderr.write(`gsd [auto-worktree]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } // 7b. Clean up stale merge state before attempting squash merge (#2912). @@ -1577,7 +1613,9 @@ export function mergeMilestoneToMain( const p = join(gitDir_, f); if (existsSync(p)) unlinkSync(p); } - } catch { /* best-effort */ } + } catch (err) { /* best-effort */ + process.stderr.write(`gsd [auto-worktree]: file unlink failed: ${err instanceof Error ? err.message : String(err)}\n`); + } // 8. Squash merge — auto-resolve .gsd/ state file conflicts (#530) const mergeResult = nativeMergeSquash(originalBasePath_, milestoneBranch); @@ -1595,7 +1633,9 @@ export function mergeMilestoneToMain( const p = join(gitDir_, f); if (existsSync(p)) unlinkSync(p); } - } catch { /* best-effort */ } + } catch (err) { /* best-effort */ + process.stderr.write(`gsd [auto-worktree]: file unlink failed: ${err instanceof Error ? err.message : String(err)}\n`); + } // Pop stash before throwing so local work is not lost. if (stashed) { @@ -1605,7 +1645,9 @@ export function mergeMilestoneToMain( stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8", }); - } catch { /* stash pop conflict is non-fatal */ } + } catch (err) { /* stash pop conflict is non-fatal */ + process.stderr.write(`gsd [auto-worktree]: git stash failed: ${err instanceof Error ? err.message : String(err)}\n`); + } } restoreShelter(); // Restore cwd so the caller is not stranded on the integration branch @@ -1657,14 +1699,18 @@ export function mergeMilestoneToMain( // Abort merge state so MERGE_HEAD is not left on disk (#2912). // libgit2's merge creates MERGE_HEAD even for squash merges; if left // dangling, subsequent merges fail and doctor reports corrupt state. - try { nativeMergeAbort(originalBasePath_); } catch { /* best-effort */ } + try { nativeMergeAbort(originalBasePath_); } catch (err) { /* best-effort */ + process.stderr.write(`gsd [auto-worktree]: git merge-abort failed: ${err instanceof Error ? err.message : String(err)}\n`); + } try { const gitDir_ = resolveGitDir(originalBasePath_); for (const f of ["SQUASH_MSG", "MERGE_MSG", "MERGE_HEAD"]) { const p = join(gitDir_, f); if (existsSync(p)) unlinkSync(p); } - } catch { /* best-effort */ } + } catch (err) { /* best-effort */ + process.stderr.write(`gsd [auto-worktree]: file unlink failed: ${err instanceof Error ? err.message : String(err)}\n`); + } // Pop stash before throwing so local work is not lost (#2151). if (stashed) { @@ -1674,7 +1720,9 @@ export function mergeMilestoneToMain( stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8", }); - } catch { /* stash pop conflict is non-fatal */ } + } catch (err) { /* stash pop conflict is non-fatal */ + process.stderr.write(`gsd [auto-worktree]: git stash failed: ${err instanceof Error ? err.message : String(err)}\n`); + } } restoreShelter(); throw new MergeConflictError( @@ -1704,7 +1752,9 @@ export function mergeMilestoneToMain( const p = join(gitDir_, f); if (existsSync(p)) unlinkSync(p); } - } catch { /* best-effort */ } + } catch (err) { /* best-effort */ + process.stderr.write(`gsd [auto-worktree]: file unlink failed: ${err instanceof Error ? err.message : String(err)}\n`); + } // 9a-ii. Restore stashed files now that the merge+commit is complete (#2151). // Pop after commit so stashed changes do not interfere with the squash merge @@ -1752,7 +1802,9 @@ export function mergeMilestoneToMain( stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8", }); - } catch { /* stash may already be consumed */ } + } catch (err) { /* stash may already be consumed */ + process.stderr.write(`gsd [auto-worktree]: git stash failed: ${err instanceof Error ? err.message : String(err)}\n`); + } } else { // Non-.gsd conflicts remain — leave stash for manual resolution logWarning("reconcile", "Stash pop conflict on non-.gsd files after merge", { @@ -1822,8 +1874,9 @@ export function mergeMilestoneToMain( encoding: "utf-8", }); pushed = true; - } catch { + } catch (err) { // Push failure is non-fatal + process.stderr.write(`gsd [auto-worktree]: git push failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -1852,8 +1905,9 @@ export function mergeMilestoneToMain( encoding: "utf-8", }); prCreated = true; - } catch { + } catch (err) { // PR creation failure is non-fatal — gh may not be installed or authenticated + process.stderr.write(`gsd [auto-worktree]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -1891,15 +1945,17 @@ export function mergeMilestoneToMain( branch: null as unknown as string, deleteBranch: false, }); - } catch { + } catch (err) { // Best-effort -- worktree dir may already be gone + process.stderr.write(`gsd [auto-worktree]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } // 13. Delete milestone branch (after worktree removal so ref is unlocked) try { nativeBranchDelete(originalBasePath_, milestoneBranch); - } catch { + } catch (err) { // Best-effort + process.stderr.write(`gsd [auto-worktree]: git branch-delete failed: ${err instanceof Error ? err.message : String(err)}\n`); } // 14. Clear module state @@ -1908,3 +1964,4 @@ export function mergeMilestoneToMain( return { commitMessage, pushed, prCreated, codeFilesChanged }; } + diff --git a/src/resources/extensions/gsd/auto.ts b/src/resources/extensions/gsd/auto.ts index 2fc826095..ab00160bc 100644 --- a/src/resources/extensions/gsd/auto.ts +++ b/src/resources/extensions/gsd/auto.ts @@ -316,8 +316,9 @@ export function getAutoDashboardData(): AutoDashboardData { if (s.basePath) { pendingCaptureCount = countPendingCaptures(s.basePath); } - } catch { + } catch (err) { // Non-fatal — captures module may not be loaded + process.stderr.write(`gsd [auto]: capture count failed: ${err instanceof Error ? err.message : String(err)}\n`); } return { active: s.active, @@ -565,8 +566,9 @@ function cleanupAfterLoopExit(ctx: ExtensionContext): void { try { if (lockBase()) clearLock(lockBase()); if (lockBase()) releaseSessionLock(lockBase()); - } catch { + } catch (err) { /* best-effort — mirror stopAuto cleanup */ + process.stderr.write(`gsd [auto]: lock cleanup failed: ${err instanceof Error ? err.message : String(err)}\n`); } ctx.ui.setStatus("gsd-auto", undefined); @@ -578,8 +580,9 @@ function cleanupAfterLoopExit(ctx: ExtensionContext): void { s.basePath = s.originalBasePath; try { process.chdir(s.basePath); - } catch { + } catch (err) { /* best-effort */ + process.stderr.write(`gsd [auto]: chdir failed: ${err instanceof Error ? err.message : String(err)}\n`); } } } @@ -651,8 +654,9 @@ export async function stopAuto( } else { milestoneComplete = true; } - } catch { + } catch (err) { // Non-fatal — fall through to preserveBranch path + process.stderr.write(`gsd [auto]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } if (milestoneComplete) { @@ -687,8 +691,9 @@ export async function stopAuto( s.basePath = s.originalBasePath; try { process.chdir(s.basePath); - } catch { + } catch (err) { /* best-effort */ + process.stderr.write(`gsd [auto]: chdir failed: ${err instanceof Error ? err.message : String(err)}\n`); } } } catch (e) { @@ -760,7 +765,9 @@ export async function stopAuto( try { const pausedPath = join(gsdRoot(s.originalBasePath || s.basePath), "runtime", "paused-session.json"); if (existsSync(pausedPath)) unlinkSync(pausedPath); - } catch { /* non-fatal */ } + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto]: file unlink failed: ${err instanceof Error ? err.message : String(err)}\n`); + } // ── Step 13: Restore original model (before reset clears IDs) ── try { @@ -794,7 +801,9 @@ export async function stopAuto( const { closeBrowser } = await import("../browser-tools/lifecycle.js"); await closeBrowser(); } - } catch { /* non-fatal: browser-tools may not be loaded */ } + } catch (err) { /* non-fatal: browser-tools may not be loaded */ + process.stderr.write(`gsd [auto]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); + } // External cleanup (not covered by session reset) clearInFlightTools(); @@ -852,16 +861,18 @@ export async function pauseAuto( JSON.stringify(pausedMeta, null, 2), "utf-8", ); - } catch { + } catch (err) { // Non-fatal — resume will still work via full bootstrap, just without worktree context + process.stderr.write(`gsd [auto]: file write failed: ${err instanceof Error ? err.message : String(err)}\n`); } // Close out the current unit so its runtime record doesn't stay at "dispatched" if (s.currentUnit && ctx) { try { await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt); - } catch { + } catch (err) { // Non-fatal — best-effort closeout on pause + process.stderr.write(`gsd [auto]: dispatch failed: ${err instanceof Error ? err.message : String(err)}\n`); } s.currentUnit = null; } @@ -1085,7 +1096,9 @@ export async function startAuto( s.originalBasePath = meta.originalBasePath || base; s.stepMode = meta.stepMode ?? requestedStepMode; s.paused = true; - try { unlinkSync(pausedPath); } catch { /* non-fatal */ } + try { unlinkSync(pausedPath); } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto]: pause file cleanup failed: ${err instanceof Error ? err.message : String(err)}\n`); + } ctx.ui.notify( `Resuming paused custom workflow${meta.activeRunDir ? ` (${meta.activeRunDir})` : ""}.`, "info", @@ -1096,7 +1109,9 @@ export async function startAuto( const summaryFile = resolveMilestoneFile(base, meta.milestoneId, "SUMMARY"); if (!mDir || summaryFile) { // Stale milestone — clean up and fall through to fresh bootstrap - try { unlinkSync(pausedPath); } catch { /* non-fatal */ } + try { unlinkSync(pausedPath); } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto]: pause file cleanup failed: ${err instanceof Error ? err.message : String(err)}\n`); + } ctx.ui.notify( `Paused milestone ${meta.milestoneId} is ${!mDir ? "missing" : "already complete"}. Starting fresh.`, "info", @@ -1107,7 +1122,9 @@ export async function startAuto( s.stepMode = meta.stepMode ?? requestedStepMode; s.paused = true; // Clean up the persisted file — we're consuming it - try { unlinkSync(pausedPath); } catch { /* non-fatal */ } + try { unlinkSync(pausedPath); } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto]: pause file cleanup failed: ${err instanceof Error ? err.message : String(err)}\n`); + } ctx.ui.notify( `Resuming paused session for ${meta.milestoneId}${meta.worktreePath ? ` (worktree)` : ""}.`, "info", @@ -1115,8 +1132,9 @@ export async function startAuto( } } } - } catch { + } catch (err) { // Malformed or missing — proceed with fresh bootstrap + process.stderr.write(`gsd [auto]: operation failed: ${err instanceof Error ? err.message : String(err)}\n`); } } @@ -1242,8 +1260,9 @@ export async function startAuto( try { syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath)); - } catch { + } catch (err) { // Best-effort only — sidebar sync must never block auto-mode startup + process.stderr.write(`gsd [auto]: cmux sync failed: ${err instanceof Error ? err.message : String(err)}\n`); } logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress"); @@ -1415,8 +1434,9 @@ export async function dispatchHookUnit( if (match) { try { await pi.setModel(match); - } catch { + } catch (err) { /* non-fatal */ + process.stderr.write(`gsd [auto]: dispatch failed: ${err instanceof Error ? err.message : String(err)}\n`); } } else { ctx.ui.notify( @@ -1453,7 +1473,9 @@ export async function dispatchHookUnit( ctx.ui.notify(`Running post-unit hook: ${hookName}`, "info"); // Ensure cwd matches basePath before hook dispatch (#1389) - try { if (process.cwd() !== s.basePath) process.chdir(s.basePath); } catch {} + try { if (process.cwd() !== s.basePath) process.chdir(s.basePath); } catch (err) { + process.stderr.write(`gsd [auto]: chdir failed: ${err instanceof Error ? err.message : String(err)}\n`); + } debugLog("dispatchHookUnit", { phase: "send-message", @@ -1475,3 +1497,4 @@ export { buildLoopRemediationSteps, } from "./auto-recovery.js"; export { resolveExpectedArtifactPath } from "./auto-artifact-paths.js"; + diff --git a/src/resources/extensions/gsd/auto/phases.ts b/src/resources/extensions/gsd/auto/phases.ts index 04a68c5a8..83fdae0e6 100644 --- a/src/resources/extensions/gsd/auto/phases.ts +++ b/src/resources/extensions/gsd/auto/phases.ts @@ -1261,7 +1261,9 @@ export async function runUnitPhase( blockers: [], nextSteps: [], }); - } catch { /* non-fatal — anchor is advisory */ } + } catch (err) { /* non-fatal — anchor is advisory */ + process.stderr.write(`gsd [phases]: phase anchor failed: ${err instanceof Error ? err.message : String(err)}\n`); + } } deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "unit-end", data: { unitType, unitId, status: unitResult.status, artifactVerified, ...(unitResult.errorContext ? { errorContext: unitResult.errorContext } : {}) }, causedBy: { flowId: ic.flowId, seq: unitStartSeq } }); @@ -1384,3 +1386,4 @@ export async function runFinalize( return { action: "next", data: undefined as void }; } + diff --git a/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts b/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts new file mode 100644 index 000000000..dca1bb63b --- /dev/null +++ b/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts @@ -0,0 +1,113 @@ +/** + * Verify that auto-mode catch blocks emit diagnostic output instead of + * silently swallowing errors (#3348, #3345). + * + * This test scans the auto-mode source files and asserts that no empty + * catch blocks remain — every catch must contain at least one statement + * beyond comments. + */ + +import { describe, test } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync, readdirSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const gsdDir = join(__dirname, ".."); + +function getAutoModeFiles(): string[] { + const files: string[] = []; + + // Top-level auto*.ts files + for (const f of readdirSync(gsdDir)) { + if (f.startsWith("auto") && f.endsWith(".ts") && !f.endsWith(".test.ts")) { + files.push(join(gsdDir, f)); + } + } + + // auto/ subdirectory + const autoSubDir = join(gsdDir, "auto"); + for (const f of readdirSync(autoSubDir)) { + if (f.endsWith(".ts") && !f.endsWith(".test.ts")) { + files.push(join(autoSubDir, f)); + } + } + + return files; +} + +/** + * Scan a file for empty catch blocks — catches whose body contains + * only whitespace and/or comments but no executable statements. + */ +function findEmptyCatches(filePath: string): Array<{ line: number; text: string }> { + const content = readFileSync(filePath, "utf-8"); + const lines = content.split("\n"); + const results: Array<{ line: number; text: string }> = []; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + // Match catch block opening + if (!/\}\s*catch\s*(\([^)]*\))?\s*\{/.test(line)) continue; + + // Inline single-line catch: } catch { ... } + const inlineMatch = line.match(/\}\s*catch\s*(\([^)]*\))?\s*\{(.*)\}\s*;?\s*$/); + if (inlineMatch) { + const body = inlineMatch[2].trim(); + // Check if body is only comments + const stripped = body.replace(/\/\*.*?\*\//g, "").replace(/\/\/.*/g, "").trim(); + if (!stripped) { + results.push({ line: i + 1, text: line.trim() }); + } + continue; + } + + // Multi-line catch — scan until matching } + let j = i + 1; + let depth = 1; + const bodyLines: string[] = []; + while (j < lines.length && depth > 0) { + for (const ch of lines[j]) { + if (ch === "{") depth++; + else if (ch === "}") depth--; + } + bodyLines.push(lines[j].trim()); + j++; + } + + // Check if body (excluding closing brace) has any executable statements + const meaningful = bodyLines.slice(0, -1).filter( + (l) => l && !l.startsWith("//") && !l.startsWith("/*") && !l.startsWith("*") && l !== "}", + ); + + if (meaningful.length === 0) { + results.push({ line: i + 1, text: line.trim() }); + } + } + + return results; +} + +describe("auto-mode diagnostic catch blocks (#3348)", () => { + test("no empty catch blocks remain in auto-mode files", () => { + const files = getAutoModeFiles(); + assert.ok(files.length > 0, "should find auto-mode source files"); + + const violations: string[] = []; + for (const file of files) { + const empties = findEmptyCatches(file); + for (const empty of empties) { + const rel = file.replace(gsdDir + "/", ""); + violations.push(`${rel}:${empty.line} — ${empty.text}`); + } + } + + assert.equal( + violations.length, + 0, + `Found ${violations.length} empty catch block(s) in auto-mode files:\n${violations.join("\n")}`, + ); + }); +});