@draht/coding-agent 2026.3.4 → 2026.3.6

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 (112) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/agents/architect.md +1 -0
  3. package/agents/debugger.md +1 -0
  4. package/agents/git-committer.md +1 -0
  5. package/agents/implementer.md +1 -0
  6. package/agents/reviewer.md +1 -0
  7. package/agents/security-auditor.md +1 -0
  8. package/agents/verifier.md +1 -0
  9. package/dist/agents/architect.md +1 -0
  10. package/dist/agents/debugger.md +1 -0
  11. package/dist/agents/git-committer.md +1 -0
  12. package/dist/agents/implementer.md +1 -0
  13. package/dist/agents/reviewer.md +1 -0
  14. package/dist/agents/security-auditor.md +1 -0
  15. package/dist/agents/verifier.md +1 -0
  16. package/dist/config.d.ts +7 -0
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +15 -0
  19. package/dist/config.js.map +1 -1
  20. package/dist/core/package-manager.d.ts.map +1 -1
  21. package/dist/core/package-manager.js +7 -7
  22. package/dist/core/package-manager.js.map +1 -1
  23. package/dist/core/prompt-templates.d.ts.map +1 -1
  24. package/dist/core/prompt-templates.js +8 -6
  25. package/dist/core/prompt-templates.js.map +1 -1
  26. package/dist/core/resource-loader.d.ts.map +1 -1
  27. package/dist/core/resource-loader.js +9 -8
  28. package/dist/core/resource-loader.js.map +1 -1
  29. package/dist/core/settings-manager.d.ts.map +1 -1
  30. package/dist/core/settings-manager.js +2 -2
  31. package/dist/core/settings-manager.js.map +1 -1
  32. package/dist/core/skills.d.ts.map +1 -1
  33. package/dist/core/skills.js +3 -3
  34. package/dist/core/skills.js.map +1 -1
  35. package/dist/gsd/domain-validator.d.ts +18 -0
  36. package/dist/gsd/domain-validator.d.ts.map +1 -0
  37. package/dist/gsd/domain-validator.js +61 -0
  38. package/dist/gsd/domain-validator.js.map +1 -0
  39. package/dist/gsd/domain.d.ts +12 -0
  40. package/dist/gsd/domain.d.ts.map +1 -0
  41. package/dist/gsd/domain.js +113 -0
  42. package/dist/gsd/domain.js.map +1 -0
  43. package/dist/gsd/git.d.ts +20 -0
  44. package/dist/gsd/git.d.ts.map +1 -0
  45. package/dist/gsd/git.js +59 -0
  46. package/dist/gsd/git.js.map +1 -0
  47. package/dist/gsd/hook-utils.d.ts +22 -0
  48. package/dist/gsd/hook-utils.d.ts.map +1 -0
  49. package/dist/gsd/hook-utils.js +100 -0
  50. package/dist/gsd/hook-utils.js.map +1 -0
  51. package/dist/gsd/index.d.ts +9 -0
  52. package/dist/gsd/index.d.ts.map +1 -0
  53. package/dist/gsd/index.js +8 -0
  54. package/dist/gsd/index.js.map +1 -0
  55. package/dist/gsd/planning.d.ts +20 -0
  56. package/dist/gsd/planning.d.ts.map +1 -0
  57. package/dist/gsd/planning.js +167 -0
  58. package/dist/gsd/planning.js.map +1 -0
  59. package/dist/hooks/gsd/draht-post-task.js +44 -11
  60. package/dist/hooks/gsd/draht-quality-gate.js +99 -57
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +2 -0
  64. package/dist/index.js.map +1 -1
  65. package/dist/migrations.d.ts.map +1 -1
  66. package/dist/migrations.js +2 -2
  67. package/dist/migrations.js.map +1 -1
  68. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  69. package/dist/modes/interactive/components/config-selector.js +3 -3
  70. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  71. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  72. package/dist/modes/interactive/interactive-mode.js +2 -2
  73. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  74. package/dist/prompts/agents/build.md +5 -1
  75. package/dist/prompts/agents/plan.md +5 -1
  76. package/dist/prompts/agents/verify.md +5 -1
  77. package/dist/prompts/commands/atomic-commit.md +8 -16
  78. package/dist/prompts/commands/discuss-phase.md +19 -3
  79. package/dist/prompts/commands/execute-phase.md +59 -35
  80. package/dist/prompts/commands/fix.md +14 -6
  81. package/dist/prompts/commands/init-project.md +21 -3
  82. package/dist/prompts/commands/map-codebase.md +24 -19
  83. package/dist/prompts/commands/new-project.md +20 -2
  84. package/dist/prompts/commands/next-milestone.md +8 -2
  85. package/dist/prompts/commands/pause-work.md +4 -0
  86. package/dist/prompts/commands/plan-phase.md +35 -7
  87. package/dist/prompts/commands/progress.md +4 -0
  88. package/dist/prompts/commands/quick.md +20 -7
  89. package/dist/prompts/commands/resume-work.md +4 -0
  90. package/dist/prompts/commands/review.md +16 -10
  91. package/dist/prompts/commands/verify-work.md +37 -17
  92. package/hooks/gsd/draht-post-task.js +44 -11
  93. package/hooks/gsd/draht-quality-gate.js +99 -57
  94. package/package.json +5 -5
  95. package/prompts/agents/build.md +5 -1
  96. package/prompts/agents/plan.md +5 -1
  97. package/prompts/agents/verify.md +5 -1
  98. package/prompts/commands/atomic-commit.md +8 -16
  99. package/prompts/commands/discuss-phase.md +19 -3
  100. package/prompts/commands/execute-phase.md +59 -35
  101. package/prompts/commands/fix.md +14 -6
  102. package/prompts/commands/init-project.md +21 -3
  103. package/prompts/commands/map-codebase.md +24 -19
  104. package/prompts/commands/new-project.md +20 -2
  105. package/prompts/commands/next-milestone.md +8 -2
  106. package/prompts/commands/pause-work.md +4 -0
  107. package/prompts/commands/plan-phase.md +35 -7
  108. package/prompts/commands/progress.md +4 -0
  109. package/prompts/commands/quick.md +20 -7
  110. package/prompts/commands/resume-work.md +4 -0
  111. package/prompts/commands/review.md +16 -10
  112. package/prompts/commands/verify-work.md +37 -17
@@ -1,27 +1,47 @@
1
+ ---
2
+ description: "Acceptance testing of completed phase work"
3
+ ---
4
+
1
5
  # /verify-work
2
6
 
3
- Walk through acceptance testing of completed phase work.
7
+ Walk through acceptance testing of completed phase work, using subagents for parallel verification.
4
8
 
5
9
  ## Usage
6
10
  ```
7
11
  /verify-work [N]
8
12
  ```
9
13
 
14
+ Phase: $1
15
+
10
16
  ## Steps
11
- 1. Run full test suite and capture results:
12
- - Execute all tests (`bun test` or project-specific runner)
13
- - Record pass/fail counts verification cannot proceed if tests are failing
14
- 2. Check domain language violations:
15
- - Load `.planning/DOMAIN.md` and extract all defined terms
16
- - Scan source files for PascalCase identifiers not present in the glossary
17
- - Flag any bounded context boundary violations (cross-context direct imports)
18
- 3. Run quality gate: `draht-tools quality-gate --strict`
19
- 4. Run `draht-tools extract-deliverables N` to get testable items
20
- 5. Walk user through each deliverable one at a time
21
- 6. Record results (pass/fail/partially/skip)
22
- 7. For failures: diagnose and create fix plans via `draht-tools create-fix-plan N P`
17
+ 1. Run `draht-tools extract-deliverables $1` to get testable items
18
+ 2. **Run parallel verification via subagents:**
19
+ Use the `subagent` tool in **parallel mode** with these tasks:
20
+ - `verifier` agent: "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. Do NOT run draht, draht-tools, or pi commands."
21
+ - `security-auditor` agent: "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. Do NOT run draht, draht-tools, or pi commands."
22
+ - `reviewer` agent: "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. Do NOT run draht, draht-tools, or pi commands."
23
+
24
+ 3. Collect results from all subagents
25
+ 4. Walk user through each deliverable one at a time, incorporating findings from the parallel checks
26
+ 5. Record results (pass/fail/partially/skip)
27
+ 6. For failures: diagnose and create fix plans via `draht-tools create-fix-plan $1 P`
23
28
  - Fix plans MUST include a reproducing test that demonstrates the failure before any implementation
24
- 8. Write UAT report: `draht-tools write-uat N`
25
- - Report must include: test health summary (pass/fail/coverage), domain model status (any glossary violations), deliverable results
26
- 9. If all passed: mark phase complete
27
- 10. If failures: route to `execute-phase N --gaps-only`
29
+ 7. Write UAT report: `draht-tools write-uat $1`
30
+ - Report must include: test health summary (pass/fail/coverage), security audit results, domain model status (any glossary violations), deliverable results
31
+ 8. If all passed: mark phase complete.
32
+ - If more phases remain in the milestone: tell the user to start a new session and run `/discuss-phase N+1`.
33
+ - If ALL phases in the milestone are complete: tell the user to start a new session and run `/next-milestone`.
34
+ 9. If failures: route to `execute-phase $1 --gaps-only`
35
+
36
+ ## Workflow
37
+ This is the last step in the per-phase cycle. Each step runs in its own session (`/new` between steps):
38
+
39
+ ```
40
+ /discuss-phase N → /new → /plan-phase N → /new → /execute-phase N → /new → /verify-work N
41
+ ```
42
+
43
+ After verify-work passes:
44
+ - More phases remaining → `/new` → `/discuss-phase N+1`
45
+ - ALL phases in milestone verified → `/new` → `/next-milestone`
46
+
47
+ `/next-milestone` is ONLY for generating new phases after every phase in the current milestone is complete.
@@ -19,28 +19,60 @@ if (!phaseNum || !planNum || !taskNum || !status) {
19
19
  process.exit(1);
20
20
  }
21
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
+
22
51
  const PLANNING = ".planning";
23
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);
24
56
 
25
57
  // 0. TDD cycle compliance check
26
- // If the current commit message starts with "green:", the previous commit for this
27
- // task should start with "red:" — enforce the Red → Green order.
28
58
  if (commitHash) {
29
59
  try {
30
- // Find the commit message for commitHash and the one before it
31
60
  const currentMsg = execSync(`git log --format=%s -n 1 ${commitHash} 2>/dev/null`, { encoding: "utf-8" }).trim();
32
61
  if (/^green:/i.test(currentMsg)) {
33
- // Scope search to commits that mention this phase/plan/task in their message
34
- // to avoid false positives from unrelated older commits
35
62
  const taskPrefix = `${phaseNum}-${planNum}-${taskNum}`;
36
63
  const recentMsgs = execSync(`git log --format=%s -n 50 ${commitHash}~1 2>/dev/null`, { encoding: "utf-8" })
37
64
  .trim()
38
65
  .split("\n")
39
66
  .filter((m) => m.includes(taskPrefix) || /^(red|green|refactor):/i.test(m));
40
- // Find the nearest TDD-cycle commit scoped to this task
41
67
  const prevTaskMsg = recentMsgs.find((m) => /^(red|green|refactor):/i.test(m) && m.includes(taskPrefix));
42
68
  if (!prevTaskMsg || !/^red:/i.test(prevTaskMsg)) {
43
- console.log(`⚠️ TDD violation: "green:" commit detected but no preceding "red:" commit found for task ${phaseNum}-${planNum}-${taskNum}`);
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
+ }
44
76
  fs.appendFileSync(LOG_FILE, JSON.stringify({
45
77
  timestamp: new Date().toISOString(),
46
78
  phase: parseInt(phaseNum, 10),
@@ -69,13 +101,15 @@ const entry = {
69
101
 
70
102
  fs.appendFileSync(LOG_FILE, JSON.stringify(entry) + "\n");
71
103
 
72
- // 2. Run type check if status is pass
104
+ // 2. Run type check and tests if status is pass
73
105
  if (status === "pass") {
106
+ // Type check
74
107
  try {
75
- execSync("bun run tsgo --noEmit 2>&1", { timeout: 30000, encoding: "utf-8" });
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 });
76
110
  // Run tests
77
111
  try {
78
- const testOutput = execSync("bun test 2>&1", { timeout: 60000, encoding: "utf-8" });
112
+ const testOutput = execSync(`${toolchain.testCmd} 2>&1`, { timeout: 60000, encoding: "utf-8", cwd });
79
113
  const testMatch = testOutput.match(/(\d+) pass/);
80
114
  const testCount = testMatch ? testMatch[1] : "?";
81
115
  console.log(`✅ Task ${phaseNum}-${planNum}-${taskNum}: passed + types clean + ${testCount} tests pass`);
@@ -93,7 +127,6 @@ if (status === "pass") {
93
127
  const errorCount = (output.match(/error TS/g) || []).length;
94
128
  if (errorCount > 0) {
95
129
  console.log(`⚠️ Task ${phaseNum}-${planNum}-${taskNum}: passed but ${errorCount} type error(s) introduced`);
96
- // Append warning to log
97
130
  fs.appendFileSync(LOG_FILE, JSON.stringify({
98
131
  ...entry,
99
132
  warning: `${errorCount} type errors introduced`,
@@ -12,13 +12,67 @@
12
12
 
13
13
  const { execSync } = require("node:child_process");
14
14
  const fs = require("node:fs");
15
+ const path = require("node:path");
15
16
 
16
- const strict = process.argv.includes("--strict");
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;
17
70
  const issues = [];
18
71
 
19
72
  // 1. TypeScript check
20
73
  try {
21
- execSync("bun run tsgo --noEmit 2>&1", { timeout: 60000, encoding: "utf-8" });
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 });
22
76
  } catch (error) {
23
77
  const output = error.stdout || error.stderr || "";
24
78
  const errorCount = (output.match(/error TS/g) || []).length;
@@ -27,19 +81,19 @@ try {
27
81
  }
28
82
  }
29
83
 
30
- // 2. Biome lint check (if biome.json exists)
31
- if (fs.existsSync("biome.json")) {
84
+ // 2. Lint check (if biome.json exists use biome, else use toolchain lint)
85
+ if (fs.existsSync(path.join(cwd, "biome.json"))) {
32
86
  try {
33
- execSync("bunx biome check --error-on-warnings . 2>&1", { timeout: 30000, encoding: "utf-8" });
87
+ execSync(`${toolchain.lintCmd} --error-on-warnings 2>&1`, { timeout: 30000, encoding: "utf-8", cwd });
34
88
  } catch (error) {
35
89
  const output = error.stdout || error.stderr || "";
36
- issues.push({ severity: strict ? "error" : "warning", message: "Biome lint issues", details: output.slice(0, 500) });
90
+ issues.push({ severity: strict ? "error" : "warning", message: "Lint issues", details: output.slice(0, 500) });
37
91
  }
38
92
  }
39
93
 
40
94
  // 3. Run tests
41
95
  try {
42
- const testOutput = execSync("bun test 2>&1", { timeout: 120000, encoding: "utf-8" });
96
+ const testOutput = execSync(`${toolchain.testCmd} 2>&1`, { timeout: 120000, encoding: "utf-8", cwd });
43
97
  const failMatch = testOutput.match(/(\d+) fail/);
44
98
  if (failMatch && parseInt(failMatch[1], 10) > 0) {
45
99
  issues.push({ severity: strict ? "error" : "warning", message: `${failMatch[1]} test(s) failing` });
@@ -56,71 +110,56 @@ try {
56
110
  try {
57
111
  const result = execSync(
58
112
  "grep -rn 'console\\.log' src/ --include='*.ts' --include='*.tsx' 2>/dev/null | grep -v '// debug' | head -5",
59
- { encoding: "utf-8" }
113
+ { encoding: "utf-8", cwd }
60
114
  ).trim();
61
115
  if (result) {
62
- issues.push({ severity: "warning", message: `console.log found in source`, details: result });
116
+ issues.push({ severity: "warning", message: "console.log found in source", details: result });
63
117
  }
64
- } catch { /* grep returns 1 when no match — that's good */ }
118
+ } catch { /* grep returns 1 when no match — that's fine */ }
65
119
 
66
- // 5. Domain glossary compliance
67
- // Extract terms from DOMAIN.md and flag new PascalCase classes not in the glossary
68
- const domainMdPath = ".planning/DOMAIN.md";
69
- if (fs.existsSync(domainMdPath)) {
120
+ // 5. Domain glossary compliance (checks DOMAIN-MODEL.md, falls back to DOMAIN.md)
121
+ const domainContent = loadDomainContent(cwd);
122
+ if (domainContent) {
70
123
  try {
71
- const domainContent = fs.readFileSync(domainMdPath, "utf-8");
72
- // Collect all PascalCase tokens that appear in the glossary section
73
- const glossarySection = domainContent.match(/## Ubiquitous Language([\s\S]*?)(?:^##|\Z)/m)?.[1] || "";
74
- // Each glossary entry is assumed to start the line with a PascalCase term (e.g. "**Order**" or "- Order:")
75
- const glossaryTerms = new Set(
76
- [...glossarySection.matchAll(/\b([A-Z][a-zA-Z0-9]+)\b/g)].map((m) => m[1])
77
- );
78
-
79
- // Scan changed source files for PascalCase class/interface/type declarations
80
- try {
81
- const changedFiles = execSync(
82
- "git diff --cached --name-only 2>/dev/null || git diff --name-only HEAD~1",
83
- { encoding: "utf-8" }
84
- ).trim().split("\n").filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
85
-
86
- const unknownTerms = [];
87
- for (const file of changedFiles) {
88
- if (!fs.existsSync(file)) continue;
89
- const src = fs.readFileSync(file, "utf-8");
90
- const declarations = [...src.matchAll(/(?:class|interface|type|enum)\s+([A-Z][a-zA-Z0-9]+)/g)].map((m) => m[1]);
91
- for (const term of declarations) {
92
- if (!glossaryTerms.has(term)) {
93
- unknownTerms.push(`${file}: ${term}`);
94
- }
95
- }
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}`);
96
137
  }
97
- if (unknownTerms.length > 0) {
98
- issues.push({
99
- severity: "warning",
100
- message: `${unknownTerms.length} PascalCase type(s) not in DOMAIN.md glossary`,
101
- details: unknownTerms.slice(0, 5).join(", "),
102
- });
103
- }
104
- } catch { /* ignore git errors */ }
105
- } catch { /* ignore read errors */ }
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 */ }
106
147
  }
107
148
 
108
149
  // 6. Bounded context boundary check — flag suspicious cross-directory imports
109
150
  try {
110
151
  const changedSrcFiles = execSync(
111
152
  "git diff --cached --name-only 2>/dev/null || git diff --name-only HEAD~1",
112
- { encoding: "utf-8" }
153
+ { encoding: "utf-8", cwd }
113
154
  ).trim().split("\n").filter((f) => /^src\/[^/]+\//.test(f) && (f.endsWith(".ts") || f.endsWith(".tsx")));
114
155
 
115
156
  const crossContextImports = [];
116
157
  for (const file of changedSrcFiles) {
117
- if (!fs.existsSync(file)) continue;
118
- // Determine this file's context (first path segment under src/)
158
+ if (!fs.existsSync(path.join(cwd, file))) continue;
119
159
  const ownContext = file.split("/")[1];
120
- const src = fs.readFileSync(file, "utf-8");
160
+ const src = fs.readFileSync(path.join(cwd, file), "utf-8");
121
161
  const imports = [...src.matchAll(/from\s+['"](\.\.\/.+?)['"]/g)].map((m) => m[1]);
122
162
  for (const imp of imports) {
123
- // Resolve relative import against the file's directory
124
163
  const resolved = path.normalize(path.join(path.dirname(file), imp));
125
164
  const parts = resolved.split(path.sep);
126
165
  const srcIdx = parts.indexOf("src");
@@ -142,11 +181,11 @@ try {
142
181
  try {
143
182
  const allSrc = execSync(
144
183
  "find src -name '*.ts' -not -name '*.test.ts' -not -name '*.spec.ts' 2>/dev/null | wc -l",
145
- { encoding: "utf-8" }
184
+ { encoding: "utf-8", cwd }
146
185
  ).trim();
147
186
  const allTests = execSync(
148
187
  "find src -name '*.test.ts' -o -name '*.spec.ts' 2>/dev/null | wc -l",
149
- { encoding: "utf-8" }
188
+ { encoding: "utf-8", cwd }
150
189
  ).trim();
151
190
  const srcCount = parseInt(allSrc, 10) || 0;
152
191
  const testCount = parseInt(allTests, 10) || 0;
@@ -163,12 +202,15 @@ try {
163
202
 
164
203
  // 8. Check for TODO/FIXME/HACK comments in changed files
165
204
  try {
166
- const diff = execSync("git diff --cached --name-only 2>/dev/null || git diff --name-only HEAD~1", { encoding: "utf-8" }).trim();
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();
167
209
  if (diff) {
168
210
  const files = diff.split("\n").filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
169
211
  for (const file of files) {
170
212
  try {
171
- const content = fs.readFileSync(file, "utf-8");
213
+ const content = fs.readFileSync(path.join(cwd, file), "utf-8");
172
214
  const todos = content.match(/\/\/\s*(TODO|FIXME|HACK|XXX):/gi) || [];
173
215
  if (todos.length > 0) {
174
216
  issues.push({ severity: "info", message: `${file}: ${todos.length} TODO/FIXME comment(s)` });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@draht/coding-agent",
3
- "version": "2026.3.4",
3
+ "version": "2026.3.6",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "drahtConfig": {
@@ -46,9 +46,9 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@mariozechner/jiti": "^2.6.2",
49
- "@draht/agent-core": "2026.3.4",
50
- "@draht/ai": "2026.3.4",
51
- "@draht/tui": "2026.3.4",
49
+ "@draht/agent-core": "2026.3.6",
50
+ "@draht/ai": "2026.3.6",
51
+ "@draht/tui": "2026.3.6",
52
52
  "@silvia-odwyer/photon-node": "^0.3.4",
53
53
  "chalk": "^5.5.0",
54
54
  "cli-highlight": "^2.1.11",
@@ -92,7 +92,7 @@
92
92
  "tui",
93
93
  "agent"
94
94
  ],
95
- "author": "Mario Zechner",
95
+ "author": "Oskar Freye",
96
96
  "license": "MIT",
97
97
  "publishConfig": {
98
98
  "access": "public"
@@ -1,6 +1,10 @@
1
+ ---
2
+ description: "Execution agent that implements plans precisely"
3
+ ---
4
+
1
5
  # Draht Build Agent
2
6
 
3
- You are an execution agent for the Get Shit Done methodology. Your job is to implement plans precisely.
7
+ You are an execution agent. Your job is to implement plans precisely.
4
8
 
5
9
  ## Core Rules
6
10
  1. Read the plan FIRST — it is your instruction set
@@ -1,6 +1,10 @@
1
+ ---
2
+ description: "Planning agent that creates atomic, executable plans"
3
+ ---
4
+
1
5
  # Draht Plan Agent
2
6
 
3
- You are a planning agent for the Get Shit Done methodology. Your job is to create atomic, executable plans.
7
+ You are a planning agent. Your job is to create atomic, executable plans.
4
8
 
5
9
  ## Core Rules
6
10
  1. Plans are prompts — they tell the executor EXACTLY what to build
@@ -1,6 +1,10 @@
1
+ ---
2
+ description: "Verification agent that tests work against acceptance criteria"
3
+ ---
4
+
1
5
  # Draht Verify Agent
2
6
 
3
- You are a verification agent for the Get Shit Done methodology. Your job is to test completed work against acceptance criteria.
7
+ You are a verification agent. Your job is to test completed work against acceptance criteria.
4
8
 
5
9
  ## Core Rules
6
10
  1. Test from the USER's perspective, not the developer's
@@ -4,19 +4,13 @@ description: "Analyze changes and create atomic commits"
4
4
 
5
5
  # Git Atomic Commit Analysis
6
6
 
7
- First, check the current status:
7
+ First, gather the current state:
8
8
 
9
- *! git status*
9
+ 1. Run `git status` to see what changed
10
+ 2. Run `git diff` to see unstaged changes
11
+ 3. Run `git diff --cached` to see staged changes
10
12
 
11
- Now get the full diff of all changes:
12
-
13
- *! git diff*
14
-
15
- And staged changes:
16
-
17
- *! git diff --cached*
18
-
19
- Based on the changes shown above, analyze and group them into logical, ATOMIC commits. Each commit should:
13
+ Based on the changes, analyze and group them into logical, ATOMIC commits. Each commit should:
20
14
 
21
15
  1. Contain ONE logical change only
22
16
  2. Be self-contained and complete
@@ -27,13 +21,11 @@ For each group of changes you identify:
27
21
  - Generate a clear, descriptive commit message following conventional commits format
28
22
  - Explain WHY these changes belong in one commit
29
23
 
30
- Then provide the exact `git add` and `git commit` commands to execute for each atomic commit in the order they should be applied.
24
+ Then execute the `git add` and `git commit` commands for each atomic commit in the order they should be applied.
31
25
 
32
26
  Format each commit message as:
27
+ ```
33
28
  type(scope): brief description
34
- - Detailed explanation of what changed
35
- - Why the change was necessary
36
-
29
+ ```
37
30
 
38
31
  Common types: feat, fix, refactor, docs, test, chore, style, perf
39
-
@@ -1,3 +1,7 @@
1
+ ---
2
+ description: "Capture implementation decisions before planning a phase"
3
+ ---
4
+
1
5
  # /discuss-phase
2
6
 
3
7
  Capture implementation decisions before planning a phase.
@@ -7,13 +11,25 @@ Capture implementation decisions before planning a phase.
7
11
  /discuss-phase [N]
8
12
  ```
9
13
 
14
+ Phase: $1
15
+
10
16
  ## Steps
11
- 1. Run `draht-tools phase-info N` to load phase context
17
+ 1. Run `draht-tools phase-info $1` to load phase context
12
18
  2. Identify gray areas based on what's being built
13
19
  3. Present 1-2 questions at a time about preferences
14
20
  4. If `.planning/DOMAIN.md` exists, load it and validate discovered terms against the glossary. Add any new domain terms found during discussion.
15
- 5. Record decisions with `draht-tools save-context N`
16
- 6. Commit: `draht-tools commit-docs "capture phase N context"`
21
+ 5. Record decisions with `draht-tools save-context $1`
22
+ 6. Commit: `draht-tools commit-docs "capture phase $1 context"`
23
+
24
+ ## Workflow
25
+ This is one step in the per-phase cycle. Each step runs in its own session (`/new` between steps):
26
+
27
+ ```
28
+ /discuss-phase N → /new → /plan-phase N → /new → /execute-phase N → /new → /verify-work N → /new → /discuss-phase N+1 → ...
29
+ ```
30
+
31
+ After completing this command, tell the user to start a new session and run `/plan-phase $1`.
32
+ Do NOT suggest `/next-milestone` — that is only after ALL phases in the milestone are verified.
17
33
 
18
34
  ## Gray Area Categories
19
35
  - **Visual features** → Layout, density, interactions, empty states