@draht/coding-agent 2026.3.2 → 2026.3.4
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 +194 -13
- package/README.md +90 -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/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +0 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -14
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +14 -4
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +167 -49
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +2 -1
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/builtins/subagent.d.ts +14 -0
- package/dist/core/builtins/subagent.d.ts.map +1 -0
- package/dist/core/builtins/subagent.js +492 -0
- package/dist/core/builtins/subagent.js.map +1 -0
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +4 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js +6 -0
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +19 -8
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +1 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +8 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/model-registry.d.ts +1 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +9 -6
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +35 -5
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/resource-loader.d.ts +2 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +5 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +3 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +4 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts +4 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +34 -12
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/path-utils.js.map +1 -1
- 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/main.d.ts.map +1 -1
- package/dist/main.js +5 -5
- package/dist/main.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/components/armin.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/daxnuts.js.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts +5 -2
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +9 -1
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +1 -1
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +1 -1
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +1 -1
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +2 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +28 -3
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts +1 -0
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +11 -0
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +28 -27
- 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/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +5 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/prompts/commands/discuss-phase.md +3 -3
- package/dist/prompts/commands/execute-phase.md +9 -9
- package/dist/prompts/commands/fix.md +29 -0
- package/dist/prompts/commands/init-project.md +49 -0
- package/dist/prompts/commands/map-codebase.md +2 -2
- package/dist/prompts/commands/new-project.md +9 -9
- package/dist/prompts/commands/next-milestone.md +44 -0
- 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/review.md +26 -0
- package/dist/prompts/commands/verify-work.md +4 -4
- package/docs/compaction.md +14 -14
- package/docs/custom-provider.md +19 -11
- package/docs/development.md +1 -1
- package/docs/extensions.md +52 -33
- package/docs/json.md +4 -4
- package/docs/packages.md +1 -1
- package/docs/providers.md +4 -2
- package/docs/rpc.md +1 -1
- package/docs/sdk.md +24 -24
- package/docs/session.md +6 -6
- package/docs/settings.md +1 -0
- package/docs/termux.md +1 -1
- package/docs/themes.md +2 -2
- package/docs/tui.md +20 -20
- package/examples/extensions/README.md +5 -4
- package/examples/extensions/antigravity-image-gen.ts +3 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/doom-overlay/README.md +1 -1
- package/examples/extensions/dynamic-resources/dynamic.json +1 -1
- package/examples/extensions/dynamic-tools.ts +74 -0
- package/examples/extensions/subagent/README.md +11 -11
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/README.md +3 -3
- package/package.json +11 -8
- package/prompts/commands/discuss-phase.md +3 -3
- package/prompts/commands/execute-phase.md +9 -9
- package/prompts/commands/fix.md +29 -0
- package/prompts/commands/init-project.md +49 -0
- package/prompts/commands/map-codebase.md +2 -2
- package/prompts/commands/new-project.md +9 -9
- package/prompts/commands/next-milestone.md +44 -0
- 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/review.md +26 -0
- package/prompts/commands/verify-work.md +4 -4
- package/dist/extensions/gsd-commands.ts +0 -338
- package/dist/extensions/subagent.ts +0 -312
- package/extensions/gsd-commands.ts +0 -338
- package/extensions/subagent.ts +0 -312
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Subagent Tool for Draht — adapted from the pi subagent example
|
|
3
|
-
*
|
|
4
|
-
* Spawns isolated draht processes for delegated tasks.
|
|
5
|
-
* Agents are defined in .draht/agents/*.md (project) or ~/.draht/agent/agents/*.md (global).
|
|
6
|
-
*
|
|
7
|
-
* Modes:
|
|
8
|
-
* single — { agent, task }
|
|
9
|
-
* parallel — { tasks: [{ agent, task }] } (max 8, concurrency 4)
|
|
10
|
-
* chain — { chain: [{ agent, task }] } (sequential, {previous} placeholder)
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { spawn } from "node:child_process";
|
|
14
|
-
import * as fs from "node:fs";
|
|
15
|
-
import * as os from "node:os";
|
|
16
|
-
import * as path from "node:path";
|
|
17
|
-
import type { Message } from "@draht/ai";
|
|
18
|
-
import { StringEnum } from "@draht/ai";
|
|
19
|
-
import { type ExtensionAPI, getAgentDir, parseFrontmatter } from "@draht/coding-agent";
|
|
20
|
-
import { Type } from "@sinclair/typebox";
|
|
21
|
-
|
|
22
|
-
const MAX_PARALLEL = 8;
|
|
23
|
-
const MAX_CONCURRENCY = 4;
|
|
24
|
-
// Use the same binary that's currently running
|
|
25
|
-
const DRAHT_BIN = process.execPath;
|
|
26
|
-
|
|
27
|
-
// ─── Agent discovery ────────────────────────────────────────────────────────
|
|
28
|
-
|
|
29
|
-
interface AgentConfig {
|
|
30
|
-
name: string;
|
|
31
|
-
description: string;
|
|
32
|
-
tools?: string[];
|
|
33
|
-
model?: string;
|
|
34
|
-
systemPrompt: string;
|
|
35
|
-
source: "user" | "project";
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig[] {
|
|
39
|
-
if (!fs.existsSync(dir)) return [];
|
|
40
|
-
let entries: fs.Dirent[];
|
|
41
|
-
try {
|
|
42
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
43
|
-
} catch {
|
|
44
|
-
return [];
|
|
45
|
-
}
|
|
46
|
-
const agents: AgentConfig[] = [];
|
|
47
|
-
for (const entry of entries) {
|
|
48
|
-
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
49
|
-
try {
|
|
50
|
-
const content = fs.readFileSync(path.join(dir, entry.name), "utf-8");
|
|
51
|
-
const { frontmatter, body } = parseFrontmatter<Record<string, string>>(content);
|
|
52
|
-
if (!frontmatter.name || !frontmatter.description) continue;
|
|
53
|
-
const tools = frontmatter.tools?.split(",").map((t: string) => t.trim()).filter(Boolean);
|
|
54
|
-
agents.push({
|
|
55
|
-
name: frontmatter.name,
|
|
56
|
-
description: frontmatter.description,
|
|
57
|
-
tools: tools?.length ? tools : undefined,
|
|
58
|
-
model: frontmatter.model,
|
|
59
|
-
systemPrompt: body,
|
|
60
|
-
source,
|
|
61
|
-
});
|
|
62
|
-
} catch {
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return agents;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function findProjectAgentsDir(cwd: string): string | null {
|
|
70
|
-
let dir = cwd;
|
|
71
|
-
while (true) {
|
|
72
|
-
const candidate = path.join(dir, ".draht", "agents");
|
|
73
|
-
try {
|
|
74
|
-
if (fs.statSync(candidate).isDirectory()) return candidate;
|
|
75
|
-
} catch {}
|
|
76
|
-
const parent = path.dirname(dir);
|
|
77
|
-
if (parent === dir) return null;
|
|
78
|
-
dir = parent;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
type AgentScope = "user" | "project" | "both";
|
|
83
|
-
|
|
84
|
-
function discoverAgents(cwd: string, scope: AgentScope): AgentConfig[] {
|
|
85
|
-
const userDir = path.join(getAgentDir(), "agents");
|
|
86
|
-
const projectDir = findProjectAgentsDir(cwd);
|
|
87
|
-
const userAgents = scope !== "project" ? loadAgentsFromDir(userDir, "user") : [];
|
|
88
|
-
const projectAgents = scope !== "user" && projectDir ? loadAgentsFromDir(projectDir, "project") : [];
|
|
89
|
-
const map = new Map<string, AgentConfig>();
|
|
90
|
-
for (const a of userAgents) map.set(a.name, a);
|
|
91
|
-
for (const a of projectAgents) map.set(a.name, a); // project overrides global
|
|
92
|
-
return Array.from(map.values());
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// ─── Runner ─────────────────────────────────────────────────────────────────
|
|
96
|
-
|
|
97
|
-
interface RunResult {
|
|
98
|
-
agent: string;
|
|
99
|
-
task: string;
|
|
100
|
-
exitCode: number;
|
|
101
|
-
output: string;
|
|
102
|
-
stderr: string;
|
|
103
|
-
step?: number;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function writeTemp(name: string, content: string): { file: string; dir: string } {
|
|
107
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "draht-subagent-"));
|
|
108
|
-
const file = path.join(dir, `${name.replace(/[^\w.-]/g, "_")}.md`);
|
|
109
|
-
fs.writeFileSync(file, content, { encoding: "utf-8", mode: 0o600 });
|
|
110
|
-
return { file, dir };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function cleanTemp(file: string, dir: string) {
|
|
114
|
-
try { fs.unlinkSync(file); } catch {}
|
|
115
|
-
try { fs.rmdirSync(dir); } catch {}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function getFinalText(messages: Message[]): string {
|
|
119
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
120
|
-
const msg = messages[i];
|
|
121
|
-
if (msg.role === "assistant") {
|
|
122
|
-
for (const part of msg.content) {
|
|
123
|
-
if (part.type === "text") return part.text;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return "";
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async function runAgent(
|
|
131
|
-
cwd: string,
|
|
132
|
-
agent: AgentConfig,
|
|
133
|
-
task: string,
|
|
134
|
-
signal?: AbortSignal,
|
|
135
|
-
step?: number,
|
|
136
|
-
): Promise<RunResult> {
|
|
137
|
-
const args: string[] = ["--mode", "json", "-p", "--no-session"];
|
|
138
|
-
if (agent.model) args.push("--model", agent.model);
|
|
139
|
-
if (agent.tools?.length) args.push("--tools", agent.tools.join(","));
|
|
140
|
-
|
|
141
|
-
let tmpFile: string | null = null;
|
|
142
|
-
let tmpDir: string | null = null;
|
|
143
|
-
|
|
144
|
-
if (agent.systemPrompt.trim()) {
|
|
145
|
-
const tmp = writeTemp(agent.name, agent.systemPrompt);
|
|
146
|
-
tmpFile = tmp.file;
|
|
147
|
-
tmpDir = tmp.dir;
|
|
148
|
-
args.push("--append-system-prompt", tmpFile);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
args.push(`Task: ${task}`);
|
|
152
|
-
|
|
153
|
-
const messages: Message[] = [];
|
|
154
|
-
let stderr = "";
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
const exitCode = await new Promise<number>((resolve) => {
|
|
158
|
-
const proc = spawn(DRAHT_BIN, args, { cwd, shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
159
|
-
let buf = "";
|
|
160
|
-
|
|
161
|
-
const processLine = (line: string) => {
|
|
162
|
-
if (!line.trim()) return;
|
|
163
|
-
try {
|
|
164
|
-
const event = JSON.parse(line);
|
|
165
|
-
if ((event.type === "message_end" || event.type === "tool_result_end") && event.message) {
|
|
166
|
-
messages.push(event.message as Message);
|
|
167
|
-
}
|
|
168
|
-
} catch {}
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
proc.stdout.on("data", (d) => {
|
|
172
|
-
buf += d.toString();
|
|
173
|
-
const lines = buf.split("\n");
|
|
174
|
-
buf = lines.pop() || "";
|
|
175
|
-
for (const l of lines) processLine(l);
|
|
176
|
-
});
|
|
177
|
-
proc.stderr.on("data", (d) => { stderr += d.toString(); });
|
|
178
|
-
proc.on("close", (code) => {
|
|
179
|
-
if (buf.trim()) processLine(buf);
|
|
180
|
-
resolve(code ?? 0);
|
|
181
|
-
});
|
|
182
|
-
proc.on("error", () => resolve(1));
|
|
183
|
-
|
|
184
|
-
if (signal) {
|
|
185
|
-
const kill = () => { proc.kill("SIGTERM"); setTimeout(() => { if (!proc.killed) proc.kill("SIGKILL"); }, 5000); };
|
|
186
|
-
if (signal.aborted) kill();
|
|
187
|
-
else signal.addEventListener("abort", kill, { once: true });
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
return { agent: agent.name, task, exitCode, output: getFinalText(messages), stderr, step };
|
|
192
|
-
} finally {
|
|
193
|
-
if (tmpFile && tmpDir) cleanTemp(tmpFile, tmpDir);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
async function runParallel<T>(
|
|
198
|
-
items: T[],
|
|
199
|
-
concurrency: number,
|
|
200
|
-
fn: (item: T, i: number) => Promise<T extends unknown ? RunResult : never>,
|
|
201
|
-
): Promise<RunResult[]> {
|
|
202
|
-
const results: RunResult[] = new Array(items.length);
|
|
203
|
-
let next = 0;
|
|
204
|
-
await Promise.all(
|
|
205
|
-
Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
|
206
|
-
while (true) {
|
|
207
|
-
const i = next++;
|
|
208
|
-
if (i >= items.length) return;
|
|
209
|
-
results[i] = await (fn as (item: T, i: number) => Promise<RunResult>)(items[i], i);
|
|
210
|
-
}
|
|
211
|
-
}),
|
|
212
|
-
);
|
|
213
|
-
return results;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// ─── Extension ──────────────────────────────────────────────────────────────
|
|
217
|
-
|
|
218
|
-
const TaskItem = Type.Object({
|
|
219
|
-
agent: Type.String({ description: "Agent name" }),
|
|
220
|
-
task: Type.String({ description: "Task description" }),
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
const ChainItem = Type.Object({
|
|
224
|
-
agent: Type.String({ description: "Agent name" }),
|
|
225
|
-
task: Type.String({ description: "Task, optionally using {previous} placeholder" }),
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
const Params = Type.Object({
|
|
229
|
-
agent: Type.Optional(Type.String()),
|
|
230
|
-
task: Type.Optional(Type.String()),
|
|
231
|
-
tasks: Type.Optional(Type.Array(TaskItem, { description: "Parallel tasks" })),
|
|
232
|
-
chain: Type.Optional(Type.Array(ChainItem, { description: "Chained tasks" })),
|
|
233
|
-
agentScope: Type.Optional(
|
|
234
|
-
StringEnum(["user", "project", "both"] as const, { default: "both" }),
|
|
235
|
-
),
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
export default function (pi: ExtensionAPI) {
|
|
239
|
-
pi.registerTool({
|
|
240
|
-
name: "subagent",
|
|
241
|
-
label: "Subagent",
|
|
242
|
-
description:
|
|
243
|
-
"Delegate to specialized agents. single: {agent,task} | parallel: {tasks:[]} | chain: {chain:[]} with {previous} placeholder. agentScope: 'both' (default) uses project .draht/agents/ + global.",
|
|
244
|
-
parameters: Params,
|
|
245
|
-
|
|
246
|
-
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
247
|
-
const scope: AgentScope = (params.agentScope as AgentScope) ?? "both";
|
|
248
|
-
const agents = discoverAgents(ctx.cwd, scope);
|
|
249
|
-
const available = agents.map((a) => a.name).join(", ") || "none";
|
|
250
|
-
|
|
251
|
-
const find = (name: string) => agents.find((a) => a.name === name);
|
|
252
|
-
const notFound = (name: string) => ({
|
|
253
|
-
content: [{ type: "text" as const, text: `Unknown agent "${name}". Available: ${available}` }],
|
|
254
|
-
isError: true,
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
// ── Chain mode ──
|
|
258
|
-
if (params.chain?.length) {
|
|
259
|
-
let previous = "";
|
|
260
|
-
const results: RunResult[] = [];
|
|
261
|
-
for (let i = 0; i < params.chain.length; i++) {
|
|
262
|
-
const step = params.chain[i];
|
|
263
|
-
const agent = find(step.agent);
|
|
264
|
-
if (!agent) return notFound(step.agent);
|
|
265
|
-
const task = step.task.replace(/\{previous\}/g, previous);
|
|
266
|
-
const result = await runAgent(ctx.cwd, agent, task, signal, i + 1);
|
|
267
|
-
results.push(result);
|
|
268
|
-
if (result.exitCode !== 0) {
|
|
269
|
-
return {
|
|
270
|
-
content: [{ type: "text" as const, text: `Chain failed at step ${i + 1} (${step.agent}):\n${result.output || result.stderr}` }],
|
|
271
|
-
isError: true,
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
previous = result.output;
|
|
275
|
-
}
|
|
276
|
-
return { content: [{ type: "text" as const, text: results[results.length - 1].output || "(no output)" }] };
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// ── Parallel mode ──
|
|
280
|
-
if (params.tasks?.length) {
|
|
281
|
-
if (params.tasks.length > MAX_PARALLEL) {
|
|
282
|
-
return { content: [{ type: "text" as const, text: `Too many tasks (max ${MAX_PARALLEL})` }], isError: true };
|
|
283
|
-
}
|
|
284
|
-
for (const t of params.tasks) { if (!find(t.agent)) return notFound(t.agent); }
|
|
285
|
-
|
|
286
|
-
const results = await runParallel(params.tasks, MAX_CONCURRENCY, async (t, i) => {
|
|
287
|
-
return runAgent(ctx.cwd, find(t.agent)!, t.task, signal);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
const ok = results.filter((r) => r.exitCode === 0).length;
|
|
291
|
-
const summary = results
|
|
292
|
-
.map((r) => `[${r.agent}] ${r.exitCode === 0 ? "✓" : "✗"} ${r.output.slice(0, 200)}`)
|
|
293
|
-
.join("\n\n");
|
|
294
|
-
return { content: [{ type: "text" as const, text: `Parallel: ${ok}/${results.length} succeeded\n\n${summary}` }] };
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// ── Single mode ──
|
|
298
|
-
if (params.agent && params.task) {
|
|
299
|
-
const agent = find(params.agent);
|
|
300
|
-
if (!agent) return notFound(params.agent);
|
|
301
|
-
const result = await runAgent(ctx.cwd, agent, params.task, signal);
|
|
302
|
-
const isError = result.exitCode !== 0;
|
|
303
|
-
return {
|
|
304
|
-
content: [{ type: "text" as const, text: result.output || result.stderr || "(no output)" }],
|
|
305
|
-
...(isError ? { isError: true } : {}),
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return { content: [{ type: "text" as const, text: `Provide exactly one mode. Available agents: ${available}` }], isError: true };
|
|
310
|
-
},
|
|
311
|
-
});
|
|
312
|
-
}
|
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GSD — Get Shit Done
|
|
3
|
-
*
|
|
4
|
-
* Batteries-included phase commands for structured AI-assisted development.
|
|
5
|
-
*
|
|
6
|
-
* Full workflow:
|
|
7
|
-
* /discuss <feature> — clarify requirements before planning
|
|
8
|
-
* /plan <feature> — architect produces implementation plan
|
|
9
|
-
* /execute <task1, task2...> — parallel implement → review → commit
|
|
10
|
-
* /verify — parallel lint/typecheck/tests + security audit
|
|
11
|
-
*
|
|
12
|
-
* Utilities:
|
|
13
|
-
* /review <scope> — ad-hoc code review + security audit
|
|
14
|
-
* /fix <issue> — targeted fix plan for a failing task
|
|
15
|
-
* /quick <task> — one-shot implement + commit (skip full GSD)
|
|
16
|
-
* /resume — pick up interrupted work from CONTINUE-HERE.md
|
|
17
|
-
* /status — show .planning/STATE.md overview
|
|
18
|
-
* /new-project <name> [path] — create project dir, git init, scaffold .draht/
|
|
19
|
-
* /init-project — scaffold .draht/ in existing project
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import * as fs from "node:fs";
|
|
23
|
-
import * as path from "node:path";
|
|
24
|
-
import type { ExtensionAPI } from "@draht/coding-agent";
|
|
25
|
-
|
|
26
|
-
function isBusy(ctx: { isIdle: () => boolean }, ui: { notify: (msg: string, level: string) => void }): boolean {
|
|
27
|
-
if (!ctx.isIdle()) {
|
|
28
|
-
ui.notify("Agent is busy", "warning");
|
|
29
|
-
return true;
|
|
30
|
-
}
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export default function (pi: ExtensionAPI) {
|
|
35
|
-
// ── /discuss ─────────────────────────────────────────────────────────────
|
|
36
|
-
pi.registerCommand("discuss", {
|
|
37
|
-
description: "Clarify requirements before planning. Architect asks questions and defines scope. Usage: /discuss <feature>",
|
|
38
|
-
handler: async (args, ctx) => {
|
|
39
|
-
if (!args.trim()) {
|
|
40
|
-
ctx.ui.notify("Usage: /discuss <feature description>", "warning");
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (isBusy(ctx, ctx.ui)) return;
|
|
44
|
-
|
|
45
|
-
pi.sendUserMessage(
|
|
46
|
-
`Use the subagent tool to delegate to the architect agent with this task:
|
|
47
|
-
|
|
48
|
-
"We are in the DISCUSS phase for: ${args.trim()}
|
|
49
|
-
|
|
50
|
-
Your job is NOT to plan yet. First:
|
|
51
|
-
1. Read relevant existing code to understand the current state
|
|
52
|
-
2. Identify ambiguities, unknowns, and risks
|
|
53
|
-
3. List clarifying questions that need answers before planning can begin
|
|
54
|
-
4. Define a clear, bounded scope for what will and won't be built
|
|
55
|
-
5. Output a DISCUSS summary with: scope, assumptions, open questions, risks
|
|
56
|
-
|
|
57
|
-
Do not produce file lists or implementation details yet."
|
|
58
|
-
|
|
59
|
-
Set agentScope to "project".`,
|
|
60
|
-
);
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// ── /plan ────────────────────────────────────────────────────────────────
|
|
65
|
-
pi.registerCommand("plan", {
|
|
66
|
-
description: "Plan a feature — architect reads codebase and produces structured implementation plan. Usage: /plan <feature>",
|
|
67
|
-
handler: async (args, ctx) => {
|
|
68
|
-
if (!args.trim()) {
|
|
69
|
-
ctx.ui.notify("Usage: /plan <feature description>", "warning");
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (isBusy(ctx, ctx.ui)) return;
|
|
73
|
-
|
|
74
|
-
pi.sendUserMessage(
|
|
75
|
-
`Use the subagent tool to delegate to the architect agent with this task: "${args.trim()}"\n\nSet agentScope to "project".`,
|
|
76
|
-
);
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// ── /execute ─────────────────────────────────────────────────────────────
|
|
81
|
-
pi.registerCommand("execute", {
|
|
82
|
-
description: "Execute tasks in parallel, then chain reviewer + git-committer. Usage: /execute task1, task2, task3",
|
|
83
|
-
handler: async (args, ctx) => {
|
|
84
|
-
if (!args.trim()) {
|
|
85
|
-
ctx.ui.notify("Usage: /execute task1, task2, task3", "warning");
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (isBusy(ctx, ctx.ui)) return;
|
|
89
|
-
|
|
90
|
-
const tasks = args.split(",").map((t) => t.trim()).filter(Boolean);
|
|
91
|
-
|
|
92
|
-
if (tasks.length === 1) {
|
|
93
|
-
pi.sendUserMessage(
|
|
94
|
-
`Use the subagent tool in chain mode with agentScope "project":
|
|
95
|
-
1. agent: implementer — task: "${tasks[0]}"
|
|
96
|
-
2. agent: reviewer — task: "Review the changes just made: {previous}"
|
|
97
|
-
3. agent: git-committer — task: "Commit all changes. Review context: {previous}"`,
|
|
98
|
-
);
|
|
99
|
-
} else {
|
|
100
|
-
const parallelList = tasks.map((t, i) => `${i + 1}. agent: implementer — task: "${t}"`).join("\n");
|
|
101
|
-
pi.sendUserMessage(
|
|
102
|
-
`Use the subagent tool with agentScope "project":
|
|
103
|
-
|
|
104
|
-
Step 1 — PARALLEL mode (run all simultaneously):
|
|
105
|
-
${parallelList}
|
|
106
|
-
|
|
107
|
-
Step 2 — CHAIN mode (after all parallel tasks complete):
|
|
108
|
-
1. agent: reviewer — task: "Review all changes just implemented"
|
|
109
|
-
2. agent: git-committer — task: "Commit all changes. Review findings: {previous}"`,
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// ── /verify ──────────────────────────────────────────────────────────────
|
|
116
|
-
pi.registerCommand("verify", {
|
|
117
|
-
description: "Parallel verification: lint, typecheck, tests, and security audit",
|
|
118
|
-
handler: async (_args, ctx) => {
|
|
119
|
-
if (isBusy(ctx, ctx.ui)) return;
|
|
120
|
-
|
|
121
|
-
pi.sendUserMessage(
|
|
122
|
-
`Use the subagent tool in PARALLEL mode with agentScope "project":
|
|
123
|
-
1. agent: verifier — task: "Run all lint, typecheck, and test checks"
|
|
124
|
-
2. agent: security-auditor — task: "Audit all recent changes for security vulnerabilities"
|
|
125
|
-
|
|
126
|
-
After both complete, merge findings into a single prioritized report. List what must be fixed before this is production-ready.`,
|
|
127
|
-
);
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// ── /review ──────────────────────────────────────────────────────────────
|
|
132
|
-
pi.registerCommand("review", {
|
|
133
|
-
description: "Ad-hoc code review + security audit. Usage: /review <scope or files>",
|
|
134
|
-
handler: async (args, ctx) => {
|
|
135
|
-
if (isBusy(ctx, ctx.ui)) return;
|
|
136
|
-
|
|
137
|
-
const scope = args.trim() || "all recent changes";
|
|
138
|
-
pi.sendUserMessage(
|
|
139
|
-
`Use the subagent tool in PARALLEL mode with agentScope "project":
|
|
140
|
-
1. agent: reviewer — task: "Review ${scope} for correctness, type safety, and conventions"
|
|
141
|
-
2. agent: security-auditor — task: "Audit ${scope} for security vulnerabilities"
|
|
142
|
-
|
|
143
|
-
After both complete, merge into a single prioritized findings report.`,
|
|
144
|
-
);
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
// ── /fix ─────────────────────────────────────────────────────────────────
|
|
149
|
-
pi.registerCommand("fix", {
|
|
150
|
-
description: "Create a targeted fix plan for a failing task or bug. Usage: /fix <description of what's broken>",
|
|
151
|
-
handler: async (args, ctx) => {
|
|
152
|
-
if (!args.trim()) {
|
|
153
|
-
ctx.ui.notify("Usage: /fix <description of what's broken>", "warning");
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
if (isBusy(ctx, ctx.ui)) return;
|
|
157
|
-
|
|
158
|
-
pi.sendUserMessage(
|
|
159
|
-
`Use the subagent tool in chain mode with agentScope "project":
|
|
160
|
-
1. agent: architect — task: "Diagnose this issue and produce a minimal fix plan: ${args.trim()}. Read the relevant code first. Output: root cause, exact files to change, fix steps."
|
|
161
|
-
2. agent: implementer — task: "Apply this fix plan exactly: {previous}"
|
|
162
|
-
3. agent: reviewer — task: "Verify the fix is correct and doesn't introduce regressions: {previous}"
|
|
163
|
-
4. agent: git-committer — task: "Commit the fix. Fix summary: {previous}"`,
|
|
164
|
-
);
|
|
165
|
-
},
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// ── /quick ───────────────────────────────────────────────────────────────
|
|
169
|
-
pi.registerCommand("quick", {
|
|
170
|
-
description: "One-shot task: implement + commit. Skips full GSD workflow. Usage: /quick <task>",
|
|
171
|
-
handler: async (args, ctx) => {
|
|
172
|
-
if (!args.trim()) {
|
|
173
|
-
ctx.ui.notify("Usage: /quick <task description>", "warning");
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
if (isBusy(ctx, ctx.ui)) return;
|
|
177
|
-
|
|
178
|
-
pi.sendUserMessage(
|
|
179
|
-
`Use the subagent tool in chain mode with agentScope "project":
|
|
180
|
-
1. agent: implementer — task: "${args.trim()}"
|
|
181
|
-
2. agent: git-committer — task: "Commit the changes just made: {previous}"`,
|
|
182
|
-
);
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// ── /resume ──────────────────────────────────────────────────────────────
|
|
187
|
-
pi.registerCommand("resume", {
|
|
188
|
-
description: "Resume interrupted work — reads CONTINUE-HERE.md and picks up where we left off",
|
|
189
|
-
handler: async (_args, ctx) => {
|
|
190
|
-
if (isBusy(ctx, ctx.ui)) return;
|
|
191
|
-
|
|
192
|
-
const continueFile = path.join(ctx.cwd, ".planning", "CONTINUE-HERE.md");
|
|
193
|
-
const stateFile = path.join(ctx.cwd, ".planning", "STATE.md");
|
|
194
|
-
|
|
195
|
-
if (!fs.existsSync(continueFile) && !fs.existsSync(stateFile)) {
|
|
196
|
-
ctx.ui.notify("No .planning/CONTINUE-HERE.md or STATE.md found. Nothing to resume.", "warning");
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
let context = "";
|
|
201
|
-
if (fs.existsSync(continueFile)) {
|
|
202
|
-
context += `\nCONTINUE-HERE.md:\n${fs.readFileSync(continueFile, "utf-8")}`;
|
|
203
|
-
}
|
|
204
|
-
if (fs.existsSync(stateFile)) {
|
|
205
|
-
context += `\nSTATE.md:\n${fs.readFileSync(stateFile, "utf-8")}`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
pi.sendUserMessage(
|
|
209
|
-
`Read the following project state and resume work from where it was interrupted. Identify the next incomplete task and proceed with it using the appropriate subagent.${context}`,
|
|
210
|
-
);
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// ── /status ──────────────────────────────────────────────────────────────
|
|
215
|
-
pi.registerCommand("status", {
|
|
216
|
-
description: "Show current GSD project state from .planning/STATE.md",
|
|
217
|
-
handler: async (_args, ctx) => {
|
|
218
|
-
const stateFile = path.join(ctx.cwd, ".planning", "STATE.md");
|
|
219
|
-
const logFile = path.join(ctx.cwd, ".planning", "execution-log.jsonl");
|
|
220
|
-
|
|
221
|
-
if (!fs.existsSync(stateFile)) {
|
|
222
|
-
ctx.ui.notify("No .planning/STATE.md found. Run /init-project or /new-project first.", "warning");
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
let output = fs.readFileSync(stateFile, "utf-8");
|
|
227
|
-
|
|
228
|
-
if (fs.existsSync(logFile)) {
|
|
229
|
-
const entries = fs.readFileSync(logFile, "utf-8")
|
|
230
|
-
.split("\n").filter(Boolean)
|
|
231
|
-
.map((l) => { try { return JSON.parse(l); } catch { return null; } })
|
|
232
|
-
.filter(Boolean);
|
|
233
|
-
|
|
234
|
-
const passed = entries.filter((e) => e.status === "pass").length;
|
|
235
|
-
const failed = entries.filter((e) => e.status === "fail").length;
|
|
236
|
-
const skipped = entries.filter((e) => e.status === "skip").length;
|
|
237
|
-
output += `\n\n---\n**Execution log:** ${passed} passed, ${failed} failed, ${skipped} skipped`;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
pi.sendUserMessage(`Here is the current project state:\n\n${output}`);
|
|
241
|
-
},
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
// ── /new-project ─────────────────────────────────────────────────────────
|
|
245
|
-
pi.registerCommand("new-project", {
|
|
246
|
-
description: "Create a new project: mkdir, git init, scaffold .draht/. Usage: /new-project <name> [/base/path]",
|
|
247
|
-
handler: async (args, ctx) => {
|
|
248
|
-
const parts = args.trim().split(/\s+/);
|
|
249
|
-
const name = parts[0];
|
|
250
|
-
if (!name) {
|
|
251
|
-
ctx.ui.notify("Usage: /new-project <name> [/optional/base/path]", "warning");
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const basePath = parts[1] ?? ctx.cwd;
|
|
256
|
-
const projectDir = path.join(basePath, name);
|
|
257
|
-
|
|
258
|
-
if (fs.existsSync(projectDir)) {
|
|
259
|
-
ctx.ui.notify(`Directory already exists: ${projectDir}`, "warning");
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
fs.mkdirSync(projectDir, { recursive: true });
|
|
264
|
-
|
|
265
|
-
const { execSync } = await import("node:child_process");
|
|
266
|
-
execSync("git init", { cwd: projectDir });
|
|
267
|
-
|
|
268
|
-
// Scaffold .draht/ from global agents
|
|
269
|
-
const agentSrc = path.join(process.env.HOME ?? "~", ".draht", "agent", "agents");
|
|
270
|
-
const agentDest = path.join(projectDir, ".draht", "agents");
|
|
271
|
-
const extDest = path.join(projectDir, ".draht", "extensions");
|
|
272
|
-
fs.mkdirSync(agentDest, { recursive: true });
|
|
273
|
-
fs.mkdirSync(extDest, { recursive: true });
|
|
274
|
-
|
|
275
|
-
let agentsCopied = 0;
|
|
276
|
-
if (fs.existsSync(agentSrc)) {
|
|
277
|
-
for (const file of fs.readdirSync(agentSrc)) {
|
|
278
|
-
if (!file.endsWith(".md")) continue;
|
|
279
|
-
fs.copyFileSync(path.join(agentSrc, file), path.join(agentDest, file));
|
|
280
|
-
agentsCopied++;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Copy shipped extensions into the new project
|
|
285
|
-
const extSrc = path.dirname(new URL(import.meta.url).pathname);
|
|
286
|
-
for (const file of ["subagent.ts", "gsd-commands.ts"]) {
|
|
287
|
-
const src = path.join(extSrc, file);
|
|
288
|
-
if (fs.existsSync(src)) {
|
|
289
|
-
fs.copyFileSync(src, path.join(extDest, file));
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
fs.writeFileSync(path.join(projectDir, ".gitignore"), "node_modules/\n.env\n.env.local\n");
|
|
294
|
-
|
|
295
|
-
ctx.ui.notify(
|
|
296
|
-
`✓ ${projectDir} created — ${agentsCopied} agents, git initialized. Customize .draht/agents/*.md for your stack.`,
|
|
297
|
-
"info",
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
pi.sendUserMessage(
|
|
301
|
-
`New project "${name}" created at ${projectDir} with ${agentsCopied} GSD agents scaffolded. What should we build first?`,
|
|
302
|
-
);
|
|
303
|
-
},
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
// ── /init-project ────────────────────────────────────────────────────────
|
|
307
|
-
pi.registerCommand("init-project", {
|
|
308
|
-
description: "Scaffold .draht/ config into an existing project from global agent defaults",
|
|
309
|
-
handler: async (_args, ctx) => {
|
|
310
|
-
const targetDir = path.join(ctx.cwd, ".draht");
|
|
311
|
-
|
|
312
|
-
if (fs.existsSync(targetDir)) {
|
|
313
|
-
ctx.ui.notify(".draht/ already exists in this project", "warning");
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const agentSrc = path.join(process.env.HOME ?? "~", ".draht", "agent", "agents");
|
|
318
|
-
const agentDest = path.join(targetDir, "agents");
|
|
319
|
-
const extDest = path.join(targetDir, "extensions");
|
|
320
|
-
fs.mkdirSync(agentDest, { recursive: true });
|
|
321
|
-
fs.mkdirSync(extDest, { recursive: true });
|
|
322
|
-
|
|
323
|
-
let copied = 0;
|
|
324
|
-
if (fs.existsSync(agentSrc)) {
|
|
325
|
-
for (const file of fs.readdirSync(agentSrc)) {
|
|
326
|
-
if (!file.endsWith(".md")) continue;
|
|
327
|
-
fs.copyFileSync(path.join(agentSrc, file), path.join(agentDest, file));
|
|
328
|
-
copied++;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
ctx.ui.notify(
|
|
333
|
-
`.draht/ scaffolded with ${copied} agents. Customize .draht/agents/*.md for this project's stack.`,
|
|
334
|
-
"info",
|
|
335
|
-
);
|
|
336
|
-
},
|
|
337
|
-
});
|
|
338
|
-
}
|