draht-claude 2026.4.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.claude-plugin/plugin.json +21 -0
  2. package/CHANGELOG.md +8 -0
  3. package/LICENSE +22 -0
  4. package/README.md +199 -0
  5. package/agents/architect.md +45 -0
  6. package/agents/debugger.md +57 -0
  7. package/agents/git-committer.md +52 -0
  8. package/agents/implementer.md +35 -0
  9. package/agents/reviewer.md +57 -0
  10. package/agents/security-auditor.md +109 -0
  11. package/agents/verifier.md +44 -0
  12. package/bin/draht-tools.cjs +1067 -0
  13. package/cli.mjs +348 -0
  14. package/commands/atomic-commit.md +61 -0
  15. package/commands/discuss-phase.md +54 -0
  16. package/commands/execute-phase.md +111 -0
  17. package/commands/fix.md +50 -0
  18. package/commands/init-project.md +65 -0
  19. package/commands/map-codebase.md +52 -0
  20. package/commands/new-project.md +73 -0
  21. package/commands/next-milestone.md +49 -0
  22. package/commands/orchestrate.md +58 -0
  23. package/commands/pause-work.md +38 -0
  24. package/commands/plan-phase.md +107 -0
  25. package/commands/progress.md +30 -0
  26. package/commands/quick.md +50 -0
  27. package/commands/resume-work.md +35 -0
  28. package/commands/review.md +55 -0
  29. package/commands/verify-work.md +72 -0
  30. package/hooks/hooks.json +26 -0
  31. package/package.json +50 -0
  32. package/scripts/gsd-post-phase.cjs +133 -0
  33. package/scripts/gsd-post-task.cjs +165 -0
  34. package/scripts/gsd-pre-execute.cjs +146 -0
  35. package/scripts/gsd-quality-gate.cjs +252 -0
  36. package/scripts/prompt-context.cjs +36 -0
  37. package/scripts/session-start.cjs +52 -0
  38. package/skills/ddd-workflow/SKILL.md +108 -0
  39. package/skills/gsd-workflow/SKILL.md +111 -0
  40. package/skills/tdd-workflow/SKILL.md +115 -0
@@ -0,0 +1,252 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * Draht Quality Gate Hook
6
+ * Runs after task completion to enforce quality standards.
7
+ * Called by the build agent after each verify step.
8
+ *
9
+ * Usage: node gsd-quality-gate.js [--strict]
10
+ * Exit 0 = quality OK, Exit 1 = quality issues
11
+ */
12
+
13
+ const { execSync } = require("node:child_process");
14
+ const fs = require("node:fs");
15
+ const path = require("node:path");
16
+
17
+ // ── Toolchain detection — mirrors src/gsd/hook-utils.ts ──────────────────────
18
+ function detectToolchain(cwd) {
19
+ if (fs.existsSync(path.join(cwd, "bun.lockb")) || fs.existsSync(path.join(cwd, "bun.lock"))) {
20
+ return { pm: "bun", testCmd: "bun test", coverageCmd: "bun test --coverage", lintCmd: "bunx biome check ." };
21
+ }
22
+ if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) {
23
+ return { pm: "pnpm", testCmd: "pnpm test", coverageCmd: "pnpm run test:coverage", lintCmd: "pnpm run lint" };
24
+ }
25
+ if (fs.existsSync(path.join(cwd, "yarn.lock"))) {
26
+ return { pm: "yarn", testCmd: "yarn test", coverageCmd: "yarn run test:coverage", lintCmd: "yarn run lint" };
27
+ }
28
+ return { pm: "npm", testCmd: "npm test", coverageCmd: "npm run test:coverage", lintCmd: "npm run lint" };
29
+ }
30
+
31
+ function readHookConfig(cwd) {
32
+ const defaults = { coverageThreshold: 80, tddMode: "advisory", qualityGateStrict: false };
33
+ const configPath = path.join(cwd, ".planning", "config.json");
34
+ if (!fs.existsSync(configPath)) return defaults;
35
+ try {
36
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
37
+ const h = raw.hooks || {};
38
+ return {
39
+ coverageThreshold: typeof h.coverageThreshold === "number" ? h.coverageThreshold : defaults.coverageThreshold,
40
+ tddMode: h.tddMode === "strict" || h.tddMode === "advisory" ? h.tddMode : defaults.tddMode,
41
+ qualityGateStrict: typeof h.qualityGateStrict === "boolean" ? h.qualityGateStrict : defaults.qualityGateStrict,
42
+ };
43
+ } catch { return defaults; }
44
+ }
45
+
46
+ // Inline domain validator — mirrors src/gsd/domain-validator.ts
47
+ function extractGlossaryTerms(content) {
48
+ const terms = new Set();
49
+ const sectionMatch = content.match(/## Ubiquitous Language([\s\S]*?)(?:\n## |$)/);
50
+ const section = sectionMatch ? sectionMatch[1] : content;
51
+ for (const m of section.matchAll(/\*\*([A-Z][a-zA-Z0-9]+)\*\*/g)) terms.add(m[1]);
52
+ for (const m of section.matchAll(/^[-*]\s+([A-Z][a-zA-Z0-9]+)\s*:/gm)) terms.add(m[1]);
53
+ for (const m of section.matchAll(/\|\s*([A-Z][a-zA-Z0-9]+)\s*\|/g)) terms.add(m[1]);
54
+ return terms;
55
+ }
56
+
57
+ function loadDomainContent(cwd) {
58
+ const modelPath = path.join(cwd, ".planning", "DOMAIN-MODEL.md");
59
+ if (fs.existsSync(modelPath)) return fs.readFileSync(modelPath, "utf-8");
60
+ const domainPath = path.join(cwd, ".planning", "DOMAIN.md");
61
+ if (fs.existsSync(domainPath)) return fs.readFileSync(domainPath, "utf-8");
62
+ return "";
63
+ }
64
+
65
+ // ── Main ──────────────────────────────────────────────────────────────────────
66
+ const cwd = process.cwd();
67
+ const toolchain = detectToolchain(cwd);
68
+ const hookConfig = readHookConfig(cwd);
69
+ const strict = process.argv.includes("--strict") || hookConfig.qualityGateStrict;
70
+ const issues = [];
71
+
72
+ // 1. TypeScript check
73
+ try {
74
+ const tsCmd = toolchain.pm === "bun" ? "bun run tsgo --noEmit 2>&1" : "npx tsc --noEmit 2>&1";
75
+ execSync(tsCmd, { timeout: 60000, encoding: "utf-8", cwd });
76
+ } catch (error) {
77
+ const output = error.stdout || error.stderr || "";
78
+ const errorCount = (output.match(/error TS/g) || []).length;
79
+ if (errorCount > 0) {
80
+ issues.push({ severity: strict ? "error" : "warning", message: `${errorCount} TypeScript error(s)`, details: output.slice(0, 500) });
81
+ }
82
+ }
83
+
84
+ // 2. Lint check (if biome.json exists use biome, else use toolchain lint)
85
+ if (fs.existsSync(path.join(cwd, "biome.json"))) {
86
+ try {
87
+ execSync(`${toolchain.lintCmd} --error-on-warnings 2>&1`, { timeout: 30000, encoding: "utf-8", cwd });
88
+ } catch (error) {
89
+ const output = error.stdout || error.stderr || "";
90
+ issues.push({ severity: strict ? "error" : "warning", message: "Lint issues", details: output.slice(0, 500) });
91
+ }
92
+ }
93
+
94
+ // 3. Run tests
95
+ try {
96
+ const testOutput = execSync(`${toolchain.testCmd} 2>&1`, { timeout: 120000, encoding: "utf-8", cwd });
97
+ const failMatch = testOutput.match(/(\d+) fail/);
98
+ if (failMatch && parseInt(failMatch[1], 10) > 0) {
99
+ issues.push({ severity: strict ? "error" : "warning", message: `${failMatch[1]} test(s) failing` });
100
+ }
101
+ } catch (error) {
102
+ const output = error.stdout || error.stderr || "";
103
+ const failMatch = output.match(/(\d+) fail/);
104
+ if (failMatch && parseInt(failMatch[1], 10) > 0) {
105
+ issues.push({ severity: strict ? "error" : "warning", message: `${failMatch[1]} test(s) failing` });
106
+ }
107
+ }
108
+
109
+ // 4. Check for console.log in source files (not tests)
110
+ try {
111
+ const result = execSync(
112
+ "grep -rn 'console\\.log' src/ --include='*.ts' --include='*.tsx' 2>/dev/null | grep -v '// debug' | head -5",
113
+ { encoding: "utf-8", cwd }
114
+ ).trim();
115
+ if (result) {
116
+ issues.push({ severity: "warning", message: "console.log found in source", details: result });
117
+ }
118
+ } catch { /* grep returns 1 when no match — that's fine */ }
119
+
120
+ // 5. Domain glossary compliance (checks DOMAIN-MODEL.md, falls back to DOMAIN.md)
121
+ const domainContent = loadDomainContent(cwd);
122
+ if (domainContent) {
123
+ try {
124
+ const glossaryTerms = extractGlossaryTerms(domainContent);
125
+ const changedFiles = execSync(
126
+ "git diff --cached --name-only 2>/dev/null || git diff --name-only HEAD~1",
127
+ { encoding: "utf-8", cwd }
128
+ ).trim().split("\n").filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
129
+
130
+ const unknownTerms = [];
131
+ for (const file of changedFiles) {
132
+ if (!fs.existsSync(path.join(cwd, file))) continue;
133
+ const src = fs.readFileSync(path.join(cwd, file), "utf-8");
134
+ const declarations = [...src.matchAll(/(?:class|interface|type|enum)\s+([A-Z][a-zA-Z0-9]+)/g)].map((m) => m[1]);
135
+ for (const term of declarations) {
136
+ if (!glossaryTerms.has(term)) unknownTerms.push(`${file}: ${term}`);
137
+ }
138
+ }
139
+ if (unknownTerms.length > 0) {
140
+ issues.push({
141
+ severity: hookConfig.tddMode === "strict" ? "error" : "warning",
142
+ message: `${unknownTerms.length} PascalCase type(s) not in domain glossary (DOMAIN-MODEL.md)`,
143
+ details: unknownTerms.slice(0, 5).join(", "),
144
+ });
145
+ }
146
+ } catch { /* ignore */ }
147
+ }
148
+
149
+ // 6. Bounded context boundary check — flag suspicious cross-directory imports
150
+ try {
151
+ const changedSrcFiles = execSync(
152
+ "git diff --cached --name-only 2>/dev/null || git diff --name-only HEAD~1",
153
+ { encoding: "utf-8", cwd }
154
+ ).trim().split("\n").filter((f) => /^src\/[^/]+\//.test(f) && (f.endsWith(".ts") || f.endsWith(".tsx")));
155
+
156
+ const crossContextImports = [];
157
+ for (const file of changedSrcFiles) {
158
+ if (!fs.existsSync(path.join(cwd, file))) continue;
159
+ const ownContext = file.split("/")[1];
160
+ const src = fs.readFileSync(path.join(cwd, file), "utf-8");
161
+ const imports = [...src.matchAll(/from\s+['"](\.\.\/.+?)['"]/g)].map((m) => m[1]);
162
+ for (const imp of imports) {
163
+ const resolved = path.normalize(path.join(path.dirname(file), imp));
164
+ const parts = resolved.split(path.sep);
165
+ const srcIdx = parts.indexOf("src");
166
+ if (srcIdx !== -1 && parts[srcIdx + 1] && parts[srcIdx + 1] !== ownContext) {
167
+ crossContextImports.push(`${file} → ${parts.slice(srcIdx).join("/")}`);
168
+ }
169
+ }
170
+ }
171
+ if (crossContextImports.length > 0) {
172
+ issues.push({
173
+ severity: "warning",
174
+ message: `${crossContextImports.length} suspicious cross-context import(s) detected`,
175
+ details: crossContextImports.slice(0, 3).join("; "),
176
+ });
177
+ }
178
+ } catch { /* ignore */ }
179
+
180
+ // 7. TDD health — check test-to-source file ratio
181
+ try {
182
+ const allSrc = execSync(
183
+ "find src -name '*.ts' -not -name '*.test.ts' -not -name '*.spec.ts' 2>/dev/null | wc -l",
184
+ { encoding: "utf-8", cwd }
185
+ ).trim();
186
+ const allTests = execSync(
187
+ "find src -name '*.test.ts' -o -name '*.spec.ts' 2>/dev/null | wc -l",
188
+ { encoding: "utf-8", cwd }
189
+ ).trim();
190
+ const srcCount = parseInt(allSrc, 10) || 0;
191
+ const testCount = parseInt(allTests, 10) || 0;
192
+ if (srcCount > 0) {
193
+ const ratio = testCount / srcCount;
194
+ if (ratio < 0.3) {
195
+ issues.push({
196
+ severity: "warning",
197
+ message: `TDD health: test-to-source ratio is ${(ratio * 100).toFixed(0)}% (${testCount} tests / ${srcCount} sources) — target ≥ 30%`,
198
+ });
199
+ }
200
+ }
201
+ } catch { /* ignore — src/ may not exist */ }
202
+
203
+ // 8. Check for TODO/FIXME/HACK comments in changed files
204
+ try {
205
+ const diff = execSync(
206
+ "git diff --cached --name-only 2>/dev/null || git diff --name-only HEAD~1",
207
+ { encoding: "utf-8", cwd }
208
+ ).trim();
209
+ if (diff) {
210
+ const files = diff.split("\n").filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
211
+ for (const file of files) {
212
+ try {
213
+ const content = fs.readFileSync(path.join(cwd, file), "utf-8");
214
+ const todos = content.match(/\/\/\s*(TODO|FIXME|HACK|XXX):/gi) || [];
215
+ if (todos.length > 0) {
216
+ issues.push({ severity: "info", message: `${file}: ${todos.length} TODO/FIXME comment(s)` });
217
+ }
218
+ } catch { /* file may not exist */ }
219
+ }
220
+ }
221
+ } catch { /* ignore */ }
222
+
223
+ // Output
224
+ const errors = issues.filter((i) => i.severity === "error");
225
+ const warnings = issues.filter((i) => i.severity === "warning");
226
+ const infos = issues.filter((i) => i.severity === "info");
227
+
228
+ if (errors.length > 0) {
229
+ console.log(`\n❌ Quality Gate FAILED (${errors.length} error(s)):`);
230
+ for (const e of errors) {
231
+ console.log(` ❌ ${e.message}`);
232
+ if (e.details) console.log(` ${e.details.split("\n")[0]}`);
233
+ }
234
+ }
235
+
236
+ if (warnings.length > 0) {
237
+ console.log(`\n⚠️ ${warnings.length} warning(s):`);
238
+ for (const w of warnings) {
239
+ console.log(` ⚠️ ${w.message}`);
240
+ }
241
+ }
242
+
243
+ if (infos.length > 0) {
244
+ console.log(`\nℹ️ ${infos.length} note(s):`);
245
+ for (const i of infos) console.log(` ℹ️ ${i.message}`);
246
+ }
247
+
248
+ if (errors.length === 0 && warnings.length === 0) {
249
+ console.log("✅ Quality gate passed");
250
+ }
251
+
252
+ process.exit(errors.length > 0 ? 1 : 0);
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * UserPromptSubmit Hook
6
+ * Injects minimal draht planning context before each user prompt is sent to the model.
7
+ * Only activates in projects with .planning/ and adds at most a short reminder line.
8
+ * Keep output tiny — this runs on every prompt.
9
+ */
10
+
11
+ const fs = require("node:fs");
12
+ const path = require("node:path");
13
+
14
+ const cwd = process.cwd();
15
+ const PLANNING = path.join(cwd, ".planning");
16
+
17
+ if (!fs.existsSync(PLANNING)) {
18
+ process.exit(0);
19
+ }
20
+
21
+ const statePath = path.join(PLANNING, "STATE.md");
22
+ if (!fs.existsSync(statePath)) {
23
+ process.exit(0);
24
+ }
25
+
26
+ try {
27
+ const state = fs.readFileSync(statePath, "utf-8");
28
+ const phaseMatch = state.match(/## Current Phase: (.+)/);
29
+ const statusMatch = state.match(/## Status: (.+)/);
30
+ if (phaseMatch && statusMatch) {
31
+ // Print a single-line reminder. Claude Code prepends stdout to the prompt context.
32
+ console.log(`[draht] ${phaseMatch[1].trim()} — ${statusMatch[1].trim()}`);
33
+ }
34
+ } catch {}
35
+
36
+ process.exit(0);
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * Session Start Hook
6
+ * Surfaces draht planning state when a Claude Code session starts in a project.
7
+ * - Reports current phase and task from .planning/STATE.md
8
+ * - Flags CONTINUE-HERE.md if the previous session was paused
9
+ * - Silent in projects without .planning/
10
+ */
11
+
12
+ const fs = require("node:fs");
13
+ const path = require("node:path");
14
+
15
+ const cwd = process.cwd();
16
+ const PLANNING = path.join(cwd, ".planning");
17
+
18
+ if (!fs.existsSync(PLANNING)) {
19
+ // No draht planning — silent
20
+ process.exit(0);
21
+ }
22
+
23
+ const lines = [];
24
+
25
+ // STATE.md — current phase + status
26
+ const statePath = path.join(PLANNING, "STATE.md");
27
+ if (fs.existsSync(statePath)) {
28
+ try {
29
+ const state = fs.readFileSync(statePath, "utf-8");
30
+ const phaseMatch = state.match(/## Current Phase: (.+)/);
31
+ const statusMatch = state.match(/## Status: (.+)/);
32
+ const activityMatch = state.match(/## Last Activity: (.+)/);
33
+ if (phaseMatch) lines.push(`Phase: ${phaseMatch[1].trim()}`);
34
+ if (statusMatch) lines.push(`Status: ${statusMatch[1].trim()}`);
35
+ if (activityMatch) lines.push(`Last activity: ${activityMatch[1].trim()}`);
36
+ } catch {}
37
+ }
38
+
39
+ // CONTINUE-HERE.md — resume marker
40
+ const continuePath = path.join(PLANNING, "CONTINUE-HERE.md");
41
+ if (fs.existsSync(continuePath)) {
42
+ lines.push("");
43
+ lines.push("CONTINUE-HERE.md present — the previous session was paused.");
44
+ lines.push("Run /resume-work to continue, or read .planning/CONTINUE-HERE.md for the handoff.");
45
+ }
46
+
47
+ if (lines.length > 0) {
48
+ console.log("━ Draht planning state ━");
49
+ for (const line of lines) console.log(line);
50
+ }
51
+
52
+ process.exit(0);
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: ddd-workflow
3
+ description: Domain-driven design discipline — bounded contexts, ubiquitous language, aggregates, domain events, context maps, and how the .planning/DOMAIN.md file drives code structure and naming. Use whenever the user is modelling a new domain, extracting domain concepts from existing code, deciding where code should live, or naming things.
4
+ ---
5
+
6
+ # DDD Workflow
7
+
8
+ Draht embeds domain-driven design into project initialization, planning, and execution. The `.planning/DOMAIN.md` file is the single source of truth for domain concepts.
9
+
10
+ ## .planning/DOMAIN.md Structure
11
+
12
+ ```markdown
13
+ ## Bounded Contexts
14
+ - **Billing** — everything about invoices, payments, subscriptions
15
+ - **Catalog** — products, pricing, availability
16
+ - **Fulfillment** — order processing, shipping, returns
17
+
18
+ ## Ubiquitous Language
19
+ - **Invoice** — a document requesting payment for delivered goods or services
20
+ - **Order** — a customer's request to purchase goods, before fulfillment
21
+ - **Line Item** — a single row on an invoice or order
22
+ - **SKU** — a unique identifier for a product variant in the catalog
23
+
24
+ ## Context Map
25
+ - Billing ← Catalog (downstream — billing reads product info)
26
+ - Fulfillment ← Billing (downstream — fulfillment needs invoice status)
27
+ - Shared kernel: Money, TaxRate (used by Billing and Fulfillment)
28
+
29
+ ## Aggregates
30
+ ### Billing
31
+ - Invoice (root) — LineItem, Payment
32
+ - Subscription (root) — BillingCycle
33
+
34
+ ### Catalog
35
+ - Product (root) — Variant, Price
36
+
37
+ ## Domain Events
38
+ - `InvoiceIssued` — Billing → Fulfillment, Notification
39
+ - `PaymentReceived` — Billing → Notification
40
+ - `OrderShipped` — Fulfillment → Notification, Customer
41
+ ```
42
+
43
+ ## The Five Rules
44
+
45
+ ### 1. Bounded contexts shape the code
46
+ - File/module structure mirrors bounded contexts: `src/billing/`, `src/catalog/`, `src/fulfillment/`
47
+ - Each context owns its aggregates, value objects, services, and domain events
48
+ - Cross-context imports are suspicious — prefer domain events or ACL adapters
49
+
50
+ ### 2. Code uses the ubiquitous language
51
+ - Class names, method names, variable names must match the glossary
52
+ - If you need a new term, update `DOMAIN.md` **first**, then write the code
53
+ - Never invent terms in code that aren't in the glossary
54
+
55
+ ### 3. Aggregates enforce invariants
56
+ - Each aggregate has one root entity
57
+ - All writes go through the root — never modify child entities directly from outside
58
+ - Aggregate boundaries align with transaction boundaries
59
+ - Aggregates reference each other by ID, not by reference
60
+
61
+ ### 4. Domain events cross context boundaries
62
+ - Upstream context publishes an event (`InvoiceIssued`)
63
+ - Downstream contexts subscribe and react (Notification sends email, Fulfillment releases order)
64
+ - No direct function call from Billing into Fulfillment — always via event
65
+
66
+ ### 5. Shared kernel is explicit
67
+ - If two contexts must share a type (e.g. `Money`, `TaxRate`), put it in `src/shared/` and document it in the Context Map
68
+ - Shared kernel changes are high-cost — they affect multiple contexts
69
+ - Prefer duplication over coupling when in doubt
70
+
71
+ ## The Post-Phase Domain Health Check
72
+
73
+ The `gsd-post-phase.cjs` hook checks `DOMAIN.md` after each phase:
74
+ - Is `## Bounded Contexts` section present?
75
+ - Is `## Ubiquitous Language` section present?
76
+ - Count of unique PascalCase terms (proxy for glossary size)
77
+
78
+ The `gsd-quality-gate.cjs` script also runs a domain validator that compares identifiers in code against the glossary and flags unknown terms.
79
+
80
+ ## Extracting Domain from Existing Code
81
+
82
+ When running `/init-project` or `/map-codebase` on a codebase that wasn't built with DDD:
83
+
84
+ 1. List top-level `src/` subdirectories — candidates for bounded contexts
85
+ 2. Scan PascalCase class / interface / type names — candidates for entities and value objects
86
+ 3. Scan repeated nouns in function names — candidates for domain concepts
87
+ 4. Look for cross-directory imports — candidates for context coupling to fix
88
+ 5. Write `DOMAIN.md` with what you found + what should exist
89
+ 6. Use subsequent phases to refactor toward the target model
90
+
91
+ ## Anti-patterns
92
+
93
+ **Anemic domain model** — entities that are just data bags with no behaviour. Push logic into the entities.
94
+
95
+ **Scattered aggregates** — one aggregate's logic spread across multiple contexts. Consolidate or introduce an ACL.
96
+
97
+ **Terminology drift** — the same concept called different things in different files. Fix in `DOMAIN.md` first, rename code second.
98
+
99
+ **Shared database** — multiple contexts writing to the same tables without explicit shared-kernel agreement. Break the coupling.
100
+
101
+ **Direct cross-context imports** — `import { ... } from '../billing/...'` in `src/fulfillment/`. Use domain events or ACL adapters.
102
+
103
+ ## When to Update DOMAIN.md
104
+
105
+ - Before writing code that introduces a new term → add it to the glossary first
106
+ - During `/discuss-phase` when gray areas reveal missing concepts
107
+ - After `/verify-work` when the reviewer agent flags domain language drift
108
+ - Whenever a refactor reveals that existing names don't match reality
@@ -0,0 +1,111 @@
1
+ ---
2
+ name: gsd-workflow
3
+ description: Draht's Get Shit Done workflow — how to use /new-project, /discuss-phase, /plan-phase, /execute-phase, /verify-work, /next-milestone, /pause-work, /resume-work, /progress, /fix, /quick and the .planning/ directory structure to drive a project from idea to shipping. Use when the user asks how to plan work, structure a project, set up milestones, track progress, or wants to start using draht's workflow.
4
+ ---
5
+
6
+ # GSD (Get Shit Done) Workflow
7
+
8
+ Draht's GSD workflow is a milestone → phase → plan → task hierarchy that lives in `.planning/` and is driven by slash commands + hooks.
9
+
10
+ ## Directory Structure
11
+
12
+ ```
13
+ .planning/
14
+ ├── PROJECT.md # what are we building
15
+ ├── REQUIREMENTS.md # v1 / v2 / out-of-scope
16
+ ├── ROADMAP.md # phases grouped into milestones
17
+ ├── DOMAIN.md # bounded contexts + ubiquitous language (DDD)
18
+ ├── TEST-STRATEGY.md # test framework, levels, coverage
19
+ ├── STATE.md # current phase, status, last activity
20
+ ├── CONTINUE-HERE.md # handoff doc (only when paused)
21
+ ├── execution-log.jsonl # append-only task execution log
22
+ ├── phases/
23
+ │ └── 01-phase-slug/
24
+ │ ├── 01-01-PLAN.md
25
+ │ ├── 01-01-SUMMARY.md
26
+ │ └── 01-02-PLAN.md
27
+ └── phase-N-report.md # generated by post-phase hook
28
+ ```
29
+
30
+ ## The Cycle
31
+
32
+ ### Project initialization (once)
33
+ - **`/new-project`** — greenfield: questioning → domain model → requirements → roadmap
34
+ - **`/init-project`** — existing codebase: map → extract domain → questioning → roadmap
35
+ - **`/map-codebase`** — standalone codebase analysis
36
+
37
+ ### Per-phase cycle (fresh session between each step)
38
+ 1. **`/discuss-phase N`** — capture decisions, gray areas, domain terms
39
+ 2. **`/plan-phase N`** — create atomic execution plans (parallel via architect subagents)
40
+ 3. **`/execute-phase N`** — TDD red→green→refactor (parallel via implementer subagents)
41
+ 4. **`/verify-work N`** — parallel verifier + security-auditor + reviewer, produce UAT report
42
+
43
+ Start a fresh session (`/clear`) between steps. Each command assumes a clean context.
44
+
45
+ ### Milestone transition
46
+ - **`/next-milestone`** — only after ALL phases in the current milestone are `complete`
47
+
48
+ ### Session continuity
49
+ - **`/pause-work`** — create `CONTINUE-HERE.md` with in-progress state
50
+ - **`/resume-work`** — read handoff, verify state, continue
51
+ - **`/progress`** — show current position in the roadmap
52
+
53
+ ### Ad-hoc
54
+ - **`/quick`** — small task with tracking but without full phase ceremony
55
+ - **`/fix`** — bug fix with TDD discipline (reproducing test first)
56
+ - **`/review`** — parallel code review + security audit
57
+ - **`/atomic-commit`** — analyze diff, split into atomic conventional commits
58
+
59
+ ## Task Format (XML inside PLAN.md files)
60
+
61
+ ```xml
62
+ <task type="auto">
63
+ <n>Task name</n>
64
+ <context>Bounded context</context>
65
+ <domain>Aggregates/entities touched</domain>
66
+ <files>affected files</files>
67
+ <test>RED phase — write failing tests first</test>
68
+ <action>GREEN phase — minimal impl to pass tests</action>
69
+ <refactor>REFACTOR phase — improve without breaking tests</refactor>
70
+ <verify>How to verify (tests pass + manual check)</verify>
71
+ <done>What "done" looks like as assertions</done>
72
+ </task>
73
+ ```
74
+
75
+ Task types: `auto`, `checkpoint:human-verify`, `checkpoint:decision`.
76
+
77
+ ## Hooks
78
+
79
+ The plugin ships workflow hooks under `${CLAUDE_PLUGIN_ROOT}/scripts/`:
80
+
81
+ - `gsd-pre-execute.cjs <phase>` — preconditions before execution (DOMAIN.md, plans, uncommitted changes)
82
+ - `gsd-post-task.cjs <phase> <plan> <task> <status> [commit]` — record result + type check + tests + TDD cycle check
83
+ - `gsd-post-phase.cjs <phase>` — generate phase report, update ROADMAP status
84
+ - `gsd-quality-gate.cjs [--strict]` — lint + typecheck + test + coverage against `.planning/config.json` threshold
85
+
86
+ These are invoked from inside commands (not as Claude Code lifecycle hooks).
87
+
88
+ ## Configuration
89
+
90
+ `.planning/config.json` (optional):
91
+
92
+ ```json
93
+ {
94
+ "hooks": {
95
+ "coverageThreshold": 80,
96
+ "tddMode": "advisory",
97
+ "qualityGateStrict": false
98
+ }
99
+ }
100
+ ```
101
+
102
+ - `tddMode: "strict"` — post-task hook aborts on green: commit without preceding red:
103
+ - `tddMode: "advisory"` — logs a warning instead
104
+ - `qualityGateStrict: true` — fail the gate on any lint/type/test/coverage miss
105
+
106
+ ## Key Rules
107
+
108
+ - One phase at a time, one cycle step per session
109
+ - `/next-milestone` ONLY after every phase in the current milestone is verified
110
+ - Fix plans include a reproducing test before any implementation
111
+ - Never skip verification — it's the only thing that marks a phase complete
@@ -0,0 +1,115 @@
1
+ ---
2
+ name: tdd-workflow
3
+ description: Test-driven development discipline — red→green→refactor cycle, commit conventions (red:, green:, refactor:), TDD cycle violations, reproducing tests before fixes, and how to write tests that actually drive design. Use whenever the user is writing code that has testable behaviour, fixing bugs, or asks about TDD.
4
+ ---
5
+
6
+ # TDD Workflow
7
+
8
+ Draht enforces strict test-driven development through commit conventions, post-task hooks, and the plan task format.
9
+
10
+ ## The Cycle
11
+
12
+ ### RED — Write a failing test
13
+ 1. Write a test that describes the behaviour you want
14
+ 2. Run the test runner — it MUST fail for the right reason (not a syntax error, not a missing import)
15
+ 3. Commit with prefix `red:`
16
+ ```
17
+ git add <test-files>
18
+ git commit -m "red: <what the test proves>"
19
+ ```
20
+
21
+ ### GREEN — Make it pass with the smallest possible change
22
+ 1. Write the minimum implementation that makes the failing test pass
23
+ 2. Run the test — confirm it passes
24
+ 3. Run the full test suite — confirm no regressions
25
+ 4. Commit with prefix `green:`
26
+ ```
27
+ git add <impl-files>
28
+ git commit -m "green: <task name>"
29
+ ```
30
+
31
+ ### REFACTOR — Improve structure while staying green
32
+ 1. Tests must stay green after every change — run them often
33
+ 2. Extract value objects, push logic into domain layer, remove duplication
34
+ 3. Keep to the ubiquitous language from `.planning/DOMAIN.md`
35
+ 4. Commit with prefix `refactor:`
36
+ ```
37
+ git add <files>
38
+ git commit -m "refactor: <what was improved>"
39
+ ```
40
+
41
+ ## Rules
42
+
43
+ 1. **Never write implementation before a failing test.** If you find yourself writing code that doesn't have a failing test waiting for it, stop and write the test first.
44
+
45
+ 2. **A test that passes on first run is suspect.** It means you're not testing what you think. Make it fail by breaking the implementation temporarily — does it fail? If not, the test is useless.
46
+
47
+ 3. **One red → one green → optional refactor.** Keep cycles small. A red commit with 20 failing tests is too big.
48
+
49
+ 4. **Test behaviour, not implementation.** Write tests against the public API. Tests that mock everything are tests of the mocks.
50
+
51
+ 5. **Domain tests use domain language.** Class names, test names, fixture names must match `.planning/DOMAIN.md` if it exists. Domain tests read like specs.
52
+
53
+ 6. **Fix bugs with a reproducing test first.** No exceptions. The test must fail before the fix, pass after.
54
+
55
+ ## TDD Cycle Violations
56
+
57
+ The post-task hook (`gsd-post-task.cjs`) checks commit history for cycle violations:
58
+
59
+ - A `green:` commit with no preceding `red:` commit for the same task → violation
60
+ - In `strict` mode: the hook aborts execution
61
+ - In `advisory` mode: the hook logs a warning to `.planning/execution-log.jsonl`
62
+
63
+ Set the mode in `.planning/config.json`:
64
+ ```json
65
+ { "hooks": { "tddMode": "strict" } }
66
+ ```
67
+
68
+ ## When to Skip the Cycle
69
+
70
+ Only skip TDD for:
71
+ - Pure configuration (tsconfig, biome, prettier)
72
+ - Documentation-only changes
73
+ - Generated code (auto-generated clients, schema bindings)
74
+ - Mechanical refactors with no behaviour change (e.g., rename)
75
+
76
+ Never skip for:
77
+ - Bug fixes
78
+ - New features
79
+ - Changes to domain logic
80
+ - Changes to APIs
81
+
82
+ ## The Plan Task Format Drives TDD
83
+
84
+ Plan tasks use `<test>`, `<action>`, `<refactor>` sections precisely to force the cycle:
85
+
86
+ ```xml
87
+ <task type="auto">
88
+ <n>Add user authentication</n>
89
+ <test>
90
+ RED phase: Write failing tests FIRST.
91
+ - test/auth.test.ts: valid credentials → returns session token
92
+ - test/auth.test.ts: invalid password → throws UnauthorizedError
93
+ - test/auth.test.ts: expired token → returns null
94
+ </test>
95
+ <action>
96
+ GREEN phase: Minimal implementation.
97
+ - src/auth/login.ts: verify password hash, return token
98
+ - src/auth/session.ts: read/write session store
99
+ </action>
100
+ <refactor>
101
+ Extract password verification into domain layer. Keep session IO at the boundary.
102
+ </refactor>
103
+ </task>
104
+ ```
105
+
106
+ When executing, the implementer subagent follows this order strictly. Commits get the `red:` / `green:` / `refactor:` prefixes automatically.
107
+
108
+ ## Coverage Goals
109
+
110
+ `.planning/config.json` sets the threshold:
111
+ ```json
112
+ { "hooks": { "coverageThreshold": 80 } }
113
+ ```
114
+
115
+ The `gsd-quality-gate.cjs` script enforces this at verification time. Coverage is a floor, not a target — aim for the meaningful paths.