diff --git a/src/resources/extensions/gsd/commands/context.ts b/src/resources/extensions/gsd/commands/context.ts index 7bbaa5790..771cba6ec 100644 --- a/src/resources/extensions/gsd/commands/context.ts +++ b/src/resources/extensions/gsd/commands/context.ts @@ -1,7 +1,7 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent"; import { checkRemoteAutoSession, isAutoActive, isAutoPaused, stopAutoRemote } from "../auto.js"; -import { assertSafeDirectory } from "../validate-directory.js"; +import { validateDirectory } from "../validate-directory.js"; import { resolveProjectRoot } from "../worktree.js"; import { showNextAction } from "../../shared/tui.js"; import { handleStatus } from "./handlers/core.js"; @@ -12,13 +12,24 @@ export interface GsdDispatchContext { trimmed: string; } +/** + * Typed error for when GSD is run outside a valid project directory. + * Command handlers catch this to show a friendly message instead of a raw exception. + */ +export class GSDNoProjectError extends Error { + constructor(reason: string) { + super(reason); + this.name = "GSDNoProjectError"; + } +} + export function projectRoot(): string { const cwd = process.cwd(); const root = resolveProjectRoot(cwd); - if (root !== cwd) { - assertSafeDirectory(cwd); - } else { - assertSafeDirectory(root); + const pathToCheck = root !== cwd ? cwd : root; + const result = validateDirectory(pathToCheck); + if (result.severity === "blocked") { + throw new GSDNoProjectError(result.reason ?? "GSD must be run inside a project directory."); } return root; } diff --git a/src/resources/extensions/gsd/commands/dispatcher.ts b/src/resources/extensions/gsd/commands/dispatcher.ts index 9f28cbbaa..6b635ff1a 100644 --- a/src/resources/extensions/gsd/commands/dispatcher.ts +++ b/src/resources/extensions/gsd/commands/dispatcher.ts @@ -1,5 +1,6 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent"; +import { GSDNoProjectError } from "./context.js"; import { handleAutoCommand } from "./handlers/auto.js"; import { handleCoreCommand } from "./handlers/core.js"; import { handleOpsCommand } from "./handlers/ops.js"; @@ -21,10 +22,21 @@ export async function handleGSDCommand( () => handleOpsCommand(trimmed, ctx, pi), ]; - for (const handler of handlers) { - if (await handler()) { + try { + for (const handler of handlers) { + if (await handler()) { + return; + } + } + } catch (err) { + if (err instanceof GSDNoProjectError) { + ctx.ui.notify( + "No project found in this directory. `cd` into a project directory or run `/gsd new-project` to initialize one.", + "warning", + ); return; } + throw err; } ctx.ui.notify(`Unknown: /gsd ${trimmed}. Run /gsd help for available commands.`, "warning");