fix(autocomplete): repair /gsd skip, add widget/next completions, add discuss to hint (#1675)

* fix(autocomplete): repair /gsd skip, add widget/next --debug completions, add discuss to description

- fix: bare `/gsd skip` (no args) fell through all handlers and hit the
  "Unknown command" warning — add a usage message handler matching
  `trimmed === "skip"` consistent with steer/knowledge/run-hook
- fix: `next` handler supports `--debug` (enables debug logging) but it
  was absent from NESTED_COMPLETIONS; add alongside --verbose/--dry-run
- fix: `widget` accepts full|small|min|off args but had no autocomplete
  entries; add widget to NESTED_COMPLETIONS with all four modes
- fix: `discuss` was in TOP_LEVEL_SUBCOMMANDS and fully implemented but
  omitted from GSD_COMMAND_DESCRIPTION hint string; add it

* test(gsd): add autocomplete regressions for skip/widget/next/discuss
This commit is contained in:
Jeremy McSpadden 2026-03-21 09:36:08 -05:00 committed by GitHub
parent 9e21abfc19
commit 137a80b9bf
3 changed files with 95 additions and 1 deletions

View file

@ -14,7 +14,7 @@ export interface GsdCommandDefinition {
type CompletionMap = Record<string, readonly GsdCommandDefinition[]>;
export const GSD_COMMAND_DESCRIPTION =
"GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|capture|triage|dispatch|history|undo|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update";
"GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update";
export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
{ cmd: "help", desc: "Categorized command reference with descriptions" },
@ -74,6 +74,13 @@ const NESTED_COMPLETIONS: CompletionMap = {
next: [
{ cmd: "--verbose", desc: "Show detailed step output" },
{ cmd: "--dry-run", desc: "Preview next step without executing" },
{ cmd: "--debug", desc: "Enable debug logging" },
],
widget: [
{ cmd: "full", desc: "Full widget display" },
{ cmd: "small", desc: "Compact widget display" },
{ cmd: "min", desc: "Minimal widget display" },
{ cmd: "off", desc: "Hide widget" },
],
mode: [
{ cmd: "global", desc: "Edit global workflow mode" },

View file

@ -57,6 +57,10 @@ export async function handleOpsCommand(trimmed: string, ctx: ExtensionCommandCon
await handleUndo(trimmed.replace(/^undo\s*/, "").trim(), ctx, pi, projectRoot());
return true;
}
if (trimmed === "skip") {
ctx.ui.notify("Usage: /gsd skip <unit-id> Example: /gsd skip M001/S01/T03", "warning");
return true;
}
if (trimmed.startsWith("skip ")) {
await handleSkip(trimmed.replace(/^skip\s*/, "").trim(), ctx, projectRoot());
return true;

View file

@ -0,0 +1,83 @@
import test from "node:test";
import assert from "node:assert/strict";
import { registerGSDCommand } from "../commands.ts";
import { handleGSDCommand } from "../commands/dispatcher.ts";
function createMockPi() {
const commands = new Map<string, any>();
return {
registerCommand(name: string, options: any) {
commands.set(name, options);
},
registerTool() {},
registerShortcut() {},
on() {},
sendMessage() {},
commands,
};
}
function createMockCtx() {
const notifications: { message: string; level: string }[] = [];
return {
notifications,
ui: {
notify(message: string, level: string) {
notifications.push({ message, level });
},
custom: async () => {},
},
shutdown: async () => {},
};
}
test("/gsd description includes discuss", () => {
const pi = createMockPi();
registerGSDCommand(pi as any);
const gsd = pi.commands.get("gsd");
assert.ok(gsd, "registerGSDCommand should register /gsd");
assert.ok(
gsd.description.includes("discuss"),
"description should include discuss",
);
});
test("/gsd next completions include --debug", () => {
const pi = createMockPi();
registerGSDCommand(pi as any);
const gsd = pi.commands.get("gsd");
const completions = gsd.getArgumentCompletions("next ");
const debug = completions.find((c: any) => c.value === "next --debug");
assert.ok(debug, "next --debug should appear in completions");
});
test("/gsd widget completions include full|small|min|off", () => {
const pi = createMockPi();
registerGSDCommand(pi as any);
const gsd = pi.commands.get("gsd");
const completions = gsd.getArgumentCompletions("widget ");
const values = completions.map((c: any) => c.value);
for (const expected of ["widget full", "widget small", "widget min", "widget off"]) {
assert.ok(values.includes(expected), `missing completion: ${expected}`);
}
});
test("bare /gsd skip shows usage and does not fall through to unknown-command warning", async () => {
const ctx = createMockCtx();
await handleGSDCommand("skip", ctx as any, {} as any);
assert.ok(
ctx.notifications.some((n) => n.message.includes("Usage: /gsd skip <unit-id>")),
"should show skip usage guidance",
);
assert.ok(
!ctx.notifications.some((n) => n.message.startsWith("Unknown: /gsd skip")),
"should not emit unknown-command warning for bare skip",
);
});