@draht/coding-agent 2026.3.2 → 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.
- package/CHANGELOG.md +74 -13
- package/README.md +89 -106
- package/agents/architect.md +45 -0
- package/agents/debugger.md +57 -0
- package/agents/git-committer.md +46 -0
- package/agents/implementer.md +25 -0
- package/agents/reviewer.md +52 -0
- package/agents/security-auditor.md +61 -0
- package/agents/verifier.md +44 -0
- package/bin/draht-tools.cjs +20 -20
- package/dist/agents/architect.md +45 -0
- package/dist/agents/debugger.md +57 -0
- package/dist/agents/git-committer.md +46 -0
- package/dist/agents/implementer.md +25 -0
- package/dist/agents/reviewer.md +52 -0
- package/dist/agents/security-auditor.md +61 -0
- package/dist/agents/verifier.md +44 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +10 -1
- package/dist/core/package-manager.js.map +1 -1
- package/dist/extensions/gsd-commands.ts +69 -4
- package/dist/extensions/subagent.ts +212 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/migrations.d.ts +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +3 -3
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +1 -1
- package/dist/modes/interactive/theme/light.json +1 -1
- package/dist/prompts/commands/discuss-phase.md +3 -3
- package/dist/prompts/commands/execute-phase.md +9 -9
- package/dist/prompts/commands/map-codebase.md +2 -2
- package/dist/prompts/commands/new-project.md +9 -9
- package/dist/prompts/commands/pause-work.md +2 -2
- package/dist/prompts/commands/plan-phase.md +5 -5
- package/dist/prompts/commands/progress.md +1 -1
- package/dist/prompts/commands/quick.md +4 -4
- package/dist/prompts/commands/resume-work.md +1 -1
- package/dist/prompts/commands/verify-work.md +4 -4
- package/docs/compaction.md +14 -14
- package/docs/custom-provider.md +9 -9
- package/docs/development.md +1 -1
- package/docs/extensions.md +32 -32
- package/docs/json.md +4 -4
- package/docs/packages.md +1 -1
- package/docs/providers.md +1 -1
- package/docs/rpc.md +1 -1
- package/docs/sdk.md +24 -24
- package/docs/session.md +6 -6
- package/docs/termux.md +1 -1
- package/docs/themes.md +2 -2
- package/docs/tui.md +20 -20
- package/examples/extensions/README.md +4 -4
- package/examples/extensions/doom-overlay/README.md +1 -1
- package/examples/extensions/dynamic-resources/dynamic.json +1 -1
- package/examples/extensions/subagent/README.md +11 -11
- package/examples/sdk/README.md +3 -3
- package/extensions/gsd-commands.ts +69 -4
- package/extensions/subagent.ts +212 -9
- package/package.json +9 -7
- package/prompts/commands/discuss-phase.md +3 -3
- package/prompts/commands/execute-phase.md +9 -9
- package/prompts/commands/map-codebase.md +2 -2
- package/prompts/commands/new-project.md +9 -9
- package/prompts/commands/pause-work.md +2 -2
- package/prompts/commands/plan-phase.md +5 -5
- package/prompts/commands/progress.md +1 -1
- package/prompts/commands/quick.md +4 -4
- package/prompts/commands/resume-work.md +1 -1
- package/prompts/commands/verify-work.md +4 -4
package/extensions/subagent.ts
CHANGED
|
@@ -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
|
-
|
|
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);
|
|
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
|
-
|
|
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 ? "
|
|
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
|
-
|
|
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.
|
|
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": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"configDir": ".draht"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
|
+
"coding-agent": "dist/cli.js",
|
|
11
12
|
"draht": "dist/cli.js",
|
|
12
13
|
"draht-tools": "bin/draht-tools.cjs"
|
|
13
14
|
},
|
|
@@ -30,6 +31,7 @@
|
|
|
30
31
|
"prompts",
|
|
31
32
|
"hooks",
|
|
32
33
|
"extensions",
|
|
34
|
+
"agents",
|
|
33
35
|
"bin",
|
|
34
36
|
"CHANGELOG.md"
|
|
35
37
|
],
|
|
@@ -38,16 +40,16 @@
|
|
|
38
40
|
"dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
|
|
39
41
|
"build": "tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js && bun run copy-assets",
|
|
40
42
|
"build:binary": "bun run build && bun build --compile ./dist/cli.js --outfile dist/pi && bun run copy-binary-assets",
|
|
41
|
-
"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",
|
|
42
|
-
"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/",
|
|
43
45
|
"test": "vitest --run",
|
|
44
46
|
"prepublishOnly": "bun run clean && bun run build"
|
|
45
47
|
},
|
|
46
48
|
"dependencies": {
|
|
47
49
|
"@mariozechner/jiti": "^2.6.2",
|
|
48
|
-
"@draht/agent-core": "
|
|
49
|
-
"@draht/ai": "
|
|
50
|
-
"@draht/tui": "
|
|
50
|
+
"@draht/agent-core": "2026.3.3",
|
|
51
|
+
"@draht/ai": "2026.3.3",
|
|
52
|
+
"@draht/tui": "2026.3.3",
|
|
51
53
|
"@silvia-odwyer/photon-node": "^0.3.4",
|
|
52
54
|
"chalk": "^5.5.0",
|
|
53
55
|
"cli-highlight": "^2.1.11",
|
|
@@ -96,7 +98,7 @@
|
|
|
96
98
|
},
|
|
97
99
|
"repository": {
|
|
98
100
|
"type": "git",
|
|
99
|
-
"url": "git+https://github.com/
|
|
101
|
+
"url": "git+https://github.com/draht-dev/draht.git",
|
|
100
102
|
"directory": "packages/coding-agent"
|
|
101
103
|
},
|
|
102
104
|
"engines": {
|
|
@@ -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"`
|
|
@@ -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,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`
|