refactor: remove unnecessary 'as any' casts, dead exports, and duplicate code (#786)

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)
This commit is contained in:
Tom Boucher 2026-03-16 23:47:04 -04:00 committed by GitHub
parent ad4e68551d
commit 7712abe7d1
7 changed files with 38 additions and 78 deletions

View file

@ -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,
}
}

View file

@ -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)

View file

@ -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) {

View file

@ -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 {

View file

@ -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

View file

@ -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/`,

View file

@ -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,