From 7712abe7d155371f00857bc472ffe583c13586be Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Mon, 16 Mar 2026 23:47:04 -0400 Subject: [PATCH] refactor: remove unnecessary 'as any' casts, dead exports, and duplicate code (#786) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issues addressed: 1. guided-flow.ts: Remove 12 unnecessary 'ctx as any' casts - ctx is already ExtensionCommandContext, matching showNextAction/showConfirm signatures - The casts masked type-checking with no benefit 2. triage-ui.ts: Remove 1 unnecessary 'ctx as any' cast (same issue as #1) 3. migrate/command.ts: Remove 2 unnecessary 'ctx as any' casts (same issue as #1) 4. models-resolver.ts: Remove dead exports hasBothModelsFiles() and getModelsPaths() - Never imported outside the module or in any test file - resolveModelsJsonPath() (the only consumer) remains 5. resource-loader.ts: Remove dead export readManagedResourceSyncedAt() - Exported but never imported anywhere in the entire codebase 6. bg-shell/overlay.ts: Extract processStatusHeader() helper - DRYs the duplicated status icon + name + uptime + tab indicator construction shared between renderOutput() and renderEvents() 7. get-secrets-from-user.ts: Merge duplicate vercel/convex deployment blocks - Both had identical exec → check result code → push applied/errors pattern - Merged into single conditional with destination-specific command string Documented but not changed (boundary constraints): - src/mcp-server.ts ↔ src/resources/extensions/gsd/mcp-server.ts (compiled/jiti boundary prevents sharing) - src/remote-questions-config.ts ↔ remote-questions/remote-command.ts (same compiled/jiti boundary per #592) - cli.ts internal duplication of session setup (structural, different resource loader configs) --- src/models-resolver.ts | 15 -------- src/resource-loader.ts | 8 ----- src/resources/extensions/bg-shell/overlay.ts | 35 ++++++++++--------- .../extensions/get-secrets-from-user.ts | 28 +++------------ src/resources/extensions/gsd/guided-flow.ts | 24 ++++++------- .../extensions/gsd/migrate/command.ts | 4 +-- src/resources/extensions/gsd/triage-ui.ts | 2 +- 7 files changed, 38 insertions(+), 78 deletions(-) diff --git a/src/models-resolver.ts b/src/models-resolver.ts index 8d39a4072..db9a61a45 100644 --- a/src/models-resolver.ts +++ b/src/models-resolver.ts @@ -37,19 +37,4 @@ export function resolveModelsJsonPath(): string { return GSD_MODELS_PATH } -/** - * Check if both GSD and PI models.json files exist. - */ -export function hasBothModelsFiles(): boolean { - return existsSync(GSD_MODELS_PATH) && existsSync(PI_MODELS_PATH) -} -/** - * Get the paths to both models.json files. - */ -export function getModelsPaths(): { gsd: string; pi: string } { - return { - gsd: GSD_MODELS_PATH, - pi: PI_MODELS_PATH, - } -} diff --git a/src/resource-loader.ts b/src/resource-loader.ts index ed32d7ab0..d5fdc9a7f 100644 --- a/src/resource-loader.ts +++ b/src/resource-loader.ts @@ -116,14 +116,6 @@ export function readManagedResourceVersion(agentDir: string): string | null { } } -export function readManagedResourceSyncedAt(agentDir: string): number | null { - try { - const manifest = JSON.parse(readFileSync(getManagedResourceManifestPath(agentDir), 'utf-8')) as ManagedResourceManifest - return typeof manifest?.syncedAt === 'number' ? manifest.syncedAt : null - } catch { - return null - } -} export function getNewerManagedResourceVersion(agentDir: string, currentVersion: string): string | null { const managedVersion = readManagedResourceVersion(agentDir) diff --git a/src/resources/extensions/bg-shell/overlay.ts b/src/resources/extensions/bg-shell/overlay.ts index ed8c45c74..7c3599c1f 100644 --- a/src/resources/extensions/bg-shell/overlay.ts +++ b/src/resources/extensions/bg-shell/overlay.ts @@ -328,12 +328,9 @@ export class BgManagerOverlay { return this.box(inner, width); } - private renderOutput(width: number): string[] { + private processStatusHeader(p: typeof this.viewingProcess, activeTab: "output" | "events"): { statusIcon: string; headerLine: string } { const th = this.theme; - const p = this.viewingProcess; - if (!p) return [""]; - const inner: string[] = []; - + if (!p) return { statusIcon: "", headerLine: "" }; const statusIcon = p.alive ? (p.status === "ready" ? th.fg("success", "●") : p.status === "error" ? th.fg("error", "●") @@ -343,9 +340,21 @@ export class BgManagerOverlay { const uptime = th.fg("dim", formatUptime(Date.now() - p.startedAt)); const typeTag = th.fg("dim", `[${p.processType}]`); const portInfo = p.ports.length > 0 ? th.fg("dim", ` :${p.ports.join(",")}`) : ""; - const tabIndicator = th.fg("accent", "[Output]") + " " + th.fg("dim", "Events"); + const tabIndicator = activeTab === "output" + ? th.fg("accent", "[Output]") + " " + th.fg("dim", "Events") + : th.fg("dim", "Output") + " " + th.fg("accent", "[Events]"); + const headerLine = `${statusIcon} ${name} ${typeTag} ${uptime}${portInfo} ${tabIndicator}`; + return { statusIcon, headerLine }; + } - inner.push(`${statusIcon} ${name} ${typeTag} ${uptime}${portInfo} ${tabIndicator}`); + private renderOutput(width: number): string[] { + const th = this.theme; + const p = this.viewingProcess; + if (!p) return [""]; + const inner: string[] = []; + + const { headerLine } = this.processStatusHeader(p, "output"); + inner.push(headerLine); inner.push(""); // Unified buffer is already chronologically interleaved @@ -384,16 +393,8 @@ export class BgManagerOverlay { if (!p) return [""]; const inner: string[] = []; - const statusIcon = p.alive - ? (p.status === "ready" ? th.fg("success", "●") - : p.status === "error" ? th.fg("error", "●") - : th.fg("warning", "●")) - : th.fg("dim", "○"); - const name = th.fg("muted", p.label); - const uptime = th.fg("dim", formatUptime(Date.now() - p.startedAt)); - const tabIndicator = th.fg("dim", "Output") + " " + th.fg("accent", "[Events]"); - - inner.push(`${statusIcon} ${name} ${uptime} ${tabIndicator}`); + const { headerLine } = this.processStatusHeader(p, "events"); + inner.push(headerLine); inner.push(""); if (p.events.length === 0) { diff --git a/src/resources/extensions/get-secrets-from-user.ts b/src/resources/extensions/get-secrets-from-user.ts index 08e68e55e..e889a947d 100644 --- a/src/resources/extensions/get-secrets-from-user.ts +++ b/src/resources/extensions/get-secrets-from-user.ts @@ -369,32 +369,14 @@ async function applySecrets( } } - if (destination === "vercel" && opts.exec) { + if ((destination === "vercel" || destination === "convex") && opts.exec) { const env = opts.environment ?? "development"; for (const { key, value } of provided) { + const cmd = destination === "vercel" + ? `printf %s ${shellEscapeSingle(value)} | vercel env add ${key} ${env}` + : `npx convex env set ${key} ${shellEscapeSingle(value)}`; try { - const result = await opts.exec("sh", [ - "-c", - `printf %s ${shellEscapeSingle(value)} | vercel env add ${key} ${env}`, - ]); - if (result.code !== 0) { - errors.push(`${key}: ${result.stderr.slice(0, 200)}`); - } else { - applied.push(key); - } - } catch (err: any) { - errors.push(`${key}: ${err.message}`); - } - } - } - - if (destination === "convex" && opts.exec) { - for (const { key, value } of provided) { - try { - const result = await opts.exec("sh", [ - "-c", - `npx convex env set ${key} ${shellEscapeSingle(value)}`, - ]); + const result = await opts.exec("sh", ["-c", cmd]); if (result.code !== 0) { errors.push(`${key}: ${result.stderr.slice(0, 200)}`); } else { diff --git a/src/resources/extensions/gsd/guided-flow.ts b/src/resources/extensions/gsd/guided-flow.ts index 76fad5447..b63c77278 100644 --- a/src/resources/extensions/gsd/guided-flow.ts +++ b/src/resources/extensions/gsd/guided-flow.ts @@ -870,7 +870,7 @@ export async function showDiscuss( const draftFile = resolveMilestoneFile(basePath, mid, "CONTEXT-DRAFT"); const draftContent = draftFile ? await loadFile(draftFile) : null; - const choice = await showNextAction(ctx as any, { + const choice = await showNextAction(ctx, { title: `GSD — ${mid}: ${milestoneTitle}`, summary: ["This milestone has a draft context from a prior discussion.", "It needs a dedicated discussion before auto-planning can begin."], actions: [ @@ -947,7 +947,7 @@ export async function showDiscuss( recommended: i === 0, })); - const choice = await showNextAction(ctx as any, { + const choice = await showNextAction(ctx, { title: "GSD — Discuss a slice", summary: [ `${mid}: ${milestoneTitle}`, @@ -1056,7 +1056,7 @@ export async function showSmartEntry( const crashLock = readCrashLock(basePath); if (crashLock) { clearLock(basePath); - const resume = await showNextAction(ctx as any, { + const resume = await showNextAction(ctx, { title: "GSD — Interrupted Session Detected", summary: [formatCrashInfo(crashLock)], actions: [ @@ -1116,7 +1116,7 @@ export async function showSmartEntry( basePath )); } else { - const choice = await showNextAction(ctx as any, { + const choice = await showNextAction(ctx, { title: "GSD — Get Shit Done", summary: ["No active milestone."], actions: [ @@ -1146,7 +1146,7 @@ export async function showSmartEntry( // ── All milestones complete → New milestone ────────────────────────── if (state.phase === "complete") { - const choice = await showNextAction(ctx as any, { + const choice = await showNextAction(ctx, { title: `GSD — ${milestoneId}: ${milestoneTitle}`, summary: ["All milestones complete."], actions: [ @@ -1187,7 +1187,7 @@ export async function showSmartEntry( const draftFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT-DRAFT"); const draftContent = draftFile ? await loadFile(draftFile) : null; - const choice = await showNextAction(ctx as any, { + const choice = await showNextAction(ctx, { title: `GSD — ${milestoneId}: ${milestoneTitle}`, summary: ["This milestone has a draft context from a prior discussion.", "It needs a dedicated discussion before auto-planning can begin."], actions: [ @@ -1278,7 +1278,7 @@ export async function showSmartEntry( }, ]; - const choice = await showNextAction(ctx as any, { + const choice = await showNextAction(ctx, { title: `GSD — ${milestoneId}: ${milestoneTitle}`, summary: [hasContext ? "Context captured. Ready to create roadmap." : "New milestone — no roadmap yet."], actions, @@ -1315,7 +1315,7 @@ export async function showSmartEntry( } else if (choice === "discard_milestone") { const mDir = resolveMilestonePath(basePath, milestoneId); if (!mDir) return; - const confirmed = await showConfirm(ctx as any, { + const confirmed = await showConfirm(ctx, { title: "Discard milestone?", message: `This will permanently delete ${milestoneId} and all its contents.`, confirmLabel: "Discard", @@ -1342,7 +1342,7 @@ export async function showSmartEntry( }, ]; - const choice = await showNextAction(ctx as any, { + const choice = await showNextAction(ctx, { title: `GSD — ${milestoneId}: ${milestoneTitle}`, summary: ["Roadmap exists. Ready to execute."], actions, @@ -1400,7 +1400,7 @@ export async function showSmartEntry( ? `${sliceId}: ${sliceTitle} (${summaryParts.join(", ")})` : `${sliceId}: ${sliceTitle} — ready for planning.`; - const choice = await showNextAction(ctx as any, { + const choice = await showNextAction(ctx, { title: `GSD — ${milestoneId} / ${sliceId}: ${sliceTitle}`, summary: [summaryLine], actions, @@ -1431,7 +1431,7 @@ export async function showSmartEntry( // ── All tasks done → Complete slice ────────────────────────────────── if (state.phase === "summarizing") { - const choice = await showNextAction(ctx as any, { + const choice = await showNextAction(ctx, { title: `GSD — ${milestoneId} / ${sliceId}: ${sliceTitle}`, summary: ["All tasks complete. Ready for slice summary."], actions: [ @@ -1475,7 +1475,7 @@ export async function showSmartEntry( const hasInterrupted = !!(continueFile && await loadFile(continueFile)) || !!(sDir && await loadFile(join(sDir, "continue.md"))); - const choice = await showNextAction(ctx as any, { + const choice = await showNextAction(ctx, { title: `GSD — ${milestoneId} / ${sliceId}: ${sliceTitle}`, summary: [ hasInterrupted diff --git a/src/resources/extensions/gsd/migrate/command.ts b/src/resources/extensions/gsd/migrate/command.ts index ef4e1f409..60bbe1285 100644 --- a/src/resources/extensions/gsd/migrate/command.ts +++ b/src/resources/extensions/gsd/migrate/command.ts @@ -151,7 +151,7 @@ export async function handleMigrate( } // ── Confirmation via showNextAction ──────────────────────────────────────── - const choice = await showNextAction(ctx as any, { + const choice = await showNextAction(ctx, { title: "Migration preview", summary: lines, actions: [ @@ -187,7 +187,7 @@ export async function handleMigrate( ); // ── Post-write review offer ──────────────────────────────────────────────── - const reviewChoice = await showNextAction(ctx as any, { + const reviewChoice = await showNextAction(ctx, { title: "Migration written", summary: [ `${result.paths.length} files written to .gsd/`, diff --git a/src/resources/extensions/gsd/triage-ui.ts b/src/resources/extensions/gsd/triage-ui.ts index ce7473a0e..e713732ec 100644 --- a/src/resources/extensions/gsd/triage-ui.ts +++ b/src/resources/extensions/gsd/triage-ui.ts @@ -135,7 +135,7 @@ export async function showTriageConfirmation( recommended: cls === proposed, })); - const choice = await showNextAction(ctx as any, { + const choice = await showNextAction(ctx, { title: `Triage: ${result.captureId}`, summary, actions,