fix(commands): show friendly message when /gsd runs from $HOME instead of unhandled error

Replace assertSafeDirectory with validateDirectory in projectRoot() and throw a typed
GSDNoProjectError that the dispatcher catches and renders as a user-friendly warning.

Fixes #3023
This commit is contained in:
Jeremy 2026-03-28 18:09:30 -05:00
parent 0a2c9b64c6
commit cb6b9c38d6
2 changed files with 30 additions and 7 deletions

View file

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

View file

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