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:
parent
0fdf52625b
commit
b0c3ab5781
15 changed files with 1754 additions and 1 deletions
77
.plans/workflow-templates.md
Normal file
77
.plans/workflow-templates.md
Normal 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
|
||||
544
src/resources/extensions/gsd/commands-workflow-templates.ts
Normal file
544
src/resources/extensions/gsd/commands-workflow-templates.ts
Normal 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 [];
|
||||
}
|
||||
}
|
||||
|
|
@ -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]",
|
||||
|
|
|
|||
28
src/resources/extensions/gsd/prompts/workflow-start.md
Normal file
28
src/resources/extensions/gsd/prompts/workflow-start.md
Normal 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.
|
||||
173
src/resources/extensions/gsd/tests/workflow-templates.test.ts
Normal file
173
src/resources/extensions/gsd/tests/workflow-templates.test.ts
Normal 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();
|
||||
241
src/resources/extensions/gsd/workflow-templates.ts
Normal file
241
src/resources/extensions/gsd/workflow-templates.ts
Normal 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");
|
||||
}
|
||||
87
src/resources/extensions/gsd/workflow-templates/bugfix.md
Normal file
87
src/resources/extensions/gsd/workflow-templates/bugfix.md
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
45
src/resources/extensions/gsd/workflow-templates/hotfix.md
Normal file
45
src/resources/extensions/gsd/workflow-templates/hotfix.md
Normal 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>
|
||||
83
src/resources/extensions/gsd/workflow-templates/refactor.md
Normal file
83
src/resources/extensions/gsd/workflow-templates/refactor.md
Normal 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>
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
69
src/resources/extensions/gsd/workflow-templates/spike.md
Normal file
69
src/resources/extensions/gsd/workflow-templates/spike.md
Normal 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>
|
||||
Loading…
Add table
Reference in a new issue