@draht/coding-agent 2026.3.2-9 → 2026.3.3

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 (51) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/agents/architect.md +45 -0
  3. package/agents/debugger.md +57 -0
  4. package/agents/git-committer.md +46 -0
  5. package/agents/implementer.md +25 -0
  6. package/agents/reviewer.md +52 -0
  7. package/agents/security-auditor.md +61 -0
  8. package/agents/verifier.md +44 -0
  9. package/bin/draht-tools.cjs +20 -20
  10. package/dist/agents/architect.md +45 -0
  11. package/dist/agents/debugger.md +57 -0
  12. package/dist/agents/git-committer.md +46 -0
  13. package/dist/agents/implementer.md +25 -0
  14. package/dist/agents/reviewer.md +52 -0
  15. package/dist/agents/security-auditor.md +61 -0
  16. package/dist/agents/verifier.md +44 -0
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +1 -1
  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 +10 -1
  22. package/dist/core/package-manager.js.map +1 -1
  23. package/dist/extensions/gsd-commands.ts +69 -4
  24. package/dist/extensions/subagent.ts +212 -9
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/prompts/commands/discuss-phase.md +3 -3
  30. package/dist/prompts/commands/execute-phase.md +9 -9
  31. package/dist/prompts/commands/map-codebase.md +2 -2
  32. package/dist/prompts/commands/new-project.md +9 -9
  33. package/dist/prompts/commands/pause-work.md +2 -2
  34. package/dist/prompts/commands/plan-phase.md +5 -5
  35. package/dist/prompts/commands/progress.md +1 -1
  36. package/dist/prompts/commands/quick.md +4 -4
  37. package/dist/prompts/commands/resume-work.md +1 -1
  38. package/dist/prompts/commands/verify-work.md +4 -4
  39. package/extensions/gsd-commands.ts +69 -4
  40. package/extensions/subagent.ts +212 -9
  41. package/package.json +7 -6
  42. package/prompts/commands/discuss-phase.md +3 -3
  43. package/prompts/commands/execute-phase.md +9 -9
  44. package/prompts/commands/map-codebase.md +2 -2
  45. package/prompts/commands/new-project.md +9 -9
  46. package/prompts/commands/pause-work.md +2 -2
  47. package/prompts/commands/plan-phase.md +5 -5
  48. package/prompts/commands/progress.md +1 -1
  49. package/prompts/commands/quick.md +4 -4
  50. package/prompts/commands/resume-work.md +1 -1
  51. package/prompts/commands/verify-work.md +4 -4
@@ -8,11 +8,11 @@ Initialize a new GSD project: questioning → research → requirements → road
8
8
  ```
9
9
 
10
10
  ## Steps
11
- 1. Run `draht init` to check preconditions
12
- 2. If existing code detected, run `draht map-codebase` first
11
+ 1. Run `draht-tools init` to check preconditions
12
+ 2. If existing code detected, run `draht-tools map-codebase` first
13
13
  3. Deep questioning phase (3-7 rounds, 1-2 questions at a time)
14
- 4. Run `draht create-project` with gathered info
15
- 5. Run `draht create-domain-model` to define bounded contexts, entities, and ubiquitous language
14
+ 4. Run `draht-tools create-project` with gathered info
15
+ 5. Run `draht-tools create-domain-model` to define bounded contexts, entities, and ubiquitous language
16
16
  6. Create `.planning/DOMAIN.md` with:
17
17
  - `## Bounded Contexts` — each context with name, responsibility, and brief description
18
18
  - `## Ubiquitous Language` — glossary of domain terms agreed with the user (term → definition)
@@ -25,11 +25,11 @@ Initialize a new GSD project: questioning → research → requirements → road
25
25
  - `## Coverage Goals` — target coverage percentage and which paths are critical
26
26
  - `## Testing Levels` — what is tested at unit level vs integration vs e2e, with examples
27
27
  - `## Excluded` — what is explicitly not tested and why (config files, generated code, etc.)
28
- 8. Optional research phase via `draht research`
29
- 9. Run `draht create-requirements` with v1/v2/out-of-scope (map requirements to bounded contexts)
30
- 10. Run `draht create-roadmap` with phases
31
- 11. Run `draht init-state`
32
- 12. Git commit via `draht commit-docs "initialize GSD project"`
28
+ 8. Optional research phase via `draht-tools research`
29
+ 9. Run `draht-tools create-requirements` with v1/v2/out-of-scope (map requirements to bounded contexts)
30
+ 10. Run `draht-tools create-roadmap` with phases
31
+ 11. Run `draht-tools init-state`
32
+ 12. Git commit via `draht-tools commit-docs "initialize GSD project"`
33
33
 
34
34
  ## Rules
35
35
  - Ask 1-2 questions at a time, never dump 10 at once
@@ -8,5 +8,5 @@ Create a handoff document for session continuity.
8
8
  ```
9
9
 
10
10
  ## Steps
11
- 1. Run `draht pause` — creates CONTINUE-HERE.md
12
- 2. Commit: `draht commit-docs "pause work"`
11
+ 1. Run `draht-tools pause` — creates CONTINUE-HERE.md
12
+ 2. Commit: `draht-tools commit-docs "pause work"`
@@ -8,17 +8,17 @@ Create atomic execution plans for a roadmap phase.
8
8
  ```
9
9
 
10
10
  ## Steps
11
- 1. Run `draht load-phase-context N` to gather all context
12
- 2. Optional: `draht research-phase N` for domain research
11
+ 1. Run `draht-tools load-phase-context N` to gather all context
12
+ 2. Optional: `draht-tools research-phase N` for domain research
13
13
  3. Goal-backward planning:
14
14
  a. State the goal (outcome, not activity)
15
15
  b. Derive observable truths (3-7 from user perspective)
16
16
  c. From each observable truth, derive the test scenarios that would prove it (specific inputs → expected outputs or state changes)
17
17
  d. Map to required artifacts (files, endpoints, schemas)
18
18
  e. Break into atomic tasks (2-5 per plan)
19
- 4. Write plans: `draht create-plan N P`
20
- 5. Validate: `draht validate-plans N`
21
- 6. Commit: `draht commit-docs "create phase N plans"`
19
+ 4. Write plans: `draht-tools create-plan N P`
20
+ 5. Validate: `draht-tools validate-plans N`
21
+ 6. Commit: `draht-tools commit-docs "create phase N plans"`
22
22
 
23
23
  ## Plan Format
24
24
  Plans use XML task format:
@@ -8,5 +8,5 @@ Show current project status.
8
8
  ```
9
9
 
10
10
  ## Steps
11
- 1. Run `draht progress` — outputs formatted status
11
+ 1. Run `draht-tools progress` — outputs formatted status
12
12
  2. Display the result as-is
@@ -8,12 +8,12 @@ Execute a small ad-hoc task with GSD tracking.
8
8
  ```
9
9
 
10
10
  ## Steps
11
- 1. Run `draht next-quick-number` to get task number
12
- 2. Create quick plan: `draht create-quick-plan NNN "description"`
11
+ 1. Run `draht-tools next-quick-number` to get task number
12
+ 2. Create quick plan: `draht-tools create-quick-plan NNN "description"`
13
13
  3. Execute tasks following the TDD cycle:
14
14
  - **🔴 RED** — Write a failing test that describes the desired behaviour
15
15
  - **🟢 GREEN** — Write the minimum implementation to make it pass
16
16
  - **🔵 REFACTOR** — Clean up while keeping the test green
17
17
  - *Exception: skip the TDD cycle only for pure config or documentation-only tasks that have no testable behaviour*
18
- 4. Write summary: `draht write-quick-summary NNN`
19
- 5. Update state: `draht update-state`
18
+ 4. Write summary: `draht-tools write-quick-summary NNN`
19
+ 5. Update state: `draht-tools update-state`
@@ -8,6 +8,6 @@ Resume from last session state.
8
8
  ```
9
9
 
10
10
  ## Steps
11
- 1. Run `draht resume` — loads CONTINUE-HERE.md or STATE.md
11
+ 1. Run `draht-tools resume` — loads CONTINUE-HERE.md or STATE.md
12
12
  2. Display context and ask to continue
13
13
  3. Delete CONTINUE-HERE.md after confirmation
@@ -15,13 +15,13 @@ Walk through acceptance testing of completed phase work.
15
15
  - Load `.planning/DOMAIN.md` and extract all defined terms
16
16
  - Scan source files for PascalCase identifiers not present in the glossary
17
17
  - Flag any bounded context boundary violations (cross-context direct imports)
18
- 3. Run quality gate: `draht quality-gate --strict`
19
- 4. Run `draht extract-deliverables N` to get testable items
18
+ 3. Run quality gate: `draht-tools quality-gate --strict`
19
+ 4. Run `draht-tools extract-deliverables N` to get testable items
20
20
  5. Walk user through each deliverable one at a time
21
21
  6. Record results (pass/fail/partially/skip)
22
- 7. For failures: diagnose and create fix plans via `draht create-fix-plan N P`
22
+ 7. For failures: diagnose and create fix plans via `draht-tools create-fix-plan N P`
23
23
  - Fix plans MUST include a reproducing test that demonstrates the failure before any implementation
24
- 8. Write UAT report: `draht write-uat N`
24
+ 8. Write UAT report: `draht-tools write-uat N`
25
25
  - Report must include: test health summary (pass/fail/coverage), domain model status (any glossary violations), deliverable results
26
26
  9. If all passed: mark phase complete
27
27
  10. If failures: route to `execute-phase N --gaps-only`
@@ -13,8 +13,9 @@
13
13
  * /review <scope> — ad-hoc code review + security audit
14
14
  * /fix <issue> — targeted fix plan for a failing task
15
15
  * /quick <task> — one-shot implement + commit (skip full GSD)
16
- * /resume — pick up interrupted work from CONTINUE-HERE.md
16
+ * /continue — pick up interrupted work from CONTINUE-HERE.md
17
17
  * /status — show .planning/STATE.md overview
18
+ * /next-milestone — plan next milestone after current one is complete
18
19
  * /new-project <name> [path] — create project dir, git init, scaffold .draht/
19
20
  * /init-project — scaffold .draht/ in existing project
20
21
  */
@@ -183,9 +184,9 @@ After both complete, merge into a single prioritized findings report.`,
183
184
  },
184
185
  });
185
186
 
186
- // ── /resume ──────────────────────────────────────────────────────────────
187
- pi.registerCommand("resume", {
188
- description: "Resume interrupted work — reads CONTINUE-HERE.md and picks up where we left off",
187
+ // ── /continue ────────────────────────────────────────────────────────────
188
+ pi.registerCommand("continue", {
189
+ description: "Continue interrupted work — reads CONTINUE-HERE.md and picks up where we left off",
189
190
  handler: async (_args, ctx) => {
190
191
  if (isBusy(ctx, ctx.ui)) return;
191
192
 
@@ -241,6 +242,70 @@ After both complete, merge into a single prioritized findings report.`,
241
242
  },
242
243
  });
243
244
 
245
+ // ── /next-milestone ──────────────────────────────────────────────────────
246
+ pi.registerCommand("next-milestone", {
247
+ description: "Plan the next milestone after the current one is complete — review progress, update requirements, create new phases",
248
+ handler: async (_args, ctx) => {
249
+ if (isBusy(ctx, ctx.ui)) return;
250
+
251
+ const planningDir = path.join(ctx.cwd, ".planning");
252
+ const roadmapFile = path.join(planningDir, "ROADMAP.md");
253
+ const stateFile = path.join(planningDir, "STATE.md");
254
+ const requirementsFile = path.join(planningDir, "REQUIREMENTS.md");
255
+
256
+ if (!fs.existsSync(roadmapFile)) {
257
+ ctx.ui.notify("No .planning/ROADMAP.md found. Run /new-project first.", "warning");
258
+ return;
259
+ }
260
+
261
+ let context = `\nROADMAP.md:\n${fs.readFileSync(roadmapFile, "utf-8")}`;
262
+ if (fs.existsSync(stateFile)) {
263
+ context += `\nSTATE.md:\n${fs.readFileSync(stateFile, "utf-8")}`;
264
+ }
265
+ if (fs.existsSync(requirementsFile)) {
266
+ context += `\nREQUIREMENTS.md:\n${fs.readFileSync(requirementsFile, "utf-8")}`;
267
+ }
268
+
269
+ // Collect UAT and summary files for completed phases
270
+ const phasesDir = path.join(planningDir, "phases");
271
+ if (fs.existsSync(phasesDir)) {
272
+ for (const dir of fs.readdirSync(phasesDir)) {
273
+ const phaseDir = path.join(phasesDir, dir);
274
+ if (!fs.statSync(phaseDir).isDirectory()) continue;
275
+ for (const file of fs.readdirSync(phaseDir)) {
276
+ if (file.endsWith("-UAT.md") || file.endsWith("-SUMMARY.md")) {
277
+ context += `\n${dir}/${file}:\n${fs.readFileSync(path.join(phaseDir, file), "utf-8")}`;
278
+ }
279
+ }
280
+ }
281
+ }
282
+
283
+ pi.sendUserMessage(
284
+ `Use the subagent tool to delegate to the architect agent with this task:
285
+
286
+ "We have completed a milestone. Your job is to plan the NEXT milestone.
287
+
288
+ 1. Read the current ROADMAP.md and identify the completed milestone and its phases
289
+ 2. Review all UAT reports and summaries to understand what was built
290
+ 3. Review REQUIREMENTS.md — identify which v1 requirements are now satisfied and which remain
291
+ 4. Assess if any v2 requirements should be promoted to the next milestone based on what we learned
292
+ 5. Propose the next milestone with new phases:
293
+ - Each phase has a clear goal (outcome, not activity)
294
+ - Phases are ordered by dependency
295
+ - Map phases to remaining/new requirements
296
+ 6. Update ROADMAP.md with the new milestone and phases
297
+ 7. Update STATE.md to reflect milestone transition
298
+ 8. Update REQUIREMENTS.md if requirements changed based on learnings
299
+
300
+ Present the proposed milestone for user approval before writing files.
301
+
302
+ Project context:${context}"
303
+
304
+ Set agentScope to "project".`,
305
+ );
306
+ },
307
+ });
308
+
244
309
  // ── /new-project ─────────────────────────────────────────────────────────
245
310
  pi.registerCommand("new-project", {
246
311
  description: "Create a new project: mkdir, git init, scaffold .draht/. Usage: /new-project <name> [/base/path]",
@@ -16,13 +16,18 @@ import * as os from "node:os";
16
16
  import * as path from "node:path";
17
17
  import type { Message } from "@draht/ai";
18
18
  import { StringEnum } from "@draht/ai";
19
- import { type ExtensionAPI, getAgentDir, parseFrontmatter } from "@draht/coding-agent";
19
+ import { type ExtensionAPI, getAgentDir, getPackageDir, isBunBinary, parseFrontmatter } from "@draht/coding-agent";
20
+ import { Text } from "@draht/tui";
20
21
  import { Type } from "@sinclair/typebox";
21
22
 
22
23
  const MAX_PARALLEL = 8;
23
24
  const MAX_CONCURRENCY = 4;
24
- // Use the same binary that's currently running
25
+
26
+ // Build the command to spawn a subagent process.
27
+ // Compiled Bun binary: process.execPath IS the CLI binary, args go directly.
28
+ // Source/dev mode: process.execPath is the runtime (bun/node), process.argv[1] is the CLI script.
25
29
  const DRAHT_BIN = process.execPath;
30
+ const DRAHT_ARGS_PREFIX: string[] = isBunBinary ? [] : [process.argv[1]];
26
31
 
27
32
  // ─── Agent discovery ────────────────────────────────────────────────────────
28
33
 
@@ -82,13 +87,20 @@ function findProjectAgentsDir(cwd: string): string | null {
82
87
  type AgentScope = "user" | "project" | "both";
83
88
 
84
89
  function discoverAgents(cwd: string, scope: AgentScope): AgentConfig[] {
90
+ // Shipped agents (bundled with the package) — lowest priority
91
+ const shippedDir = path.join(getPackageDir(), "agents");
92
+ const shippedAgents = loadAgentsFromDir(shippedDir, "user");
93
+
85
94
  const userDir = path.join(getAgentDir(), "agents");
86
95
  const projectDir = findProjectAgentsDir(cwd);
87
96
  const userAgents = scope !== "project" ? loadAgentsFromDir(userDir, "user") : [];
88
97
  const projectAgents = scope !== "user" && projectDir ? loadAgentsFromDir(projectDir, "project") : [];
98
+
99
+ // Priority: shipped < user < project
89
100
  const map = new Map<string, AgentConfig>();
101
+ for (const a of shippedAgents) map.set(a.name, a);
90
102
  for (const a of userAgents) map.set(a.name, a);
91
- for (const a of projectAgents) map.set(a.name, a); // project overrides global
103
+ for (const a of projectAgents) map.set(a.name, a);
92
104
  return Array.from(map.values());
93
105
  }
94
106
 
@@ -127,12 +139,15 @@ function getFinalText(messages: Message[]): string {
127
139
  return "";
128
140
  }
129
141
 
142
+ type ProgressFn = (activity: string) => void;
143
+
130
144
  async function runAgent(
131
145
  cwd: string,
132
146
  agent: AgentConfig,
133
147
  task: string,
134
148
  signal?: AbortSignal,
135
149
  step?: number,
150
+ onProgress?: ProgressFn,
136
151
  ): Promise<RunResult> {
137
152
  const args: string[] = ["--mode", "json", "-p", "--no-session"];
138
153
  if (agent.model) args.push("--model", agent.model);
@@ -155,7 +170,7 @@ async function runAgent(
155
170
 
156
171
  try {
157
172
  const exitCode = await new Promise<number>((resolve) => {
158
- const proc = spawn(DRAHT_BIN, args, { cwd, shell: false, stdio: ["ignore", "pipe", "pipe"] });
173
+ const proc = spawn(DRAHT_BIN, [...DRAHT_ARGS_PREFIX, ...args], { cwd, shell: false, stdio: ["ignore", "pipe", "pipe"] });
159
174
  let buf = "";
160
175
 
161
176
  const processLine = (line: string) => {
@@ -165,6 +180,23 @@ async function runAgent(
165
180
  if ((event.type === "message_end" || event.type === "tool_result_end") && event.message) {
166
181
  messages.push(event.message as Message);
167
182
  }
183
+ // Stream activity updates to the parent
184
+ if (onProgress) {
185
+ if (event.type === "tool_execution_start") {
186
+ const toolArgs = event.args ?? {};
187
+ const detail = toolArgs.command || toolArgs.path || toolArgs.file_path || toolArgs.pattern || "";
188
+ const short = typeof detail === "string" && detail.length > 60 ? `${detail.slice(0, 60)}...` : detail;
189
+ onProgress(short ? `${event.toolName} ${short}` : event.toolName);
190
+ } else if (event.type === "text_delta") {
191
+ // Show first line of streaming text as activity
192
+ const text = event.text?.trim();
193
+ if (text) {
194
+ const firstLine = text.split("\n")[0];
195
+ const short = firstLine.length > 80 ? `${firstLine.slice(0, 80)}...` : firstLine;
196
+ onProgress(short);
197
+ }
198
+ }
199
+ }
168
200
  } catch {}
169
201
  };
170
202
 
@@ -235,6 +267,17 @@ const Params = Type.Object({
235
267
  ),
236
268
  });
237
269
 
270
+ // ─── Rendering helpers ──────────────────────────────────────────────────────
271
+
272
+ function truncateTask(task: string, maxLen = 120): string {
273
+ const oneLine = task.replace(/\n/g, " ").trim();
274
+ return oneLine.length > maxLen ? `${oneLine.slice(0, maxLen)}...` : oneLine;
275
+ }
276
+
277
+ interface SubagentDetails {
278
+ status?: string;
279
+ }
280
+
238
281
  export default function (pi: ExtensionAPI) {
239
282
  pi.registerTool({
240
283
  name: "subagent",
@@ -243,7 +286,72 @@ export default function (pi: ExtensionAPI) {
243
286
  "Delegate to specialized agents. single: {agent,task} | parallel: {tasks:[]} | chain: {chain:[]} with {previous} placeholder. agentScope: 'both' (default) uses project .draht/agents/ + global.",
244
287
  parameters: Params,
245
288
 
246
- async execute(_id, params, signal, _onUpdate, ctx) {
289
+ renderCall(args, theme) {
290
+ const lines: string[] = [];
291
+
292
+ if (args.chain?.length) {
293
+ const agents = args.chain.map((s: { agent: string }) => s.agent).join(" -> ");
294
+ lines.push(theme.fg("toolTitle", theme.bold(`subagent chain`)) + " " + theme.fg("accent", agents));
295
+ for (let i = 0; i < args.chain.length; i++) {
296
+ const step = args.chain[i];
297
+ const prefix = theme.fg("muted", ` ${i + 1}.`);
298
+ lines.push(`${prefix} ${theme.fg("accent", step.agent)} ${theme.fg("toolOutput", truncateTask(step.task))}`);
299
+ }
300
+ } else if (args.tasks?.length) {
301
+ lines.push(theme.fg("toolTitle", theme.bold(`subagent parallel`)) + theme.fg("muted", ` (${args.tasks.length} tasks)`));
302
+ for (const t of args.tasks) {
303
+ lines.push(` ${theme.fg("accent", t.agent)} ${theme.fg("toolOutput", truncateTask(t.task))}`);
304
+ }
305
+ } else if (args.agent) {
306
+ lines.push(
307
+ theme.fg("toolTitle", theme.bold("subagent")) +
308
+ " " +
309
+ theme.fg("accent", args.agent) +
310
+ (args.task ? " " + theme.fg("toolOutput", truncateTask(args.task)) : ""),
311
+ );
312
+ } else {
313
+ lines.push(theme.fg("toolTitle", theme.bold("subagent")));
314
+ }
315
+
316
+ return new Text(lines.join("\n"), 0, 0);
317
+ },
318
+
319
+ renderResult(result, options, theme) {
320
+ const status = result.details?.status;
321
+ const output = result.content
322
+ ?.filter((c) => c.type === "text")
323
+ .map((c) => ("text" in c ? c.text : ""))
324
+ .join("\n") || "";
325
+
326
+ const lines: string[] = [];
327
+
328
+ if (status) {
329
+ lines.push(theme.fg("muted", status));
330
+ }
331
+
332
+ if (output.trim()) {
333
+ const trimmed = output.trim();
334
+ if (options.expanded || options.isPartial) {
335
+ // When expanded or during execution, show full activity log
336
+ for (const line of trimmed.split("\n")) {
337
+ lines.push(theme.fg("toolOutput", line));
338
+ }
339
+ } else {
340
+ // Collapsed final result — show preview
341
+ const allLines = trimmed.split("\n");
342
+ const previewLines = allLines.slice(0, 8);
343
+ lines.push(theme.fg("toolOutput", previewLines.join("\n")));
344
+ if (allLines.length > 8) {
345
+ lines.push(theme.fg("muted", `... (${allLines.length - 8} more lines)`));
346
+ }
347
+ }
348
+ }
349
+
350
+ if (lines.length === 0) return new Text("", 0, 0);
351
+ return new Text(lines.join("\n"), 0, 0);
352
+ },
353
+
354
+ async execute(_id, params, signal, onUpdate, ctx) {
247
355
  const scope: AgentScope = (params.agentScope as AgentScope) ?? "both";
248
356
  const agents = discoverAgents(ctx.cwd, scope);
249
357
  const available = agents.map((a) => a.name).join(", ") || "none";
@@ -254,6 +362,22 @@ export default function (pi: ExtensionAPI) {
254
362
  isError: true,
255
363
  });
256
364
 
365
+ // Stream live activity from the subagent process
366
+ const activityLines: string[] = [];
367
+ const emitProgress = (agentName: string, activity?: string) => {
368
+ const status = activity ? `${agentName}: ${activity}` : `${agentName} working...`;
369
+ onUpdate?.({
370
+ content: [{ type: "text" as const, text: activityLines.join("\n") }],
371
+ details: { status },
372
+ });
373
+ };
374
+ const makeProgressFn = (agentName: string): ProgressFn => (activity) => {
375
+ // Keep a rolling log of recent activities
376
+ activityLines.push(`${agentName}: ${activity}`);
377
+ if (activityLines.length > 50) activityLines.splice(0, activityLines.length - 50);
378
+ emitProgress(agentName, activity);
379
+ };
380
+
257
381
  // ── Chain mode ──
258
382
  if (params.chain?.length) {
259
383
  let previous = "";
@@ -262,8 +386,10 @@ export default function (pi: ExtensionAPI) {
262
386
  const step = params.chain[i];
263
387
  const agent = find(step.agent);
264
388
  if (!agent) return notFound(step.agent);
389
+ activityLines.length = 0;
390
+ emitProgress(step.agent, `step ${i + 1}/${params.chain.length}`);
265
391
  const task = step.task.replace(/\{previous\}/g, previous);
266
- const result = await runAgent(ctx.cwd, agent, task, signal, i + 1);
392
+ const result = await runAgent(ctx.cwd, agent, task, signal, i + 1, makeProgressFn(step.agent));
267
393
  results.push(result);
268
394
  if (result.exitCode !== 0) {
269
395
  return {
@@ -283,13 +409,16 @@ export default function (pi: ExtensionAPI) {
283
409
  }
284
410
  for (const t of params.tasks) { if (!find(t.agent)) return notFound(t.agent); }
285
411
 
412
+ const agentNames = params.tasks.map((t: { agent: string }) => t.agent).join(", ");
413
+ emitProgress(agentNames);
414
+
286
415
  const results = await runParallel(params.tasks, MAX_CONCURRENCY, async (t, i) => {
287
- return runAgent(ctx.cwd, find(t.agent)!, t.task, signal);
416
+ return runAgent(ctx.cwd, find(t.agent)!, t.task, signal, undefined, makeProgressFn(t.agent));
288
417
  });
289
418
 
290
419
  const ok = results.filter((r) => r.exitCode === 0).length;
291
420
  const summary = results
292
- .map((r) => `[${r.agent}] ${r.exitCode === 0 ? "" : ""} ${r.output.slice(0, 200)}`)
421
+ .map((r) => `[${r.agent}] ${r.exitCode === 0 ? "ok" : "fail"} ${r.output.slice(0, 200)}`)
293
422
  .join("\n\n");
294
423
  return { content: [{ type: "text" as const, text: `Parallel: ${ok}/${results.length} succeeded\n\n${summary}` }] };
295
424
  }
@@ -298,7 +427,8 @@ export default function (pi: ExtensionAPI) {
298
427
  if (params.agent && params.task) {
299
428
  const agent = find(params.agent);
300
429
  if (!agent) return notFound(params.agent);
301
- const result = await runAgent(ctx.cwd, agent, params.task, signal);
430
+ emitProgress(params.agent);
431
+ const result = await runAgent(ctx.cwd, agent, params.task, signal, undefined, makeProgressFn(params.agent));
302
432
  const isError = result.exitCode !== 0;
303
433
  return {
304
434
  content: [{ type: "text" as const, text: result.output || result.stderr || "(no output)" }],
@@ -309,4 +439,77 @@ export default function (pi: ExtensionAPI) {
309
439
  return { content: [{ type: "text" as const, text: `Provide exactly one mode. Available agents: ${available}` }], isError: true };
310
440
  },
311
441
  });
442
+
443
+ // ── Agent selection for user prompts ─────────────────────────────────────
444
+
445
+ let selectedAgent: string | undefined;
446
+
447
+ function updateAgentStatus(ctx: { ui: { setStatus: (key: string, text: string | undefined) => void } }) {
448
+ ctx.ui.setStatus("agent", selectedAgent ? `agent: ${selectedAgent}` : undefined);
449
+ }
450
+
451
+ // /agent command — select an agent or clear selection
452
+ pi.registerCommand("agent", {
453
+ description: "Select an agent to handle your next prompts, or clear selection. Usage: /agent [name]",
454
+ handler: async (args, ctx) => {
455
+ const agents = discoverAgents(ctx.cwd, "both");
456
+
457
+ if (args.trim()) {
458
+ // Direct selection: /agent architect
459
+ const name = args.trim();
460
+ if (name === "none" || name === "off" || name === "clear") {
461
+ selectedAgent = undefined;
462
+ updateAgentStatus(ctx);
463
+ ctx.ui.notify("Agent cleared — prompts go to default model", "info");
464
+ return;
465
+ }
466
+ const agent = agents.find((a) => a.name === name);
467
+ if (!agent) {
468
+ const available = agents.map((a) => a.name).join(", ") || "none";
469
+ ctx.ui.notify(`Unknown agent "${name}". Available: ${available}`, "warning");
470
+ return;
471
+ }
472
+ selectedAgent = name;
473
+ updateAgentStatus(ctx);
474
+ ctx.ui.notify(`Agent set to "${name}" — your prompts will be handled by this agent`, "info");
475
+ return;
476
+ }
477
+
478
+ // Interactive selection
479
+ if (!ctx.hasUI) {
480
+ ctx.ui.notify("Usage: /agent <name> or /agent none", "warning");
481
+ return;
482
+ }
483
+
484
+ const options = ["(none — default model)", ...agents.map((a) => `${a.name} — ${a.description}`)];
485
+ const choice = await ctx.ui.select("Select agent for your prompts", options);
486
+ if (choice === undefined) return; // cancelled
487
+
488
+ if (choice === options[0]) {
489
+ selectedAgent = undefined;
490
+ updateAgentStatus(ctx);
491
+ ctx.ui.notify("Agent cleared", "info");
492
+ } else {
493
+ const name = choice.split(" — ")[0];
494
+ selectedAgent = name;
495
+ updateAgentStatus(ctx);
496
+ ctx.ui.notify(`Agent set to "${name}"`, "info");
497
+ }
498
+ },
499
+ getArgumentCompletions: (partial) => {
500
+ const agents = discoverAgents(process.cwd(), "both");
501
+ const names = ["none", ...agents.map((a) => a.name)];
502
+ return names.filter((n) => n.startsWith(partial));
503
+ },
504
+ });
505
+
506
+ // Intercept user input when an agent is selected
507
+ pi.on("input", (event) => {
508
+ if (!selectedAgent) return { action: "continue" as const };
509
+ // Don't intercept slash commands
510
+ if (event.text.startsWith("/") || event.text.startsWith("!")) return { action: "continue" as const };
511
+
512
+ const wrapped = `Use the subagent tool to delegate to the "${selectedAgent}" agent with this task:\n\n"${event.text}"\n\nSet agentScope to "both".`;
513
+ return { action: "transform" as const, text: wrapped, images: event.images };
514
+ });
312
515
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@draht/coding-agent",
3
- "version": "2026.3.2-9",
3
+ "version": "2026.3.3",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "drahtConfig": {
@@ -31,6 +31,7 @@
31
31
  "prompts",
32
32
  "hooks",
33
33
  "extensions",
34
+ "agents",
34
35
  "bin",
35
36
  "CHANGELOG.md"
36
37
  ],
@@ -39,16 +40,16 @@
39
40
  "dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
40
41
  "build": "tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js && bun run copy-assets",
41
42
  "build:binary": "bun run build && bun build --compile ./dist/cli.js --outfile dist/pi && bun run copy-binary-assets",
42
- "copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/ && shx rm -rf dist/prompts dist/hooks dist/extensions && shx cp -r prompts dist/prompts && shx cp -r hooks dist/hooks && shx cp -r extensions dist/extensions",
43
- "copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp -r prompts dist/ && shx cp -r hooks dist/ && shx cp -r extensions dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/",
43
+ "copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/ && shx rm -rf dist/prompts dist/hooks dist/extensions dist/agents && shx cp -r prompts dist/prompts && shx cp -r hooks dist/hooks && shx cp -r extensions dist/extensions && shx cp -r agents dist/agents",
44
+ "copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp -r prompts dist/ && shx cp -r hooks dist/ && shx cp -r extensions dist/ && shx cp -r agents dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/",
44
45
  "test": "vitest --run",
45
46
  "prepublishOnly": "bun run clean && bun run build"
46
47
  },
47
48
  "dependencies": {
48
49
  "@mariozechner/jiti": "^2.6.2",
49
- "@draht/agent-core": "2026.3.2-9",
50
- "@draht/ai": "2026.3.2-9",
51
- "@draht/tui": "2026.3.2-9",
50
+ "@draht/agent-core": "2026.3.3",
51
+ "@draht/ai": "2026.3.3",
52
+ "@draht/tui": "2026.3.3",
52
53
  "@silvia-odwyer/photon-node": "^0.3.4",
53
54
  "chalk": "^5.5.0",
54
55
  "cli-highlight": "^2.1.11",
@@ -8,12 +8,12 @@ Capture implementation decisions before planning a phase.
8
8
  ```
9
9
 
10
10
  ## Steps
11
- 1. Run `draht phase-info N` to load phase context
11
+ 1. Run `draht-tools phase-info N` to load phase context
12
12
  2. Identify gray areas based on what's being built
13
13
  3. Present 1-2 questions at a time about preferences
14
14
  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 save-context N`
16
- 6. Commit: `draht commit-docs "capture phase N context"`
15
+ 5. Record decisions with `draht-tools save-context N`
16
+ 6. Commit: `draht-tools commit-docs "capture phase N context"`
17
17
 
18
18
  ## Gray Area Categories
19
19
  - **Visual features** → Layout, density, interactions, empty states
@@ -8,36 +8,36 @@ Execute all plans in a phase with atomic commits.
8
8
  ```
9
9
 
10
10
  ## Steps
11
- 1. Run `draht discover-plans N` to find and order plans
11
+ 1. Run `draht-tools discover-plans N` to find and order plans
12
12
  2. For each plan in dependency order:
13
- a. Load plan: `draht read-plan N P`
13
+ a. Load plan: `draht-tools read-plan N P`
14
14
  b. Execute each task in strict TDD cycle:
15
15
 
16
16
  **🔴 RED — Write failing tests first**
17
17
  - Write the test cases from `<test>`
18
18
  - Run tests — confirm they FAIL (if they pass, the test is wrong)
19
- - Commit failing tests: `draht commit-task N P T "red: test description"`
19
+ - Commit failing tests: `draht-tools commit-task N P T "red: test description"`
20
20
 
21
21
  **🟢 GREEN — Minimal implementation**
22
22
  - Write the minimum code from `<action>` to make tests pass
23
23
  - Use domain language from `<context>` and `<domain>` for all names
24
24
  - Run tests — confirm they PASS
25
- - Commit: `draht commit-task N P T "green: task name"`
25
+ - Commit: `draht-tools commit-task N P T "green: task name"`
26
26
 
27
27
  **🔵 REFACTOR — Clean up with safety net**
28
28
  - Apply improvements from `<refactor>` (if any)
29
29
  - Run tests after each change — must stay green
30
30
  - Verify domain language compliance (names match DOMAIN.md)
31
- - Commit: `draht commit-task N P T "refactor: description"`
31
+ - Commit: `draht-tools commit-task N P T "refactor: description"`
32
32
 
33
33
  **✅ VERIFY**
34
34
  - Run the `<verify>` step
35
35
  - Confirm `<done>` criteria met
36
36
 
37
- c. Write summary: `draht write-summary N P`
38
- 3. Phase verification: `draht verify-phase N`
39
- 4. Update state: `draht update-state`
40
- 5. Final commit: `draht commit-docs "complete phase N execution"`
37
+ c. Write summary: `draht-tools write-summary N P`
38
+ 3. Phase verification: `draht-tools verify-phase N`
39
+ 4. Update state: `draht-tools update-state`
40
+ 5. Final commit: `draht-tools commit-docs "complete phase N execution"`
41
41
 
42
42
  ## TDD Rules
43
43
  - Never write implementation before a failing test exists
@@ -8,7 +8,7 @@ Analyze existing codebase before planning.
8
8
  ```
9
9
 
10
10
  ## Steps
11
- 1. Run `draht map-codebase [dir]`
11
+ 1. Run `draht-tools map-codebase [dir]`
12
12
  2. Tool generates: STACK.md, ARCHITECTURE.md, CONVENTIONS.md, CONCERNS.md
13
13
  3. Review output, supplement with your own analysis if needed
14
14
  4. Identify implicit bounded contexts from directory structure:
@@ -29,4 +29,4 @@ Analyze existing codebase before planning.
29
29
  - Existing coverage configuration and goals (if any)
30
30
  - Which layers have tests today (unit, integration, e2e)
31
31
  - Gaps and recommendations
32
- 8. Commit: `draht commit-docs "map existing codebase"`
32
+ 8. Commit: `draht-tools commit-docs "map existing codebase"`