feat: workflow templates — right-sized workflows for every task type (#1185)

* feat: add workflow templates — named workflow shapes for different types of work

Introduces `/gsd start <template>` and `/gsd templates` commands with 8 built-in
workflow templates: bugfix, small-feature, spike, hotfix, refactor, security-audit,
dep-upgrade, and full-project. Each template defines purpose-specific phases so
work gets the right level of ceremony instead of forcing everything through the
full milestone pipeline or /gsd quick.

Includes auto-detection from natural language, --dry-run preview, state tracking
for resume support, git branch management, and artifact directory organization.

* fix: guard workflow templates against concurrent auto-mode sessions

Block /gsd start when auto-mode is active to prevent git branch conflicts
and competing message dispatch. When auto-mode is paused, allow templates
to run with an informational notice.

* feat: add workflow resume and in-progress detection

- /gsd start resume — resumes the most recent in-progress workflow
- /gsd start (no args) — shows in-progress workflow if one exists
- STATE.json tracks artifactDir and completedAt for lifecycle management
- Scans .gsd/workflows/*/STATE.json to find unfinished workflows

* chore: remove copyright headers per project conventions
This commit is contained in:
Jeremy McSpadden 2026-03-18 13:25:28 -05:00 committed by GitHub
parent 0fdf52625b
commit b0c3ab5781
15 changed files with 1754 additions and 1 deletions

View file

@ -0,0 +1,77 @@
# GSD Workflow Templates — Implementation Plan (Updated)
**Date:** 2026-03-18
**Branch:** `feat/workflow-templates`
**Status:** In Progress — Phase 1
---
## Architecture Mapping (Plan → Actual Codebase)
The original plan referenced `gsd-tools.cjs`, `lib/init.cjs`, `lib/core.cjs` — these don't exist.
The actual architecture is a TypeScript extension system:
| Plan Reference | Actual Location |
|---|---|
| `gsd-tools.cjs` command routing | `src/resources/extensions/gsd/commands.ts` |
| `lib/workflow-template.cjs` | `src/resources/extensions/gsd/workflow-templates.ts` (new) |
| `lib/init.cjs` | No separate init; logic lives in handler module |
| `lib/core.cjs` | Utilities spread across `paths.ts`, `state.ts`, etc. |
| `~/.claude/get-shit-done/workflow-templates/` | `src/resources/extensions/gsd/workflow-templates/` (new dir) |
| `/gsd:start`, `/gsd:templates` | `/gsd start`, `/gsd templates` subcommands |
| Prompt templates | `src/resources/extensions/gsd/prompts/` |
---
## Phase 1: Foundation (Core Infrastructure)
### Files to Create
1. **`src/resources/extensions/gsd/workflow-templates/registry.json`**
- Template metadata: name, description, phases, triggers, artifact_dir, complexity, agents
2. **`src/resources/extensions/gsd/workflow-templates.ts`**
- `loadRegistry()` — parse registry.json from extension dir
- `resolveTemplate(nameOrTrigger)` — match by name, alias, or trigger keywords
- `autoDetect(context)` — analyze user input + project state for best template match
- `listTemplates()` — formatted template list for display
- `getTemplateInfo(name)` — detailed template metadata
3. **`src/resources/extensions/gsd/commands-workflow-templates.ts`**
- `handleStart(args, ctx, pi)``/gsd start [template] [args]`
- `handleTemplates(args, ctx)``/gsd templates [info <name>]`
4. **Wire into `commands.ts`**:
- Add `start` and `templates` to subcommand completions
- Add handler routing for both commands
### Files to Create (Phase 2 — Templates)
5. **`src/resources/extensions/gsd/workflow-templates/bugfix.md`**
6. **`src/resources/extensions/gsd/workflow-templates/small-feature.md`**
7. **`src/resources/extensions/gsd/workflow-templates/spike.md`**
8. **`src/resources/extensions/gsd/workflow-templates/hotfix.md`**
9. **`src/resources/extensions/gsd/workflow-templates/refactor.md`**
10. **`src/resources/extensions/gsd/workflow-templates/security-audit.md`**
11. **`src/resources/extensions/gsd/workflow-templates/dep-upgrade.md`**
12. **`src/resources/extensions/gsd/workflow-templates/full-project.md`**
### Prompt Templates
13. **`src/resources/extensions/gsd/prompts/workflow-start.md`** — dispatched when `/gsd start` resolves a template
14. **`src/resources/extensions/gsd/prompts/workflow-bugfix.md`** — bugfix-specific dispatch prompt
15. **`src/resources/extensions/gsd/prompts/workflow-small-feature.md`**
16. **`src/resources/extensions/gsd/prompts/workflow-spike.md`**
17. **`src/resources/extensions/gsd/prompts/workflow-hotfix.md`**
---
## Success Criteria
- [ ] `/gsd start bugfix` resolves template and dispatches workflow prompt
- [ ] `/gsd start` with no args auto-detects from context or shows choices
- [ ] `/gsd templates` lists all available templates
- [ ] `/gsd templates info bugfix` shows detailed template info
- [ ] All existing `/gsd *` commands work unchanged (zero regression)
- [ ] Registry validates (all referenced template files exist)
- [ ] Templates reuse existing agents and prompt patterns

View file

@ -0,0 +1,544 @@
/**
* GSD Workflow Template Commands /gsd start, /gsd templates
*
* Handles the `/gsd start [template] [description]` and `/gsd templates` commands.
* Resolves templates by name or auto-detection, then dispatches the workflow prompt.
*/
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import {
resolveByName,
autoDetect,
listTemplates,
getTemplateInfo,
loadWorkflowTemplate,
loadRegistry,
type TemplateMatch,
} from "./workflow-templates.js";
import { loadPrompt } from "./prompt-loader.js";
import { gsdRoot } from "./paths.js";
import { GitServiceImpl, runGit } from "./git-service.js";
import { loadEffectiveGSDPreferences } from "./preferences.js";
import { isAutoActive, isAutoPaused } from "./auto.js";
// ─── Helpers ─────────────────────────────────────────────────────────────────
/**
* Generate a URL-friendly slug from text.
*/
function slugify(text: string): string {
return text
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "")
.slice(0, 40)
.replace(/-$/, "");
}
/**
* Get the next workflow task number by scanning existing directories.
*/
function getNextWorkflowNum(workflowDir: string): number {
if (!existsSync(workflowDir)) return 1;
try {
const entries = readdirSync(workflowDir, { withFileTypes: true });
let max = 0;
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const match = entry.name.match(/^(\d{6})-(\d+)-/);
if (match) {
const num = parseInt(match[2], 10);
if (num > max) max = num;
}
}
return max + 1;
} catch {
return 1;
}
}
/**
* Format the date as YYMMDD for directory naming.
*/
function datePrefix(): string {
const d = new Date();
const yy = String(d.getFullYear()).slice(2);
const mm = String(d.getMonth() + 1).padStart(2, "0");
const dd = String(d.getDate()).padStart(2, "0");
return `${yy}${mm}${dd}`;
}
// ─── State Types ─────────────────────────────────────────────────────────────
interface WorkflowPhaseState {
name: string;
index: number;
status: "pending" | "active" | "completed";
}
interface WorkflowState {
template: string;
templateName: string;
description: string;
branch: string;
phases: WorkflowPhaseState[];
currentPhase: number;
startedAt: string;
updatedAt: string;
completedAt?: string;
artifactDir: string;
}
/**
* Write a STATE.json file to track workflow execution state.
*/
function writeWorkflowState(
artifactDir: string,
templateId: string,
templateName: string,
phases: string[],
description: string,
branch: string,
): void {
const statePath = join(artifactDir, "STATE.json");
const state: WorkflowState = {
template: templateId,
templateName,
description,
branch,
phases: phases.map((p, i) => ({
name: p,
index: i,
status: i === 0 ? "active" as const : "pending" as const,
})),
currentPhase: 0,
startedAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
artifactDir,
};
writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n");
}
/**
* Scan all workflow artifact directories for in-progress STATE.json files.
* Returns workflows that were started but not completed.
*/
function findInProgressWorkflows(basePath: string): WorkflowState[] {
const workflowsRoot = join(gsdRoot(basePath), "workflows");
if (!existsSync(workflowsRoot)) return [];
const results: WorkflowState[] = [];
try {
// Scan each category dir (bugfixes/, features/, spikes/, etc.)
for (const category of readdirSync(workflowsRoot, { withFileTypes: true })) {
if (!category.isDirectory()) continue;
const categoryDir = join(workflowsRoot, category.name);
for (const workflow of readdirSync(categoryDir, { withFileTypes: true })) {
if (!workflow.isDirectory()) continue;
const statePath = join(categoryDir, workflow.name, "STATE.json");
if (!existsSync(statePath)) continue;
try {
const raw = readFileSync(statePath, "utf-8");
const state = JSON.parse(raw) as WorkflowState;
if (!state.completedAt) {
results.push(state);
}
} catch { /* corrupted state file — skip */ }
}
}
} catch { /* workflows dir unreadable — skip */ }
// Sort by most recently updated
results.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
return results;
}
// ─── /gsd start ──────────────────────────────────────────────────────────────
export async function handleStart(
args: string,
ctx: ExtensionCommandContext,
pi: ExtensionAPI,
): Promise<void> {
const trimmed = args.trim();
// /gsd start --list → same as /gsd templates
if (trimmed === "--list" || trimmed === "list") {
ctx.ui.notify(listTemplates(), "info");
return;
}
// ─── Auto-mode conflict guard ──────────────────────────────────────────
// Workflow templates dispatch their own messages and switch git branches,
// which would conflict with an active auto-mode dispatch loop.
if (isAutoActive()) {
ctx.ui.notify(
"Cannot start a workflow template while auto-mode is running.\n" +
"Run /gsd pause first, then /gsd start.",
"warning",
);
return;
}
if (isAutoPaused()) {
ctx.ui.notify(
"Auto-mode is paused. Starting a workflow template will run independently.\n" +
"The paused auto-mode session can be resumed later with /gsd auto.",
"info",
);
}
// ─── Resume detection ───────────────────────────────────────────────────
// /gsd start --resume or /gsd start resume → resume in-progress workflow
if (trimmed === "--resume" || trimmed === "resume") {
const basePath = process.cwd();
const inProgress = findInProgressWorkflows(basePath);
if (inProgress.length === 0) {
ctx.ui.notify("No in-progress workflows found.", "info");
return;
}
// Resume the most recent one
const wf = inProgress[0];
const activePhase = wf.phases.find(p => p.status === "active");
const completedCount = wf.phases.filter(p => p.status === "completed").length;
ctx.ui.notify(
`Resuming: ${wf.templateName}\n` +
`Description: ${wf.description}\n` +
`Progress: ${completedCount}/${wf.phases.length} phases completed\n` +
`Current phase: ${activePhase?.name ?? "unknown"}\n` +
`Branch: ${wf.branch}\n` +
`Artifacts: ${wf.artifactDir}`,
"info",
);
const workflowContent = loadWorkflowTemplate(wf.template);
if (!workflowContent) {
ctx.ui.notify(`Template "${wf.template}" workflow file not found.`, "warning");
return;
}
const prompt = loadPrompt("workflow-start", {
templateId: wf.template,
templateName: wf.templateName,
templateDescription: `RESUMING — pick up from phase "${activePhase?.name ?? "unknown"}" (${completedCount}/${wf.phases.length} phases done)`,
phases: wf.phases.map(p => `${p.name}${p.status === "completed" ? " ✓" : p.status === "active" ? " ←" : ""}`).join(" → "),
complexity: "resume",
artifactDir: wf.artifactDir,
branch: wf.branch,
description: wf.description,
issueRef: "(none)",
date: new Date().toISOString().split("T")[0],
workflowContent,
});
pi.sendMessage(
{ customType: "gsd-workflow-template", content: prompt, display: false },
{ triggerTurn: true },
);
return;
}
// Show in-progress workflows when /gsd start is called with no args
if (!trimmed) {
const basePath = process.cwd();
const inProgress = findInProgressWorkflows(basePath);
if (inProgress.length > 0) {
const wf = inProgress[0];
const activePhase = wf.phases.find(p => p.status === "active");
const completedCount = wf.phases.filter(p => p.status === "completed").length;
ctx.ui.notify(
`In-progress workflow found:\n` +
` ${wf.templateName}: "${wf.description}"\n` +
` Phase ${completedCount + 1}/${wf.phases.length}: ${activePhase?.name ?? "unknown"}\n\n` +
`Run /gsd start resume to continue it.\n`,
"info",
);
}
}
// /gsd start --dry-run <template> → preview without executing
const dryRun = trimmed.includes("--dry-run");
const cleanedArgs = trimmed.replace(/--dry-run\s*/, "").trim();
// Parse: first word might be a template name, rest is description
const parts = cleanedArgs.split(/\s+/);
const firstWord = parts[0] ?? "";
// Check for --issue flag (bugfix shortcut)
const issueMatch = cleanedArgs.match(/--issue\s+(\S+)/);
const issueRef = issueMatch ? issueMatch[1] : null;
// Try resolving first word as a template name
let match: TemplateMatch | null = null;
let description = "";
if (firstWord) {
match = resolveByName(firstWord);
if (match) {
// First word was a template name; rest is description
description = parts.slice(1).join(" ").replace(/--issue\s+\S+/, "").trim();
}
}
// If no explicit template, try auto-detection from the full input
if (!match && cleanedArgs) {
const detected = autoDetect(cleanedArgs);
if (detected.length === 1 || (detected.length > 0 && detected[0].confidence === "high")) {
match = detected[0];
description = cleanedArgs;
ctx.ui.notify(
`Auto-detected template: ${match.template.name} (matched: "${match.matchedTrigger}")`,
"info",
);
} else if (detected.length > 1) {
const choices = detected.slice(0, 4).map(
(m) => ` /gsd start ${m.id} ${cleanedArgs}`
);
ctx.ui.notify(
`Multiple templates could match. Pick one:\n\n${choices.join("\n")}\n\nOr specify explicitly: /gsd start <template> <description>`,
"info",
);
return;
}
}
// No template resolved at all
if (!match) {
if (!trimmed) {
ctx.ui.notify(
"Usage: /gsd start <template> [description]\n\n" +
"Templates:\n" +
" bugfix Triage → fix → verify → ship\n" +
" small-feature Scope → plan → implement → verify\n" +
" spike Scope → research → synthesize\n" +
" hotfix Fix → ship (minimal ceremony)\n" +
" refactor Inventory → plan → migrate → verify\n" +
" security-audit Scan → triage → remediate → re-scan\n" +
" dep-upgrade Assess → upgrade → fix → verify\n" +
" full-project Complete GSD with full ceremony\n\n" +
"Examples:\n" +
" /gsd start bugfix fix login button not responding\n" +
" /gsd start spike evaluate auth libraries\n" +
" /gsd start hotfix critical: API returns 500\n\n" +
"Flags:\n" +
" --dry-run Preview what would happen without executing\n" +
" --issue <ref> Link to a GitHub issue\n\n" +
"Run /gsd templates for detailed template info.",
"info",
);
} else {
ctx.ui.notify(
`No template matched "${firstWord}". Run /gsd start to see available templates.`,
"warning",
);
}
return;
}
// ─── Resolved template ───────────────────────────────────────────────────
const templateId = match.id;
const template = match.template;
const basePath = process.cwd();
const date = new Date().toISOString().split("T")[0];
// Load the workflow template content
const workflowContent = loadWorkflowTemplate(templateId);
if (!workflowContent) {
ctx.ui.notify(
`Template "${templateId}" is registered but its workflow file (${template.file}) hasn't been created yet.`,
"warning",
);
return;
}
// ─── Dry-run mode: preview without executing ────────────────────────────
if (dryRun) {
const slug = slugify(description || templateId);
const lines = [
`DRY RUN — ${template.name} (${templateId})\n`,
`Description: ${description || "(none)"}`,
`Complexity: ${template.estimated_complexity}`,
`Phases: ${template.phases.join(" → ")}`,
"",
];
if (template.artifact_dir) {
const prefix = datePrefix();
const num = getNextWorkflowNum(join(basePath, template.artifact_dir));
lines.push(`Artifact dir: ${template.artifact_dir}${prefix}-${num}-${slug}`);
} else {
lines.push("Artifact dir: (none — hotfix mode)");
}
lines.push(`Branch: gsd/${templateId}/${slug}`);
if (issueRef) lines.push(`Issue: ${issueRef}`);
lines.push("", "No changes made. Remove --dry-run to execute.");
ctx.ui.notify(lines.join("\n"), "info");
return;
}
// ─── Route full-project to standard GSD workflow ────────────────────────
if (templateId === "full-project") {
const root = gsdRoot(basePath);
if (!existsSync(root)) {
ctx.ui.notify(
"Routing to /gsd init for full project setup...",
"info",
);
// Trigger /gsd init by dispatching to the handler
pi.sendMessage(
{
customType: "gsd-workflow-template",
content: "The user wants to start a full GSD project. Run `/gsd init` to bootstrap the project, then `/gsd auto` to begin execution.",
display: false,
},
{ triggerTurn: true },
);
} else {
ctx.ui.notify(
"Project already initialized. Use `/gsd auto` to continue or `/gsd discuss` to start a new milestone.",
"info",
);
}
return;
}
// ─── Create artifact directory ──────────────────────────────────────────
let artifactDir = "";
if (template.artifact_dir) {
const slug = slugify(description || templateId);
const prefix = datePrefix();
const num = getNextWorkflowNum(join(basePath, template.artifact_dir));
artifactDir = `${template.artifact_dir}${prefix}-${num}-${slug}`;
mkdirSync(join(basePath, artifactDir), { recursive: true });
}
// ─── Create git branch (unless isolation: none) ─────────────────────────
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git ?? {};
const git = new GitServiceImpl(basePath, gitPrefs);
const skipBranch = gitPrefs.isolation === "none";
const slug = slugify(description || templateId);
const branchName = `gsd/${templateId}/${slug}`;
let branchCreated = false;
if (!skipBranch) {
try {
const current = git.getCurrentBranch();
if (current !== branchName) {
try {
git.autoCommit("workflow-template", templateId, []);
} catch { /* nothing to commit */ }
runGit(basePath, ["checkout", "-b", branchName]);
branchCreated = true;
}
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
ctx.ui.notify(
`Could not create branch ${branchName}: ${message}. Working on current branch.`,
"warning",
);
}
}
const actualBranch = branchCreated ? branchName : git.getCurrentBranch();
// ─── Write workflow state for resume support ────────────────────────────
if (artifactDir) {
writeWorkflowState(
join(basePath, artifactDir),
templateId,
template.name,
template.phases,
description,
actualBranch,
);
}
// ─── Notify and dispatch ────────────────────────────────────────────────
const infoLines = [
`Starting workflow: ${template.name}`,
`Phases: ${template.phases.join(" → ")}`,
];
if (artifactDir) infoLines.push(`Artifacts: ${artifactDir}`);
infoLines.push(`Branch: ${actualBranch}`);
ctx.ui.notify(infoLines.join("\n"), "info");
const prompt = loadPrompt("workflow-start", {
templateId,
templateName: template.name,
templateDescription: template.description,
phases: template.phases.join(" → "),
complexity: template.estimated_complexity,
artifactDir: artifactDir || "(none)",
branch: actualBranch,
description: description || "(none provided)",
issueRef: issueRef || "(none)",
date,
workflowContent,
});
pi.sendMessage(
{
customType: "gsd-workflow-template",
content: prompt,
display: false,
},
{ triggerTurn: true },
);
}
// ─── /gsd templates ──────────────────────────────────────────────────────────
export async function handleTemplates(
args: string,
ctx: ExtensionCommandContext,
): Promise<void> {
const trimmed = args.trim();
// /gsd templates info <name>
if (trimmed.startsWith("info ")) {
const name = trimmed.replace(/^info\s+/, "").trim();
const info = getTemplateInfo(name);
if (info) {
ctx.ui.notify(info, "info");
} else {
ctx.ui.notify(
`Unknown template "${name}". Run /gsd templates to see available templates.`,
"warning",
);
}
return;
}
// /gsd templates — list all
ctx.ui.notify(listTemplates(), "info");
}
/**
* Return template IDs for autocomplete in /gsd templates info <name>.
*/
export function getTemplateCompletions(prefix: string): Array<{ value: string; label: string; description: string }> {
try {
const registry = loadRegistry();
return Object.entries(registry.templates)
.filter(([id]) => id.startsWith(prefix))
.map(([id, entry]) => ({
value: `info ${id}`,
label: id,
description: entry.description,
}));
} catch {
return [];
}
}

View file

@ -43,6 +43,7 @@ import { handleInspect } from "./commands-inspect.js";
import { handleCleanupBranches, handleCleanupSnapshots, handleSkip, handleDryRun } from "./commands-maintenance.js";
import { handleDoctor, handleSteer, handleCapture, handleTriage, handleKnowledge, handleRunHook, handleUpdate, handleSkillHealth } from "./commands-handlers.js";
import { handleLogs } from "./commands-logs.js";
import { handleStart, handleTemplates, getTemplateCompletions } from "./commands-workflow-templates.js";
/** Resolve the effective project root, accounting for worktree paths. */
@ -54,7 +55,7 @@ export function projectRoot(): string {
export function registerGSDCommand(pi: ExtensionAPI): void {
pi.registerCommand("gsd", {
description: "GSD — Get Shit Done: /gsd help|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|migrate|remote|steer|knowledge|new-milestone|parallel|update",
description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|migrate|remote|steer|knowledge|new-milestone|parallel|update",
getArgumentCompletions: (prefix: string) => {
const subcommands = [
{ cmd: "help", desc: "Categorized command reference with descriptions" },
@ -97,6 +98,8 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
{ cmd: "park", desc: "Park a milestone — skip without deleting" },
{ cmd: "unpark", desc: "Reactivate a parked milestone" },
{ cmd: "update", desc: "Update GSD to the latest version" },
{ cmd: "start", desc: "Start a workflow template (bugfix, spike, feature, etc.)" },
{ cmd: "templates", desc: "List available workflow templates" },
];
const parts = prefix.trim().split(/\s+/);
@ -282,6 +285,42 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
.map((s) => ({ value: `knowledge ${s.cmd}`, label: s.cmd, description: s.desc }));
}
if (parts[0] === "start" && parts.length <= 2) {
const subPrefix = parts[1] ?? "";
const subs = [
{ cmd: "bugfix", desc: "Triage, fix, test, and ship a bug fix" },
{ cmd: "small-feature", desc: "Lightweight feature with optional discussion" },
{ cmd: "spike", desc: "Research, prototype, and document findings" },
{ cmd: "hotfix", desc: "Minimal: fix it, test it, ship it" },
{ cmd: "refactor", desc: "Inventory, plan waves, migrate, verify" },
{ cmd: "security-audit", desc: "Scan, triage, remediate, re-scan" },
{ cmd: "dep-upgrade", desc: "Assess, upgrade, fix breaks, verify" },
{ cmd: "full-project", desc: "Complete GSD workflow with full ceremony" },
{ cmd: "resume", desc: "Resume an in-progress workflow" },
{ cmd: "--list", desc: "List all available templates" },
{ cmd: "--dry-run", desc: "Preview workflow without executing" },
];
return subs
.filter((s) => s.cmd.startsWith(subPrefix))
.map((s) => ({ value: `start ${s.cmd}`, label: s.cmd, description: s.desc }));
}
if (parts[0] === "templates" && parts.length <= 2) {
const subPrefix = parts[1] ?? "";
const subs = [
{ cmd: "info", desc: "Show detailed template info" },
];
return subs
.filter((s) => s.cmd.startsWith(subPrefix))
.map((s) => ({ value: `templates ${s.cmd}`, label: s.cmd, description: s.desc }));
}
if (parts[0] === "templates" && parts[1] === "info" && parts.length <= 3) {
const namePrefix = parts[2] ?? "";
return getTemplateCompletions(namePrefix)
.map((c) => ({ value: `templates ${c.value}`, label: c.label, description: c.description }));
}
if (parts[0] === "doctor") {
const modePrefix = parts[1] ?? "";
const modes = [
@ -773,6 +812,17 @@ Examples:
return;
}
// ─── Workflow Templates ────────────────────────────────────────
if (trimmed === "start" || trimmed.startsWith("start ")) {
await handleStart(trimmed.replace(/^start\s*/, "").trim(), ctx, pi);
return;
}
if (trimmed === "templates" || trimmed.startsWith("templates ")) {
await handleTemplates(trimmed.replace(/^templates\s*/, "").trim(), ctx);
return;
}
if (trimmed === "") {
// Bare /gsd defaults to step mode
await startAuto(ctx, pi, projectRoot(), false, { step: true });
@ -791,6 +841,8 @@ function showHelp(ctx: ExtensionCommandContext): void {
const lines = [
"GSD — Get Shit Done\n",
"WORKFLOW",
" /gsd start <tpl> Start a workflow template (bugfix, spike, feature, hotfix, etc.)",
" /gsd templates List available workflow templates [info <name>]",
" /gsd Run next unit in step mode (same as /gsd next)",
" /gsd next Execute next task, then pause [--dry-run] [--verbose]",
" /gsd auto Run all queued units continuously [--verbose]",

View file

@ -0,0 +1,28 @@
# Workflow Template: {{templateName}}
You are executing a **{{templateName}}** workflow (template: `{{templateId}}`).
## Context
- **Description:** {{description}}
- **Issue reference:** {{issueRef}}
- **Date:** {{date}}
- **Branch:** {{branch}}
- **Artifact directory:** {{artifactDir}}
- **Phases:** {{phases}}
- **Complexity:** {{complexity}}
## Workflow Definition
Follow the workflow defined below. Execute each phase in order, completing one before moving to the next. At each phase gate, confirm with the user before proceeding.
{{workflowContent}}
## Execution Rules
1. **Follow the phases in order.** Do not skip phases unless the workflow explicitly allows it.
2. **Artifact discipline.** If an artifact directory is specified, write all planning/summary documents there.
3. **Atomic commits.** Commit working code after each meaningful change. Use conventional commit format: `<type>(<scope>): <description>`.
4. **Verify before shipping.** Run the project's test suite and build before marking the workflow complete.
5. **Gate between phases.** After each phase, summarize what was done and ask the user to confirm before moving to the next phase.
6. **Stay focused.** This is a {{complexity}}-complexity workflow. Match your ceremony level to the task — don't over-engineer or under-deliver.

View file

@ -0,0 +1,173 @@
// GSD Workflow Templates — Unit Tests
//
// Tests registry loading, template resolution, auto-detection, and listing.
import { createTestContext } from './test-helpers.ts';
import {
loadRegistry,
resolveByName,
autoDetect,
listTemplates,
getTemplateInfo,
loadWorkflowTemplate,
} from '../workflow-templates.ts';
const { assertEq, assertTrue, assertMatch, report } = createTestContext();
// ═══════════════════════════════════════════════════════════════════════════
// Registry Loading
// ═══════════════════════════════════════════════════════════════════════════
console.log('\n── Registry Loading ──');
{
const registry = loadRegistry();
assertTrue(registry !== null, 'Registry should load');
assertEq(registry.version, 1, 'Registry version should be 1');
assertTrue(Object.keys(registry.templates).length >= 8, 'Should have at least 8 templates');
// Verify required template keys exist
const expectedIds = ['full-project', 'bugfix', 'small-feature', 'refactor', 'spike', 'hotfix', 'security-audit', 'dep-upgrade'];
for (const id of expectedIds) {
assertTrue(id in registry.templates, `Template "${id}" should exist in registry`);
}
// Verify each template has required fields
for (const [id, entry] of Object.entries(registry.templates)) {
assertTrue(typeof entry.name === 'string' && entry.name.length > 0, `${id}: name should be non-empty string`);
assertTrue(typeof entry.description === 'string' && entry.description.length > 0, `${id}: description should be non-empty`);
assertTrue(typeof entry.file === 'string' && entry.file.endsWith('.md'), `${id}: file should be a .md path`);
assertTrue(Array.isArray(entry.phases) && entry.phases.length > 0, `${id}: phases should be non-empty array`);
assertTrue(Array.isArray(entry.triggers) && entry.triggers.length > 0, `${id}: triggers should be non-empty array`);
}
}
// ═══════════════════════════════════════════════════════════════════════════
// Resolve by Name
// ═══════════════════════════════════════════════════════════════════════════
console.log('\n── Resolve by Name ──');
{
// Exact match
const bugfix = resolveByName('bugfix');
assertTrue(bugfix !== null, 'Should resolve "bugfix"');
assertEq(bugfix!.id, 'bugfix', 'ID should be "bugfix"');
assertEq(bugfix!.confidence, 'exact', 'Exact name should have exact confidence');
// Case-insensitive name match
const spike = resolveByName('Research Spike');
assertTrue(spike !== null, 'Should resolve "Research Spike" by name');
assertEq(spike!.id, 'spike', 'Should resolve to spike');
// Alias match
const bug = resolveByName('bug');
assertTrue(bug !== null, 'Should resolve "bug" alias');
assertEq(bug!.id, 'bugfix', 'Alias "bug" should map to bugfix');
const feat = resolveByName('feat');
assertTrue(feat !== null, 'Should resolve "feat" alias');
assertEq(feat!.id, 'small-feature', 'Alias "feat" should map to small-feature');
const deps = resolveByName('deps');
assertTrue(deps !== null, 'Should resolve "deps" alias');
assertEq(deps!.id, 'dep-upgrade', 'Alias "deps" should map to dep-upgrade');
// No match
const missing = resolveByName('nonexistent-template');
assertTrue(missing === null, 'Should return null for unknown template');
}
// ═══════════════════════════════════════════════════════════════════════════
// Auto-Detection
// ═══════════════════════════════════════════════════════════════════════════
console.log('\n── Auto-Detection ──');
{
// Should detect bugfix from "fix" keyword
const fixMatches = autoDetect('fix the login button');
assertTrue(fixMatches.length > 0, 'Should detect matches for "fix the login button"');
assertTrue(fixMatches.some(m => m.id === 'bugfix'), 'Should include bugfix in matches');
// Should detect spike from "research" keyword
const researchMatches = autoDetect('research authentication libraries');
assertTrue(researchMatches.length > 0, 'Should detect matches for "research"');
assertTrue(researchMatches.some(m => m.id === 'spike'), 'Should include spike in matches');
// Should detect hotfix from "urgent" keyword
const urgentMatches = autoDetect('urgent production is down');
assertTrue(urgentMatches.length > 0, 'Should detect matches for "urgent"');
assertTrue(urgentMatches.some(m => m.id === 'hotfix'), 'Should include hotfix in matches');
// Should detect dep-upgrade from "upgrade" keyword
const upgradeMatches = autoDetect('upgrade react to v19');
assertTrue(upgradeMatches.length > 0, 'Should detect matches for "upgrade"');
assertTrue(upgradeMatches.some(m => m.id === 'dep-upgrade'), 'Should include dep-upgrade in matches');
// Multi-word triggers should score higher
const projectMatches = autoDetect('create a new project from scratch');
const projectMatch = projectMatches.find(m => m.id === 'full-project');
assertTrue(projectMatch !== undefined, 'Should detect full-project for "from scratch"');
// Empty input should return no matches
const emptyMatches = autoDetect('');
assertEq(emptyMatches.length, 0, 'Empty input should return no matches');
}
// ═══════════════════════════════════════════════════════════════════════════
// List Templates
// ═══════════════════════════════════════════════════════════════════════════
console.log('\n── List Templates ──');
{
const output = listTemplates();
assertTrue(output.includes('Workflow Templates'), 'Should have header');
assertTrue(output.includes('bugfix'), 'Should list bugfix');
assertTrue(output.includes('spike'), 'Should list spike');
assertTrue(output.includes('hotfix'), 'Should list hotfix');
assertTrue(output.includes('/gsd start'), 'Should include usage hint');
}
// ═══════════════════════════════════════════════════════════════════════════
// Template Info
// ═══════════════════════════════════════════════════════════════════════════
console.log('\n── Template Info ──');
{
const info = getTemplateInfo('bugfix');
assertTrue(info !== null, 'Should return info for bugfix');
assertTrue(info!.includes('Bug Fix'), 'Should include template name');
assertTrue(info!.includes('triage'), 'Should include phase names');
assertTrue(info!.includes('Triggers'), 'Should include triggers section');
const missing = getTemplateInfo('nonexistent');
assertTrue(missing === null, 'Should return null for unknown template');
}
// ═══════════════════════════════════════════════════════════════════════════
// Load Workflow Template Content
// ═══════════════════════════════════════════════════════════════════════════
console.log('\n── Load Workflow Template ──');
{
const content = loadWorkflowTemplate('bugfix');
assertTrue(content !== null, 'Should load bugfix template');
assertTrue(content!.includes('Bugfix Workflow'), 'Should contain workflow title');
assertTrue(content!.includes('Phase 1: Triage'), 'Should contain triage phase');
assertTrue(content!.includes('Phase 4: Ship'), 'Should contain ship phase');
const hotfixContent = loadWorkflowTemplate('hotfix');
assertTrue(hotfixContent !== null, 'Should load hotfix template');
assertTrue(hotfixContent!.includes('Hotfix Workflow'), 'Should contain hotfix title');
const missingContent = loadWorkflowTemplate('nonexistent');
assertTrue(missingContent === null, 'Should return null for unknown template');
}
// ═══════════════════════════════════════════════════════════════════════════
report();

View file

@ -0,0 +1,241 @@
/**
* GSD Workflow Templates Registry & Resolution
*
* Loads the workflow template registry and resolves templates by name,
* alias, or trigger-keyword matching against user input.
*/
import { readFileSync, existsSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __extensionDir = dirname(fileURLToPath(import.meta.url));
const registryPath = join(__extensionDir, "workflow-templates", "registry.json");
// ─── Types ───────────────────────────────────────────────────────────────────
export interface TemplateEntry {
name: string;
description: string;
file: string;
phases: string[];
triggers: string[];
artifact_dir: string | null;
estimated_complexity: string;
requires_project: boolean;
}
export interface TemplateRegistry {
version: number;
templates: Record<string, TemplateEntry>;
}
export interface TemplateMatch {
id: string;
template: TemplateEntry;
confidence: "exact" | "high" | "medium" | "low";
matchedTrigger?: string;
}
// ─── Registry Cache ──────────────────────────────────────────────────────────
let cachedRegistry: TemplateRegistry | null = null;
/**
* Load and cache the workflow template registry.
*/
export function loadRegistry(): TemplateRegistry {
if (cachedRegistry) return cachedRegistry;
const content = readFileSync(registryPath, "utf-8");
cachedRegistry = JSON.parse(content) as TemplateRegistry;
return cachedRegistry;
}
/**
* Resolve a template by exact name or alias.
* Returns null if no match found.
*/
export function resolveByName(nameOrAlias: string): TemplateMatch | null {
const registry = loadRegistry();
const normalized = nameOrAlias.toLowerCase().trim();
// Exact key match
if (registry.templates[normalized]) {
return {
id: normalized,
template: registry.templates[normalized],
confidence: "exact",
};
}
// Match by template name (case-insensitive)
for (const [id, entry] of Object.entries(registry.templates)) {
if (entry.name.toLowerCase() === normalized) {
return { id, template: entry, confidence: "exact" };
}
}
// Fuzzy: prefix match on id
for (const [id, entry] of Object.entries(registry.templates)) {
if (id.startsWith(normalized) || normalized.startsWith(id)) {
return { id, template: entry, confidence: "high" };
}
}
// Common aliases
const aliases: Record<string, string> = {
"bug": "bugfix",
"fix": "bugfix",
"feature": "small-feature",
"feat": "small-feature",
"research": "spike",
"investigate": "spike",
"hot": "hotfix",
"urgent": "hotfix",
"security": "security-audit",
"audit": "security-audit",
"upgrade": "dep-upgrade",
"deps": "dep-upgrade",
"update-deps": "dep-upgrade",
"migration": "refactor",
"project": "full-project",
"full": "full-project",
};
const aliasMatch = aliases[normalized];
if (aliasMatch && registry.templates[aliasMatch]) {
return {
id: aliasMatch,
template: registry.templates[aliasMatch],
confidence: "high",
};
}
return null;
}
/**
* Auto-detect the best template based on user description text.
* Returns ranked matches sorted by confidence.
*/
export function autoDetect(description: string): TemplateMatch[] {
const registry = loadRegistry();
const lower = description.toLowerCase();
const words = lower.split(/\s+/);
const matches: TemplateMatch[] = [];
for (const [id, entry] of Object.entries(registry.templates)) {
let bestScore = 0;
let bestTrigger = "";
for (const trigger of entry.triggers) {
const triggerLower = trigger.toLowerCase();
// Exact phrase match in description
if (lower.includes(triggerLower)) {
const score = triggerLower.split(/\s+/).length * 2; // multi-word triggers score higher
if (score > bestScore) {
bestScore = score;
bestTrigger = trigger;
}
continue;
}
// Single-word trigger match against description words
if (!triggerLower.includes(" ") && words.includes(triggerLower)) {
if (1 > bestScore) {
bestScore = 1;
bestTrigger = trigger;
}
}
}
if (bestScore > 0) {
const confidence = bestScore >= 4 ? "high" : bestScore >= 2 ? "medium" : "low";
matches.push({
id,
template: entry,
confidence,
matchedTrigger: bestTrigger,
});
}
}
// Sort by confidence (high > medium > low), then alphabetically
const order = { exact: 0, high: 1, medium: 2, low: 3 };
matches.sort((a, b) => order[a.confidence] - order[b.confidence] || a.id.localeCompare(b.id));
return matches;
}
/**
* List all templates as formatted text for display.
*/
export function listTemplates(): string {
const registry = loadRegistry();
const lines: string[] = ["Workflow Templates\n"];
for (const [id, entry] of Object.entries(registry.templates)) {
const phases = entry.phases.join(" → ");
const complexity = entry.estimated_complexity;
lines.push(` ${id.padEnd(16)} ${entry.name}`);
lines.push(` ${"".padEnd(16)} ${entry.description}`);
lines.push(` ${"".padEnd(16)} Phases: ${phases} | Complexity: ${complexity}`);
lines.push("");
}
lines.push("Usage: /gsd start <template> [description]");
lines.push(" /gsd templates info <name>");
return lines.join("\n");
}
/**
* Get detailed info about a specific template.
*/
export function getTemplateInfo(name: string): string | null {
const match = resolveByName(name);
if (!match) return null;
const { id, template: t } = match;
const lines = [
`Template: ${t.name} (${id})`,
"",
`Description: ${t.description}`,
`Complexity: ${t.estimated_complexity}`,
`Requires .gsd/: ${t.requires_project ? "yes" : "no"}`,
"",
"Phases:",
...t.phases.map((p, i) => ` ${i + 1}. ${p}`),
"",
"Triggers:",
` ${t.triggers.join(", ")}`,
];
if (t.artifact_dir) {
lines.push("", `Artifacts: ${t.artifact_dir}`);
}
const templateFilePath = join(__extensionDir, "workflow-templates", t.file);
if (existsSync(templateFilePath)) {
lines.push("", "Template file: loaded");
} else {
lines.push("", "Template file: not yet created");
}
return lines.join("\n");
}
/**
* Load the raw content of a workflow template .md file.
*/
export function loadWorkflowTemplate(templateId: string): string | null {
const match = resolveByName(templateId);
if (!match) return null;
const filePath = join(__extensionDir, "workflow-templates", match.template.file);
if (!existsSync(filePath)) return null;
return readFileSync(filePath, "utf-8");
}

View file

@ -0,0 +1,87 @@
# Bugfix Workflow
<template_meta>
name: bugfix
version: 1
requires_project: false
artifact_dir: .gsd/workflows/bugfixes/
</template_meta>
<purpose>
Fix a bug from identification through to PR submission. Designed for issues reported
via GitHub, user reports, or developer discovery. Emphasizes root cause analysis
before jumping to fixes.
</purpose>
<phases>
1. triage — Identify root cause, reproduce the bug
2. fix — Implement the fix with tests
3. verify — Run full test suite, check for regressions
4. ship — Create PR with detailed explanation
</phases>
<process>
## Phase 1: Triage
**Goal:** Understand the bug before touching any code.
1. **Gather context:**
- If a GitHub issue was referenced, read the issue description, labels, and comments
- Identify the expected behavior vs actual behavior
- Note any error messages, stack traces, or reproduction steps provided
2. **Reproduce:**
- Find the minimal reproduction path
- Identify the affected code paths (files, functions, lines)
- If the bug is intermittent, note the conditions that trigger it
3. **Root cause analysis:**
- Trace the bug to its root cause (not just the symptom)
- Identify when the bug was introduced if possible (git blame/log)
- Assess blast radius: what else could be affected?
4. **Produce:** Write a brief `TRIAGE.md` in the artifact directory with:
- Root cause explanation
- Reproduction steps
- Affected files/functions
- Proposed fix approach
5. **Gate:** Present the triage findings and proposed fix to the user for confirmation.
## Phase 2: Fix
**Goal:** Implement a clean, tested fix.
1. **Plan the fix:** Write a brief plan (1-3 tasks max)
2. **Write the fix:** Implement the code change
3. **Write tests:** Add or update tests that:
- Reproduce the original bug (test fails without fix)
- Verify the fix works
- Cover edge cases
4. **Commit:** Atomic commit with message: `fix(<scope>): <description>`
## Phase 3: Verify
**Goal:** Ensure the fix doesn't break anything else.
1. Run the project's full test suite
2. Run the build (if applicable)
3. Run the linter (if applicable)
4. Check for regressions in related functionality
5. If any failures, fix them before proceeding
## Phase 4: Ship
**Goal:** Create a well-documented PR.
1. Ensure all changes are committed on the workflow branch
2. Build the PR body:
- Link to the original issue (if applicable)
- Explain the root cause
- Describe the fix approach
- List the test coverage added
3. Present the PR details to the user for review
4. Create the PR via `gh pr create` (with user approval)
</process>

View file

@ -0,0 +1,74 @@
# Dependency Upgrade Workflow
<template_meta>
name: dep-upgrade
version: 1
requires_project: false
artifact_dir: .gsd/workflows/upgrades/
</template_meta>
<purpose>
Upgrade project dependencies safely. Assess breaking changes before upgrading,
fix issues incrementally, and verify everything works. Handles both single-package
upgrades and bulk dependency refresh.
</purpose>
<phases>
1. assess — Analyze what's outdated and what will break
2. upgrade — Apply upgrades incrementally
3. fix — Resolve breaking changes
4. verify — Full test suite and build validation
</phases>
<process>
## Phase 1: Assess
**Goal:** Know what you're getting into before changing versions.
1. **List outdated dependencies:** Run `npm outdated` / equivalent
2. **For each target upgrade:**
- Read the changelog / release notes
- Identify breaking changes
- Check for known migration guides
- Assess impact on the codebase (grep for affected APIs)
3. **Prioritize:** Which upgrades to do now, which to defer
4. **Produce:** Write `ASSESSMENT.md` with:
- Dependency list with current → target versions
- Breaking changes per dependency
- Upgrade order (dependencies before dependents)
- Risk assessment
5. **Gate:** Review assessment with user. Confirm upgrade scope.
## Phase 2: Upgrade
**Goal:** Apply version bumps incrementally.
1. Upgrade one dependency (or one group of related dependencies) at a time
2. Run tests after each upgrade
3. Commit each upgrade: `chore(deps): upgrade <package> to <version>`
4. If tests fail, move to Phase 3 for that dependency before continuing
## Phase 3: Fix
**Goal:** Resolve any breaking changes from upgrades.
1. Fix API changes, type errors, deprecations
2. Update configuration if needed
3. Commit fixes separately from the upgrade: `fix(deps): adapt to <package> v<version> changes`
## Phase 4: Verify
**Goal:** Ensure everything works together.
1. Run the full test suite
2. Run the build
3. Run the linter
4. Check for deprecation warnings in output
5. **Produce:** Write `SUMMARY.md` with:
- Dependencies upgraded (from → to)
- Breaking changes encountered and how they were resolved
- Any deferred upgrades and why
</process>

View file

@ -0,0 +1,41 @@
# Full Project Workflow
<template_meta>
name: full-project
version: 1
requires_project: true
artifact_dir: .gsd/
</template_meta>
<purpose>
The complete GSD workflow with full ceremony: roadmap, milestones, slices, tasks,
research, planning, execution, and verification. Use for greenfield projects or
major features that need the full planning apparatus.
This template wraps the existing GSD workflow for registry completeness.
When selected, it routes to the standard /gsd init → /gsd auto pipeline.
</purpose>
<phases>
1. init — Initialize project, detect stack, create .gsd/
2. discuss — Define requirements, decisions, and architecture
3. plan — Create roadmap with milestones and slices
4. execute — Execute slices: research → plan → implement → verify per slice
5. verify — Milestone-level verification and completion
</phases>
<process>
## Routing to Standard GSD
This template is a convenience entry point. When selected via `/gsd start full-project`,
it should route to the standard GSD workflow:
1. If `.gsd/` doesn't exist: Run `/gsd init` to bootstrap the project
2. If `.gsd/` exists but no milestones: Start the discuss phase via `/gsd discuss`
3. If milestones exist: Resume via `/gsd auto` or `/gsd next`
The full GSD workflow protocol is defined in `GSD-WORKFLOW.md` and handles all
phases, state tracking, and agent orchestration.
</process>

View file

@ -0,0 +1,45 @@
# Hotfix Workflow
<template_meta>
name: hotfix
version: 1
requires_project: false
artifact_dir: null
</template_meta>
<purpose>
Minimal ceremony for urgent fixes. Fix it, test it, ship it. No planning artifacts,
no research phase, no lengthy documentation. For when production is broken and
speed matters.
</purpose>
<phases>
1. fix — Identify and fix the issue
2. ship — Test, commit, and create PR
</phases>
<process>
## Phase 1: Fix
**Goal:** Find and fix the issue as fast as possible.
1. Identify the broken behavior
2. Locate the root cause
3. Implement the minimal fix
4. Add a regression test if possible (don't block on this if the fix is urgent)
5. Commit: `fix(<scope>): <description>`
## Phase 2: Ship
**Goal:** Get the fix deployed.
1. Run tests — fix any failures
2. Run the build
3. Push and create PR with:
- What broke
- What the fix does
- How to verify
4. Present PR to user for approval
</process>

View file

@ -0,0 +1,83 @@
# Refactor / Migration Workflow
<template_meta>
name: refactor
version: 1
requires_project: false
artifact_dir: .gsd/workflows/refactors/
</template_meta>
<purpose>
Systematic code transformation with inventory-driven planning. Designed for
renames, restructures, pattern migrations, and API modernization. Executes in
waves to minimize risk and enable incremental verification.
</purpose>
<phases>
1. inventory — Catalog everything that needs to change
2. plan — Group changes into safe waves
3. migrate — Execute waves with verification between each
4. verify — Full regression testing and cleanup
</phases>
<process>
## Phase 1: Inventory
**Goal:** Know the full scope before changing anything.
1. **Scan the codebase:** Find all instances of what needs to change
- Files, functions, types, imports, tests, docs, config
- Use grep/glob to be exhaustive — don't rely on memory
2. **Categorize:** Group by type (source, test, config, docs)
3. **Identify dependencies:** What order must changes happen in?
4. **Produce:** Write `INVENTORY.md` with:
- Complete list of files/locations that need changes
- Dependency relationships
- Estimated scope (number of files, lines affected)
5. **Gate:** Review inventory with user. Confirm nothing is missing.
## Phase 2: Plan
**Goal:** Break the migration into safe, independently-verifiable waves.
1. **Define waves:** Group related changes so each wave:
- Leaves the codebase in a working state
- Can be committed and tested independently
- Handles dependencies (change the definition before the consumers)
2. **Typical wave structure:**
- Wave 1: Types/interfaces
- Wave 2: Core implementation
- Wave 3: Consumers/callers
- Wave 4: Tests
- Wave 5: Documentation and config
3. **Produce:** Write `PLAN.md` with waves and per-wave file lists
4. **Gate:** Confirm plan with user.
## Phase 3: Migrate
**Goal:** Execute waves one at a time with verification between each.
1. For each wave:
- Make the changes
- Run tests (at minimum, the build must pass)
- Commit: `refactor(<scope>): wave N — <description>`
2. If a wave introduces failures, fix them before moving to the next wave
3. If unexpected scope is discovered, update the inventory and plan
## Phase 4: Verify
**Goal:** Ensure the full refactor is complete and clean.
1. Run the complete test suite
2. Run the build
3. Run the linter — fix any new warnings
4. Search for any remnants of the old pattern (grep for old names/patterns)
5. **Produce:** Write `SUMMARY.md` with:
- What was changed and why
- Files modified (count and list)
- Any remaining follow-up items
</process>

View file

@ -0,0 +1,85 @@
{
"version": 1,
"templates": {
"full-project": {
"name": "Full Project",
"description": "Complete GSD workflow with roadmap, milestones, slices, and full ceremony",
"file": "full-project.md",
"phases": ["init", "discuss", "plan", "execute", "verify"],
"triggers": ["new project", "greenfield", "from scratch", "build an app", "create a new"],
"artifact_dir": ".gsd/",
"estimated_complexity": "high",
"requires_project": true
},
"bugfix": {
"name": "Bug Fix",
"description": "Triage, reproduce, fix, test, and ship a bug fix",
"file": "bugfix.md",
"phases": ["triage", "fix", "verify", "ship"],
"triggers": ["bug", "issue", "fix", "broken", "regression", "error", "crash", "failing", "github.com/*/issues/*"],
"artifact_dir": ".gsd/workflows/bugfixes/",
"estimated_complexity": "low",
"requires_project": false
},
"small-feature": {
"name": "Small Feature",
"description": "Lightweight feature development with optional discussion and research",
"file": "small-feature.md",
"phases": ["scope", "plan", "implement", "verify"],
"triggers": ["add", "feature", "implement", "build", "create", "new command", "new endpoint"],
"artifact_dir": ".gsd/workflows/features/",
"estimated_complexity": "medium",
"requires_project": false
},
"refactor": {
"name": "Refactor / Migration",
"description": "Systematic code transformation with inventory and wave-based execution",
"file": "refactor.md",
"phases": ["inventory", "plan", "migrate", "verify"],
"triggers": ["refactor", "migrate", "rename", "restructure", "move", "reorganize", "clean up"],
"artifact_dir": ".gsd/workflows/refactors/",
"estimated_complexity": "medium",
"requires_project": false
},
"spike": {
"name": "Research Spike",
"description": "Investigate a question, prototype, and document findings",
"file": "spike.md",
"phases": ["scope", "research", "synthesize"],
"triggers": ["research", "investigate", "explore", "spike", "compare", "evaluate", "should we", "what if", "how does"],
"artifact_dir": ".gsd/workflows/spikes/",
"estimated_complexity": "low",
"requires_project": false
},
"hotfix": {
"name": "Hotfix",
"description": "Minimal ceremony: fix the thing, test it, ship it",
"file": "hotfix.md",
"phases": ["fix", "ship"],
"triggers": ["hotfix", "urgent", "critical", "asap", "production down", "p0"],
"artifact_dir": null,
"estimated_complexity": "minimal",
"requires_project": false
},
"security-audit": {
"name": "Security Audit",
"description": "Scan for vulnerabilities, triage findings, remediate, and verify",
"file": "security-audit.md",
"phases": ["scan", "triage", "remediate", "re-scan"],
"triggers": ["security", "audit", "vulnerability", "owasp", "cve", "penetration", "hardening"],
"artifact_dir": ".gsd/workflows/audits/",
"estimated_complexity": "medium",
"requires_project": false
},
"dep-upgrade": {
"name": "Dependency Upgrade",
"description": "Assess impact, upgrade dependencies, fix breaking changes",
"file": "dep-upgrade.md",
"phases": ["assess", "upgrade", "fix", "verify"],
"triggers": ["upgrade", "update", "dependency", "deps", "bump", "outdated", "npm update", "renovate"],
"artifact_dir": ".gsd/workflows/upgrades/",
"estimated_complexity": "medium",
"requires_project": false
}
}
}

View file

@ -0,0 +1,73 @@
# Security Audit Workflow
<template_meta>
name: security-audit
version: 1
requires_project: false
artifact_dir: .gsd/workflows/audits/
</template_meta>
<purpose>
Systematic security review of the codebase. Scan for vulnerabilities, triage
findings by severity, remediate issues, and verify fixes. Covers OWASP Top 10,
dependency vulnerabilities, and project-specific security concerns.
</purpose>
<phases>
1. scan — Identify potential vulnerabilities
2. triage — Prioritize findings by severity and exploitability
3. remediate — Fix critical and high-severity issues
4. re-scan — Verify fixes and document remaining items
</phases>
<process>
## Phase 1: Scan
**Goal:** Identify potential security issues across the codebase.
1. **Dependency audit:** Run `npm audit` / `pip audit` / equivalent
2. **Code review for common vulnerabilities:**
- Injection (SQL, command, XSS)
- Authentication/authorization flaws
- Sensitive data exposure (hardcoded secrets, logs)
- Insecure configuration
- Missing input validation at boundaries
3. **Check security headers and CORS** (if web application)
4. **Review secrets management:** .env files, config, environment variables
5. **Produce:** Write `SCAN-RESULTS.md` with all findings
## Phase 2: Triage
**Goal:** Prioritize what to fix now vs later.
1. **Rate each finding:**
- Critical: exploitable, high impact, fix immediately
- High: likely exploitable, fix in this workflow
- Medium: lower risk, fix if time allows
- Low: informational, document for later
2. **Assess exploitability:** Is this theoretical or practically exploitable?
3. **Produce:** Update `SCAN-RESULTS.md` with severity ratings and triage decisions
4. **Gate:** Review triage with user. Agree on what to remediate now.
## Phase 3: Remediate
**Goal:** Fix critical and high-severity issues.
1. Fix each issue with proper testing
2. Commit each fix individually: `fix(security): <description>`
3. Don't introduce new functionality — security fixes only
## Phase 4: Re-scan
**Goal:** Verify fixes and document the final state.
1. Re-run the scans from Phase 1
2. Verify all targeted issues are resolved
3. **Produce:** Write `AUDIT-REPORT.md` with:
- Summary of findings and fixes
- Remaining medium/low items for future attention
- Recommendations for ongoing security practices
</process>

View file

@ -0,0 +1,81 @@
# Small Feature Workflow
<template_meta>
name: small-feature
version: 1
requires_project: false
artifact_dir: .gsd/workflows/features/
</template_meta>
<purpose>
Build a small-to-medium feature with lightweight planning. Designed for work that
needs more structure than /gsd quick but doesn't warrant full milestone ceremony.
Typical scope: a new command, endpoint, component, or module.
</purpose>
<phases>
1. scope — Define what we're building and confirm boundaries
2. plan — Break into 2-5 implementable tasks
3. implement — Execute the plan with atomic commits
4. verify — Run tests, build, and validate
</phases>
<process>
## Phase 1: Scope
**Goal:** Align on what to build and what's out of scope.
1. **Understand the request:** Clarify the feature's purpose and user-facing behavior
2. **Identify gray areas:** Surface 3-4 design decisions that need answers:
- API shape / interface design
- Where in the codebase this fits
- What existing patterns to follow
- Edge cases to handle (or explicitly skip)
3. **Define boundaries:** What's in scope vs out of scope for this workflow
4. **Produce:** Write a brief `CONTEXT.md` in the artifact directory with:
- Feature description
- Key decisions made
- Scope boundaries
5. **Gate:** Confirm scope with user before planning.
## Phase 2: Plan
**Goal:** Create a clear, executable plan.
1. **Research (if needed):** Read relevant existing code to understand patterns
2. **Break into tasks:** 2-5 tasks, each independently committable:
- Each task should take ~10-30 minutes of AI work
- Include file paths and specific changes
- Include verification steps per task
3. **Produce:** Write `PLAN.md` in the artifact directory
4. **Gate:** Present plan to user for approval. Adjust if needed.
## Phase 3: Implement
**Goal:** Build the feature following the plan.
1. Execute tasks in order
2. After each task:
- Verify the specific task's acceptance criteria
- Commit with message: `feat(<scope>): <description>`
3. If a task reveals the plan needs adjustment, note the deviation and adapt
4. Run incremental tests as you go (don't wait until the end)
## Phase 4: Verify
**Goal:** Ensure everything works together.
1. Run the full test suite
2. Run the build
3. Run the linter
4. Manual smoke check if applicable
5. **Produce:** Write a brief `SUMMARY.md` with:
- What was built
- Files changed
- How to test/use the feature
6. Present summary to user
</process>

View file

@ -0,0 +1,69 @@
# Research Spike Workflow
<template_meta>
name: spike
version: 1
requires_project: false
artifact_dir: .gsd/workflows/spikes/
</template_meta>
<purpose>
Investigate a question, evaluate options, prototype if needed, and produce a
clear recommendation. No production code is shipped — the output is knowledge.
Use for: technology evaluation, architecture decisions, "should we X?" questions.
</purpose>
<phases>
1. scope — Define the question and success criteria
2. research — Investigate from multiple angles
3. synthesize — Combine findings into a recommendation
</phases>
<process>
## Phase 1: Scope
**Goal:** Define exactly what we're investigating and what a good answer looks like.
1. **Frame the question:** What specific question(s) need answering?
2. **Define success criteria:** What would a useful answer include?
- Comparison criteria (performance, DX, maintenance, ecosystem, etc.)
- Constraints (must integrate with X, must support Y)
- Decision format (go/no-go, pick from options, tradeoff matrix)
3. **Identify research angles:** 2-3 distinct approaches to investigate:
- e.g., "evaluate library A", "evaluate library B", "evaluate building our own"
- e.g., "performance implications", "DX implications", "migration path"
4. **Produce:** Write `SCOPE.md` in the artifact directory
5. **Gate:** Confirm scope and research angles with user.
## Phase 2: Research
**Goal:** Investigate each angle thoroughly.
1. For each research angle:
- Search for relevant documentation, benchmarks, comparisons
- Read relevant source code in the project
- Build small prototypes or proof-of-concepts if needed
- Note pros, cons, risks, and unknowns
2. **Produce:** Write a research doc per angle in `research/` subdirectory:
- `research/ANGLE-1.md`, `research/ANGLE-2.md`, etc.
- Each doc: findings, evidence, pros/cons, confidence level
## Phase 3: Synthesize
**Goal:** Combine findings into a clear recommendation.
1. **Compare across angles:** Build a comparison matrix or summary table
2. **Make a recommendation:** Based on the evidence, what should we do?
- Primary recommendation with rationale
- Alternative if the primary doesn't work out
- What would change the recommendation (risk factors)
3. **Produce:** Write `RECOMMENDATION.md` with:
- Executive summary (1-2 paragraphs)
- Comparison matrix
- Recommendation with rationale
- Next steps if the recommendation is accepted
4. **Present** the recommendation to the user for discussion
</process>