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,72 @@
1
+ ---
2
+ description: Acceptance testing of completed phase work (parallel verifier, security-auditor, and reviewer subagents)
3
+ argument-hint: "<phase-number>"
4
+ allowed-tools: Bash, Read, Write, Edit, Task
5
+ ---
6
+
7
+ # /verify-work
8
+
9
+ Walk through acceptance testing of completed phase work, using subagents for parallel verification.
10
+
11
+ Phase: $1
12
+
13
+ > **Tool note**: Invoke `draht-tools <subcommand>` as `node "${CLAUDE_PLUGIN_ROOT}/bin/draht-tools.cjs" <subcommand>`. For subagents, use the **Task tool** and dispatch multiple in parallel (single assistant turn = multiple Task tool calls).
14
+
15
+ ## Atomic Reasoning
16
+
17
+ Before verifying, decompose phase acceptance into atomic reasoning units:
18
+
19
+ **For each deliverable:**
20
+ 1. **State the logical component** — What was this deliverable meant to produce? What user value does it provide?
21
+ 2. **Validate independence** — Can this deliverable be tested independently? What are its dependencies?
22
+ 3. **Verify correctness** — What tests prove it works? What edge cases must pass? What security concerns exist?
23
+
24
+ **Synthesize verification strategy:**
25
+ - Group parallel verification tasks (test suite, security audit, code review, domain compliance)
26
+ - Map each deliverable to specific test scenarios and acceptance criteria
27
+ - Identify critical vs optional checks
28
+ - Plan fix strategies for potential failures
29
+
30
+ ## Steps
31
+ 1. Run `draht-tools extract-deliverables $1` to get testable items
32
+
33
+ 2. **Run parallel verification via the Task tool**:
34
+ Dispatch these three subagents in parallel (single assistant turn, three Task tool calls):
35
+
36
+ - **Task tool** with `subagent_type: "verifier"` and prompt:
37
+ "Run the full test suite for this project. Check package.json for the test command. Record pass/fail counts. Then run any available lint and typecheck commands (e.g. npm run check, npm run lint, npx tsc --noEmit). Report all results with error details."
38
+
39
+ - **Task tool** with `subagent_type: "security-auditor"` and prompt:
40
+ "Audit the recent code changes (use git log and git diff to find them). Check for injection risks, auth bypasses, secrets in code, unsafe patterns. Report findings by severity."
41
+
42
+ - **Task tool** with `subagent_type: "reviewer"` and prompt:
43
+ "Review the recent code changes (use git log and git diff). Check domain language compliance against `.planning/DOMAIN.md` if it exists — scan for identifiers not in the glossary and cross-context boundary violations. Report findings."
44
+
45
+ 3. Run the quality gate check:
46
+ ```bash
47
+ node "${CLAUDE_PLUGIN_ROOT}/scripts/gsd-quality-gate.cjs"
48
+ ```
49
+ 4. Collect results from all subagents and the quality gate
50
+ 5. Walk the user through each deliverable one at a time, incorporating findings from the parallel checks
51
+ 6. Record results (pass/fail/partially/skip)
52
+ 7. For failures: diagnose and create fix plans via `draht-tools create-fix-plan $1 P`
53
+ - Fix plans MUST include a reproducing test that demonstrates the failure before any implementation
54
+ 8. Write UAT report: `draht-tools write-uat $1`
55
+ - Report must include: test health summary (pass/fail/coverage), security audit results, domain model status (any glossary violations), deliverable results
56
+ 9. If all passed: mark phase complete.
57
+ - If more phases remain in the milestone: tell the user to start a fresh session and run `/discuss-phase N+1`.
58
+ - If ALL phases in the milestone are complete: tell the user to start a fresh session and run `/next-milestone`.
59
+ 10. If failures: route to `/execute-phase $1 --gaps-only`
60
+
61
+ ## Workflow
62
+ This is the last step in the per-phase cycle:
63
+
64
+ ```
65
+ /discuss-phase N → /plan-phase N → /execute-phase N → /verify-work N
66
+ ```
67
+
68
+ After verify-work passes:
69
+ - More phases remaining → `/discuss-phase N+1`
70
+ - ALL phases in milestone verified → `/next-milestone`
71
+
72
+ `/next-milestone` is ONLY for generating new phases after every phase in the current milestone is complete.
@@ -0,0 +1,26 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/session-start.cjs\""
10
+ }
11
+ ]
12
+ }
13
+ ],
14
+ "UserPromptSubmit": [
15
+ {
16
+ "matcher": "",
17
+ "hooks": [
18
+ {
19
+ "type": "command",
20
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/prompt-context.cjs\""
21
+ }
22
+ ]
23
+ }
24
+ ]
25
+ }
26
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "draht-claude",
3
+ "version": "2026.4.23",
4
+ "description": "Draht GSD, multi-agent, TDD and DDD workflows as a Claude Code plugin. Install with `npx draht-claude install`.",
5
+ "type": "module",
6
+ "bin": {
7
+ "draht-claude": "./cli.mjs"
8
+ },
9
+ "files": [
10
+ ".claude-plugin",
11
+ "bin",
12
+ "scripts",
13
+ "hooks",
14
+ "commands",
15
+ "agents",
16
+ "skills",
17
+ "cli.mjs",
18
+ "README.md",
19
+ "LICENSE",
20
+ "CHANGELOG.md"
21
+ ],
22
+ "scripts": {
23
+ "test": "node cli.mjs --help > /dev/null && node scripts/gsd-pre-execute.cjs --self-check 2>/dev/null || true"
24
+ },
25
+ "keywords": [
26
+ "claude-code",
27
+ "claude-code-plugin",
28
+ "draht",
29
+ "gsd",
30
+ "planning",
31
+ "tdd",
32
+ "ddd",
33
+ "multi-agent",
34
+ "workflow",
35
+ "installer"
36
+ ],
37
+ "author": "Mario Zechner",
38
+ "license": "MIT",
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/draht-dev/draht.git",
45
+ "directory": "packages/draht-claude"
46
+ },
47
+ "engines": {
48
+ "node": ">=20.6.0"
49
+ }
50
+ }
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * Draht Post-Phase Hook
6
+ * Runs after phase completion to generate reports and update state.
7
+ *
8
+ * Usage: node gsd-post-phase.js <phase-number>
9
+ */
10
+
11
+ const fs = require("node:fs");
12
+ const path = require("node:path");
13
+
14
+ const phaseNum = process.argv[2];
15
+ if (!phaseNum) {
16
+ console.error("Usage: gsd-post-phase.js <phase-number>");
17
+ process.exit(1);
18
+ }
19
+
20
+ const PLANNING = ".planning";
21
+ const LOG_FILE = path.join(PLANNING, "execution-log.jsonl");
22
+
23
+ // 1. Read execution log for this phase
24
+ let entries = [];
25
+ if (fs.existsSync(LOG_FILE)) {
26
+ entries = fs.readFileSync(LOG_FILE, "utf-8")
27
+ .split("\n")
28
+ .filter(Boolean)
29
+ .map((l) => JSON.parse(l))
30
+ .filter((e) => e.phase === parseInt(phaseNum, 10));
31
+ }
32
+
33
+ const passed = entries.filter((e) => e.status === "pass").length;
34
+ const failed = entries.filter((e) => e.status === "fail").length;
35
+ const skipped = entries.filter((e) => e.status === "skip").length;
36
+ const warnings = entries.filter((e) => e.warning).length;
37
+
38
+ // 2. Compute TDD and domain health metrics
39
+ const tddViolations = entries.filter((e) => e.status === "tdd-violation").length;
40
+
41
+ // TDD commit counts: derive from git log scoped to commits since phase start
42
+ // (use the earliest entry timestamp as the lower bound)
43
+ let tddSummary = "⚠️ No TDD commit data in log";
44
+ try {
45
+ const { execSync } = require("node:child_process");
46
+ // Scope to commits that follow TDD naming convention for this phase
47
+ const gitLog = execSync(
48
+ `git log --format=%s --grep="^[0-9]" -E --extended-regexp 2>/dev/null || git log --format=%s 2>/dev/null`,
49
+ { encoding: "utf-8" }
50
+ ).trim();
51
+ const lines = gitLog.split("\n");
52
+ const redCount = lines.filter((l) => /^red:/i.test(l)).length;
53
+ const greenCount = lines.filter((l) => /^green:/i.test(l)).length;
54
+ const refactorCount = lines.filter((l) => /^refactor:/i.test(l)).length;
55
+ if (redCount + greenCount + refactorCount > 0) {
56
+ tddSummary = `🔴 Red: ${redCount} 🟢 Green: ${greenCount} 🔵 Refactor: ${refactorCount}`;
57
+ } else {
58
+ tddSummary = "⚠️ No red:/green:/refactor: commits found — TDD cycle may not have been followed";
59
+ }
60
+ } catch { /* ignore */ }
61
+
62
+ // Domain health: check DOMAIN.md presence and glossary size
63
+ let domainSummary = "⚠️ .planning/DOMAIN.md not found";
64
+ const domainPath = path.join(PLANNING, "DOMAIN.md");
65
+ if (fs.existsSync(domainPath)) {
66
+ const domainContent = fs.readFileSync(domainPath, "utf-8");
67
+ const termMatches = [...domainContent.matchAll(/\b([A-Z][a-zA-Z0-9]+)\b/g)];
68
+ const termCount = new Set(termMatches.map((m) => m[1])).size;
69
+ const hasContexts = domainContent.includes("## Bounded Contexts");
70
+ const hasGlossary = domainContent.includes("## Ubiquitous Language");
71
+ domainSummary = `${hasContexts ? "✅" : "❌"} Bounded Contexts ${hasGlossary ? "✅" : "❌"} Ubiquitous Language 📖 ~${termCount} terms`;
72
+ }
73
+
74
+ // TEST-STRATEGY health
75
+ let testStrategySummary = "⚠️ .planning/TEST-STRATEGY.md not found";
76
+ if (fs.existsSync(path.join(PLANNING, "TEST-STRATEGY.md"))) {
77
+ testStrategySummary = "✅ TEST-STRATEGY.md present";
78
+ }
79
+
80
+ // 3. Generate phase report
81
+ const reportPath = path.join(PLANNING, `phase-${phaseNum}-report.md`);
82
+ const report = `# Phase ${phaseNum} Execution Report
83
+
84
+ Generated: ${new Date().toISOString().replace("T", " ").slice(0, 19)}
85
+
86
+ ## Task Results
87
+ - ✅ Passed: ${passed}
88
+ - ❌ Failed: ${failed}
89
+ - ⏭️ Skipped: ${skipped}
90
+ - ⚠️ Warnings: ${warnings}
91
+
92
+ ## Execution Log
93
+ | Timestamp | Plan | Task | Status | Commit |
94
+ |-----------|------|------|--------|--------|
95
+ ${entries.map((e) => `| ${e.timestamp.slice(0, 19)} | ${e.plan} | ${e.task} | ${e.status} | ${e.commit || "-"} |`).join("\n")}
96
+
97
+ ## Quality Gate
98
+ ${failed === 0 ? "✅ All tasks passed — ready for verification" : `❌ ${failed} failure(s) — fix plans may be needed`}
99
+ ${warnings > 0 ? `⚠️ ${warnings} task(s) introduced type errors` : "✅ No type errors introduced"}
100
+
101
+ ## TDD Health
102
+ ${tddSummary}
103
+ ${tddViolations > 0 ? `❌ ${tddViolations} TDD cycle violation(s) recorded (green: without red:)` : "✅ No TDD cycle violations recorded"}
104
+
105
+ ## Domain Model Health
106
+ ${domainSummary}
107
+ ${testStrategySummary}
108
+ `;
109
+
110
+ fs.writeFileSync(reportPath, report);
111
+ console.log(`Phase ${phaseNum} report: ${reportPath}`);
112
+
113
+ // 4. Update ROADMAP.md status
114
+ const roadmapPath = path.join(PLANNING, "ROADMAP.md");
115
+ if (fs.existsSync(roadmapPath)) {
116
+ let roadmap = fs.readFileSync(roadmapPath, "utf-8");
117
+ const newStatus = failed === 0 ? "complete" : "needs-fixes";
118
+ const regex = new RegExp(`(## Phase ${phaseNum}:.+?)— \`\\w+\``, "m");
119
+ roadmap = roadmap.replace(regex, `$1— \`${newStatus}\``);
120
+ fs.writeFileSync(roadmapPath, roadmap);
121
+ console.log(`ROADMAP.md: Phase ${phaseNum} → ${newStatus}`);
122
+ }
123
+
124
+ // 5. Summary
125
+ console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
126
+ console.log(` Draht ► PHASE ${phaseNum} ${failed === 0 ? "COMPLETE ✅" : "NEEDS FIXES ❌"}`);
127
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
128
+ console.log(`\n${passed} passed, ${failed} failed, ${skipped} skipped`);
129
+ if (failed === 0) {
130
+ console.log(`\nNext: gsd-verify-work ${phaseNum}`);
131
+ } else {
132
+ console.log(`\nNext: gsd-execute-phase ${phaseNum} --gaps-only`);
133
+ }
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * Draht Post-Task Hook
6
+ * Runs after each task execution to validate and record results.
7
+ *
8
+ * Usage: node gsd-post-task.js <phase> <plan> <task-num> <status> [commit-hash]
9
+ * Status: pass | fail | skip
10
+ */
11
+
12
+ const fs = require("node:fs");
13
+ const path = require("node:path");
14
+ const { execSync } = require("node:child_process");
15
+
16
+ const [phaseNum, planNum, taskNum, status, commitHash] = process.argv.slice(2);
17
+ if (!phaseNum || !planNum || !taskNum || !status) {
18
+ console.error("Usage: gsd-post-task.js <phase> <plan> <task-num> <status> [commit-hash]");
19
+ process.exit(1);
20
+ }
21
+
22
+ // ── Toolchain detection — mirrors src/gsd/hook-utils.ts ──────────────────────
23
+ function detectToolchain(cwd) {
24
+ if (fs.existsSync(path.join(cwd, "bun.lockb")) || fs.existsSync(path.join(cwd, "bun.lock"))) {
25
+ return { pm: "bun", testCmd: "bun test", coverageCmd: "bun test --coverage", lintCmd: "bunx biome check ." };
26
+ }
27
+ if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) {
28
+ return { pm: "pnpm", testCmd: "pnpm test", coverageCmd: "pnpm run test:coverage", lintCmd: "pnpm run lint" };
29
+ }
30
+ if (fs.existsSync(path.join(cwd, "yarn.lock"))) {
31
+ return { pm: "yarn", testCmd: "yarn test", coverageCmd: "yarn run test:coverage", lintCmd: "yarn run lint" };
32
+ }
33
+ return { pm: "npm", testCmd: "npm test", coverageCmd: "npm run test:coverage", lintCmd: "npm run lint" };
34
+ }
35
+
36
+ function readHookConfig(cwd) {
37
+ const defaults = { coverageThreshold: 80, tddMode: "advisory", qualityGateStrict: false };
38
+ const configPath = path.join(cwd, ".planning", "config.json");
39
+ if (!fs.existsSync(configPath)) return defaults;
40
+ try {
41
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
42
+ const h = raw.hooks || {};
43
+ return {
44
+ coverageThreshold: typeof h.coverageThreshold === "number" ? h.coverageThreshold : defaults.coverageThreshold,
45
+ tddMode: h.tddMode === "strict" || h.tddMode === "advisory" ? h.tddMode : defaults.tddMode,
46
+ qualityGateStrict: typeof h.qualityGateStrict === "boolean" ? h.qualityGateStrict : defaults.qualityGateStrict,
47
+ };
48
+ } catch { return defaults; }
49
+ }
50
+
51
+ const PLANNING = ".planning";
52
+ const LOG_FILE = path.join(PLANNING, "execution-log.jsonl");
53
+ const cwd = process.cwd();
54
+ const toolchain = detectToolchain(cwd);
55
+ const hookConfig = readHookConfig(cwd);
56
+
57
+ // 0. TDD cycle compliance check
58
+ if (commitHash) {
59
+ try {
60
+ const currentMsg = execSync(`git log --format=%s -n 1 ${commitHash} 2>/dev/null`, { encoding: "utf-8" }).trim();
61
+ if (/^green:/i.test(currentMsg)) {
62
+ const taskPrefix = `${phaseNum}-${planNum}-${taskNum}`;
63
+ const recentMsgs = execSync(`git log --format=%s -n 50 ${commitHash}~1 2>/dev/null`, { encoding: "utf-8" })
64
+ .trim()
65
+ .split("\n")
66
+ .filter((m) => m.includes(taskPrefix) || /^(red|green|refactor):/i.test(m));
67
+ const prevTaskMsg = recentMsgs.find((m) => /^(red|green|refactor):/i.test(m) && m.includes(taskPrefix));
68
+ if (!prevTaskMsg || !/^red:/i.test(prevTaskMsg)) {
69
+ const violation = `TDD violation: "green:" commit without preceding "red:" for task ${phaseNum}-${planNum}-${taskNum}`;
70
+ if (hookConfig.tddMode === "strict") {
71
+ console.error(`❌ ${violation}`);
72
+ process.exit(1);
73
+ } else {
74
+ console.log(`⚠️ ${violation}`);
75
+ }
76
+ fs.appendFileSync(LOG_FILE, JSON.stringify({
77
+ timestamp: new Date().toISOString(),
78
+ phase: parseInt(phaseNum, 10),
79
+ plan: parseInt(planNum, 10),
80
+ task: parseInt(taskNum, 10),
81
+ status: "tdd-violation",
82
+ warning: "green: commit without preceding red: commit",
83
+ commit: commitHash,
84
+ }) + "\n");
85
+ }
86
+ }
87
+ } catch {
88
+ // Not a git repo or commit not found — ignore
89
+ }
90
+ }
91
+
92
+ // 1. Append to execution log
93
+ const entry = {
94
+ timestamp: new Date().toISOString(),
95
+ phase: parseInt(phaseNum, 10),
96
+ plan: parseInt(planNum, 10),
97
+ task: parseInt(taskNum, 10),
98
+ status,
99
+ commit: commitHash || null,
100
+ };
101
+
102
+ fs.appendFileSync(LOG_FILE, JSON.stringify(entry) + "\n");
103
+
104
+ // 2. Run type check and tests if status is pass
105
+ if (status === "pass") {
106
+ // Type check
107
+ try {
108
+ const tsCmd = toolchain.pm === "bun" ? "bun run tsgo --noEmit 2>&1" : "npx tsc --noEmit 2>&1";
109
+ execSync(tsCmd, { timeout: 30000, encoding: "utf-8", cwd });
110
+ // Run tests
111
+ try {
112
+ const testOutput = execSync(`${toolchain.testCmd} 2>&1`, { timeout: 60000, encoding: "utf-8", cwd });
113
+ const testMatch = testOutput.match(/(\d+) pass/);
114
+ const testCount = testMatch ? testMatch[1] : "?";
115
+ console.log(`✅ Task ${phaseNum}-${planNum}-${taskNum}: passed + types clean + ${testCount} tests pass`);
116
+ } catch (testErr) {
117
+ const testOut = testErr.stdout || testErr.stderr || "";
118
+ const failMatch = testOut.match(/(\d+) fail/);
119
+ if (failMatch) {
120
+ console.log(`⚠️ Task ${phaseNum}-${planNum}-${taskNum}: passed + types clean but ${failMatch[1]} test(s) failed`);
121
+ } else {
122
+ console.log(`✅ Task ${phaseNum}-${planNum}-${taskNum}: passed + types clean (no test runner found)`);
123
+ }
124
+ }
125
+ } catch (error) {
126
+ const output = error.stdout || "";
127
+ const errorCount = (output.match(/error TS/g) || []).length;
128
+ if (errorCount > 0) {
129
+ console.log(`⚠️ Task ${phaseNum}-${planNum}-${taskNum}: passed but ${errorCount} type error(s) introduced`);
130
+ fs.appendFileSync(LOG_FILE, JSON.stringify({
131
+ ...entry,
132
+ warning: `${errorCount} type errors introduced`,
133
+ }) + "\n");
134
+ } else {
135
+ console.log(`✅ Task ${phaseNum}-${planNum}-${taskNum}: passed`);
136
+ }
137
+ }
138
+ } else if (status === "fail") {
139
+ console.log(`❌ Task ${phaseNum}-${planNum}-${taskNum}: FAILED`);
140
+
141
+ // Check if we should create a fix plan
142
+ const logContent = fs.readFileSync(LOG_FILE, "utf-8");
143
+ const taskFailures = logContent.split("\n")
144
+ .filter(Boolean)
145
+ .map((l) => JSON.parse(l))
146
+ .filter((e) => e.phase === parseInt(phaseNum, 10) && e.plan === parseInt(planNum, 10) && e.task === parseInt(taskNum, 10) && e.status === "fail");
147
+
148
+ if (taskFailures.length >= 3) {
149
+ console.log(`\n⚠️ Task has failed 3+ times. Consider creating a fix plan:`);
150
+ console.log(` draht create-fix-plan ${phaseNum} ${planNum} "Task ${taskNum} failed repeatedly"`);
151
+ }
152
+ } else {
153
+ console.log(`⏭️ Task ${phaseNum}-${planNum}-${taskNum}: skipped`);
154
+ }
155
+
156
+ // 3. Update STATE.md last activity
157
+ const statePath = path.join(PLANNING, "STATE.md");
158
+ if (fs.existsSync(statePath)) {
159
+ let state = fs.readFileSync(statePath, "utf-8");
160
+ state = state.replace(
161
+ /## Last Activity:.*/,
162
+ `## Last Activity: ${new Date().toISOString().replace("T", " ").slice(0, 19)}`
163
+ );
164
+ fs.writeFileSync(statePath, state);
165
+ }
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ /**
5
+ * Draht Pre-Execute Hook
6
+ * Runs before gsd-execute-phase to validate preconditions.
7
+ *
8
+ * Usage: node gsd-pre-execute.js <phase-number>
9
+ * Exit 0 = proceed, Exit 1 = abort
10
+ */
11
+
12
+ const fs = require("node:fs");
13
+ const path = require("node:path");
14
+ const { execSync } = require("node:child_process");
15
+
16
+ const phaseNum = process.argv[2];
17
+ if (!phaseNum) {
18
+ console.error("Usage: gsd-pre-execute.js <phase-number>");
19
+ process.exit(1);
20
+ }
21
+
22
+ const PLANNING = ".planning";
23
+ const errors = [];
24
+ const warnings = [];
25
+
26
+ // 1. Check .planning/ exists
27
+ if (!fs.existsSync(PLANNING)) {
28
+ errors.push("No .planning/ directory found. Run gsd-new-project first.");
29
+ }
30
+
31
+ // 2. Check STATE.md exists
32
+ if (!fs.existsSync(path.join(PLANNING, "STATE.md"))) {
33
+ errors.push("No STATE.md found. Project not initialized.");
34
+ }
35
+
36
+ // 3. Check ROADMAP.md exists and phase is defined
37
+ const roadmapPath = path.join(PLANNING, "ROADMAP.md");
38
+ if (fs.existsSync(roadmapPath)) {
39
+ const roadmap = fs.readFileSync(roadmapPath, "utf-8");
40
+ if (!roadmap.includes(`Phase ${phaseNum}`)) {
41
+ errors.push(`Phase ${phaseNum} not found in ROADMAP.md`);
42
+ }
43
+ } else {
44
+ errors.push("No ROADMAP.md found.");
45
+ }
46
+
47
+ // 4. Check phase directory exists with plans
48
+ const phasesDir = path.join(PLANNING, "phases");
49
+ if (fs.existsSync(phasesDir)) {
50
+ const entries = fs.readdirSync(phasesDir);
51
+ const phaseDir = entries.find((e) => e.startsWith(String(phaseNum).padStart(2, "0") + "-"));
52
+ if (!phaseDir) {
53
+ errors.push(`No phase directory found for phase ${phaseNum}. Run gsd-plan-phase first.`);
54
+ } else {
55
+ const phaseFiles = fs.readdirSync(path.join(phasesDir, phaseDir));
56
+ const plans = phaseFiles.filter((f) => f.endsWith("-PLAN.md") && !f.includes("FIX"));
57
+ if (plans.length === 0) {
58
+ errors.push(`No plans found in phase ${phaseNum}. Run gsd-plan-phase first.`);
59
+ }
60
+
61
+ // Check plans have required elements
62
+ for (const planFile of plans) {
63
+ const content = fs.readFileSync(path.join(phasesDir, phaseDir, planFile), "utf-8");
64
+ if (!content.includes("<task")) {
65
+ warnings.push(`${planFile}: No <task> elements — plan may be incomplete`);
66
+ }
67
+ if (!content.includes("<verify>")) {
68
+ warnings.push(`${planFile}: Missing <verify> steps — tasks won't be verifiable`);
69
+ }
70
+ }
71
+ }
72
+ }
73
+
74
+ // 5. Check git status
75
+ try {
76
+ const status = execSync("git status --porcelain", { encoding: "utf-8" }).trim();
77
+ if (status) {
78
+ const lines = status.split("\n").length;
79
+ warnings.push(`${lines} uncommitted file(s) — consider committing first`);
80
+ }
81
+ } catch {
82
+ warnings.push("Not a git repository — no commit tracking");
83
+ }
84
+
85
+ // 6. Check for CONTINUE-HERE.md (unfinished session)
86
+ if (fs.existsSync(path.join(PLANNING, "CONTINUE-HERE.md"))) {
87
+ warnings.push("CONTINUE-HERE.md exists — previous session may be unfinished. Consider gsd-resume-work.");
88
+ }
89
+
90
+ // 7. Check DOMAIN.md exists and has required sections
91
+ const domainPath = path.join(PLANNING, "DOMAIN.md");
92
+ if (!fs.existsSync(domainPath)) {
93
+ warnings.push("No .planning/DOMAIN.md found — domain model not documented. Run /new-project or /map-codebase to create it.");
94
+ } else {
95
+ const domainContent = fs.readFileSync(domainPath, "utf-8");
96
+ if (!domainContent.includes("## Bounded Contexts")) {
97
+ errors.push("DOMAIN.md is missing '## Bounded Contexts' section.");
98
+ }
99
+ if (!domainContent.includes("## Ubiquitous Language")) {
100
+ errors.push("DOMAIN.md is missing '## Ubiquitous Language' section.");
101
+ }
102
+ }
103
+
104
+ // 8. Check TEST-STRATEGY.md exists
105
+ if (!fs.existsSync(path.join(PLANNING, "TEST-STRATEGY.md"))) {
106
+ warnings.push("No .planning/TEST-STRATEGY.md found — test strategy not documented. Run /new-project or /map-codebase to create it.");
107
+ }
108
+
109
+ // 9. Check all plans have non-empty <test> sections
110
+ if (fs.existsSync(phasesDir)) {
111
+ const entries2 = fs.readdirSync(phasesDir);
112
+ const phaseDir2 = entries2.find((e) => e.startsWith(String(phaseNum).padStart(2, "0") + "-"));
113
+ if (phaseDir2) {
114
+ const phaseFiles2 = fs.readdirSync(path.join(phasesDir, phaseDir2));
115
+ const plans2 = phaseFiles2.filter((f) => f.endsWith("-PLAN.md") && !f.includes("FIX"));
116
+ for (const planFile of plans2) {
117
+ const content = fs.readFileSync(path.join(phasesDir, phaseDir2, planFile), "utf-8");
118
+ // Extract all <test>...</test> blocks and check they are non-empty
119
+ const testMatches = [...content.matchAll(/<test>([\s\S]*?)<\/test>/g)];
120
+ if (testMatches.length === 0) {
121
+ errors.push(`${planFile}: Missing <test> sections — TDD requires tests for every task`);
122
+ } else {
123
+ for (const m of testMatches) {
124
+ if (!m[1].trim()) {
125
+ errors.push(`${planFile}: Empty <test> section found — fill in test cases before executing`);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ // Output
134
+ if (warnings.length > 0) {
135
+ console.log(`⚠️ ${warnings.length} warning(s):`);
136
+ for (const w of warnings) console.log(` - ${w}`);
137
+ }
138
+
139
+ if (errors.length > 0) {
140
+ console.log(`\n❌ ${errors.length} error(s) — cannot proceed:`);
141
+ for (const e of errors) console.log(` - ${e}`);
142
+ process.exit(1);
143
+ }
144
+
145
+ console.log(`✅ Pre-execute checks passed for phase ${phaseNum}`);
146
+ process.exit(0);