From 04322f110a8381b7eabeec53e146cce7da807119 Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Mon, 11 May 2026 14:46:30 +0200 Subject: [PATCH] refactor: replace all inline error message ternaries with getErrorMessage() Eliminates ~120 repetitions of `err instanceof Error ? err.message : String(err)` across the entire extension source tree. All callers now import and use `getErrorMessage` from the canonical `./error-utils.js`. Files updated (56 files): - auto.js, auto-worktree.js, auto-recovery.js, auto-dashboard.js, auto-timers.js - auto-prompts.js, auto-start.js, auto-post-unit.js, auto-model-selection.js - auto/phases.js, auto/loop.js, auto/infra-errors.js - autonomous-solver-eval.js, bootstrap/agent-end-recovery.js, bootstrap/db-tools.js - bootstrap/exec-tools.js, bootstrap/journal-tools.js, bootstrap/register-extension.js - bootstrap/register-hooks.js, canonical-milestone-plan.js, changelog.js - clean-root-preflight.js, code-intelligence.js, commands-add-tests.js - commands-debug.js, commands-eval-review.js, commands-handlers.js - commands-maintenance.js, commands-pr-branch.js, commands-scan.js, commands-ship.js - commands-todo.js, commands-worktree.js, definition-io.js, doctor.js - doctor-config-checks.js, doctor-engine-checks.js, ecosystem/loader.js - eval-review-schema.js, exec-sandbox.js, execution-instruction-guard.js - graph-context.js, hook-emitter.js, index.js, learning/runtime.js - lifecycle-hooks.js, onboarding-state.js, orphan-worktree-sweep.js - planning-depth.js, quick.js, scaffold-keeper.js, sf-db/sf-db-core.js - slice-cadence.js, sm-client.js, spec-projections.js, subagent/background-jobs.js - subagent/isolation.js, sync-scheduler.js, tools/exec-tool.js - tools/sift-search-tool.js, tools/workflow-tool-executors.js, ui/index.js - uok/a2a-agent-server.js, uok/auto-dispatch.js, uok/auto-unit-closeout.js - uok/auto-verification.js, uok/chaos-monkey.js, uok/gate-runner.js - vault-resolver.js, workflow-install.js, workflow-plugins.js, worktree-manager.js - worktree-resolver.js Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/resources/extensions/sf/auto-dashboard.js | 14 +-- .../extensions/sf/auto-model-selection.js | 3 +- src/resources/extensions/sf/auto-post-unit.js | 9 +- src/resources/extensions/sf/auto-prompts.js | 25 ++--- src/resources/extensions/sf/auto-recovery.js | 16 +-- src/resources/extensions/sf/auto-start.js | 23 ++-- src/resources/extensions/sf/auto-timers.js | 11 +- src/resources/extensions/sf/auto-worktree.js | 101 +++++++++--------- src/resources/extensions/sf/auto.js | 30 +++--- .../extensions/sf/auto/infra-errors.js | 5 +- src/resources/extensions/sf/auto/loop.js | 13 +-- src/resources/extensions/sf/auto/phases.js | 15 +-- .../extensions/sf/autonomous-solver-eval.js | 9 +- .../sf/bootstrap/agent-end-recovery.js | 9 +- .../extensions/sf/bootstrap/db-tools.js | 25 ++--- .../extensions/sf/bootstrap/exec-tools.js | 3 +- .../extensions/sf/bootstrap/journal-tools.js | 3 +- .../sf/bootstrap/register-extension.js | 5 +- .../extensions/sf/bootstrap/register-hooks.js | 3 +- .../extensions/sf/canonical-milestone-plan.js | 5 +- src/resources/extensions/sf/changelog.js | 3 +- .../extensions/sf/clean-root-preflight.js | 7 +- .../extensions/sf/code-intelligence.js | 3 +- .../extensions/sf/commands-add-tests.js | 3 +- src/resources/extensions/sf/commands-debug.js | 7 +- .../extensions/sf/commands-eval-review.js | 7 +- .../extensions/sf/commands-handlers.js | 3 +- .../extensions/sf/commands-maintenance.js | 3 +- .../extensions/sf/commands-pr-branch.js | 3 +- src/resources/extensions/sf/commands-scan.js | 3 +- src/resources/extensions/sf/commands-ship.js | 3 +- src/resources/extensions/sf/commands-todo.js | 5 +- .../extensions/sf/commands-worktree.js | 15 +-- .../sf/commands/handlers/workflow.js | 5 +- src/resources/extensions/sf/definition-io.js | 3 +- .../extensions/sf/doctor-config-checks.js | 7 +- .../extensions/sf/doctor-engine-checks.js | 3 +- src/resources/extensions/sf/doctor.js | 3 +- .../extensions/sf/ecosystem/loader.js | 11 +- .../extensions/sf/eval-review-schema.js | 3 +- src/resources/extensions/sf/exec-sandbox.js | 5 +- .../sf/execution-instruction-guard.js | 3 +- src/resources/extensions/sf/graph-context.js | 3 +- src/resources/extensions/sf/hook-emitter.js | 3 +- src/resources/extensions/sf/index.js | 17 +-- .../extensions/sf/learning/runtime.js | 5 +- .../extensions/sf/lifecycle-hooks.js | 3 +- .../extensions/sf/onboarding-state.js | 3 +- .../extensions/sf/orphan-worktree-sweep.js | 5 +- src/resources/extensions/sf/planning-depth.js | 3 +- src/resources/extensions/sf/quick.js | 3 +- .../extensions/sf/scaffold-keeper.js | 3 +- .../extensions/sf/sf-db/sf-db-core.js | 5 +- src/resources/extensions/sf/slice-cadence.js | 3 +- src/resources/extensions/sf/sm-client.js | 5 +- .../extensions/sf/spec-projections.js | 3 +- .../extensions/sf/subagent/background-jobs.js | 3 +- .../extensions/sf/subagent/isolation.js | 3 +- src/resources/extensions/sf/sync-scheduler.js | 3 +- .../extensions/sf/tools/exec-tool.js | 3 +- .../extensions/sf/tools/sift-search-tool.js | 3 +- .../sf/tools/workflow-tool-executors.js | 25 ++--- src/resources/extensions/sf/ui/index.js | 3 +- .../extensions/sf/uok/a2a-agent-server.js | 3 +- .../extensions/sf/uok/auto-dispatch.js | 9 +- .../extensions/sf/uok/auto-unit-closeout.js | 3 +- .../extensions/sf/uok/auto-verification.js | 3 +- .../extensions/sf/uok/chaos-monkey.js | 3 +- .../extensions/sf/uok/gate-runner.js | 5 +- src/resources/extensions/sf/vault-resolver.js | 5 +- .../extensions/sf/workflow-install.js | 3 +- .../extensions/sf/workflow-plugins.js | 3 +- .../extensions/sf/worktree-manager.js | 3 +- .../extensions/sf/worktree-resolver.js | 13 +-- 74 files changed, 333 insertions(+), 262 deletions(-) diff --git a/src/resources/extensions/sf/auto-dashboard.js b/src/resources/extensions/sf/auto-dashboard.js index b5a83f6f5..a1c23fca4 100644 --- a/src/resources/extensions/sf/auto-dashboard.js +++ b/src/resources/extensions/sf/auto-dashboard.js @@ -348,7 +348,7 @@ export function updateSliceProgressCache(_base, mid, activeSid) { // Non-fatal — just omit task count logWarning( "dashboard", - `operation failed: ${err instanceof Error ? err.message : String(err)}`, + `operation failed: ${getErrorMessage(err)}`, ); } } @@ -363,7 +363,7 @@ export function updateSliceProgressCache(_base, mid, activeSid) { // Non-fatal — widget just won't show progress bar logWarning( "dashboard", - `operation failed: ${err instanceof Error ? err.message : String(err)}`, + `operation failed: ${getErrorMessage(err)}`, ); } } @@ -397,7 +397,7 @@ function refreshLastCommit(basePath) { // Non-fatal — just skip last commit display logWarning( "dashboard", - `operation failed: ${err instanceof Error ? err.message : String(err)}`, + `operation failed: ${getErrorMessage(err)}`, ); } } @@ -521,7 +521,7 @@ function persistWidgetMode( /* non-fatal — mode still set in memory */ logWarning( "dashboard", - `file write failed: ${err instanceof Error ? err.message : String(err)}`, + `file write failed: ${getErrorMessage(err)}`, ); } } @@ -588,7 +588,7 @@ export function updateProgressWidget( /* not in git repo */ logWarning( "dashboard", - `git branch detection failed: ${err instanceof Error ? err.message : String(err)}`, + `git branch detection failed: ${getErrorMessage(err)}`, ); } // Cache short pwd (last 2 path segments only) + worktree/branch info @@ -632,7 +632,7 @@ export function updateProgressWidget( } catch (err) { logWarning( "dashboard", - `RTK savings lookup failed: ${err instanceof Error ? err.message : String(err)}`, + `RTK savings lookup failed: ${getErrorMessage(err)}`, ); cachedRtkLabel = null; } @@ -656,7 +656,7 @@ export function updateProgressWidget( /* non-fatal */ logWarning( "dashboard", - `DB status update failed: ${err instanceof Error ? err.message : String(err)}`, + `DB status update failed: ${getErrorMessage(err)}`, ); } }, 15_000); diff --git a/src/resources/extensions/sf/auto-model-selection.js b/src/resources/extensions/sf/auto-model-selection.js index e2107e1f6..ebc6f3f2a 100644 --- a/src/resources/extensions/sf/auto-model-selection.js +++ b/src/resources/extensions/sf/auto-model-selection.js @@ -31,6 +31,7 @@ import { resolveUokFlags } from "./uok/flags.js"; import { applyModelPolicyFilter } from "./uok/model-policy.js"; import { logWarning } from "./workflow-logger.js"; import { getRequiredWorkflowToolsForAutoUnit } from "./workflow-tools.js"; +import { getErrorMessage } from "./error-utils.js"; /** * Thrown when the model-policy gate rejects every candidate model for a unit * dispatch (#4959 / #4681 / #4850). The auto-loop catches this specifically @@ -941,7 +942,7 @@ export function buildFlatRateContext(provider, ctx, prefs) { // fall through with authMode undefined and surface the cause. logWarning( "dispatch", - `flat-rate auth-mode lookup failed for ${provider}: ${err instanceof Error ? err.message : String(err)}`, + `flat-rate auth-mode lookup failed for ${provider}: ${getErrorMessage(err)}`, ); } } diff --git a/src/resources/extensions/sf/auto-post-unit.js b/src/resources/extensions/sf/auto-post-unit.js index dde6d296a..40062f48d 100644 --- a/src/resources/extensions/sf/auto-post-unit.js +++ b/src/resources/extensions/sf/auto-post-unit.js @@ -168,6 +168,7 @@ import { getAutoSession } from "./auto/session.js"; import { describeNextUnit } from "./auto-dashboard.js"; import { _resetHasChangesCache } from "./native-git-bridge.js"; import { autoCommitCurrentBranch } from "./worktree.js"; +import { getErrorMessage } from "./error-utils.js"; /** * Detect summary files written directly to disk without the LLM calling @@ -312,7 +313,7 @@ export async function autoCommitUnit(basePath, unitType, unitId, ctx) { } catch (err) { logWarning( "engine", - `GitHub issue lookup failed: ${err instanceof Error ? err.message : String(err)}`, + `GitHub issue lookup failed: ${getErrorMessage(err)}`, ); } taskContext = { @@ -465,7 +466,7 @@ export async function postUnitPreVerification(pctx, opts) { // GitHub sync not available — skip logWarning( "engine", - `GitHub issue lookup failed: ${err instanceof Error ? err.message : String(err)}`, + `GitHub issue lookup failed: ${getErrorMessage(err)}`, ); } taskContext = { @@ -810,7 +811,7 @@ export async function postUnitPreVerification(pctx, opts) { } catch (err) { logWarning( "engine", - `slice-cadence: failed to record milestone start SHA: ${err instanceof Error ? err.message : String(err)}`, + `slice-cadence: failed to record milestone start SHA: ${getErrorMessage(err)}`, ); } } @@ -845,7 +846,7 @@ export async function postUnitPreVerification(pctx, opts) { return; } logError("engine", `slice-cadence merge failed for ${sid}`, { - error: err instanceof Error ? err.message : String(err), + error: getErrorMessage(err), }); // Non-conflict failures (dirty main, rev-walk error, etc.) can // leave the checkout in an unexpected state. Stop autonomous mode so diff --git a/src/resources/extensions/sf/auto-prompts.js b/src/resources/extensions/sf/auto-prompts.js index a9bf05c24..2cc848fae 100644 --- a/src/resources/extensions/sf/auto-prompts.js +++ b/src/resources/extensions/sf/auto-prompts.js @@ -88,6 +88,7 @@ import { buildResumeSection, } from "./workflow-helpers.js"; import { logWarning } from "./workflow-logger.js"; +import { getErrorMessage } from "./error-utils.js"; // ─── Preamble Cap ───────────────────────────────────────────────────────────── /** @@ -160,7 +161,7 @@ function buildCompleteSliceControlBlock(mid, sid, base) { } catch (err) { logWarning( "prompt", - `complete-slice task ledger failed: ${err instanceof Error ? err.message : String(err)}`, + `complete-slice task ledger failed: ${getErrorMessage(err)}`, ); return ""; } @@ -361,7 +362,7 @@ async function inlineDependencySummaries(mid, sid, base, budgetChars) { } catch (err) { logWarning( "prompt", - `inlineDependencySummaries DB lookup failed: ${err instanceof Error ? err.message : String(err)}`, + `inlineDependencySummaries DB lookup failed: ${getErrorMessage(err)}`, ); } // If DB didn't provide depends, fall back to roadmap parsing @@ -449,7 +450,7 @@ export async function inlineDecisionsFromDb(base, milestoneId, scope, level) { } catch (err) { logWarning( "prompt", - `inlineDecisionsFromDb failed: ${err instanceof Error ? err.message : String(err)}`, + `inlineDecisionsFromDb failed: ${getErrorMessage(err)}`, ); } // DB unavailable — fall back to filesystem @@ -485,7 +486,7 @@ export async function inlineRequirementsFromDb( } catch (err) { logWarning( "prompt", - `inlineRequirementsFromDb failed: ${err instanceof Error ? err.message : String(err)}`, + `inlineRequirementsFromDb failed: ${getErrorMessage(err)}`, ); } return inlineSfRootFile(base, "requirements.md", "Requirements"); @@ -507,7 +508,7 @@ export async function inlineProjectFromDb(base) { } catch (err) { logWarning( "prompt", - `inlineProjectFromDb failed: ${err instanceof Error ? err.message : String(err)}`, + `inlineProjectFromDb failed: ${getErrorMessage(err)}`, ); } return inlineSfRootFile(base, "project.md", "Project"); @@ -936,7 +937,7 @@ export function buildSkillActivationBlock(params) { } catch (err) { logWarning( "prompt", - `parseTaskPlanFile failed: ${err instanceof Error ? err.message : String(err)}`, + `parseTaskPlanFile failed: ${getErrorMessage(err)}`, ); } } @@ -2132,7 +2133,7 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) { } catch (err) { logWarning( "prompt", - `buildCompleteMilestonePrompt DB lookup failed: ${err instanceof Error ? err.message : String(err)}`, + `buildCompleteMilestonePrompt DB lookup failed: ${getErrorMessage(err)}`, ); } // File-based fallback: parse roadmap for slice IDs when DB has no data @@ -2278,7 +2279,7 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) { } catch (err) { logWarning( "prompt", - `buildValidateMilestonePrompt verification classes lookup failed: ${err instanceof Error ? err.message : String(err)}`, + `buildValidateMilestonePrompt verification classes lookup failed: ${getErrorMessage(err)}`, ); } // Inline all slice summaries and assessment results @@ -2293,7 +2294,7 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) { } catch (err) { logWarning( "prompt", - `buildValidateMilestonePrompt slice IDs lookup failed: ${err instanceof Error ? err.message : String(err)}`, + `buildValidateMilestonePrompt slice IDs lookup failed: ${getErrorMessage(err)}`, ); } // File-based fallback: parse roadmap for slice IDs when DB has no data @@ -2511,7 +2512,7 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) { } catch (err) { logWarning( "prompt", - `loadReplanCaptures failed: ${err instanceof Error ? err.message : String(err)}`, + `loadReplanCaptures failed: ${getErrorMessage(err)}`, ); } return loadPrompt("replan-slice", { @@ -2684,7 +2685,7 @@ export async function buildReassessRoadmapPrompt( } catch (err) { logWarning( "prompt", - `loadDeferredCaptures failed: ${err instanceof Error ? err.message : String(err)}`, + `loadDeferredCaptures failed: ${getErrorMessage(err)}`, ); } const reassessCommitInstruction = @@ -3016,7 +3017,7 @@ export async function buildRewriteDocsPrompt( } catch (err) { logWarning( "prompt", - `buildRewriteDocsPrompt DB task lookup failed: ${err instanceof Error ? err.message : String(err)}`, + `buildRewriteDocsPrompt DB task lookup failed: ${getErrorMessage(err)}`, ); } if (!incompleteTasks) { diff --git a/src/resources/extensions/sf/auto-recovery.js b/src/resources/extensions/sf/auto-recovery.js index 26adc6bde..ee191d0d2 100644 --- a/src/resources/extensions/sf/auto-recovery.js +++ b/src/resources/extensions/sf/auto-recovery.js @@ -163,7 +163,7 @@ function getChangedFilesSinceBranch(basePath, targetBranch) { // merge-base failed — fall back logWarning( "recovery", - `merge-base detection failed: ${err instanceof Error ? err.message : String(err)}`, + `merge-base detection failed: ${getErrorMessage(err)}`, ); } // Fallback: check last 20 commits @@ -259,7 +259,7 @@ export function verifyExpectedArtifact(unitType, unitId, base) { // DB unavailable — treat as verified to avoid blocking logWarning( "recovery", - `gate-evaluate DB check failed: ${err instanceof Error ? err.message : String(err)}`, + `gate-evaluate DB check failed: ${getErrorMessage(err)}`, ); } return true; @@ -306,7 +306,7 @@ export function verifyExpectedArtifact(unitType, unitId, base) { } catch (err) { logWarning( "recovery", - `parallel-research verification failed: ${err instanceof Error ? err.message : String(err)}`, + `parallel-research verification failed: ${getErrorMessage(err)}`, ); return false; } @@ -329,7 +329,7 @@ export function verifyExpectedArtifact(unitType, unitId, base) { } catch (err) { logWarning( "recovery", - `plan-milestone roadmap verification failed: ${err instanceof Error ? err.message : String(err)}`, + `plan-milestone roadmap verification failed: ${getErrorMessage(err)}`, ); return false; } @@ -410,7 +410,7 @@ export function verifyExpectedArtifact(unitType, unitId, base) { // Parse failure — don't block; slice plan may have non-standard format logWarning( "recovery", - `plan-slice task plan verification failed: ${err instanceof Error ? err.message : String(err)}`, + `plan-slice task plan verification failed: ${getErrorMessage(err)}`, ); } } @@ -555,7 +555,7 @@ function abortAndResetMerge(basePath, hasMergeHead, squashMsgPath) { /* best-effort */ logWarning( "recovery", - `git merge-abort failed: ${err instanceof Error ? err.message : String(err)}`, + `git merge-abort failed: ${getErrorMessage(err)}`, ); } } else if (squashMsgPath) { @@ -565,7 +565,7 @@ function abortAndResetMerge(basePath, hasMergeHead, squashMsgPath) { /* best-effort */ logWarning( "recovery", - `file unlink failed: ${err instanceof Error ? err.message : String(err)}`, + `file unlink failed: ${getErrorMessage(err)}`, ); } } @@ -575,7 +575,7 @@ function abortAndResetMerge(basePath, hasMergeHead, squashMsgPath) { /* best-effort */ logError( "recovery", - `git reset failed: ${err instanceof Error ? err.message : String(err)}`, + `git reset failed: ${getErrorMessage(err)}`, ); } } diff --git a/src/resources/extensions/sf/auto-start.js b/src/resources/extensions/sf/auto-start.js index 9325c5859..d79819719 100644 --- a/src/resources/extensions/sf/auto-start.js +++ b/src/resources/extensions/sf/auto-start.js @@ -112,6 +112,7 @@ import { isInsideWorktreesDir, } from "./worktree-manager.js"; import { emitWorktreeOrphaned } from "./worktree-telemetry.js"; +import { getErrorMessage } from "./error-utils.js"; /** * Bootstrap a fresh autonomous mode session. Handles everything from git init @@ -143,7 +144,7 @@ export async function openProjectDbIfPresent(basePath) { } catch (err) { logWarning( "engine", - `sf-db: failed to open existing database: ${err instanceof Error ? err.message : String(err)}`, + `sf-db: failed to open existing database: ${getErrorMessage(err)}`, ); } } @@ -242,7 +243,7 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode) { } catch (err) { logWarning( "engine", - `worktree-orphaned telemetry failed for ${milestoneId}: ${err instanceof Error ? err.message : String(err)}`, + `worktree-orphaned telemetry failed for ${milestoneId}: ${getErrorMessage(err)}`, ); } continue; @@ -261,7 +262,7 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode) { ); } catch (err) { warnings.push( - `Failed to delete merged branch ${branch}: ${err instanceof Error ? err.message : String(err)}`, + `Failed to delete merged branch ${branch}: ${getErrorMessage(err)}`, ); } // Clean up orphaned worktree directory if it exists @@ -318,7 +319,7 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode) { } catch (err) { logWarning( "engine", - `worktree-orphaned telemetry failed for ${milestoneId}: ${err instanceof Error ? err.message : String(err)}`, + `worktree-orphaned telemetry failed for ${milestoneId}: ${getErrorMessage(err)}`, ); } } @@ -470,7 +471,7 @@ export async function bootstrapAutoSession( /* nothing to commit */ logWarning( "engine", - `mkdir failed: ${err instanceof Error ? err.message : String(err)}`, + `mkdir failed: ${getErrorMessage(err)}`, ); } } @@ -524,7 +525,7 @@ export async function bootstrapAutoSession( // Non-fatal — defensive cleanup, never block bootstrap logWarning( "bootstrap", - `stale slice runtime reconciliation failed: ${err instanceof Error ? err.message : String(err)}`, + `stale slice runtime reconciliation failed: ${getErrorMessage(err)}`, ); } // Open the project-root DB before deriveState so DB-backed state @@ -543,7 +544,7 @@ export async function bootstrapAutoSession( // Non-fatal — defensive cleanup, never block bootstrap logWarning( "bootstrap", - `durable complete runtime reconciliation failed: ${err instanceof Error ? err.message : String(err)}`, + `durable complete runtime reconciliation failed: ${getErrorMessage(err)}`, ); } // ── Orphaned milestone branch audit ── @@ -571,7 +572,7 @@ export async function bootstrapAutoSession( // Non-fatal — the audit is defensive, never block bootstrap logWarning( "bootstrap", - `orphaned milestone branch audit failed: ${err instanceof Error ? err.message : String(err)}`, + `orphaned milestone branch audit failed: ${getErrorMessage(err)}`, ); } let state = await deriveState(base); @@ -953,7 +954,7 @@ export async function bootstrapAutoSession( } catch (err) { logWarning( "bootstrap", - `Could not auto-checkout from stale milestone branch: ${err instanceof Error ? err.message : String(err)}`, + `Could not auto-checkout from stale milestone branch: ${getErrorMessage(err)}`, ); } } @@ -1190,7 +1191,7 @@ export async function bootstrapAutoSession( } } catch (err) { ctx.ui.notify( - `Secrets collection error: ${err instanceof Error ? err.message : String(err)}. Continuing with next task.`, + `Secrets collection error: ${getErrorMessage(err)}. Continuing with next task.`, "warning", ); } @@ -1252,7 +1253,7 @@ export async function bootstrapAutoSession( /* non-fatal */ logWarning( "engine", - `preflight validation failed: ${err instanceof Error ? err.message : String(err)}`, + `preflight validation failed: ${getErrorMessage(err)}`, ); } return true; diff --git a/src/resources/extensions/sf/auto-timers.js b/src/resources/extensions/sf/auto-timers.js index bb3bb292a..83247914e 100644 --- a/src/resources/extensions/sf/auto-timers.js +++ b/src/resources/extensions/sf/auto-timers.js @@ -38,6 +38,7 @@ import { writeUnitRuntimeRecord, } from "./uok/unit-runtime.js"; import { logError, logWarning } from "./workflow-logger.js"; +import { getErrorMessage } from "./error-utils.js"; /** * Set up all four supervision timers for the current unit: * 1. Soft timeout warning (wrapup) @@ -104,7 +105,7 @@ export function startUnitSupervision(sctx) { // Non-fatal — fall through with no estimate logWarning( "timer", - `operation failed: ${err instanceof Error ? err.message : String(err)}`, + `operation failed: ${getErrorMessage(err)}`, ); } } @@ -424,7 +425,7 @@ export function startUnitSupervision(sctx) { ); await pauseAuto(ctx, pi); } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = getErrorMessage(err); logError("timer", `[idle-watchdog] Unhandled error: ${message}`); // Unblock any pending unit promise so the auto-loop is not orphaned. resolveAgentEndCancelled({ @@ -438,7 +439,7 @@ export function startUnitSupervision(sctx) { /* best effort */ logWarning( "timer", - `notification failed: ${err instanceof Error ? err.message : String(err)}`, + `notification failed: ${getErrorMessage(err)}`, ); } } @@ -485,7 +486,7 @@ export function startUnitSupervision(sctx) { ); await pauseAuto(ctx, pi); } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = getErrorMessage(err); logError("timer", `[hard-timeout] Unhandled error: ${message}`); // Unblock any pending unit promise so the auto-loop is not orphaned. resolveAgentEndCancelled({ @@ -499,7 +500,7 @@ export function startUnitSupervision(sctx) { /* best effort */ logWarning( "timer", - `notification failed: ${err instanceof Error ? err.message : String(err)}`, + `notification failed: ${getErrorMessage(err)}`, ); } } diff --git a/src/resources/extensions/sf/auto-worktree.js b/src/resources/extensions/sf/auto-worktree.js index 6eef0e6a9..754e99ef3 100644 --- a/src/resources/extensions/sf/auto-worktree.js +++ b/src/resources/extensions/sf/auto-worktree.js @@ -68,6 +68,7 @@ import { resolveGitDir, worktreePath, } from "./worktree-manager.js"; +import { getErrorMessage } from "./error-utils.js"; const PROJECT_PREFERENCES_FILE = "preferences.yaml"; // ─── Shared Constants & Helpers ───────────────────────────────────────────── @@ -152,7 +153,7 @@ function forceOverwriteAssessmentsWithVerdict( /* non-fatal per file */ logWarning( "worktree", - `assessment force-copy failed: ${err instanceof Error ? err.message : String(err)}`, + `assessment force-copy failed: ${getErrorMessage(err)}`, ); } } @@ -160,7 +161,7 @@ function forceOverwriteAssessmentsWithVerdict( /* non-fatal per slice */ logWarning( "worktree", - `assessment slice scan failed: ${err instanceof Error ? err.message : String(err)}`, + `assessment slice scan failed: ${getErrorMessage(err)}`, ); } } @@ -168,7 +169,7 @@ function forceOverwriteAssessmentsWithVerdict( /* non-fatal */ logWarning( "worktree", - `assessment sync failed: ${err instanceof Error ? err.message : String(err)}`, + `assessment sync failed: ${getErrorMessage(err)}`, ); } } @@ -190,7 +191,7 @@ function clearProjectRootStateFiles(basePath, milestoneId) { if (err.code !== "ENOENT") { logWarning( "worktree", - `file unlink failed: ${err instanceof Error ? err.message : String(err)}`, + `file unlink failed: ${getErrorMessage(err)}`, ); } } @@ -228,7 +229,7 @@ function clearProjectRootStateFiles(basePath, milestoneId) { if (code !== "ENOENT" && code !== "EISDIR") { logWarning( "worktree", - `untracked file unlink failed: ${err instanceof Error ? err.message : String(err)}`, + `untracked file unlink failed: ${getErrorMessage(err)}`, ); } } @@ -239,7 +240,7 @@ function clearProjectRootStateFiles(basePath, milestoneId) { /* non-fatal — git command may fail if not in repo */ logWarning( "worktree", - `untracked file cleanup failed: ${err instanceof Error ? err.message : String(err)}`, + `untracked file cleanup failed: ${getErrorMessage(err)}`, ); } } @@ -346,7 +347,7 @@ export function syncProjectRootToWorktree( /* non-fatal */ logWarning( "worktree", - `worktree DB cleanup failed: ${err instanceof Error ? err.message : String(err)}`, + `worktree DB cleanup failed: ${getErrorMessage(err)}`, ); } } @@ -496,7 +497,7 @@ export function cleanStaleRuntimeUnits(sfRootPath, hasMilestoneSummary) { /* non-fatal */ logWarning( "worktree", - `stale runtime unit unlink failed (${file}): ${err instanceof Error ? err.message : String(err)}`, + `stale runtime unit unlink failed (${file}): ${getErrorMessage(err)}`, ); } } @@ -505,7 +506,7 @@ export function cleanStaleRuntimeUnits(sfRootPath, hasMilestoneSummary) { /* non-fatal */ logWarning( "worktree", - `stale runtime unit cleanup failed: ${err instanceof Error ? err.message : String(err)}`, + `stale runtime unit cleanup failed: ${getErrorMessage(err)}`, ); } return cleaned; @@ -544,7 +545,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) { /* non-fatal */ logWarning( "worktree", - `file copy failed (${f}): ${err instanceof Error ? err.message : String(err)}`, + `file copy failed (${f}): ${getErrorMessage(err)}`, ); } } @@ -565,7 +566,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) { /* non-fatal */ logWarning( "worktree", - `preferences copy failed (${PROJECT_PREFERENCES_FILE}): ${err instanceof Error ? err.message : String(err)}`, + `preferences copy failed (${PROJECT_PREFERENCES_FILE}): ${getErrorMessage(err)}`, ); } } @@ -594,7 +595,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) { /* non-fatal */ logWarning( "worktree", - `milestone copy failed (${mid}): ${err instanceof Error ? err.message : String(err)}`, + `milestone copy failed (${mid}): ${getErrorMessage(err)}`, ); } } else { @@ -618,7 +619,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) { /* non-fatal */ logWarning( "worktree", - `milestone file copy failed (${mid}/${f}): ${err instanceof Error ? err.message : String(err)}`, + `milestone file copy failed (${mid}/${f}): ${getErrorMessage(err)}`, ); } } @@ -634,7 +635,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) { /* non-fatal */ logWarning( "worktree", - `slices copy failed (${mid}): ${err instanceof Error ? err.message : String(err)}`, + `slices copy failed (${mid}): ${getErrorMessage(err)}`, ); } } else if (existsSync(srcSlicesDir) && existsSync(dstSlicesDir)) { @@ -655,7 +656,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) { /* non-fatal */ logWarning( "worktree", - `slice copy failed (${mid}/${sid}): ${err instanceof Error ? err.message : String(err)}`, + `slice copy failed (${mid}/${sid}): ${getErrorMessage(err)}`, ); } } @@ -665,7 +666,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) { /* non-fatal */ logWarning( "worktree", - `milestone file sync failed: ${err instanceof Error ? err.message : String(err)}`, + `milestone file sync failed: ${getErrorMessage(err)}`, ); } } @@ -674,7 +675,7 @@ export function syncSfStateToWorktree(mainBasePath, worktreePath_) { /* non-fatal */ logWarning( "worktree", - `milestone directory sync failed: ${err instanceof Error ? err.message : String(err)}`, + `milestone directory sync failed: ${getErrorMessage(err)}`, ); } } @@ -722,7 +723,7 @@ export function syncWorktreeStateBack(mainBasePath, worktreePath, milestoneId) { // Non-fatal — file sync below is the fallback logError( "worktree", - `DB reconciliation failed: ${err instanceof Error ? err.message : String(err)}`, + `DB reconciliation failed: ${getErrorMessage(err)}`, ); } } @@ -743,7 +744,7 @@ export function syncWorktreeStateBack(mainBasePath, worktreePath, milestoneId) { /* non-fatal */ logWarning( "worktree", - `state file copy-back failed (${f}): ${err instanceof Error ? err.message : String(err)}`, + `state file copy-back failed (${f}): ${getErrorMessage(err)}`, ); } } @@ -768,7 +769,7 @@ export function syncWorktreeStateBack(mainBasePath, worktreePath, milestoneId) { /* non-fatal */ logWarning( "worktree", - `milestone sync-back failed: ${err instanceof Error ? err.message : String(err)}`, + `milestone sync-back failed: ${getErrorMessage(err)}`, ); } return { synced }; @@ -791,7 +792,7 @@ function syncDirFiles(srcDir, dstDir, filter, synced, prefix) { /* non-fatal */ logWarning( "worktree", - `file copy failed (${prefix}${entry.name}): ${err instanceof Error ? err.message : String(err)}`, + `file copy failed (${prefix}${entry.name}): ${getErrorMessage(err)}`, ); } } @@ -799,7 +800,7 @@ function syncDirFiles(srcDir, dstDir, filter, synced, prefix) { /* non-fatal — srcDir may not be readable */ logWarning( "worktree", - `directory read failed: ${err instanceof Error ? err.message : String(err)}`, + `directory read failed: ${getErrorMessage(err)}`, ); } } @@ -854,7 +855,7 @@ function syncMilestoneDir(wtSf, mainSf, mid, synced) { /* non-fatal */ logWarning( "worktree", - `milestone slice sync failed (${mid}): ${err instanceof Error ? err.message : String(err)}`, + `milestone slice sync failed (${mid}): ${getErrorMessage(err)}`, ); } } @@ -893,7 +894,7 @@ export function runWorktreePostCreateHook(sourceDir, worktreeDir, hookPath) { /* keep original */ logWarning( "worktree", - `realpath failed: ${err instanceof Error ? err.message : String(err)}`, + `realpath failed: ${getErrorMessage(err)}`, ); } } @@ -915,7 +916,7 @@ export function runWorktreePostCreateHook(sourceDir, worktreeDir, hookPath) { shell: needsShell, }); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); errors.push(`Worktree post-create hook failed: ${msg}`); } } @@ -939,7 +940,7 @@ export function runWorktreePostCreateHook(sourceDir, worktreeDir, hookPath) { timeout: 60_000, }); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); errors.push(`workspace.after_create hook failed: ${msg}`); } } @@ -999,7 +1000,7 @@ function reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId) { /* non-fatal */ logWarning( "worktree", - `walkMd directory read failed: ${err instanceof Error ? err.message : String(err)}`, + `walkMd directory read failed: ${getErrorMessage(err)}`, ); } return results; @@ -1051,7 +1052,7 @@ function reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId) { /* non-fatal */ logWarning( "worktree", - `plan checkbox reconcile write failed: ${err instanceof Error ? err.message : String(err)}`, + `plan checkbox reconcile write failed: ${getErrorMessage(err)}`, ); } } @@ -1148,7 +1149,7 @@ export function createAutoWorktree(basePath, milestoneId) { // Don't store originalBase -- caller can retry or clean up. throw new SFError( SF_IO_ERROR, - `Auto-worktree created at ${info.path} but chdir failed: ${err instanceof Error ? err.message : String(err)}`, + `Auto-worktree created at ${info.path} but chdir failed: ${getErrorMessage(err)}`, ); } nudgeGitBranchCache(previousCwd); @@ -1212,7 +1213,7 @@ export function teardownAutoWorktree(originalBasePath, milestoneId, opts = {}) { } catch (err) { throw new SFError( SF_IO_ERROR, - `Failed to chdir back to ${originalBasePath} during teardown: ${err instanceof Error ? err.message : String(err)}`, + `Failed to chdir back to ${originalBasePath} during teardown: ${getErrorMessage(err)}`, ); } nudgeGitBranchCache(previousCwd); @@ -1241,7 +1242,7 @@ export function teardownAutoWorktree(originalBasePath, milestoneId, opts = {}) { // Non-fatal — the warning above tells the user how to clean up logWarning( "worktree", - `worktree directory removal failed: ${err instanceof Error ? err.message : String(err)}`, + `worktree directory removal failed: ${getErrorMessage(err)}`, ); } } else { @@ -1335,7 +1336,7 @@ export function enterAutoWorktree(basePath, milestoneId) { } catch (err) { throw new SFError( SF_IO_ERROR, - `Failed to enter auto-worktree at ${p}: ${err instanceof Error ? err.message : String(err)}`, + `Failed to enter auto-worktree at ${p}: ${getErrorMessage(err)}`, ); } nudgeGitBranchCache(previousCwd); @@ -1460,7 +1461,7 @@ export function mergeMilestoneToMain( /* non-fatal */ logError( "worktree", - `DB reconciliation failed: ${err instanceof Error ? err.message : String(err)}`, + `DB reconciliation failed: ${getErrorMessage(err)}`, ); } } @@ -1632,7 +1633,7 @@ export function mergeMilestoneToMain( // report the dirty tree if it fails. logWarning( "worktree", - `git stash failed: ${err instanceof Error ? err.message : String(err)}`, + `git stash failed: ${getErrorMessage(err)}`, ); } // 7a. Shelter queued milestone directories before the squash merge (#2505). @@ -1659,7 +1660,7 @@ export function mergeMilestoneToMain( /* best-effort */ logError( "worktree", - `shelter restore failed: ${err instanceof Error ? err.message : String(err)}`, + `shelter restore failed: ${getErrorMessage(err)}`, ); } } @@ -1669,7 +1670,7 @@ export function mergeMilestoneToMain( /* best-effort */ logWarning( "worktree", - `shelter cleanup failed: ${err instanceof Error ? err.message : String(err)}`, + `shelter cleanup failed: ${getErrorMessage(err)}`, ); } }; @@ -1691,7 +1692,7 @@ export function mergeMilestoneToMain( // Non-fatal — if shelter fails, the merge may still succeed logWarning( "worktree", - `milestone shelter failed (${entry.name}): ${err instanceof Error ? err.message : String(err)}`, + `milestone shelter failed (${entry.name}): ${getErrorMessage(err)}`, ); } } @@ -1700,7 +1701,7 @@ export function mergeMilestoneToMain( // Non-fatal — proceed with merge; untracked files may block it logWarning( "worktree", - `milestone shelter operation failed: ${err instanceof Error ? err.message : String(err)}`, + `milestone shelter operation failed: ${getErrorMessage(err)}`, ); } // 7b. Clean up stale merge state before attempting squash merge (#2912). @@ -1718,7 +1719,7 @@ export function mergeMilestoneToMain( /* best-effort */ logError( "worktree", - `merge state cleanup failed: ${err instanceof Error ? err.message : String(err)}`, + `merge state cleanup failed: ${getErrorMessage(err)}`, ); } // 8. Squash merge — auto-resolve .sf/ state file conflicts (#530) @@ -1740,7 +1741,7 @@ export function mergeMilestoneToMain( /* best-effort */ logError( "worktree", - `merge state cleanup failed: ${err instanceof Error ? err.message : String(err)}`, + `merge state cleanup failed: ${getErrorMessage(err)}`, ); } // Pop stash before throwing so local work is not lost. @@ -1755,7 +1756,7 @@ export function mergeMilestoneToMain( /* stash pop conflict is non-fatal */ logWarning( "worktree", - `git stash pop failed: ${err instanceof Error ? err.message : String(err)}`, + `git stash pop failed: ${getErrorMessage(err)}`, ); } } @@ -1815,7 +1816,7 @@ export function mergeMilestoneToMain( /* best-effort */ logError( "worktree", - `git merge-abort failed: ${err instanceof Error ? err.message : String(err)}`, + `git merge-abort failed: ${getErrorMessage(err)}`, ); } try { @@ -1828,7 +1829,7 @@ export function mergeMilestoneToMain( /* best-effort */ logError( "worktree", - `merge state file cleanup failed: ${err instanceof Error ? err.message : String(err)}`, + `merge state file cleanup failed: ${getErrorMessage(err)}`, ); } // Pop stash before throwing so local work is not lost (#2151). @@ -1843,7 +1844,7 @@ export function mergeMilestoneToMain( /* stash pop conflict is non-fatal */ logWarning( "worktree", - `git stash pop failed: ${err instanceof Error ? err.message : String(err)}`, + `git stash pop failed: ${getErrorMessage(err)}`, ); } } @@ -1883,7 +1884,7 @@ export function mergeMilestoneToMain( /* best-effort */ logError( "worktree", - `post-commit merge state cleanup failed: ${err instanceof Error ? err.message : String(err)}`, + `post-commit merge state cleanup failed: ${getErrorMessage(err)}`, ); } // 9a-ii. Restore stashed files now that the merge+commit is complete (#2151). @@ -1942,7 +1943,7 @@ export function mergeMilestoneToMain( /* stash may already be consumed */ logWarning( "worktree", - `git stash drop failed: ${err instanceof Error ? err.message : String(err)}`, + `git stash drop failed: ${getErrorMessage(err)}`, ); } } else { @@ -2022,7 +2023,7 @@ export function mergeMilestoneToMain( // Push failure is non-fatal logWarning( "worktree", - `git push failed: ${err instanceof Error ? err.message : String(err)}`, + `git push failed: ${getErrorMessage(err)}`, ); } } @@ -2065,7 +2066,7 @@ export function mergeMilestoneToMain( // PR creation failure is non-fatal — gh may not be installed or authenticated logWarning( "worktree", - `PR creation failed: ${err instanceof Error ? err.message : String(err)}`, + `PR creation failed: ${getErrorMessage(err)}`, ); } } @@ -2125,7 +2126,7 @@ export function mergeMilestoneToMain( // Best-effort -- worktree dir may already be gone logWarning( "worktree", - `worktree removal failed: ${err instanceof Error ? err.message : String(err)}`, + `worktree removal failed: ${getErrorMessage(err)}`, ); } // 13. Delete milestone branch (after worktree removal so ref is unlocked) @@ -2135,7 +2136,7 @@ export function mergeMilestoneToMain( // Best-effort logWarning( "worktree", - `git branch-delete failed: ${err instanceof Error ? err.message : String(err)}`, + `git branch-delete failed: ${getErrorMessage(err)}`, ); } // 14. Clear module state diff --git a/src/resources/extensions/sf/auto.js b/src/resources/extensions/sf/auto.js index 6bb913f86..dab4859d3 100644 --- a/src/resources/extensions/sf/auto.js +++ b/src/resources/extensions/sf/auto.js @@ -418,7 +418,7 @@ export function getAutoDashboardData() { // Non-fatal — captures module may not be loaded logWarning( "engine", - `capture count failed: ${err instanceof Error ? err.message : String(err)}`, + `capture count failed: ${getErrorMessage(err)}`, { file: "auto.ts" }, ); } @@ -748,7 +748,7 @@ function cleanupAfterLoopExit(ctx) { /* best-effort — mirror stopAuto cleanup */ logWarning( "session", - `lock cleanup failed: ${err instanceof Error ? err.message : String(err)}`, + `lock cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" }, ); } @@ -769,7 +769,7 @@ function cleanupAfterLoopExit(ctx) { /* best-effort */ logWarning( "engine", - `chdir failed: ${err instanceof Error ? err.message : String(err)}`, + `chdir failed: ${getErrorMessage(err)}`, { file: "auto.ts" }, ); } @@ -869,7 +869,7 @@ export async function stopAuto(ctx, pi, reason) { // Non-fatal — fall through to preserveBranch path logWarning( "engine", - `milestone summary check failed: ${err instanceof Error ? err.message : String(err)}`, + `milestone summary check failed: ${getErrorMessage(err)}`, { file: "auto.ts" }, ); } @@ -923,7 +923,7 @@ export async function stopAuto(ctx, pi, reason) { /* best-effort */ logWarning( "engine", - `chdir failed: ${err instanceof Error ? err.message : String(err)}`, + `chdir failed: ${getErrorMessage(err)}`, { file: "auto.ts" }, ); } @@ -1051,7 +1051,7 @@ export async function stopAuto(ctx, pi, reason) { /* non-fatal */ logWarning( "engine", - `file unlink failed: ${err instanceof Error ? err.message : String(err)}`, + `file unlink failed: ${getErrorMessage(err)}`, { file: "auto.ts" }, ); } @@ -1094,7 +1094,7 @@ export async function stopAuto(ctx, pi, reason) { /* non-fatal: browser-tools may not be loaded */ logWarning( "engine", - `browser teardown failed: ${err instanceof Error ? err.message : String(err)}`, + `browser teardown failed: ${getErrorMessage(err)}`, { file: "auto.ts" }, ); } @@ -1151,7 +1151,7 @@ export async function stopAuto(ctx, pi, reason) { } catch (err) { logWarning( "engine", - `auto-exit telemetry failed: ${err instanceof Error ? err.message : String(err)}`, + `auto-exit telemetry failed: ${getErrorMessage(err)}`, ); } // Drop the active-tool baseline so a subsequent /autonomous run on the @@ -1222,7 +1222,7 @@ export async function pauseAuto(ctx, _pi, _errorContext) { // Non-fatal — resume will still work via full bootstrap, just without worktree context logWarning( "engine", - `paused-session file write failed: ${err instanceof Error ? err.message : String(err)}`, + `paused-session file write failed: ${getErrorMessage(err)}`, { file: "auto.ts" }, ); } @@ -1240,7 +1240,7 @@ export async function pauseAuto(ctx, _pi, _errorContext) { // Non-fatal — best-effort closeout on pause logWarning( "engine", - `unit closeout on pause failed: ${err instanceof Error ? err.message : String(err)}`, + `unit closeout on pause failed: ${getErrorMessage(err)}`, { file: "auto.ts" }, ); } @@ -1593,7 +1593,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) { if (err.code !== "ENOENT") { logWarning( "session", - `pause file cleanup failed: ${err instanceof Error ? err.message : String(err)}`, + `pause file cleanup failed: ${getErrorMessage(err)}`, { file: "auto.ts" }, ); } @@ -1665,7 +1665,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) { // Malformed or missing — proceed with fresh bootstrap logWarning( "session", - `paused-session restore failed: ${err instanceof Error ? err.message : String(err)}`, + `paused-session restore failed: ${getErrorMessage(err)}`, { file: "auto.ts" }, ); } @@ -1877,7 +1877,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) { // Best-effort only — sidebar sync must never block autonomous mode startup logWarning( "engine", - `cmux sync failed: ${err instanceof Error ? err.message : String(err)}`, + `cmux sync failed: ${getErrorMessage(err)}`, { file: "auto.ts" }, ); } @@ -2038,7 +2038,7 @@ export async function dispatchHookUnit( /* non-fatal */ logWarning( "dispatch", - `hook model set failed: ${err instanceof Error ? err.message : String(err)}`, + `hook model set failed: ${getErrorMessage(err)}`, { file: "auto.ts" }, ); } @@ -2075,7 +2075,7 @@ export async function dispatchHookUnit( } catch (err) { logWarning( "engine", - `chdir failed before hook dispatch: ${err instanceof Error ? err.message : String(err)}`, + `chdir failed before hook dispatch: ${getErrorMessage(err)}`, { file: "auto.ts" }, ); } diff --git a/src/resources/extensions/sf/auto/infra-errors.js b/src/resources/extensions/sf/auto/infra-errors.js index 85befd156..1ec58bb93 100644 --- a/src/resources/extensions/sf/auto/infra-errors.js +++ b/src/resources/extensions/sf/auto/infra-errors.js @@ -10,6 +10,7 @@ * retrying. Each retry re-dispatches the unit at full LLM cost, so we bail * immediately rather than burning budget on guaranteed failures. */ +import { getErrorMessage } from "../error-utils.js"; export const INFRA_ERROR_CODES = new Set([ "ENOSPC", // disk full "ENOMEM", // out of memory @@ -35,7 +36,7 @@ export function isInfrastructureError(err) { const code = err.code; if (typeof code === "string" && INFRA_ERROR_CODES.has(code)) return code; } - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); for (const code of INFRA_ERROR_CODES) { if (msg.includes(code)) return code; } @@ -65,7 +66,7 @@ export function isTransientCooldownError(err) { return true; } // Fallback: message match for cross-process error propagation - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); return /in a cooldown window/i.test(msg); } /** diff --git a/src/resources/extensions/sf/auto/loop.js b/src/resources/extensions/sf/auto/loop.js index bc3c0389e..1e694b1b5 100644 --- a/src/resources/extensions/sf/auto/loop.js +++ b/src/resources/extensions/sf/auto/loop.js @@ -40,6 +40,7 @@ import { } from "./phases.js"; import { _clearCurrentResolve } from "./resolve.js"; import { MAX_LOOP_ITERATIONS } from "./types.js"; +import { getErrorMessage } from "../error-utils.js"; // ── Stuck detection persistence (#3704) ────────────────────────────────── // Persist stuck detection state to disk so it survives session restarts. @@ -81,7 +82,7 @@ function loadStuckState(basePath) { } catch (err) { debugLog("autoLoop", { phase: "load-stuck-state-failed", - error: err instanceof Error ? err.message : String(err), + error: getErrorMessage(err), }); return { recentUnits: [], stuckRecoveryAttempts: 0 }; } @@ -102,7 +103,7 @@ function saveStuckState(basePath, state) { } catch (err) { debugLog("autoLoop", { phase: "save-stuck-state-failed", - error: err instanceof Error ? err.message : String(err), + error: getErrorMessage(err), }); } } @@ -142,7 +143,7 @@ function hydrateCustomVerifyRetryCounts(s) { } catch (err) { debugLog("autoLoop", { phase: "load-custom-verify-retries-failed", - error: err instanceof Error ? err.message : String(err), + error: getErrorMessage(err), }); } return s.verificationRetryCount; @@ -169,7 +170,7 @@ function saveCustomVerifyRetryCounts(s) { if (code !== "ENOENT") { debugLog("autoLoop", { phase: "save-custom-verify-retries-failed", - error: err instanceof Error ? err.message : String(err), + error: getErrorMessage(err), }); } } @@ -417,7 +418,7 @@ async function runExitSolverEval(ctx, s, deps, iteration) { error: result.error, }); } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = getErrorMessage(err); ctx.ui.notify(`Autonomous solver eval hook failed: ${message}`, "warning", { noticeKind: NOTICE_KIND.TOOL_NOTICE, dedupe_key: `solver-eval-hook-fail:${message.slice(0, 120)}`, @@ -587,7 +588,7 @@ export async function autoLoop(ctx, pi, s, deps) { } catch (err) { logWarning( "dispatch", - `sidecar queue scheduling failed: ${err instanceof Error ? err.message : String(err)}`, + `sidecar queue scheduling failed: ${getErrorMessage(err)}`, ); } } diff --git a/src/resources/extensions/sf/auto/phases.js b/src/resources/extensions/sf/auto/phases.js index 446c7e086..078140d72 100644 --- a/src/resources/extensions/sf/auto/phases.js +++ b/src/resources/extensions/sf/auto/phases.js @@ -121,6 +121,7 @@ import { withTimeout, } from "./finalize-timeout.js"; import { runUnit } from "./run-unit.js"; +import { getErrorMessage } from "../error-utils.js"; import { BUDGET_THRESHOLDS, MAX_FINALIZE_TIMEOUTS, @@ -657,7 +658,7 @@ export async function runPreDispatch(ic, loopState) { } catch (err) { debugLog("autoLoop", { phase: "slice-parallel-check-error", - error: err instanceof Error ? err.message : String(err), + error: getErrorMessage(err), }); // Non-fatal — fall through to sequential dispatch } @@ -696,7 +697,7 @@ export async function runPreDispatch(ic, loopState) { await generateMilestoneReport(s, ctx, s.currentMilestoneId); } catch (err) { ctx.ui.notify( - `Report generation failed: ${err instanceof Error ? err.message : String(err)}`, + `Report generation failed: ${getErrorMessage(err)}`, "warning", ); } @@ -1132,7 +1133,7 @@ export async function runDispatch(ic, preData, loopState) { } } catch (err) { logWarning("engine", "UOK diagnostics dispatch gate failed open", { - error: err instanceof Error ? err.message : String(err), + error: getErrorMessage(err), }); } deps.emitJournalEvent({ @@ -1172,7 +1173,7 @@ export async function runDispatch(ic, preData, loopState) { } } catch (err) { logWarning("engine", "Reasoning assist failed open", { - error: err instanceof Error ? err.message : String(err), + error: getErrorMessage(err), unitType, unitId, }); @@ -1720,7 +1721,7 @@ export async function runGuards(ic, mid, unitType, unitId, sliceId) { } } catch (err) { ctx.ui.notify( - `Secrets collection error: ${err instanceof Error ? err.message : String(err)}. Continuing with next task.`, + `Secrets collection error: ${getErrorMessage(err)}. Continuing with next task.`, "warning", ); } @@ -2662,7 +2663,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) { ? () => { void resumeAutoAfterProviderDelay(pi, ctx).catch((err) => { const message = - err instanceof Error ? err.message : String(err); + getErrorMessage(err); ctx.ui.notify( `Session timeout recovery failed: ${message}`, "error", @@ -2856,7 +2857,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) { /* non-fatal — anchor is advisory */ logWarning( "engine", - `phase anchor failed: ${err instanceof Error ? err.message : String(err)}`, + `phase anchor failed: ${getErrorMessage(err)}`, ); } } diff --git a/src/resources/extensions/sf/autonomous-solver-eval.js b/src/resources/extensions/sf/autonomous-solver-eval.js index 2166a0f33..8e3f73db1 100644 --- a/src/resources/extensions/sf/autonomous-solver-eval.js +++ b/src/resources/extensions/sf/autonomous-solver-eval.js @@ -20,6 +20,7 @@ import { dirname, isAbsolute, join, relative, resolve } from "node:path"; import { atomicWriteSync } from "./atomic-write.js"; import { ensureDbOpen } from "./bootstrap/dynamic-tools.js"; import { sfRoot } from "./paths.js"; +import { getErrorMessage } from "./error-utils.js"; const DEFAULT_TIMEOUT_MS = 5 * 60_000; const MAX_OUTPUT_CHARS = 20_000; @@ -46,7 +47,7 @@ function parseJsonLine(line, index, filePath) { return JSON.parse(line); } catch (err) { throw new Error( - `${filePath}:${index + 1}: invalid JSON: ${err instanceof Error ? err.message : String(err)}`, + `${filePath}:${index + 1}: invalid JSON: ${getErrorMessage(err)}`, ); } } @@ -480,7 +481,7 @@ export async function runAutomaticAutonomousSolverEval(options) { }); return { ok: true, report }; } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = getErrorMessage(err); emitJournalEvent({ ts: new Date().toISOString(), eventType: "solver-eval-auto-failed", @@ -581,7 +582,7 @@ async function recordEvalRunBestEffort(basePath, report) { } catch (err) { return { ok: false, - error: err instanceof Error ? err.message : String(err), + error: getErrorMessage(err), }; } } @@ -662,7 +663,7 @@ export async function handleAutonomousSolverEval( args = parseAutonomousSolverEvalArgs(rawArgs); } catch (err) { ctx.ui.notify( - `Usage: /solver-eval [run|history|show ] [--sample | --cases ] [--run-id ] [--limit ]\n${err instanceof Error ? err.message : String(err)}`, + `Usage: /solver-eval [run|history|show ] [--sample | --cases ] [--run-id ] [--limit ]\n${getErrorMessage(err)}`, "warning", ); return; diff --git a/src/resources/extensions/sf/bootstrap/agent-end-recovery.js b/src/resources/extensions/sf/bootstrap/agent-end-recovery.js index 2c2d3725c..27f52f496 100644 --- a/src/resources/extensions/sf/bootstrap/agent-end-recovery.js +++ b/src/resources/extensions/sf/bootstrap/agent-end-recovery.js @@ -13,6 +13,7 @@ import { import { pauseAutoForProviderError } from "../provider-error-pause.js"; import { logWarning } from "../workflow-logger.js"; import { clearDiscussionFlowState } from "./write-gate.js"; +import { getErrorMessage } from "../error-utils.js"; const retryState = createRetryState(); const GEMINI_CAPACITY_COOLDOWN_MS = 2 * 60_000; @@ -145,7 +146,7 @@ export async function handleAgentEnd(pi, event, ctx) { resetRetryState(retryState); resolveAgentEnd(event); } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = getErrorMessage(err); ctx.ui.notify( `Autonomous mode error after empty-content abort: ${message}. Stopping autonomous mode.`, "error", @@ -228,7 +229,7 @@ export async function handleAgentEnd(pi, event, ctx) { "warning", ); } catch (err) { - const m = err instanceof Error ? err.message : String(err); + const m = getErrorMessage(err); logWarning("bootstrap", `Failed to persist blocked model: ${m}`); } } @@ -280,7 +281,7 @@ export async function handleAgentEnd(pi, event, ctx) { "warning", ); } catch (err) { - const m = err instanceof Error ? err.message : String(err); + const m = getErrorMessage(err); logWarning("bootstrap", `Failed to persist model cooldown: ${m}`); } } @@ -343,7 +344,7 @@ export async function handleAgentEnd(pi, event, ctx) { resetRetryState(retryState); resolveAgentEnd(event); } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = getErrorMessage(err); ctx.ui.notify( `Autonomous mode error in agent_end handler: ${message}. Stopping autonomous mode.`, "error", diff --git a/src/resources/extensions/sf/bootstrap/db-tools.js b/src/resources/extensions/sf/bootstrap/db-tools.js index 7cccc8297..0bd7da75f 100644 --- a/src/resources/extensions/sf/bootstrap/db-tools.js +++ b/src/resources/extensions/sf/bootstrap/db-tools.js @@ -28,6 +28,7 @@ import { } from "../tools/workflow-tool-executors.js"; import { logError } from "../workflow-logger.js"; import { ensureDbOpen } from "./dynamic-tools.js"; +import { getErrorMessage } from "../error-utils.js"; export function registerDbTools(pi) { // ─── save_decision ───────────────────────────────────────────────── const decisionSaveExecute = async ( @@ -68,7 +69,7 @@ export function registerDbTools(pi) { details: { operation: "save_decision", id }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `save_decision tool failed: ${msg}`, { tool: "save_decision", error: String(err), @@ -190,7 +191,7 @@ export function registerDbTools(pi) { details: { operation: "update_requirement", id: params.id }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `update_requirement tool failed: ${msg}`, { tool: "update_requirement", error: String(err), @@ -307,7 +308,7 @@ export function registerDbTools(pi) { details: { operation: "save_requirement", id: result.id }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `save_requirement tool failed: ${msg}`, { tool: "save_requirement", error: String(err), @@ -484,7 +485,7 @@ export function registerDbTools(pi) { }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); return { content: [ { @@ -613,7 +614,7 @@ export function registerDbTools(pi) { }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `report_issue tool failed: ${msg}`, { tool: "report_issue", error: String(err), @@ -769,7 +770,7 @@ export function registerDbTools(pi) { details: { operation: "save_knowledge", id }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `save_knowledge tool failed: ${msg}`, { tool: "save_knowledge", error: String(err), @@ -900,7 +901,7 @@ export function registerDbTools(pi) { }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `resolve_issue tool failed: ${msg}`, { tool: "resolve_issue", error: String(err), @@ -1010,7 +1011,7 @@ export function registerDbTools(pi) { }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `checkpoint tool failed: ${msg}`, { tool: "checkpoint", error: String(err), @@ -1745,7 +1746,7 @@ export function registerDbTools(pi) { }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `plan_task tool failed: ${msg}`, { tool: "plan_task", error: String(err), @@ -2183,7 +2184,7 @@ export function registerDbTools(pi) { }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `skip_slice tool failed: ${msg}`, { tool: "skip_slice", error: String(err), @@ -2758,7 +2759,7 @@ export function registerDbTools(pi) { details: { operation: "chapter_open", id }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); return { content: [{ type: "text", text: `Error opening chapter: ${msg}` }], details: { operation: "chapter_open", error: msg }, @@ -2839,7 +2840,7 @@ export function registerDbTools(pi) { details: { operation: "chapter_close", id: params.id, closed }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); return { content: [{ type: "text", text: `Error closing chapter: ${msg}` }], details: { operation: "chapter_close", error: msg }, diff --git a/src/resources/extensions/sf/bootstrap/exec-tools.js b/src/resources/extensions/sf/bootstrap/exec-tools.js index 1ce1220ce..15d26853f 100644 --- a/src/resources/extensions/sf/bootstrap/exec-tools.js +++ b/src/resources/extensions/sf/bootstrap/exec-tools.js @@ -17,6 +17,7 @@ import { executeExecSearch } from "../tools/exec-search-tool.js"; import { executeSfExec } from "../tools/exec-tool.js"; import { executeResume } from "../tools/resume-tool.js"; import { logWarning } from "../workflow-logger.js"; +import { getErrorMessage } from "../error-utils.js"; export function registerExecTools(pi) { pi.registerTool({ name: "run_command", @@ -68,7 +69,7 @@ export function registerExecTools(pi) { } catch (err) { logWarning( "tool", - `run_command could not load preferences: ${err instanceof Error ? err.message : String(err)}`, + `run_command could not load preferences: ${getErrorMessage(err)}`, ); } onUpdate?.({ diff --git a/src/resources/extensions/sf/bootstrap/journal-tools.js b/src/resources/extensions/sf/bootstrap/journal-tools.js index b80875837..82a9e3c45 100644 --- a/src/resources/extensions/sf/bootstrap/journal-tools.js +++ b/src/resources/extensions/sf/bootstrap/journal-tools.js @@ -1,6 +1,7 @@ import { Type } from "@sinclair/typebox"; import { queryJournal } from "../journal.js"; import { logWarning } from "../workflow-logger.js"; +import { getErrorMessage } from "../error-utils.js"; export function registerJournalTools(pi) { pi.registerTool({ name: "query_journal", @@ -77,7 +78,7 @@ export function registerJournalTools(pi) { details: { operation: "journal_query", count: limited.length }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logWarning("tool", `query_journal tool failed: ${msg}`); return { content: [{ type: "text", text: `Error querying journal: ${msg}` }], diff --git a/src/resources/extensions/sf/bootstrap/register-extension.js b/src/resources/extensions/sf/bootstrap/register-extension.js index 02f22a98f..6ce7d2958 100644 --- a/src/resources/extensions/sf/bootstrap/register-extension.js +++ b/src/resources/extensions/sf/bootstrap/register-extension.js @@ -16,6 +16,7 @@ import { registerQueryTools } from "./query-tools.js"; import { registerHooks } from "./register-hooks.js"; import { registerShortcuts } from "./register-shortcuts.js"; import { registerSessionTodoTool } from "./session-todo-tools.js"; +import { getErrorMessage } from "../error-utils.js"; export { writeCrashLog } from "./crash-log.js"; export function handleRecoverableExtensionProcessError(err) { @@ -101,7 +102,7 @@ export function registerSfExtension(pi) { void loadEcosystemExtensions(pi, ecosystemHandlers).catch((err) => { logWarning( "bootstrap", - `Failed to load ecosystem extensions: ${err instanceof Error ? err.message : String(err)}`, + `Failed to load ecosystem extensions: ${getErrorMessage(err)}`, ); }); }, @@ -113,7 +114,7 @@ export function registerSfExtension(pi) { } catch (err) { logWarning( "bootstrap", - `Failed to register ${name}: ${err instanceof Error ? err.message : String(err)}`, + `Failed to register ${name}: ${getErrorMessage(err)}`, ); } } diff --git a/src/resources/extensions/sf/bootstrap/register-hooks.js b/src/resources/extensions/sf/bootstrap/register-hooks.js index ec5578f1c..9a5957780 100644 --- a/src/resources/extensions/sf/bootstrap/register-hooks.js +++ b/src/resources/extensions/sf/bootstrap/register-hooks.js @@ -108,6 +108,7 @@ import { shouldBlockPendingGateBash, shouldBlockQueueExecution, } from "./write-gate.js"; +import { getErrorMessage } from "../error-utils.js"; // Skip the welcome screen on the very first session_start — cli.ts already // printed it before the TUI launched. Only re-print on /clear (subsequent sessions). @@ -293,7 +294,7 @@ export function registerHooks(pi, ecosystemHandlers = []) { const { logWarning } = await import("../workflow-logger.js"); logWarning( "session-start", - `Failed to initialize metrics-central: ${err instanceof Error ? err.message : String(err)}`, + `Failed to initialize metrics-central: ${getErrorMessage(err)}`, ); } // Apply show_token_cost preference (#1515) diff --git a/src/resources/extensions/sf/canonical-milestone-plan.js b/src/resources/extensions/sf/canonical-milestone-plan.js index 37d892c46..daf0fb668 100644 --- a/src/resources/extensions/sf/canonical-milestone-plan.js +++ b/src/resources/extensions/sf/canonical-milestone-plan.js @@ -14,6 +14,7 @@ import { openDatabase, readTransaction, } from "./sf-db.js"; +import { getErrorMessage } from "./error-utils.js"; function milestoneDir(basePath, milestoneId) { return join(basePath, ".sf", "milestones", milestoneId); @@ -246,7 +247,7 @@ export function getCanonicalMilestonePlan(basePath, milestoneId, options = {}) { return blockedResult( "db-unavailable", milestoneId, - `.sf/sf.db exists but could not be read: ${err instanceof Error ? err.message : String(err)}. Runtime does not fall back to projections when DB is expected; run sf recover.`, + `.sf/sf.db exists but could not be read: ${getErrorMessage(err)}. Runtime does not fall back to projections when DB is expected; run sf recover.`, paths, ); } @@ -273,7 +274,7 @@ export function getCanonicalMilestonePlan(basePath, milestoneId, options = {}) { return blockedResult( "projection-invalid", milestoneId, - err instanceof Error ? err.message : String(err), + getErrorMessage(err), paths, ); } diff --git a/src/resources/extensions/sf/changelog.js b/src/resources/extensions/sf/changelog.js index 9b5e655d7..b953d5a84 100644 --- a/src/resources/extensions/sf/changelog.js +++ b/src/resources/extensions/sf/changelog.js @@ -8,6 +8,7 @@ * Entry point: handleChangelog() called from commands.ts */ // ─── Semver comparison ──────────────────────────────────────────────────────── +import { getErrorMessage } from "./error-utils.js"; function compareSemver(a, b) { const pa = a.split(".").map(Number); const pb = b.split(".").map(Number); @@ -89,7 +90,7 @@ export async function handleChangelog(args, ctx, pi) { } releases = await response.json(); } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = getErrorMessage(err); ctx.ui.notify(`Failed to fetch changelog: ${message}`, "error"); return; } diff --git a/src/resources/extensions/sf/clean-root-preflight.js b/src/resources/extensions/sf/clean-root-preflight.js index b4a7cde41..727732a13 100644 --- a/src/resources/extensions/sf/clean-root-preflight.js +++ b/src/resources/extensions/sf/clean-root-preflight.js @@ -16,6 +16,7 @@ import { execFileSync } from "node:child_process"; import { GIT_NO_PROMPT_ENV } from "./git-constants.js"; import { nativeHasChanges } from "./native-git-bridge.js"; import { logWarning } from "./workflow-logger.js"; +import { getErrorMessage } from "./error-utils.js"; /** * Check the working tree for dirty files before a milestone merge. * @@ -37,7 +38,7 @@ export function preflightCleanRoot(basePath, milestoneId, notify) { // If the status check itself fails, treat as clean and let the merge decide logWarning( "preflight", - `clean-root status check failed: ${err instanceof Error ? err.message : String(err)}`, + `clean-root status check failed: ${getErrorMessage(err)}`, ); return { stashPushed: false, summary: "" }; } @@ -65,7 +66,7 @@ export function preflightCleanRoot(basePath, milestoneId, notify) { }; } catch (err) { // Stash failure is non-fatal — log and let the merge attempt proceed - const msg = `git stash push failed before merge of milestone ${milestoneId}: ${err instanceof Error ? err.message : String(err)}`; + const msg = `git stash push failed before merge of milestone ${milestoneId}: ${getErrorMessage(err)}`; logWarning("preflight", msg); notify( `Auto-stash failed before milestone ${milestoneId} merge — proceeding anyway. ${msg}`, @@ -96,7 +97,7 @@ export function postflightPopStash(basePath, milestoneId, notify) { } catch (err) { // Pop conflicts mean the merged code collides with the stashed changes. // Log a warning — the user needs to resolve manually, but the merge succeeded. - const msg = `git stash pop failed after merge of milestone ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. Run "git stash pop" manually to restore your changes.`; + const msg = `git stash pop failed after merge of milestone ${milestoneId}: ${getErrorMessage(err)}. Run "git stash pop" manually to restore your changes.`; logWarning("preflight", msg); notify(msg, "warning"); } diff --git a/src/resources/extensions/sf/code-intelligence.js b/src/resources/extensions/sf/code-intelligence.js index 0b48f3c64..7f25911b7 100644 --- a/src/resources/extensions/sf/code-intelligence.js +++ b/src/resources/extensions/sf/code-intelligence.js @@ -14,6 +14,7 @@ import { writeFileSync, } from "node:fs"; import { delimiter, isAbsolute, join, relative, resolve } from "node:path"; +import { getErrorMessage } from "./error-utils.js"; const SIFT_BINARY_NAME = process.platform === "win32" ? "sift.exe" : "sift"; const DEFAULT_SIFT_WARMUP_TTL_MS = 6 * 60 * 60 * 1000; @@ -621,7 +622,7 @@ export function ensureSiftIndexWarmup(projectRoot, prefs, options = {}) { } catch (err) { return { status: "error", - reason: err instanceof Error ? err.message : String(err), + reason: getErrorMessage(err), command, args, markerPath, diff --git a/src/resources/extensions/sf/commands-add-tests.js b/src/resources/extensions/sf/commands-add-tests.js index 4efc09c44..e2798a3d6 100644 --- a/src/resources/extensions/sf/commands-add-tests.js +++ b/src/resources/extensions/sf/commands-add-tests.js @@ -9,6 +9,7 @@ import { join } from "node:path"; import { resolveSliceFile, sfRoot } from "./paths.js"; import { loadPrompt } from "./prompt-loader.js"; import { deriveState } from "./state.js"; +import { getErrorMessage } from "./error-utils.js"; function findLastCompletedSlice(basePath, milestoneId) { // Scan disk for slices that have a SUMMARY.md (indicating completion) @@ -126,7 +127,7 @@ export async function handleAddTests(args, ctx, pi) { { triggerTurn: true }, ); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); ctx.ui.notify(`Failed to dispatch test generation: ${msg}`, "error"); } } diff --git a/src/resources/extensions/sf/commands-debug.js b/src/resources/extensions/sf/commands-debug.js index 1507322fc..d2eb77502 100644 --- a/src/resources/extensions/sf/commands-debug.js +++ b/src/resources/extensions/sf/commands-debug.js @@ -6,6 +6,7 @@ import { updateDebugSession, } from "./debug-session-store.js"; import { loadPrompt } from "./prompt-loader.js"; +import { getErrorMessage } from "./error-utils.js"; const SUBCOMMANDS = new Set(["list", "status", "continue", "--diagnose"]); function isValidSlugCandidate(input) { @@ -127,7 +128,7 @@ export async function handleDebug(args, ctx, pi) { { triggerTurn: true }, ); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); ctx.ui.notify( `Debug dispatch failed: ${msg}\nSession '${s.slug}' is persisted; retry with /debug continue ${s.slug}`, "warning", @@ -364,7 +365,7 @@ export async function handleDebug(args, ctx, pi) { { triggerTurn: true }, ); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); ctx.ui.notify( `Continue dispatch failed: ${msg}\nSession '${resumed.session.slug}' is persisted; retry with /debug continue ${resumed.session.slug}`, "warning", @@ -418,7 +419,7 @@ export async function handleDebug(args, ctx, pi) { { triggerTurn: true }, ); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); ctx.ui.notify( `Diagnose dispatch failed: ${msg}\nSession '${s.slug}' is persisted; continue manually with /debug continue ${s.slug}`, "warning", diff --git a/src/resources/extensions/sf/commands-eval-review.js b/src/resources/extensions/sf/commands-eval-review.js index 879afbda7..2c86668e8 100644 --- a/src/resources/extensions/sf/commands-eval-review.js +++ b/src/resources/extensions/sf/commands-eval-review.js @@ -44,6 +44,7 @@ import { resolveSlicePath, } from "./paths.js"; import { deriveState } from "./state.js"; +import { getErrorMessage } from "./error-utils.js"; // ─── Constants ──────────────────────────────────────────────────────────────── /** * Slice-ID format. Must match the canonical `/^S\d+$/` used elsewhere in the @@ -228,7 +229,7 @@ export async function buildEvalReviewContext( specTruncated = true; } } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); spec = bestFitMarker( remaining, `[truncated: failed to read AI-SPEC.md (${msg})]`, @@ -553,7 +554,7 @@ export async function handleEvalReview(args, ctx, pi) { "info", ); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); ctx.ui.notify(`Failed to read ${action.path}: ${msg}`, "error"); } return; @@ -581,7 +582,7 @@ export async function handleEvalReview(args, ctx, pi) { try { context = await buildEvalReviewContext(detected, milestoneId); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); ctx.ui.notify(`Failed to build eval-review context: ${msg}`, "error"); return; } diff --git a/src/resources/extensions/sf/commands-handlers.js b/src/resources/extensions/sf/commands-handlers.js index 221a8f6ab..c09955654 100644 --- a/src/resources/extensions/sf/commands-handlers.js +++ b/src/resources/extensions/sf/commands-handlers.js @@ -30,6 +30,7 @@ import { sfRoot } from "./paths.js"; import { sfHome } from "./sf-home.js"; import { loadPrompt } from "./prompt-loader.js"; import { deriveState } from "./state.js"; +import { getErrorMessage } from "./error-utils.js"; const UPDATE_REGISTRY_URL = "https://registry.npmjs.org/singularity-forge/latest"; @@ -353,7 +354,7 @@ export async function handleTriage(args, ctx, pi, basePath) { ); } catch (err) { ctx.ui.notify( - `TODO triage failed: ${err instanceof Error ? err.message : String(err)}`, + `TODO triage failed: ${getErrorMessage(err)}`, "warning", ); } diff --git a/src/resources/extensions/sf/commands-maintenance.js b/src/resources/extensions/sf/commands-maintenance.js index a0c241f2a..7f3a9181e 100644 --- a/src/resources/extensions/sf/commands-maintenance.js +++ b/src/resources/extensions/sf/commands-maintenance.js @@ -13,6 +13,7 @@ import { } from "./native-git-bridge.js"; import { deriveState } from "./state.js"; import { logWarning } from "./workflow-logger.js"; +import { getErrorMessage } from "./error-utils.js"; /** * Clean up merged and stale milestone branches. */ @@ -625,7 +626,7 @@ export async function handleRecover(ctx, basePath) { ); ctx.ui.notify(lines.join("\n"), "success"); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logWarning("command", `recover failed: ${msg}`); ctx.ui.notify(`sf recover failed: ${msg}`, "error"); } diff --git a/src/resources/extensions/sf/commands-pr-branch.js b/src/resources/extensions/sf/commands-pr-branch.js index edfb7be74..ce5126d94 100644 --- a/src/resources/extensions/sf/commands-pr-branch.js +++ b/src/resources/extensions/sf/commands-pr-branch.js @@ -11,6 +11,7 @@ import { nativeDetectMainBranch, nativeGetCurrentBranch, } from "./native-git-bridge.js"; +import { getErrorMessage } from "./error-utils.js"; const EXCLUDED_PATHS = [".sf", ".planning", "PLAN.md"]; function git(basePath, args) { @@ -213,7 +214,7 @@ export async function handlePrBranch(args, ctx) { gitAllowFail(basePath, ["cherry-pick", "--abort"]); gitAllowFail(basePath, ["reset", "--hard", "HEAD"]); gitAllowFail(basePath, ["checkout", currentBranch]); - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); ctx.ui.notify(`Failed to create PR branch: ${msg}`, "error"); } } diff --git a/src/resources/extensions/sf/commands-scan.js b/src/resources/extensions/sf/commands-scan.js index 608eeee4f..1504083b6 100644 --- a/src/resources/extensions/sf/commands-scan.js +++ b/src/resources/extensions/sf/commands-scan.js @@ -16,6 +16,7 @@ import { existsSync, mkdirSync } from "node:fs"; import { join, relative } from "node:path"; import { loadPrompt } from "./prompt-loader.js"; +import { getErrorMessage } from "./error-utils.js"; // ─── Constants ──────────────────────────────────────────────────────────────── export const DEFAULT_FOCUS = "tech+arch"; export const VALID_FOCUS_AREAS = [ @@ -97,7 +98,7 @@ export async function handleScan(args, ctx, pi) { { triggerTurn: true }, ); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); ctx.ui.notify(`Failed to dispatch scan: ${msg}`, "error"); } } diff --git a/src/resources/extensions/sf/commands-ship.js b/src/resources/extensions/sf/commands-ship.js index d774fe5f8..ac128c8ff 100644 --- a/src/resources/extensions/sf/commands-ship.js +++ b/src/resources/extensions/sf/commands-ship.js @@ -25,6 +25,7 @@ import { resolveSlicePath, } from "./paths.js"; import { deriveState } from "./state.js"; +import { getErrorMessage } from "./error-utils.js"; function git(basePath, args) { return execFileSync("git", args, { cwd: basePath, encoding: "utf-8" }).trim(); @@ -231,7 +232,7 @@ export async function handleShip(args, ctx, _pi) { }).trim(); ctx.ui.notify(`PR created: ${prUrl}`, "success"); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); ctx.ui.notify(`Failed to create PR: ${msg}`, "error"); } } diff --git a/src/resources/extensions/sf/commands-todo.js b/src/resources/extensions/sf/commands-todo.js index def823575..68f160a5a 100644 --- a/src/resources/extensions/sf/commands-todo.js +++ b/src/resources/extensions/sf/commands-todo.js @@ -27,6 +27,7 @@ import { insertTriageSkill, openDatabase, } from "./sf-db.js"; +import { getErrorMessage } from "./error-utils.js"; const _EMPTY_TODO = "# TODO\n\nDump anything here.\n"; const MAX_DUMP_CHARS = 48_000; @@ -505,7 +506,7 @@ export function validateJsonlFile(path, schemaName) { } catch (err) { return { ok: false, - error: `${schemaName} line ${i + 1}: ${err instanceof Error ? err.message : String(err)}`, + error: `${schemaName} line ${i + 1}: ${getErrorMessage(err)}`, }; } } @@ -665,7 +666,7 @@ export async function handleTodo(args, ctx, _pi) { ); } catch (err) { ctx.ui.notify( - `TODO triage failed: ${err instanceof Error ? err.message : String(err)}`, + `TODO triage failed: ${getErrorMessage(err)}`, "warning", ); } diff --git a/src/resources/extensions/sf/commands-worktree.js b/src/resources/extensions/sf/commands-worktree.js index a31bd7497..7b717db75 100644 --- a/src/resources/extensions/sf/commands-worktree.js +++ b/src/resources/extensions/sf/commands-worktree.js @@ -14,6 +14,7 @@ import { nativeHasChanges, } from "./native-git-bridge.js"; import { autoCommitCurrentBranch } from "./worktree.js"; +import { getErrorMessage } from "./error-utils.js"; import { diffWorktreeAll, diffWorktreeNumstat, @@ -146,7 +147,7 @@ async function handleMerge(args, ctx) { removeWorktree(basePath, target, { deleteBranch: true }); ctx.ui.notify(`Removed empty worktree ${target}.`, "info"); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); ctx.ui.notify( `Worktree partially removed: ${msg}\n\nRun 'git worktree prune' to clean up any dangling registrations.`, "error", @@ -158,7 +159,7 @@ async function handleMerge(args, ctx) { try { autoCommitCurrentBranch(wt.path, "worktree-merge", target); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); ctx.ui.notify( [ `Auto-commit before merge failed: ${msg}`, @@ -176,7 +177,7 @@ async function handleMerge(args, ctx) { try { mergeWorktreeToMain(basePath, target, commitMessage); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); if (err instanceof SFError && err.code === SF_GIT_ERROR) { ctx.ui.notify( `Merge requires the main branch to be checked out: ${msg}\n\nSwitch to ${mainBranch} (e.g. 'git checkout ${mainBranch}'), then re-run /worktree merge ${target}.`, @@ -199,7 +200,7 @@ async function handleMerge(args, ctx) { removeWorktree(basePath, target, { deleteBranch: true }); ctx.ui.notify(successLines.join("\n"), "info"); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); const cleanupLines = [ ...successLines, "", @@ -228,7 +229,7 @@ async function handleClean(ctx) { removeWorktree(basePath, wt.name, { deleteBranch: true }); removed.push(wt.name); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); kept.push(`${wt.name} (failed: ${msg})`); } } else { @@ -286,7 +287,7 @@ async function handleRemove(args, ctx) { removeWorktree(basePath, name, { deleteBranch: true }); ctx.ui.notify(`Removed worktree ${name}.`, "info"); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); ctx.ui.notify( `Worktree partially removed: ${msg}\n\nRun 'git worktree prune' to clean up any dangling registrations.`, "error", @@ -348,7 +349,7 @@ export async function handleWorktree(args, ctx) { "warning", ); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); ctx.ui.notify(`Worktree command failed: ${msg}`, "error"); } } diff --git a/src/resources/extensions/sf/commands/handlers/workflow.js b/src/resources/extensions/sf/commands/handlers/workflow.js index 5cd7b9f26..a8fda17ee 100644 --- a/src/resources/extensions/sf/commands/handlers/workflow.js +++ b/src/resources/extensions/sf/commands/handlers/workflow.js @@ -32,6 +32,7 @@ import { handleQuick } from "../../quick.js"; import { createRun, listRuns } from "../../run-manager.js"; import { deriveState } from "../../state.js"; import { projectRoot } from "../context.js"; +import { getErrorMessage } from "../../error-utils.js"; // ─── Custom Workflow Subcommands ───────────────────────────────────────── const WORKFLOW_USAGE = [ @@ -136,7 +137,7 @@ async function handleCustomWorkflow(sub, ctx, pi) { // Clean up engine state so a failed workflow run doesn't pollute the next /autonomous setActiveEngineId(null); setActiveRunDir(null); - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); ctx.ui.notify(`Failed to run workflow "${defName}": ${msg}`, "error"); } return true; @@ -184,7 +185,7 @@ async function handleCustomWorkflow(sub, ctx, pi) { ); } } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); ctx.ui.notify(`Failed to validate "${defName}": ${msg}`, "error"); } return true; diff --git a/src/resources/extensions/sf/definition-io.js b/src/resources/extensions/sf/definition-io.js index a797bc7c8..ee1734f01 100644 --- a/src/resources/extensions/sf/definition-io.js +++ b/src/resources/extensions/sf/definition-io.js @@ -7,6 +7,7 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; import { parse } from "yaml"; +import { getErrorMessage } from "./error-utils.js"; /** Read and parse the frozen DEFINITION.yaml from a run directory. */ export function readFrozenDefinition(runDir) { const defPath = join(runDir, "DEFINITION.yaml"); @@ -14,7 +15,7 @@ export function readFrozenDefinition(runDir) { const raw = readFileSync(defPath, "utf-8"); return parse(raw, { schema: "core" }); } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = getErrorMessage(err); const wrapped = new Error( `Failed to read/parse DEFINITION.yaml at ${defPath}: ${message}`, { cause: err }, diff --git a/src/resources/extensions/sf/doctor-config-checks.js b/src/resources/extensions/sf/doctor-config-checks.js index 6cbc6e1a4..d921e745c 100644 --- a/src/resources/extensions/sf/doctor-config-checks.js +++ b/src/resources/extensions/sf/doctor-config-checks.js @@ -17,6 +17,7 @@ import { getWorktreeMode, loadEffectiveSFPreferences, } from "./preferences.js"; +import { getErrorMessage } from "./error-utils.js"; /** * Check that all Tier 1.4 config keys are well-formed and within expected ranges. @@ -253,7 +254,7 @@ export async function checkConfigHealth(issues, fixesApplied, shouldFix) { code: "config_check_error", scope: "project", unitId: "config", - message: `Config health check failed: ${err instanceof Error ? err.message : String(err)}`, + message: `Config health check failed: ${getErrorMessage(err)}`, file: ".sf/preferences.yaml", fixable: false, }); @@ -360,7 +361,7 @@ export function checkVaultHealth(issues, _shouldFix) { code: "vault_unreachable", scope: "project", unitId: "vault", - message: `Vault at ${vaultAddr} is unreachable (${err instanceof Error ? err.message : String(err)}). This is OK if vault is not yet set up.`, + message: `Vault at ${vaultAddr} is unreachable (${getErrorMessage(err)}). This is OK if vault is not yet set up.`, file: "vault server", fixable: false, }); @@ -372,7 +373,7 @@ export function checkVaultHealth(issues, _shouldFix) { code: "vault_check_error", scope: "project", unitId: "vault", - message: `Vault health check failed: ${err instanceof Error ? err.message : String(err)}`, + message: `Vault health check failed: ${getErrorMessage(err)}`, file: ".sf/preferences.yaml", fixable: false, }); diff --git a/src/resources/extensions/sf/doctor-engine-checks.js b/src/resources/extensions/sf/doctor-engine-checks.js index d184c2219..92efd3142 100644 --- a/src/resources/extensions/sf/doctor-engine-checks.js +++ b/src/resources/extensions/sf/doctor-engine-checks.js @@ -26,6 +26,7 @@ import { import { extractVerdict } from "./verdict-parser.js"; import { readEvents } from "./workflow-events.js"; import { renderAllProjections } from "./workflow-projections.js"; +import { getErrorMessage } from "./error-utils.js"; const LEGACY_MILESTONE_DIR_RE = /^(M\d+)-.+$/; const LEGACY_SLICE_DIR_RE = /^(S\d+)-.+$/; @@ -118,7 +119,7 @@ function projectionDriftIssues(basePath, milestoneId) { code: "db_projection_roadmap_invalid", scope: "milestone", unitId: milestoneId, - message: `${milestoneId}-ROADMAP.json could not be parsed: ${err instanceof Error ? err.message : String(err)}. Projection is not executable runtime state.`, + message: `${milestoneId}-ROADMAP.json could not be parsed: ${getErrorMessage(err)}. Projection is not executable runtime state.`, file: roadmapJsonPath, fixable: false, }); diff --git a/src/resources/extensions/sf/doctor.js b/src/resources/extensions/sf/doctor.js index 4a3b99daf..6f74b321e 100644 --- a/src/resources/extensions/sf/doctor.js +++ b/src/resources/extensions/sf/doctor.js @@ -57,6 +57,7 @@ import { getMilestoneSlices, getSliceTasks, isDbAvailable } from "./sf-db.js"; import { deriveState, isMilestoneComplete } from "./state.js"; import { isClosedStatus } from "./status-guards.js"; import { parseUnitId } from "./unit-id.js"; +import { getErrorMessage } from "./error-utils.js"; // ─── Flow Audit Implementation ──────────────────────────────────────────── const DEFAULT_STALE_PROGRESS_MS = 20 * 60 * 1000; @@ -814,7 +815,7 @@ export async function runFlowAudit(basePath, options = {}) { else process.kill(row.pid, "SIGTERM"); killed = true; } catch (err) { - killError = err instanceof Error ? err.message : String(err); + killError = getErrorMessage(err); warnings.push( `Failed to kill over-budget ${classification} child pid ${row.pid}: ${killError}`, ); diff --git a/src/resources/extensions/sf/ecosystem/loader.js b/src/resources/extensions/sf/ecosystem/loader.js index 72a8f1345..88a9e8c10 100644 --- a/src/resources/extensions/sf/ecosystem/loader.js +++ b/src/resources/extensions/sf/ecosystem/loader.js @@ -8,6 +8,7 @@ import { pathToFileURL } from "node:url"; import { getAgentDir } from "@singularity-forge/coding-agent"; import { logWarning } from "../workflow-logger.js"; import { createSFExtensionAPI } from "./sf-extension-api.js"; +import { getErrorMessage } from "../error-utils.js"; // ─── Trust check (inlined; pi does not export isProjectTrusted from its // package root, and constraint forbids modifying packages/coding-agent/) ─ @@ -78,7 +79,7 @@ async function _loadEcosystemExtensionsImpl(pi, sharedHandlers, cwd) { } catch (err) { logWarning( "ecosystem", - `failed to resolve extensions dir: ${err instanceof Error ? err.message : String(err)}`, + `failed to resolve extensions dir: ${getErrorMessage(err)}`, ); return; } @@ -91,7 +92,7 @@ async function _loadEcosystemExtensionsImpl(pi, sharedHandlers, cwd) { } catch (err) { logWarning( "ecosystem", - `failed to read extensions dir: ${err instanceof Error ? err.message : String(err)}`, + `failed to read extensions dir: ${getErrorMessage(err)}`, ); return; } @@ -111,7 +112,7 @@ async function _loadOne(extDir, realExtDir, entry, api) { } catch (err) { logWarning( "ecosystem", - `failed to resolve ${entry}: ${err instanceof Error ? err.message : String(err)}`, + `failed to resolve ${entry}: ${getErrorMessage(err)}`, ); return; } @@ -149,7 +150,7 @@ async function _loadOne(extDir, realExtDir, entry, api) { } catch (err) { logWarning( "ecosystem", - `failed to import ${entry}: ${err instanceof Error ? err.message : String(err)}`, + `failed to import ${entry}: ${getErrorMessage(err)}`, ); return; } @@ -163,7 +164,7 @@ async function _loadOne(extDir, realExtDir, entry, api) { } catch (err) { logWarning( "ecosystem", - `factory threw for ${entry}: ${err instanceof Error ? err.message : String(err)}`, + `factory threw for ${entry}: ${getErrorMessage(err)}`, ); } } diff --git a/src/resources/extensions/sf/eval-review-schema.js b/src/resources/extensions/sf/eval-review-schema.js index 6bd5fde3d..90135d570 100644 --- a/src/resources/extensions/sf/eval-review-schema.js +++ b/src/resources/extensions/sf/eval-review-schema.js @@ -20,6 +20,7 @@ import { Type } from "@sinclair/typebox"; import { Value } from "@sinclair/typebox/value"; import { parse as parseYaml } from "yaml"; +import { getErrorMessage } from "./error-utils.js"; // ─── Constants ──────────────────────────────────────────────────────────────── /** Schema version literal embedded in every EVAL-REVIEW.md frontmatter. */ export const EVAL_REVIEW_SCHEMA_VERSION = "eval-review/v1"; @@ -143,7 +144,7 @@ export function parseEvalReviewFrontmatter(raw) { try { parsed = parseYaml(fm.yaml, { schema: "core" }); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); return { ok: false, error: `YAML parse error: ${msg}`, pointer: "/" }; } const schema = EvalReviewFrontmatter; diff --git a/src/resources/extensions/sf/exec-sandbox.js b/src/resources/extensions/sf/exec-sandbox.js index 4c0df868c..79fa984bd 100644 --- a/src/resources/extensions/sf/exec-sandbox.js +++ b/src/resources/extensions/sf/exec-sandbox.js @@ -8,6 +8,7 @@ import { spawn } from "node:child_process"; import { randomUUID } from "node:crypto"; import { existsSync, mkdirSync, writeFileSync } from "node:fs"; import { resolve } from "node:path"; +import { getErrorMessage } from "./error-utils.js"; const ALWAYS_FORWARD_ENV = ["PATH", "HOME"]; export const EXEC_DEFAULTS = { @@ -96,7 +97,7 @@ export function runExecSandbox(request, opts) { }); } catch (err) { const duration = Date.now() - started; - const message = err instanceof Error ? err.message : String(err); + const message = getErrorMessage(err); writeFileSync(stdoutPath, ""); writeFileSync(stderrPath, `spawn error: ${message}\n`); const result = { @@ -217,7 +218,7 @@ export function runExecSandbox(request, opts) { resolveP(result); }; child.on("error", (err) => { - const message = err instanceof Error ? err.message : String(err); + const message = getErrorMessage(err); const line = `child error: ${message}\n`; const remaining = opts.stderr_cap_bytes - stderrBytes; if (remaining > 0) { diff --git a/src/resources/extensions/sf/execution-instruction-guard.js b/src/resources/extensions/sf/execution-instruction-guard.js index 229dff034..b682b171f 100644 --- a/src/resources/extensions/sf/execution-instruction-guard.js +++ b/src/resources/extensions/sf/execution-instruction-guard.js @@ -7,6 +7,7 @@ import { appendEvent } from "./workflow-events.js"; import { logWarning } from "./workflow-logger.js"; import { writeManifest } from "./workflow-manifest.js"; import { renderAllProjections } from "./workflow-projections.js"; +import { getErrorMessage } from "./error-utils.js"; const REPO_INSTRUCTION_FILES = [ "AGENTS.md", @@ -116,7 +117,7 @@ export async function skipExecuteTaskForInstructionConflict( } catch (err) { logWarning( "dispatch", - `instruction-conflict skip post-mutation hook warning: ${err instanceof Error ? err.message : String(err)}`, + `instruction-conflict skip post-mutation hook warning: ${getErrorMessage(err)}`, ); } invalidateStateCache(); diff --git a/src/resources/extensions/sf/graph-context.js b/src/resources/extensions/sf/graph-context.js index a3b232a0f..d4aadaed3 100644 --- a/src/resources/extensions/sf/graph-context.js +++ b/src/resources/extensions/sf/graph-context.js @@ -8,6 +8,7 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; import { logWarning } from "./workflow-logger.js"; +import { getErrorMessage } from "./error-utils.js"; let cachedGraphApi = null; let resolvedGraphApi = false; @@ -170,7 +171,7 @@ export async function inlineGraphSubgraph(projectDir, term, opts) { } catch (err) { logWarning( "prompt", - `inlineGraphSubgraph failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`, + `inlineGraphSubgraph failed (non-fatal): ${getErrorMessage(err)}`, ); return null; } diff --git a/src/resources/extensions/sf/hook-emitter.js b/src/resources/extensions/sf/hook-emitter.js index 24d57423c..c79621f3a 100644 --- a/src/resources/extensions/sf/hook-emitter.js +++ b/src/resources/extensions/sf/hook-emitter.js @@ -8,6 +8,7 @@ // missing `pi` (e.g. in standalone unit tests) logs a warning so callers know // hooks won't fire, but never throws. import { logWarning } from "./workflow-logger.js"; +import { getErrorMessage } from "./error-utils.js"; let _pi; let _missingPiWarningLogged = false; @@ -40,7 +41,7 @@ async function emitEvent(event) { } catch (err) { logWarning( "hook", - `emitExtensionEvent failed for ${event.type}: ${err instanceof Error ? err.message : String(err)}`, + `emitExtensionEvent failed for ${event.type}: ${getErrorMessage(err)}`, ); return undefined; } diff --git a/src/resources/extensions/sf/index.js b/src/resources/extensions/sf/index.js index 78122ff56..308c734f4 100644 --- a/src/resources/extensions/sf/index.js +++ b/src/resources/extensions/sf/index.js @@ -1,3 +1,4 @@ +import { getErrorMessage } from "./error-utils.js"; export { clearPendingGate, getPendingGate, @@ -37,7 +38,7 @@ export default async function registerExtension(pi) { const { logWarning } = await import("./workflow-logger.js"); logWarning( "bootstrap", - `Extension setup partially failed — SF commands are available but shortcuts/tools may be missing: ${err instanceof Error ? err.message : String(err)}`, + `Extension setup partially failed — SF commands are available but shortcuts/tools may be missing: ${getErrorMessage(err)}`, ); } @@ -52,7 +53,7 @@ export default async function registerExtension(pi) { const { logWarning } = await import("./workflow-logger.js"); logWarning( "subagent", - `SF subagent setup failed — delegation tools may be missing: ${err instanceof Error ? err.message : String(err)}`, + `SF subagent setup failed — delegation tools may be missing: ${getErrorMessage(err)}`, ); } @@ -66,7 +67,7 @@ export default async function registerExtension(pi) { const { logWarning } = await import("./workflow-logger.js"); logWarning( "ui", - `SF TUI setup failed — running with default header/footer: ${err instanceof Error ? err.message : String(err)}`, + `SF TUI setup failed — running with default header/footer: ${getErrorMessage(err)}`, ); } @@ -78,7 +79,7 @@ export default async function registerExtension(pi) { const { logWarning } = await import("./workflow-logger.js"); logWarning( "usage-bar", - `SF usage bar setup failed: ${err instanceof Error ? err.message : String(err)}`, + `SF usage bar setup failed: ${getErrorMessage(err)}`, ); } @@ -92,7 +93,7 @@ export default async function registerExtension(pi) { const { logWarning } = await import("./workflow-logger.js"); logWarning( "notifications", - `SF notifications setup failed: ${err instanceof Error ? err.message : String(err)}`, + `SF notifications setup failed: ${getErrorMessage(err)}`, ); } @@ -106,7 +107,7 @@ export default async function registerExtension(pi) { const { logWarning } = await import("./workflow-logger.js"); logWarning( "inturn-guard", - `SF in-turn guard setup failed: ${err instanceof Error ? err.message : String(err)}`, + `SF in-turn guard setup failed: ${getErrorMessage(err)}`, ); } @@ -120,7 +121,7 @@ export default async function registerExtension(pi) { const { logWarning } = await import("./workflow-logger.js"); logWarning( "permissions", - `SF permissions setup failed: ${err instanceof Error ? err.message : String(err)}`, + `SF permissions setup failed: ${getErrorMessage(err)}`, ); } @@ -134,7 +135,7 @@ export default async function registerExtension(pi) { const { logWarning } = await import("./workflow-logger.js"); logWarning( "legacy-commands", - `SF legacy commands setup failed: ${err instanceof Error ? err.message : String(err)}`, + `SF legacy commands setup failed: ${getErrorMessage(err)}`, ); } } diff --git a/src/resources/extensions/sf/learning/runtime.js b/src/resources/extensions/sf/learning/runtime.js index b5115c266..c194c5f12 100644 --- a/src/resources/extensions/sf/learning/runtime.js +++ b/src/resources/extensions/sf/learning/runtime.js @@ -3,6 +3,7 @@ import { logWarning } from "../workflow-logger.js"; import { createBeforeModelSelectHandler } from "./hook-handler.mjs"; import { loadCapabilityOverrides } from "./loadCapabilityOverrides.mjs"; import { validateOutcome } from "./outcome-recorder.mjs"; +import { getErrorMessage } from "../error-utils.js"; const DEFAULT_N_PRIOR = 10; const DEFAULT_ROLLING_DAYS = 30; @@ -45,7 +46,7 @@ async function ensureLearningReady() { cachedDb = null; logWarning( "dispatch", - `failed to initialize learned routing: ${err instanceof Error ? err.message : String(err)}`, + `failed to initialize learned routing: ${getErrorMessage(err)}`, ); } finally { initPromise = null; @@ -75,7 +76,7 @@ export function recordLearnedOutcome(input) { } catch (err) { logWarning( "db", - `failed to record learned routing outcome: ${err instanceof Error ? err.message : String(err)}`, + `failed to record learned routing outcome: ${getErrorMessage(err)}`, ); return false; } diff --git a/src/resources/extensions/sf/lifecycle-hooks.js b/src/resources/extensions/sf/lifecycle-hooks.js index 9afe490e2..4c9775476 100644 --- a/src/resources/extensions/sf/lifecycle-hooks.js +++ b/src/resources/extensions/sf/lifecycle-hooks.js @@ -8,6 +8,7 @@ */ import { flushSyncQueue } from "./sync-scheduler.js"; +import { getErrorMessage } from "./error-utils.js"; /** * Flush SM sync queue for a project before unit completes. @@ -36,7 +37,7 @@ export async function flushProjectMemorySync(projectId) { failed: 0, elapsed, status: "error", - reason: err instanceof Error ? err.message : String(err), + reason: getErrorMessage(err), }; } } diff --git a/src/resources/extensions/sf/onboarding-state.js b/src/resources/extensions/sf/onboarding-state.js index 1dfd2549b..3697e25e9 100644 --- a/src/resources/extensions/sf/onboarding-state.js +++ b/src/resources/extensions/sf/onboarding-state.js @@ -14,6 +14,7 @@ import { import { dirname, join } from "node:path"; import { logWarning } from "./workflow-logger.js"; import { sfHome } from "./sf-home.js"; +import { getErrorMessage } from "./error-utils.js"; /** * Bump `FLOW_VERSION` whenever a new required step is added to ONBOARDING_STEPS. * Records with an older flowVersion are treated as "needs partial re-onboarding" @@ -94,7 +95,7 @@ export function writeOnboardingRecord(patch) { } catch (err) { logWarning( "state", - `Failed to persist onboarding record: ${err instanceof Error ? err.message : String(err)}`, + `Failed to persist onboarding record: ${getErrorMessage(err)}`, { file: FILE, }, diff --git a/src/resources/extensions/sf/orphan-worktree-sweep.js b/src/resources/extensions/sf/orphan-worktree-sweep.js index db9ae3904..b4be78857 100644 --- a/src/resources/extensions/sf/orphan-worktree-sweep.js +++ b/src/resources/extensions/sf/orphan-worktree-sweep.js @@ -21,6 +21,7 @@ import { worktreePath, worktreesDir, } from "./worktree-manager.js"; +import { getErrorMessage } from "./error-utils.js"; // ─── Internal Helpers ───────────────────────────────────────────────────────── /** @@ -95,7 +96,7 @@ export function sweepOrphanWorktrees(basePath) { } catch (err) { result.errors.push({ id: "", - reason: `readdirSync failed: ${err instanceof Error ? err.message : String(err)}`, + reason: `readdirSync failed: ${getErrorMessage(err)}`, }); return result; } @@ -156,7 +157,7 @@ export function sweepOrphanWorktrees(basePath) { } catch (err) { result.errors.push({ id, - reason: err instanceof Error ? err.message : String(err), + reason: getErrorMessage(err), }); } } diff --git a/src/resources/extensions/sf/planning-depth.js b/src/resources/extensions/sf/planning-depth.js index 0ae16603e..66d4292cb 100644 --- a/src/resources/extensions/sf/planning-depth.js +++ b/src/resources/extensions/sf/planning-depth.js @@ -8,6 +8,7 @@ import { dirname, join } from "node:path"; import { parse as parseYaml, stringify as stringifyYaml } from "yaml"; import { sfRoot } from "./paths.js"; import { logWarning } from "./workflow-logger.js"; +import { getErrorMessage } from "./error-utils.js"; /** * Resolve the path to the project-level preferences file. @@ -75,7 +76,7 @@ function readProjectPreferencesParts(path) { } catch (err) { logWarning( "guided", - `preferences.yaml has invalid YAML — rewriting: ${err instanceof Error ? err.message : String(err)}`, + `preferences.yaml has invalid YAML — rewriting: ${getErrorMessage(err)}`, ); } } diff --git a/src/resources/extensions/sf/quick.js b/src/resources/extensions/sf/quick.js index 1c6f91356..71908cf60 100644 --- a/src/resources/extensions/sf/quick.js +++ b/src/resources/extensions/sf/quick.js @@ -22,6 +22,7 @@ import { nativeHasStagedChanges } from "./native-git-bridge.js"; import { sfRoot } from "./paths.js"; import { loadEffectiveSFPreferences } from "./preferences.js"; import { loadPrompt } from "./prompt-loader.js"; +import { getErrorMessage } from "./error-utils.js"; let pendingQuickReturn = null; // ─── Quick Task Helpers ─────────────────────────────────────────────────────── @@ -191,7 +192,7 @@ export async function handleQuick(args, ctx, pi) { } } catch (err) { // Branch creation failed — continue on current branch - const message = err instanceof Error ? err.message : String(err); + const message = getErrorMessage(err); ctx.ui.notify( `Could not create branch ${branchName}: ${message}. Working on current branch.`, "warning", diff --git a/src/resources/extensions/sf/scaffold-keeper.js b/src/resources/extensions/sf/scaffold-keeper.js index fb2080fe0..0e8d66480 100644 --- a/src/resources/extensions/sf/scaffold-keeper.js +++ b/src/resources/extensions/sf/scaffold-keeper.js @@ -28,6 +28,7 @@ import { dirname, join } from "node:path"; import { SCAFFOLD_FILES } from "./agentic-docs-scaffold.js"; import { detectScaffoldDrift } from "./scaffold-drift.js"; import { logWarning } from "./workflow-logger.js"; +import { getErrorMessage } from "./error-utils.js"; /** * Build the .proposed body shipped by the Phase-D stub: the current scaffold @@ -135,7 +136,7 @@ export function dispatchScaffoldKeeperFireAndForget(basePath, ctx) { queueMicrotask(() => { dispatchScaffoldKeeperIfNeeded(basePath, ctx).catch((err) => { logWarning("scaffold", "scaffold-keeper dispatch threw", { - error: err instanceof Error ? err.message : String(err), + error: getErrorMessage(err), }); }); }); diff --git a/src/resources/extensions/sf/sf-db/sf-db-core.js b/src/resources/extensions/sf/sf-db/sf-db-core.js index f7c19ef7f..de134b928 100644 --- a/src/resources/extensions/sf/sf-db/sf-db-core.js +++ b/src/resources/extensions/sf/sf-db/sf-db-core.js @@ -41,6 +41,7 @@ import { } from "../task-frontmatter.js"; import { readTraceEvents } from "../uok/trace-writer.js"; import { logError, logWarning } from "../workflow-logger.js"; +import { getErrorMessage } from "../error-utils.js"; let loadAttempted = false; function loadProvider() { @@ -207,7 +208,7 @@ function createDatabaseSnapshot(rawDb, path) { } catch (err) { logWarning( "sf-db", - `database snapshot failed: ${err instanceof Error ? err.message : String(err)}`, + `database snapshot failed: ${getErrorMessage(err)}`, ); } } @@ -242,7 +243,7 @@ function performDatabaseMaintenance(rawDb, path) { } catch (err) { logWarning( "sf-db", - `database maintenance failed: ${err instanceof Error ? err.message : String(err)}`, + `database maintenance failed: ${getErrorMessage(err)}`, ); } } diff --git a/src/resources/extensions/sf/slice-cadence.js b/src/resources/extensions/sf/slice-cadence.js index f2421170a..90ef79f69 100644 --- a/src/resources/extensions/sf/slice-cadence.js +++ b/src/resources/extensions/sf/slice-cadence.js @@ -38,6 +38,7 @@ import { emitMilestoneResquash, emitSliceMerged, } from "./worktree-telemetry.js"; +import { getErrorMessage } from "./error-utils.js"; /** * Auto-worktree milestone branch name. Must match autoWorktreeBranch() in @@ -56,7 +57,7 @@ function cleanupMergeArtifacts(projectRoot) { } catch (err) { logWarning( "worktree", - `merge artifact cleanup failed: ${err instanceof Error ? err.message : String(err)}`, + `merge artifact cleanup failed: ${getErrorMessage(err)}`, ); } } diff --git a/src/resources/extensions/sf/sm-client.js b/src/resources/extensions/sf/sm-client.js index 57f38a123..c5fb3c3cf 100644 --- a/src/resources/extensions/sf/sm-client.js +++ b/src/resources/extensions/sf/sm-client.js @@ -20,6 +20,7 @@ * Tries to connect to SM at SINGULARITY_MEMORY_ADDR (default: http://localhost:8080). * SM is opt-in: set SM_ENABLED=true to activate. Disabled by default. */ +import { getErrorMessage } from "./error-utils.js"; export async function initializeSmClient() { // SM is opt-in — disabled unless explicitly enabled if (process.env.SM_ENABLED !== "true") { @@ -56,7 +57,7 @@ export async function initializeSmClient() { return { connected: false, version: null, - reason: `SM unreachable at ${addr}: ${err instanceof Error ? err.message : String(err)}`, + reason: `SM unreachable at ${addr}: ${getErrorMessage(err)}`, }; } } @@ -111,7 +112,7 @@ export async function syncMemoryToSm(memory, opts = {}) { } } catch (err) { console.warn( - `[sm-client] Error syncing memory ${memory.id}: ${err instanceof Error ? err.message : String(err)}`, + `[sm-client] Error syncing memory ${memory.id}: ${getErrorMessage(err)}`, ); } }); diff --git a/src/resources/extensions/sf/spec-projections.js b/src/resources/extensions/sf/spec-projections.js index c0e79715d..e2f216ef2 100644 --- a/src/resources/extensions/sf/spec-projections.js +++ b/src/resources/extensions/sf/spec-projections.js @@ -11,6 +11,7 @@ import { existsSync } from "node:fs"; import { join } from "node:path"; import { DatabaseSync } from "node:sqlite"; +import { getErrorMessage } from "./error-utils.js"; const SPEC_GENERATOR_VERSION = "1"; @@ -51,7 +52,7 @@ function readDbSummary(basePath) { `- DB spec rows: milestone_specs=${milestoneSpecs}, slice_specs=${sliceSpecs}, task_specs=${taskSpecs}`, ].join("\n"); } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = getErrorMessage(err); return `- Database state: unreadable (${message})`; } finally { db?.close(); diff --git a/src/resources/extensions/sf/subagent/background-jobs.js b/src/resources/extensions/sf/subagent/background-jobs.js index 54b0d25fe..a32bb3320 100644 --- a/src/resources/extensions/sf/subagent/background-jobs.js +++ b/src/resources/extensions/sf/subagent/background-jobs.js @@ -1,4 +1,5 @@ import { randomUUID } from "node:crypto"; +import { getErrorMessage } from "../error-utils.js"; export class SubagentBackgroundJobManager { jobs = new Map(); evictionTimers = new Map(); @@ -60,7 +61,7 @@ export class SubagentBackgroundJobManager { return; } job.status = "failed"; - job.errorText = err instanceof Error ? err.message : String(err); + job.errorText = getErrorMessage(err); this.scheduleEviction(job.id); this.deliverResult(job); }); diff --git a/src/resources/extensions/sf/subagent/isolation.js b/src/resources/extensions/sf/subagent/isolation.js index 9cb9cb3a1..c0c369a2d 100644 --- a/src/resources/extensions/sf/subagent/isolation.js +++ b/src/resources/extensions/sf/subagent/isolation.js @@ -10,6 +10,7 @@ import * as fs from "node:fs"; import * as path from "node:path"; import { promisify } from "node:util"; import { sfHome } from "../sf-home.js"; +import { getErrorMessage } from "../error-utils.js"; const execFile = promisify(execFileCb); // ============================================================================ @@ -356,7 +357,7 @@ export async function mergeDeltaPatches(repoRoot, patches) { success: false, appliedPatches, failedPatches, - error: `Patch conflict: ${err instanceof Error ? err.message : String(err)}`, + error: `Patch conflict: ${getErrorMessage(err)}`, }; } // Apply for real diff --git a/src/resources/extensions/sf/sync-scheduler.js b/src/resources/extensions/sf/sync-scheduler.js index 1e61604dd..2517c387c 100644 --- a/src/resources/extensions/sf/sync-scheduler.js +++ b/src/resources/extensions/sf/sync-scheduler.js @@ -16,6 +16,7 @@ import { delay } from "./atomic-write.js"; import { syncMemoryToSm } from "./sm-client.js"; +import { getErrorMessage } from "./error-utils.js"; /** * Global sync scheduler state. @@ -201,7 +202,7 @@ async function trySyncWithRetry(item, attempt = 0) { if (typeof process !== "undefined" && process.env.DEBUG_SM_SYNC) { console.warn( `[SM sync] failed after ${MAX_RETRIES} retries: ${item.id}`, - err instanceof Error ? err.message : String(err), + getErrorMessage(err), ); } diff --git a/src/resources/extensions/sf/tools/exec-tool.js b/src/resources/extensions/sf/tools/exec-tool.js index 9115ded0d..1c3abfea4 100644 --- a/src/resources/extensions/sf/tools/exec-tool.js +++ b/src/resources/extensions/sf/tools/exec-tool.js @@ -6,6 +6,7 @@ import { EXEC_DEFAULTS, runExecSandbox } from "../exec-sandbox.js"; import { isContextModeEnabled } from "../preferences-types.js"; import { sanitizeExternalContent } from "../safety/sanitize-external-content.js"; +import { getErrorMessage } from "../error-utils.js"; export function buildExecOptions(baseDir, cfg, extras) { const allowlist = Array.isArray(cfg?.exec_env_allowlist) ? cfg.exec_env_allowlist @@ -111,7 +112,7 @@ export async function executeSfExec(params, deps) { ); return formatResult(result); } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = getErrorMessage(err); return { content: [ { type: "text", text: `Error: run_command failed — ${message}` }, diff --git a/src/resources/extensions/sf/tools/sift-search-tool.js b/src/resources/extensions/sf/tools/sift-search-tool.js index 315d6c2e7..f4f541620 100644 --- a/src/resources/extensions/sf/tools/sift-search-tool.js +++ b/src/resources/extensions/sf/tools/sift-search-tool.js @@ -18,6 +18,7 @@ import { resolveSiftSearchScope, } from "../code-intelligence.js"; import { recordRetrievalEvidence } from "../retrieval-evidence.js"; +import { getErrorMessage } from "../error-utils.js"; const _KNOWN_STRATEGIES = [ "hybrid", @@ -344,7 +345,7 @@ export function registerSiftSearchTool(pi) { }; } catch (err) { const elapsedMs = Date.now() - startedAt; - const message = err instanceof Error ? err.message : String(err); + const message = getErrorMessage(err); await recordRetrievalEvidence(projectRoot, { backend: "sift", sourceKind: "code", diff --git a/src/resources/extensions/sf/tools/workflow-tool-executors.js b/src/resources/extensions/sf/tools/workflow-tool-executors.js index a6d10f51b..182b9bcf9 100644 --- a/src/resources/extensions/sf/tools/workflow-tool-executors.js +++ b/src/resources/extensions/sf/tools/workflow-tool-executors.js @@ -26,6 +26,7 @@ import { handlePlanSlice } from "./plan-slice.js"; import { handleReassessRoadmap } from "./reassess-roadmap.js"; import { handleReplanSlice } from "./replan-slice.js"; import { handleValidateMilestone } from "./validate-milestone.js"; +import { getErrorMessage } from "../error-utils.js"; export const SUPPORTED_SUMMARY_ARTIFACT_TYPES = [ "SUMMARY", "RESEARCH", @@ -180,7 +181,7 @@ export async function executeSummarySave(params, basePath = process.cwd()) { }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `save_summary tool failed: ${msg}`, { tool: "save_summary", error: String(err), @@ -269,7 +270,7 @@ export async function executeTaskComplete(params, basePath = process.cwd()) { } catch (err) { // Escalation is additive — never block task completion if it fails, // but DO tell the agent so they don't think the issue was recorded. - escalationError = err instanceof Error ? err.message : String(err); + escalationError = getErrorMessage(err); logError( "tool", `complete_task escalation write failed: ${escalationError}`, @@ -303,7 +304,7 @@ export async function executeTaskComplete(params, basePath = process.cwd()) { }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `complete_task tool failed: ${msg}`, { tool: "complete_task", error: String(err), @@ -401,7 +402,7 @@ export async function executeSliceComplete(params, basePath = process.cwd()) { }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `complete_slice tool failed: ${msg}`, { tool: "complete_slice", error: String(err), @@ -456,7 +457,7 @@ export async function executeCompleteMilestone( }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `complete_milestone tool failed: ${msg}`, { tool: "complete_milestone", error: String(err), @@ -511,7 +512,7 @@ export async function executeValidateMilestone( }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `validate_milestone tool failed: ${msg}`, { tool: "validate_milestone", error: String(err), @@ -564,7 +565,7 @@ export async function executeReassessRoadmap(params, basePath = process.cwd()) { }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `reassess_roadmap tool failed: ${msg}`, { tool: "reassess_roadmap", error: String(err), @@ -654,7 +655,7 @@ export async function executeSaveGateResult(params, basePath = process.cwd()) { }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `record_gate failed: ${msg}`, { tool: "record_gate", error: String(err), @@ -709,7 +710,7 @@ export async function executePlanMilestone(params, basePath = process.cwd()) { }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `plan_milestone tool failed: ${msg}`, { tool: "plan_milestone", error: String(err), @@ -762,7 +763,7 @@ export async function executePlanSlice(params, basePath = process.cwd()) { }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `plan_slice tool failed: ${msg}`, { tool: "plan_slice", error: String(err), @@ -815,7 +816,7 @@ export async function executeReplanSlice(params, basePath = process.cwd()) { }, }; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logError("tool", `replan_slice tool failed: ${msg}`, { tool: "replan_slice", error: String(err), @@ -881,7 +882,7 @@ export async function executeMilestoneStatus(params, basePath = process.cwd()) { }; }); } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); logWarning("tool", `milestone_status tool failed: ${msg}`); return { content: [ diff --git a/src/resources/extensions/sf/ui/index.js b/src/resources/extensions/sf/ui/index.js index e6fd927d2..1ce31513b 100644 --- a/src/resources/extensions/sf/ui/index.js +++ b/src/resources/extensions/sf/ui/index.js @@ -28,6 +28,7 @@ import { pushPromptHistory, readPromptHistory, } from "./prompt-history.js"; +import { getErrorMessage } from "../error-utils.js"; const WORK_MODE_CYCLE = [ "chat", @@ -487,7 +488,7 @@ export default function sfTui(pi) { return { output: response }; } catch (err) { return { - output: `Agent dispatch failed: ${err instanceof Error ? err.message : String(err)}`, + output: `Agent dispatch failed: ${getErrorMessage(err)}`, }; } }, diff --git a/src/resources/extensions/sf/uok/a2a-agent-server.js b/src/resources/extensions/sf/uok/a2a-agent-server.js index 4a5ab1af7..d0c9afbfc 100644 --- a/src/resources/extensions/sf/uok/a2a-agent-server.js +++ b/src/resources/extensions/sf/uok/a2a-agent-server.js @@ -26,6 +26,7 @@ import { } from "@a2a-js/sdk/server/express"; import express from "express"; import { buildAgentCard } from "./a2a-transport.js"; +import { getErrorMessage } from "../error-utils.js"; const agentName = process.env.SF_A2A_AGENT_NAME; const agentRole = process.env.SF_A2A_AGENT_ROLE ?? "worker"; @@ -103,7 +104,7 @@ class SwarmAgentExecutor { resultBody = await this._handleEnvelope(envelope); } catch (err) { resultBody = { - error: err instanceof Error ? err.message : String(err), + error: getErrorMessage(err), agentName: this._name, role: this._role, unitId: envelope?.unitId, diff --git a/src/resources/extensions/sf/uok/auto-dispatch.js b/src/resources/extensions/sf/uok/auto-dispatch.js index d6b206f2b..6d3c6160a 100644 --- a/src/resources/extensions/sf/uok/auto-dispatch.js +++ b/src/resources/extensions/sf/uok/auto-dispatch.js @@ -184,7 +184,7 @@ function hasPriorParallelResearchFailure(basePath, mid) { } catch (err) { logWarning( "dispatch", - `Ignoring unreadable parallel-research runtime state for ${mid}: ${err instanceof Error ? err.message : String(err)}`, + `Ignoring unreadable parallel-research runtime state for ${mid}: ${getErrorMessage(err)}`, ); return false; } @@ -1576,7 +1576,7 @@ export const DISPATCH_RULES = [ } catch (err) { logWarning( "dispatch", - `failed to persist validation attention marker: ${err instanceof Error ? err.message : String(err)}`, + `failed to persist validation attention marker: ${getErrorMessage(err)}`, ); } return { @@ -1696,7 +1696,7 @@ export const DISPATCH_RULES = [ /* fall through — don't block on DB errors */ logWarning( "dispatch", - `verification class check failed: ${err instanceof Error ? err.message : String(err)}`, + `verification class check failed: ${getErrorMessage(err)}`, ); } // P5-A: Advisory check for deferred requirements targeting this milestone @@ -1915,6 +1915,7 @@ export const DISPATCH_RULES = [ ]; import { getRegistry, hasRegistry } from "../rule-registry.js"; +import { getErrorMessage } from "../error-utils.js"; // ─── Dispatch Envelope Emission ─────────────────────────────────────────── /** @@ -2027,7 +2028,7 @@ export async function resolveDispatch(ctx) { // surface real bugs, then fall back. logWarning( "dispatch", - `registry dispatch failed, falling back to inline rules: ${err instanceof Error ? err.message : String(err)}`, + `registry dispatch failed, falling back to inline rules: ${getErrorMessage(err)}`, ); } } diff --git a/src/resources/extensions/sf/uok/auto-unit-closeout.js b/src/resources/extensions/sf/uok/auto-unit-closeout.js index 7954b0558..715e0418f 100644 --- a/src/resources/extensions/sf/uok/auto-unit-closeout.js +++ b/src/resources/extensions/sf/uok/auto-unit-closeout.js @@ -8,6 +8,7 @@ import { snapshotUnitMetrics } from "../metrics.js"; import { updateSubscriptionTokensUsed } from "../preferences-models.js"; import { logWarning } from "../workflow-logger.js"; import { writeTurnGitTransaction } from "./gitops.js"; +import { getErrorMessage } from "../error-utils.js"; /** * Snapshot metrics, save activity log, and fire-and-forget memory extraction * for a completed unit. Returns the activity log file path (if any). @@ -60,7 +61,7 @@ export async function closeoutUnit( /* non-fatal */ logWarning( "engine", - `operation failed: ${err instanceof Error ? err.message : String(err)}`, + `operation failed: ${getErrorMessage(err)}`, ); } } diff --git a/src/resources/extensions/sf/uok/auto-verification.js b/src/resources/extensions/sf/uok/auto-verification.js index 0b9b5c0dc..fbc282230 100644 --- a/src/resources/extensions/sf/uok/auto-verification.js +++ b/src/resources/extensions/sf/uok/auto-verification.js @@ -45,6 +45,7 @@ import { formatExecuteTaskRecoveryStatus, inspectExecuteTaskDurability, } from "./unit-runtime.js"; +import { getErrorMessage } from "../error-utils.js"; function computeTokenCountFromSession(ctx) { const entries = ctx.sessionManager?.getEntries?.() ?? []; @@ -353,7 +354,7 @@ export async function runPostUnitVerification(vctx, pauseAuto) { .catch((err) => ({ outcome: "fail", failureClass: "unknown", - rationale: `Gate ${id} threw: ${err instanceof Error ? err.message : String(err)}`, + rationale: `Gate ${id} threw: ${getErrorMessage(err)}`, })), ), ); diff --git a/src/resources/extensions/sf/uok/chaos-monkey.js b/src/resources/extensions/sf/uok/chaos-monkey.js index 4e1142405..9a1763598 100644 --- a/src/resources/extensions/sf/uok/chaos-monkey.js +++ b/src/resources/extensions/sf/uok/chaos-monkey.js @@ -12,6 +12,7 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { delay } from "../atomic-write.js"; +import { getErrorMessage } from "../error-utils.js"; const DEFAULT_LATENCY_PROBABILITY = 0.05; const DEFAULT_PARTIAL_FAILURE_PROBABILITY = 0.03; @@ -39,7 +40,7 @@ export class ChaosMonkeyGate { return { outcome: "fail", failureClass: "execution", - rationale: `Chaos monkey injected fault: ${err instanceof Error ? err.message : String(err)}`, + rationale: `Chaos monkey injected fault: ${getErrorMessage(err)}`, findings: `Injected during verification phase (attempt ${attempt})`, }; } diff --git a/src/resources/extensions/sf/uok/gate-runner.js b/src/resources/extensions/sf/uok/gate-runner.js index c759494a8..08ed74f16 100644 --- a/src/resources/extensions/sf/uok/gate-runner.js +++ b/src/resources/extensions/sf/uok/gate-runner.js @@ -9,6 +9,7 @@ import { logWarning } from "../workflow-logger.js"; import { buildAuditEnvelope, emitUokAuditEvent } from "./audit.js"; import { validateGate } from "./contracts.js"; import { appendTraceEvent } from "./trace-writer.js"; +import { getErrorMessage } from "../error-utils.js"; const RETRY_MATRIX = { none: 0, @@ -112,7 +113,7 @@ export async function enrichGateResultWithMemory(gateResult, gateId) { // Degrade gracefully - memory enrichment never changes gate result logWarning( "gate-runner", - `Memory enrichment failed for gate ${gateId}: ${err instanceof Error ? err.message : String(err)}`, + `Memory enrichment failed for gate ${gateId}: ${getErrorMessage(err)}`, ); } @@ -392,7 +393,7 @@ export class UokGateRunner { try { result = await gate.execute(ctx, attempt); } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = getErrorMessage(err); result = { outcome: "fail", failureClass: "unknown", diff --git a/src/resources/extensions/sf/vault-resolver.js b/src/resources/extensions/sf/vault-resolver.js index 937875a26..14575a56a 100644 --- a/src/resources/extensions/sf/vault-resolver.js +++ b/src/resources/extensions/sf/vault-resolver.js @@ -21,6 +21,7 @@ import { existsSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { logWarning } from "./workflow-logger.js"; +import { getErrorMessage } from "./error-utils.js"; /** * In-memory cache for resolved vault secrets. @@ -67,7 +68,7 @@ export function parseVaultUri(uri) { return { path, field, vaultAddr, token }; } catch (err) { return { - error: `Failed to parse vault URI: ${err instanceof Error ? err.message : String(err)}`, + error: `Failed to parse vault URI: ${getErrorMessage(err)}`, }; } } @@ -140,7 +141,7 @@ async function fetchVaultSecret(path, vaultAddr, token) { // Log error but don't throw — fail open logWarning( "vault-resolver", - `Vault fetch failed for ${path}: ${err instanceof Error ? err.message : String(err)}`, + `Vault fetch failed for ${path}: ${getErrorMessage(err)}`, ); return null; } diff --git a/src/resources/extensions/sf/workflow-install.js b/src/resources/extensions/sf/workflow-install.js index 969f2be20..efab15037 100644 --- a/src/resources/extensions/sf/workflow-install.js +++ b/src/resources/extensions/sf/workflow-install.js @@ -27,6 +27,7 @@ import { sfHome } from './sf-home.js'; import { extname, join, sep as pathSep, resolve } from "node:path"; import { parse as parseYaml } from "yaml"; import { validateDefinition } from "./definition-loader.js"; +import { getErrorMessage } from "./error-utils.js"; // ─── Constants ─────────────────────────────────────────────────────────── const MAX_RESPONSE_BYTES = 256 * 1024; @@ -212,7 +213,7 @@ export function validateFetchedContent(fetched) { parsed = parseYaml(fetched.content); } catch (err) { throw new Error( - `Installed YAML failed to parse: ${err instanceof Error ? err.message : String(err)}`, + `Installed YAML failed to parse: ${getErrorMessage(err)}`, ); } const result = validateDefinition(parsed); diff --git a/src/resources/extensions/sf/workflow-plugins.js b/src/resources/extensions/sf/workflow-plugins.js index 9c21670e7..2870514db 100644 --- a/src/resources/extensions/sf/workflow-plugins.js +++ b/src/resources/extensions/sf/workflow-plugins.js @@ -17,6 +17,7 @@ import { sfHome } from './sf-home.js'; import { basename, extname, join } from "node:path"; import { parse as parseYaml } from "yaml"; import { loadRegistry } from "./workflow-templates.js"; +import { getErrorMessage } from "./error-utils.js"; // ─── Path resolution ───────────────────────────────────────────────────── function resolveBundledDir() { @@ -143,7 +144,7 @@ function loadYamlPlugin(filePath, source) { format: "yaml", source, meta: { displayName: name, mode: "yaml-step" }, - error: `YAML parse error: ${err instanceof Error ? err.message : String(err)}`, + error: `YAML parse error: ${getErrorMessage(err)}`, }; } if (parsed == null || typeof parsed !== "object") { diff --git a/src/resources/extensions/sf/worktree-manager.js b/src/resources/extensions/sf/worktree-manager.js index 964eee216..d3eb5f3f5 100644 --- a/src/resources/extensions/sf/worktree-manager.js +++ b/src/resources/extensions/sf/worktree-manager.js @@ -58,6 +58,7 @@ import { emitCanonicalRootRedirect, emitWorktreeDivergenceWarning, } from "./worktree-telemetry.js"; +import { getErrorMessage } from "./error-utils.js"; /** Commits-behind threshold above which a divergence warning is emitted. */ export const WORKTREE_DIVERGENCE_CAP = 50; // ─── Path Helpers ────────────────────────────────────────────────────────── @@ -147,7 +148,7 @@ export function resolveCanonicalMilestoneRoot(basePath, milestoneId) { } catch (err) { logWarning( "worktree", - `canonical-root-redirect telemetry failed: ${err instanceof Error ? err.message : String(err)}`, + `canonical-root-redirect telemetry failed: ${getErrorMessage(err)}`, ); } return wtPath; diff --git a/src/resources/extensions/sf/worktree-resolver.js b/src/resources/extensions/sf/worktree-resolver.js index 3cc0798e8..97ec406cb 100644 --- a/src/resources/extensions/sf/worktree-resolver.js +++ b/src/resources/extensions/sf/worktree-resolver.js @@ -25,6 +25,7 @@ import { resquashMilestoneOnMain, } from "./slice-cadence.js"; import { resolveGitDir } from "./worktree-manager.js"; +import { getErrorMessage } from "./error-utils.js"; import { emitWorktreeCreated, emitWorktreeMerged, @@ -165,7 +166,7 @@ export class WorktreeResolver { }); } } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); debugLog("WorktreeResolver", { action: "enterMilestone", milestoneId, @@ -219,7 +220,7 @@ export class WorktreeResolver { action: "exitMilestone", milestoneId, phase: "auto-commit-failed", - error: err instanceof Error ? err.message : String(err), + error: getErrorMessage(err), }); } try { @@ -231,7 +232,7 @@ export class WorktreeResolver { action: "exitMilestone", milestoneId, phase: "teardown-failed", - error: err instanceof Error ? err.message : String(err), + error: getErrorMessage(err), }); } this.restoreToProjectRoot(); @@ -355,7 +356,7 @@ export class WorktreeResolver { action: "mergeAndExit", milestoneId, phase: "resquash", - error: err instanceof Error ? err.message : String(err), + error: getErrorMessage(err), }); } // #4764 — record merge completion. Only reaches here when an actual @@ -481,7 +482,7 @@ export class WorktreeResolver { ); } } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); debugLog("WorktreeResolver", { action: "mergeAndExit", milestoneId, @@ -604,7 +605,7 @@ export class WorktreeResolver { }); return true; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = getErrorMessage(err); debugLog("WorktreeResolver", { action: "mergeAndExit", milestoneId,