@draht/coding-agent 2026.3.3 → 2026.3.5
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 +144 -0
- package/README.md +1 -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 +0 -12
- 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/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +1 -10
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +4 -2
- 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/gsd/domain-validator.d.ts +18 -0
- package/dist/gsd/domain-validator.d.ts.map +1 -0
- package/dist/gsd/domain-validator.js +61 -0
- package/dist/gsd/domain-validator.js.map +1 -0
- package/dist/gsd/domain.d.ts +12 -0
- package/dist/gsd/domain.d.ts.map +1 -0
- package/dist/gsd/domain.js +113 -0
- package/dist/gsd/domain.js.map +1 -0
- package/dist/gsd/git.d.ts +20 -0
- package/dist/gsd/git.d.ts.map +1 -0
- package/dist/gsd/git.js +59 -0
- package/dist/gsd/git.js.map +1 -0
- package/dist/gsd/hook-utils.d.ts +22 -0
- package/dist/gsd/hook-utils.d.ts.map +1 -0
- package/dist/gsd/hook-utils.js +100 -0
- package/dist/gsd/hook-utils.js.map +1 -0
- package/dist/gsd/index.d.ts +9 -0
- package/dist/gsd/index.d.ts.map +1 -0
- package/dist/gsd/index.js +8 -0
- package/dist/gsd/index.js.map +1 -0
- package/dist/gsd/planning.d.ts +20 -0
- package/dist/gsd/planning.d.ts.map +1 -0
- package/dist/gsd/planning.js +167 -0
- package/dist/gsd/planning.js.map +1 -0
- package/dist/hooks/gsd/draht-post-task.js +44 -11
- package/dist/hooks/gsd/draht-quality-gate.js +99 -57
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- 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.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 +29 -28
- package/dist/modes/interactive/interactive-mode.js.map +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/agents/build.md +5 -1
- package/dist/prompts/agents/plan.md +5 -1
- package/dist/prompts/agents/verify.md +5 -1
- package/dist/prompts/commands/atomic-commit.md +8 -16
- package/dist/prompts/commands/discuss-phase.md +9 -3
- package/dist/prompts/commands/execute-phase.md +15 -8
- package/dist/prompts/commands/fix.md +35 -0
- package/dist/prompts/commands/init-project.md +55 -0
- package/dist/prompts/commands/map-codebase.md +7 -1
- package/dist/prompts/commands/new-project.md +8 -2
- package/dist/prompts/commands/next-milestone.md +48 -0
- package/dist/prompts/commands/pause-work.md +4 -0
- package/dist/prompts/commands/plan-phase.md +11 -5
- package/dist/prompts/commands/progress.md +4 -0
- package/dist/prompts/commands/quick.md +8 -2
- package/dist/prompts/commands/resume-work.md +4 -0
- package/dist/prompts/commands/review.md +32 -0
- package/dist/prompts/commands/verify-work.md +10 -4
- package/docs/custom-provider.md +10 -2
- package/docs/extensions.md +20 -1
- package/docs/providers.md +3 -1
- package/docs/settings.md +1 -0
- package/examples/extensions/README.md +1 -0
- 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/dynamic-tools.ts +74 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/hooks/gsd/draht-post-task.js +44 -11
- package/hooks/gsd/draht-quality-gate.js +99 -57
- package/package.json +9 -8
- package/prompts/agents/build.md +5 -1
- package/prompts/agents/plan.md +5 -1
- package/prompts/agents/verify.md +5 -1
- package/prompts/commands/atomic-commit.md +8 -16
- package/prompts/commands/discuss-phase.md +9 -3
- package/prompts/commands/execute-phase.md +15 -8
- package/prompts/commands/fix.md +35 -0
- package/prompts/commands/init-project.md +55 -0
- package/prompts/commands/map-codebase.md +7 -1
- package/prompts/commands/new-project.md +8 -2
- package/prompts/commands/next-milestone.md +48 -0
- package/prompts/commands/pause-work.md +4 -0
- package/prompts/commands/plan-phase.md +11 -5
- package/prompts/commands/progress.md +4 -0
- package/prompts/commands/quick.md +8 -2
- package/prompts/commands/resume-work.md +4 -0
- package/prompts/commands/review.md +32 -0
- package/prompts/commands/verify-work.md +10 -4
- package/dist/extensions/gsd-commands.ts +0 -403
- package/dist/extensions/subagent.ts +0 -515
- package/extensions/gsd-commands.ts +0 -403
- package/extensions/subagent.ts +0 -515
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic Tools Extension
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates registering tools after session initialization.
|
|
5
|
+
*
|
|
6
|
+
* - Registers one tool during session_start
|
|
7
|
+
* - Registers additional tools at runtime via /add-echo-tool <name>
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ExtensionAPI } from "@draht/coding-agent";
|
|
11
|
+
import { Type } from "@sinclair/typebox";
|
|
12
|
+
|
|
13
|
+
const ECHO_PARAMS = Type.Object({
|
|
14
|
+
message: Type.String({ description: "Message to echo" }),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function normalizeToolName(input: string): string | undefined {
|
|
18
|
+
const trimmed = input.trim().toLowerCase();
|
|
19
|
+
if (!trimmed) return undefined;
|
|
20
|
+
if (!/^[a-z0-9_]+$/.test(trimmed)) return undefined;
|
|
21
|
+
return trimmed;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function dynamicToolsExtension(pi: ExtensionAPI) {
|
|
25
|
+
const registeredToolNames = new Set<string>();
|
|
26
|
+
|
|
27
|
+
const registerEchoTool = (name: string, label: string, prefix: string): boolean => {
|
|
28
|
+
if (registeredToolNames.has(name)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
registeredToolNames.add(name);
|
|
33
|
+
pi.registerTool({
|
|
34
|
+
name,
|
|
35
|
+
label,
|
|
36
|
+
description: `Echo a message with prefix: ${prefix}`,
|
|
37
|
+
promptSnippet: `Echo back user-provided text with ${prefix.trim()} prefix`,
|
|
38
|
+
promptGuidelines: ["Use this tool when the user asks for exact echo output."],
|
|
39
|
+
parameters: ECHO_PARAMS,
|
|
40
|
+
async execute(_toolCallId, params) {
|
|
41
|
+
return {
|
|
42
|
+
content: [{ type: "text", text: `${prefix}${params.message}` }],
|
|
43
|
+
details: { tool: name, prefix },
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return true;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
pi.on("session_start", (_event, ctx) => {
|
|
52
|
+
registerEchoTool("echo_session", "Echo Session", "[session] ");
|
|
53
|
+
ctx.ui.notify("Registered dynamic tool: echo_session", "info");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
pi.registerCommand("add-echo-tool", {
|
|
57
|
+
description: "Register a new echo tool dynamically: /add-echo-tool <tool_name>",
|
|
58
|
+
handler: async (args, ctx) => {
|
|
59
|
+
const toolName = normalizeToolName(args);
|
|
60
|
+
if (!toolName) {
|
|
61
|
+
ctx.ui.notify("Usage: /add-echo-tool <tool_name> (lowercase, numbers, underscores)", "warning");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const created = registerEchoTool(toolName, `Echo ${toolName}`, `[${toolName}] `);
|
|
66
|
+
if (!created) {
|
|
67
|
+
ctx.ui.notify(`Tool already registered: ${toolName}`, "warning");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
ctx.ui.notify(`Registered dynamic tool: ${toolName}`, "info");
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-with-deps",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-with-deps",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.20.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ms": "^2.1.3"
|
|
12
12
|
},
|
|
@@ -19,28 +19,60 @@ if (!phaseNum || !planNum || !taskNum || !status) {
|
|
|
19
19
|
process.exit(1);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// ── Toolchain detection — mirrors src/gsd/hook-utils.ts ──────────────────────
|
|
23
|
+
function detectToolchain(cwd) {
|
|
24
|
+
if (fs.existsSync(path.join(cwd, "bun.lockb")) || fs.existsSync(path.join(cwd, "bun.lock"))) {
|
|
25
|
+
return { pm: "bun", testCmd: "bun test", coverageCmd: "bun test --coverage", lintCmd: "bunx biome check ." };
|
|
26
|
+
}
|
|
27
|
+
if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) {
|
|
28
|
+
return { pm: "pnpm", testCmd: "pnpm test", coverageCmd: "pnpm run test:coverage", lintCmd: "pnpm run lint" };
|
|
29
|
+
}
|
|
30
|
+
if (fs.existsSync(path.join(cwd, "yarn.lock"))) {
|
|
31
|
+
return { pm: "yarn", testCmd: "yarn test", coverageCmd: "yarn run test:coverage", lintCmd: "yarn run lint" };
|
|
32
|
+
}
|
|
33
|
+
return { pm: "npm", testCmd: "npm test", coverageCmd: "npm run test:coverage", lintCmd: "npm run lint" };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readHookConfig(cwd) {
|
|
37
|
+
const defaults = { coverageThreshold: 80, tddMode: "advisory", qualityGateStrict: false };
|
|
38
|
+
const configPath = path.join(cwd, ".planning", "config.json");
|
|
39
|
+
if (!fs.existsSync(configPath)) return defaults;
|
|
40
|
+
try {
|
|
41
|
+
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
42
|
+
const h = raw.hooks || {};
|
|
43
|
+
return {
|
|
44
|
+
coverageThreshold: typeof h.coverageThreshold === "number" ? h.coverageThreshold : defaults.coverageThreshold,
|
|
45
|
+
tddMode: h.tddMode === "strict" || h.tddMode === "advisory" ? h.tddMode : defaults.tddMode,
|
|
46
|
+
qualityGateStrict: typeof h.qualityGateStrict === "boolean" ? h.qualityGateStrict : defaults.qualityGateStrict,
|
|
47
|
+
};
|
|
48
|
+
} catch { return defaults; }
|
|
49
|
+
}
|
|
50
|
+
|
|
22
51
|
const PLANNING = ".planning";
|
|
23
52
|
const LOG_FILE = path.join(PLANNING, "execution-log.jsonl");
|
|
53
|
+
const cwd = process.cwd();
|
|
54
|
+
const toolchain = detectToolchain(cwd);
|
|
55
|
+
const hookConfig = readHookConfig(cwd);
|
|
24
56
|
|
|
25
57
|
// 0. TDD cycle compliance check
|
|
26
|
-
// If the current commit message starts with "green:", the previous commit for this
|
|
27
|
-
// task should start with "red:" — enforce the Red → Green order.
|
|
28
58
|
if (commitHash) {
|
|
29
59
|
try {
|
|
30
|
-
// Find the commit message for commitHash and the one before it
|
|
31
60
|
const currentMsg = execSync(`git log --format=%s -n 1 ${commitHash} 2>/dev/null`, { encoding: "utf-8" }).trim();
|
|
32
61
|
if (/^green:/i.test(currentMsg)) {
|
|
33
|
-
// Scope search to commits that mention this phase/plan/task in their message
|
|
34
|
-
// to avoid false positives from unrelated older commits
|
|
35
62
|
const taskPrefix = `${phaseNum}-${planNum}-${taskNum}`;
|
|
36
63
|
const recentMsgs = execSync(`git log --format=%s -n 50 ${commitHash}~1 2>/dev/null`, { encoding: "utf-8" })
|
|
37
64
|
.trim()
|
|
38
65
|
.split("\n")
|
|
39
66
|
.filter((m) => m.includes(taskPrefix) || /^(red|green|refactor):/i.test(m));
|
|
40
|
-
// Find the nearest TDD-cycle commit scoped to this task
|
|
41
67
|
const prevTaskMsg = recentMsgs.find((m) => /^(red|green|refactor):/i.test(m) && m.includes(taskPrefix));
|
|
42
68
|
if (!prevTaskMsg || !/^red:/i.test(prevTaskMsg)) {
|
|
43
|
-
|
|
69
|
+
const violation = `TDD violation: "green:" commit without preceding "red:" for task ${phaseNum}-${planNum}-${taskNum}`;
|
|
70
|
+
if (hookConfig.tddMode === "strict") {
|
|
71
|
+
console.error(`❌ ${violation}`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
} else {
|
|
74
|
+
console.log(`⚠️ ${violation}`);
|
|
75
|
+
}
|
|
44
76
|
fs.appendFileSync(LOG_FILE, JSON.stringify({
|
|
45
77
|
timestamp: new Date().toISOString(),
|
|
46
78
|
phase: parseInt(phaseNum, 10),
|
|
@@ -69,13 +101,15 @@ const entry = {
|
|
|
69
101
|
|
|
70
102
|
fs.appendFileSync(LOG_FILE, JSON.stringify(entry) + "\n");
|
|
71
103
|
|
|
72
|
-
// 2. Run type check if status is pass
|
|
104
|
+
// 2. Run type check and tests if status is pass
|
|
73
105
|
if (status === "pass") {
|
|
106
|
+
// Type check
|
|
74
107
|
try {
|
|
75
|
-
|
|
108
|
+
const tsCmd = toolchain.pm === "bun" ? "bun run tsgo --noEmit 2>&1" : "npx tsc --noEmit 2>&1";
|
|
109
|
+
execSync(tsCmd, { timeout: 30000, encoding: "utf-8", cwd });
|
|
76
110
|
// Run tests
|
|
77
111
|
try {
|
|
78
|
-
const testOutput = execSync(
|
|
112
|
+
const testOutput = execSync(`${toolchain.testCmd} 2>&1`, { timeout: 60000, encoding: "utf-8", cwd });
|
|
79
113
|
const testMatch = testOutput.match(/(\d+) pass/);
|
|
80
114
|
const testCount = testMatch ? testMatch[1] : "?";
|
|
81
115
|
console.log(`✅ Task ${phaseNum}-${planNum}-${taskNum}: passed + types clean + ${testCount} tests pass`);
|
|
@@ -93,7 +127,6 @@ if (status === "pass") {
|
|
|
93
127
|
const errorCount = (output.match(/error TS/g) || []).length;
|
|
94
128
|
if (errorCount > 0) {
|
|
95
129
|
console.log(`⚠️ Task ${phaseNum}-${planNum}-${taskNum}: passed but ${errorCount} type error(s) introduced`);
|
|
96
|
-
// Append warning to log
|
|
97
130
|
fs.appendFileSync(LOG_FILE, JSON.stringify({
|
|
98
131
|
...entry,
|
|
99
132
|
warning: `${errorCount} type errors introduced`,
|
|
@@ -12,13 +12,67 @@
|
|
|
12
12
|
|
|
13
13
|
const { execSync } = require("node:child_process");
|
|
14
14
|
const fs = require("node:fs");
|
|
15
|
+
const path = require("node:path");
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
// ── Toolchain detection — mirrors src/gsd/hook-utils.ts ──────────────────────
|
|
18
|
+
function detectToolchain(cwd) {
|
|
19
|
+
if (fs.existsSync(path.join(cwd, "bun.lockb")) || fs.existsSync(path.join(cwd, "bun.lock"))) {
|
|
20
|
+
return { pm: "bun", testCmd: "bun test", coverageCmd: "bun test --coverage", lintCmd: "bunx biome check ." };
|
|
21
|
+
}
|
|
22
|
+
if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) {
|
|
23
|
+
return { pm: "pnpm", testCmd: "pnpm test", coverageCmd: "pnpm run test:coverage", lintCmd: "pnpm run lint" };
|
|
24
|
+
}
|
|
25
|
+
if (fs.existsSync(path.join(cwd, "yarn.lock"))) {
|
|
26
|
+
return { pm: "yarn", testCmd: "yarn test", coverageCmd: "yarn run test:coverage", lintCmd: "yarn run lint" };
|
|
27
|
+
}
|
|
28
|
+
return { pm: "npm", testCmd: "npm test", coverageCmd: "npm run test:coverage", lintCmd: "npm run lint" };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readHookConfig(cwd) {
|
|
32
|
+
const defaults = { coverageThreshold: 80, tddMode: "advisory", qualityGateStrict: false };
|
|
33
|
+
const configPath = path.join(cwd, ".planning", "config.json");
|
|
34
|
+
if (!fs.existsSync(configPath)) return defaults;
|
|
35
|
+
try {
|
|
36
|
+
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
37
|
+
const h = raw.hooks || {};
|
|
38
|
+
return {
|
|
39
|
+
coverageThreshold: typeof h.coverageThreshold === "number" ? h.coverageThreshold : defaults.coverageThreshold,
|
|
40
|
+
tddMode: h.tddMode === "strict" || h.tddMode === "advisory" ? h.tddMode : defaults.tddMode,
|
|
41
|
+
qualityGateStrict: typeof h.qualityGateStrict === "boolean" ? h.qualityGateStrict : defaults.qualityGateStrict,
|
|
42
|
+
};
|
|
43
|
+
} catch { return defaults; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Inline domain validator — mirrors src/gsd/domain-validator.ts
|
|
47
|
+
function extractGlossaryTerms(content) {
|
|
48
|
+
const terms = new Set();
|
|
49
|
+
const sectionMatch = content.match(/## Ubiquitous Language([\s\S]*?)(?:\n## |$)/);
|
|
50
|
+
const section = sectionMatch ? sectionMatch[1] : content;
|
|
51
|
+
for (const m of section.matchAll(/\*\*([A-Z][a-zA-Z0-9]+)\*\*/g)) terms.add(m[1]);
|
|
52
|
+
for (const m of section.matchAll(/^[-*]\s+([A-Z][a-zA-Z0-9]+)\s*:/gm)) terms.add(m[1]);
|
|
53
|
+
for (const m of section.matchAll(/\|\s*([A-Z][a-zA-Z0-9]+)\s*\|/g)) terms.add(m[1]);
|
|
54
|
+
return terms;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function loadDomainContent(cwd) {
|
|
58
|
+
const modelPath = path.join(cwd, ".planning", "DOMAIN-MODEL.md");
|
|
59
|
+
if (fs.existsSync(modelPath)) return fs.readFileSync(modelPath, "utf-8");
|
|
60
|
+
const domainPath = path.join(cwd, ".planning", "DOMAIN.md");
|
|
61
|
+
if (fs.existsSync(domainPath)) return fs.readFileSync(domainPath, "utf-8");
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
66
|
+
const cwd = process.cwd();
|
|
67
|
+
const toolchain = detectToolchain(cwd);
|
|
68
|
+
const hookConfig = readHookConfig(cwd);
|
|
69
|
+
const strict = process.argv.includes("--strict") || hookConfig.qualityGateStrict;
|
|
17
70
|
const issues = [];
|
|
18
71
|
|
|
19
72
|
// 1. TypeScript check
|
|
20
73
|
try {
|
|
21
|
-
|
|
74
|
+
const tsCmd = toolchain.pm === "bun" ? "bun run tsgo --noEmit 2>&1" : "npx tsc --noEmit 2>&1";
|
|
75
|
+
execSync(tsCmd, { timeout: 60000, encoding: "utf-8", cwd });
|
|
22
76
|
} catch (error) {
|
|
23
77
|
const output = error.stdout || error.stderr || "";
|
|
24
78
|
const errorCount = (output.match(/error TS/g) || []).length;
|
|
@@ -27,19 +81,19 @@ try {
|
|
|
27
81
|
}
|
|
28
82
|
}
|
|
29
83
|
|
|
30
|
-
// 2.
|
|
31
|
-
if (fs.existsSync("biome.json")) {
|
|
84
|
+
// 2. Lint check (if biome.json exists use biome, else use toolchain lint)
|
|
85
|
+
if (fs.existsSync(path.join(cwd, "biome.json"))) {
|
|
32
86
|
try {
|
|
33
|
-
execSync(
|
|
87
|
+
execSync(`${toolchain.lintCmd} --error-on-warnings 2>&1`, { timeout: 30000, encoding: "utf-8", cwd });
|
|
34
88
|
} catch (error) {
|
|
35
89
|
const output = error.stdout || error.stderr || "";
|
|
36
|
-
issues.push({ severity: strict ? "error" : "warning", message: "
|
|
90
|
+
issues.push({ severity: strict ? "error" : "warning", message: "Lint issues", details: output.slice(0, 500) });
|
|
37
91
|
}
|
|
38
92
|
}
|
|
39
93
|
|
|
40
94
|
// 3. Run tests
|
|
41
95
|
try {
|
|
42
|
-
const testOutput = execSync(
|
|
96
|
+
const testOutput = execSync(`${toolchain.testCmd} 2>&1`, { timeout: 120000, encoding: "utf-8", cwd });
|
|
43
97
|
const failMatch = testOutput.match(/(\d+) fail/);
|
|
44
98
|
if (failMatch && parseInt(failMatch[1], 10) > 0) {
|
|
45
99
|
issues.push({ severity: strict ? "error" : "warning", message: `${failMatch[1]} test(s) failing` });
|
|
@@ -56,71 +110,56 @@ try {
|
|
|
56
110
|
try {
|
|
57
111
|
const result = execSync(
|
|
58
112
|
"grep -rn 'console\\.log' src/ --include='*.ts' --include='*.tsx' 2>/dev/null | grep -v '// debug' | head -5",
|
|
59
|
-
{ encoding: "utf-8" }
|
|
113
|
+
{ encoding: "utf-8", cwd }
|
|
60
114
|
).trim();
|
|
61
115
|
if (result) {
|
|
62
|
-
issues.push({ severity: "warning", message:
|
|
116
|
+
issues.push({ severity: "warning", message: "console.log found in source", details: result });
|
|
63
117
|
}
|
|
64
|
-
} catch { /* grep returns 1 when no match — that's
|
|
118
|
+
} catch { /* grep returns 1 when no match — that's fine */ }
|
|
65
119
|
|
|
66
|
-
// 5. Domain glossary compliance
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (fs.existsSync(domainMdPath)) {
|
|
120
|
+
// 5. Domain glossary compliance (checks DOMAIN-MODEL.md, falls back to DOMAIN.md)
|
|
121
|
+
const domainContent = loadDomainContent(cwd);
|
|
122
|
+
if (domainContent) {
|
|
70
123
|
try {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
{
|
|
84
|
-
).trim().split("\n").filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
85
|
-
|
|
86
|
-
const unknownTerms = [];
|
|
87
|
-
for (const file of changedFiles) {
|
|
88
|
-
if (!fs.existsSync(file)) continue;
|
|
89
|
-
const src = fs.readFileSync(file, "utf-8");
|
|
90
|
-
const declarations = [...src.matchAll(/(?:class|interface|type|enum)\s+([A-Z][a-zA-Z0-9]+)/g)].map((m) => m[1]);
|
|
91
|
-
for (const term of declarations) {
|
|
92
|
-
if (!glossaryTerms.has(term)) {
|
|
93
|
-
unknownTerms.push(`${file}: ${term}`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
124
|
+
const glossaryTerms = extractGlossaryTerms(domainContent);
|
|
125
|
+
const changedFiles = execSync(
|
|
126
|
+
"git diff --cached --name-only 2>/dev/null || git diff --name-only HEAD~1",
|
|
127
|
+
{ encoding: "utf-8", cwd }
|
|
128
|
+
).trim().split("\n").filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
129
|
+
|
|
130
|
+
const unknownTerms = [];
|
|
131
|
+
for (const file of changedFiles) {
|
|
132
|
+
if (!fs.existsSync(path.join(cwd, file))) continue;
|
|
133
|
+
const src = fs.readFileSync(path.join(cwd, file), "utf-8");
|
|
134
|
+
const declarations = [...src.matchAll(/(?:class|interface|type|enum)\s+([A-Z][a-zA-Z0-9]+)/g)].map((m) => m[1]);
|
|
135
|
+
for (const term of declarations) {
|
|
136
|
+
if (!glossaryTerms.has(term)) unknownTerms.push(`${file}: ${term}`);
|
|
96
137
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
} catch { /* ignore
|
|
138
|
+
}
|
|
139
|
+
if (unknownTerms.length > 0) {
|
|
140
|
+
issues.push({
|
|
141
|
+
severity: hookConfig.tddMode === "strict" ? "error" : "warning",
|
|
142
|
+
message: `${unknownTerms.length} PascalCase type(s) not in domain glossary (DOMAIN-MODEL.md)`,
|
|
143
|
+
details: unknownTerms.slice(0, 5).join(", "),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
} catch { /* ignore */ }
|
|
106
147
|
}
|
|
107
148
|
|
|
108
149
|
// 6. Bounded context boundary check — flag suspicious cross-directory imports
|
|
109
150
|
try {
|
|
110
151
|
const changedSrcFiles = execSync(
|
|
111
152
|
"git diff --cached --name-only 2>/dev/null || git diff --name-only HEAD~1",
|
|
112
|
-
{ encoding: "utf-8" }
|
|
153
|
+
{ encoding: "utf-8", cwd }
|
|
113
154
|
).trim().split("\n").filter((f) => /^src\/[^/]+\//.test(f) && (f.endsWith(".ts") || f.endsWith(".tsx")));
|
|
114
155
|
|
|
115
156
|
const crossContextImports = [];
|
|
116
157
|
for (const file of changedSrcFiles) {
|
|
117
|
-
if (!fs.existsSync(file)) continue;
|
|
118
|
-
// Determine this file's context (first path segment under src/)
|
|
158
|
+
if (!fs.existsSync(path.join(cwd, file))) continue;
|
|
119
159
|
const ownContext = file.split("/")[1];
|
|
120
|
-
const src = fs.readFileSync(file, "utf-8");
|
|
160
|
+
const src = fs.readFileSync(path.join(cwd, file), "utf-8");
|
|
121
161
|
const imports = [...src.matchAll(/from\s+['"](\.\.\/.+?)['"]/g)].map((m) => m[1]);
|
|
122
162
|
for (const imp of imports) {
|
|
123
|
-
// Resolve relative import against the file's directory
|
|
124
163
|
const resolved = path.normalize(path.join(path.dirname(file), imp));
|
|
125
164
|
const parts = resolved.split(path.sep);
|
|
126
165
|
const srcIdx = parts.indexOf("src");
|
|
@@ -142,11 +181,11 @@ try {
|
|
|
142
181
|
try {
|
|
143
182
|
const allSrc = execSync(
|
|
144
183
|
"find src -name '*.ts' -not -name '*.test.ts' -not -name '*.spec.ts' 2>/dev/null | wc -l",
|
|
145
|
-
{ encoding: "utf-8" }
|
|
184
|
+
{ encoding: "utf-8", cwd }
|
|
146
185
|
).trim();
|
|
147
186
|
const allTests = execSync(
|
|
148
187
|
"find src -name '*.test.ts' -o -name '*.spec.ts' 2>/dev/null | wc -l",
|
|
149
|
-
{ encoding: "utf-8" }
|
|
188
|
+
{ encoding: "utf-8", cwd }
|
|
150
189
|
).trim();
|
|
151
190
|
const srcCount = parseInt(allSrc, 10) || 0;
|
|
152
191
|
const testCount = parseInt(allTests, 10) || 0;
|
|
@@ -163,12 +202,15 @@ try {
|
|
|
163
202
|
|
|
164
203
|
// 8. Check for TODO/FIXME/HACK comments in changed files
|
|
165
204
|
try {
|
|
166
|
-
const diff = execSync(
|
|
205
|
+
const diff = execSync(
|
|
206
|
+
"git diff --cached --name-only 2>/dev/null || git diff --name-only HEAD~1",
|
|
207
|
+
{ encoding: "utf-8", cwd }
|
|
208
|
+
).trim();
|
|
167
209
|
if (diff) {
|
|
168
210
|
const files = diff.split("\n").filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
169
211
|
for (const file of files) {
|
|
170
212
|
try {
|
|
171
|
-
const content = fs.readFileSync(file, "utf-8");
|
|
213
|
+
const content = fs.readFileSync(path.join(cwd, file), "utf-8");
|
|
172
214
|
const todos = content.match(/\/\/\s*(TODO|FIXME|HACK|XXX):/gi) || [];
|
|
173
215
|
if (todos.length > 0) {
|
|
174
216
|
issues.push({ severity: "info", message: `${file}: ${todos.length} TODO/FIXME comment(s)` });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@draht/coding-agent",
|
|
3
|
-
"version": "2026.3.
|
|
3
|
+
"version": "2026.3.5",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"drahtConfig": {
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
"examples",
|
|
31
31
|
"prompts",
|
|
32
32
|
"hooks",
|
|
33
|
-
"extensions",
|
|
34
33
|
"agents",
|
|
35
34
|
"bin",
|
|
36
35
|
"CHANGELOG.md"
|
|
@@ -40,16 +39,16 @@
|
|
|
40
39
|
"dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
|
|
41
40
|
"build": "tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js && bun run copy-assets",
|
|
42
41
|
"build:binary": "bun run build && bun build --compile ./dist/cli.js --outfile dist/pi && bun run copy-binary-assets",
|
|
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/
|
|
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
|
|
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/agents && shx cp -r prompts dist/prompts && shx cp -r hooks dist/hooks && shx cp -r agents dist/agents",
|
|
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 agents dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/",
|
|
45
44
|
"test": "vitest --run",
|
|
46
45
|
"prepublishOnly": "bun run clean && bun run build"
|
|
47
46
|
},
|
|
48
47
|
"dependencies": {
|
|
49
48
|
"@mariozechner/jiti": "^2.6.2",
|
|
50
|
-
"@draht/agent-core": "2026.3.
|
|
51
|
-
"@draht/ai": "2026.3.
|
|
52
|
-
"@draht/tui": "2026.3.
|
|
49
|
+
"@draht/agent-core": "2026.3.5",
|
|
50
|
+
"@draht/ai": "2026.3.5",
|
|
51
|
+
"@draht/tui": "2026.3.5",
|
|
53
52
|
"@silvia-odwyer/photon-node": "^0.3.4",
|
|
54
53
|
"chalk": "^5.5.0",
|
|
55
54
|
"cli-highlight": "^2.1.11",
|
|
@@ -62,6 +61,8 @@
|
|
|
62
61
|
"marked": "^15.0.12",
|
|
63
62
|
"minimatch": "^10.2.3",
|
|
64
63
|
"proper-lockfile": "^4.1.2",
|
|
64
|
+
"strip-ansi": "^7.1.0",
|
|
65
|
+
"undici": "^7.19.1",
|
|
65
66
|
"yaml": "^2.8.2"
|
|
66
67
|
},
|
|
67
68
|
"overrides": {
|
|
@@ -91,7 +92,7 @@
|
|
|
91
92
|
"tui",
|
|
92
93
|
"agent"
|
|
93
94
|
],
|
|
94
|
-
"author": "
|
|
95
|
+
"author": "Oskar Freye",
|
|
95
96
|
"license": "MIT",
|
|
96
97
|
"publishConfig": {
|
|
97
98
|
"access": "public"
|
package/prompts/agents/build.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Execution agent that implements plans precisely"
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Draht Build Agent
|
|
2
6
|
|
|
3
|
-
You are an execution agent
|
|
7
|
+
You are an execution agent. Your job is to implement plans precisely.
|
|
4
8
|
|
|
5
9
|
## Core Rules
|
|
6
10
|
1. Read the plan FIRST — it is your instruction set
|
package/prompts/agents/plan.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Planning agent that creates atomic, executable plans"
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Draht Plan Agent
|
|
2
6
|
|
|
3
|
-
You are a planning agent
|
|
7
|
+
You are a planning agent. Your job is to create atomic, executable plans.
|
|
4
8
|
|
|
5
9
|
## Core Rules
|
|
6
10
|
1. Plans are prompts — they tell the executor EXACTLY what to build
|
package/prompts/agents/verify.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Verification agent that tests work against acceptance criteria"
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Draht Verify Agent
|
|
2
6
|
|
|
3
|
-
You are a verification agent
|
|
7
|
+
You are a verification agent. Your job is to test completed work against acceptance criteria.
|
|
4
8
|
|
|
5
9
|
## Core Rules
|
|
6
10
|
1. Test from the USER's perspective, not the developer's
|
|
@@ -4,19 +4,13 @@ description: "Analyze changes and create atomic commits"
|
|
|
4
4
|
|
|
5
5
|
# Git Atomic Commit Analysis
|
|
6
6
|
|
|
7
|
-
First,
|
|
7
|
+
First, gather the current state:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
1. Run `git status` to see what changed
|
|
10
|
+
2. Run `git diff` to see unstaged changes
|
|
11
|
+
3. Run `git diff --cached` to see staged changes
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*! git diff*
|
|
14
|
-
|
|
15
|
-
And staged changes:
|
|
16
|
-
|
|
17
|
-
*! git diff --cached*
|
|
18
|
-
|
|
19
|
-
Based on the changes shown above, analyze and group them into logical, ATOMIC commits. Each commit should:
|
|
13
|
+
Based on the changes, analyze and group them into logical, ATOMIC commits. Each commit should:
|
|
20
14
|
|
|
21
15
|
1. Contain ONE logical change only
|
|
22
16
|
2. Be self-contained and complete
|
|
@@ -27,13 +21,11 @@ For each group of changes you identify:
|
|
|
27
21
|
- Generate a clear, descriptive commit message following conventional commits format
|
|
28
22
|
- Explain WHY these changes belong in one commit
|
|
29
23
|
|
|
30
|
-
Then
|
|
24
|
+
Then execute the `git add` and `git commit` commands for each atomic commit in the order they should be applied.
|
|
31
25
|
|
|
32
26
|
Format each commit message as:
|
|
27
|
+
```
|
|
33
28
|
type(scope): brief description
|
|
34
|
-
|
|
35
|
-
- Why the change was necessary
|
|
36
|
-
|
|
29
|
+
```
|
|
37
30
|
|
|
38
31
|
Common types: feat, fix, refactor, docs, test, chore, style, perf
|
|
39
|
-
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Capture implementation decisions before planning a phase"
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# /discuss-phase
|
|
2
6
|
|
|
3
7
|
Capture implementation decisions before planning a phase.
|
|
@@ -7,13 +11,15 @@ Capture implementation decisions before planning a phase.
|
|
|
7
11
|
/discuss-phase [N]
|
|
8
12
|
```
|
|
9
13
|
|
|
14
|
+
Phase: $1
|
|
15
|
+
|
|
10
16
|
## Steps
|
|
11
|
-
1. Run `draht-tools phase-info
|
|
17
|
+
1. Run `draht-tools phase-info $1` to load phase context
|
|
12
18
|
2. Identify gray areas based on what's being built
|
|
13
19
|
3. Present 1-2 questions at a time about preferences
|
|
14
20
|
4. If `.planning/DOMAIN.md` exists, load it and validate discovered terms against the glossary. Add any new domain terms found during discussion.
|
|
15
|
-
5. Record decisions with `draht-tools save-context
|
|
16
|
-
6. Commit: `draht-tools commit-docs "capture phase
|
|
21
|
+
5. Record decisions with `draht-tools save-context $1`
|
|
22
|
+
6. Commit: `draht-tools commit-docs "capture phase $1 context"`
|
|
17
23
|
|
|
18
24
|
## Gray Area Categories
|
|
19
25
|
- **Visual features** → Layout, density, interactions, empty states
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Execute all plans in a phase with atomic commits"
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# /execute-phase
|
|
2
6
|
|
|
3
7
|
Execute all plans in a phase with atomic commits.
|
|
@@ -7,37 +11,40 @@ Execute all plans in a phase with atomic commits.
|
|
|
7
11
|
/execute-phase [N] [--gaps-only]
|
|
8
12
|
```
|
|
9
13
|
|
|
14
|
+
Phase: $1
|
|
15
|
+
Arguments: $ARGUMENTS
|
|
16
|
+
|
|
10
17
|
## Steps
|
|
11
|
-
1. Run `draht-tools discover-plans
|
|
18
|
+
1. Run `draht-tools discover-plans $1` to find and order plans
|
|
12
19
|
2. For each plan in dependency order:
|
|
13
|
-
a. Load plan: `draht-tools read-plan
|
|
20
|
+
a. Load plan: `draht-tools read-plan $1 P`
|
|
14
21
|
b. Execute each task in strict TDD cycle:
|
|
15
22
|
|
|
16
23
|
**🔴 RED — Write failing tests first**
|
|
17
24
|
- Write the test cases from `<test>`
|
|
18
25
|
- Run tests — confirm they FAIL (if they pass, the test is wrong)
|
|
19
|
-
- Commit failing tests: `draht-tools commit-task
|
|
26
|
+
- Commit failing tests: `draht-tools commit-task $1 P T "red: test description"`
|
|
20
27
|
|
|
21
28
|
**🟢 GREEN — Minimal implementation**
|
|
22
29
|
- Write the minimum code from `<action>` to make tests pass
|
|
23
30
|
- Use domain language from `<context>` and `<domain>` for all names
|
|
24
31
|
- Run tests — confirm they PASS
|
|
25
|
-
- Commit: `draht-tools commit-task
|
|
32
|
+
- Commit: `draht-tools commit-task $1 P T "green: task name"`
|
|
26
33
|
|
|
27
34
|
**🔵 REFACTOR — Clean up with safety net**
|
|
28
35
|
- Apply improvements from `<refactor>` (if any)
|
|
29
36
|
- Run tests after each change — must stay green
|
|
30
37
|
- Verify domain language compliance (names match DOMAIN.md)
|
|
31
|
-
- Commit: `draht-tools commit-task
|
|
38
|
+
- Commit: `draht-tools commit-task $1 P T "refactor: description"`
|
|
32
39
|
|
|
33
40
|
**✅ VERIFY**
|
|
34
41
|
- Run the `<verify>` step
|
|
35
42
|
- Confirm `<done>` criteria met
|
|
36
43
|
|
|
37
|
-
c. Write summary: `draht-tools write-summary
|
|
38
|
-
3. Phase verification: `draht-tools verify-phase
|
|
44
|
+
c. Write summary: `draht-tools write-summary $1 P`
|
|
45
|
+
3. Phase verification: `draht-tools verify-phase $1`
|
|
39
46
|
4. Update state: `draht-tools update-state`
|
|
40
|
-
5. Final commit: `draht-tools commit-docs "complete phase
|
|
47
|
+
5. Final commit: `draht-tools commit-docs "complete phase $1 execution"`
|
|
41
48
|
|
|
42
49
|
## TDD Rules
|
|
43
50
|
- Never write implementation before a failing test exists
|