@programinglive/dev-workflow-mcp-server 1.0.8 → 1.0.11
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/.github/ISSUE_TEMPLATE/bug_report.md +34 -34
- package/.github/ISSUE_TEMPLATE/feature_request.md +25 -25
- package/.github/pull_request_template.md +22 -22
- package/.husky/commit-msg +3 -3
- package/.husky/pre-commit +1 -1
- package/CHANGELOG.md +128 -97
- package/CODE_OF_CONDUCT.md +79 -79
- package/CONTRIBUTING.md +60 -60
- package/LICENSE +21 -21
- package/README.md +64 -63
- package/SECURITY.md +20 -20
- package/commit-helpers.js +166 -0
- package/commitlint.config.js +3 -3
- package/exec.js +4 -0
- package/git-helpers.js +152 -0
- package/index.js +45 -1300
- package/package.json +91 -91
- package/tests/handlers.test.js +188 -0
- package/tests/workflowState.test.js +231 -231
- package/tools/definitions.js +211 -0
- package/tools/handlers.js +620 -0
- package/utils.js +45 -0
- package/workflow-state.js +107 -0
package/index.js
CHANGED
|
@@ -8,475 +8,34 @@ import {
|
|
|
8
8
|
ListPromptsRequestSchema,
|
|
9
9
|
GetPromptRequestSchema,
|
|
10
10
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
-
import { exec as execCallback } from "child_process";
|
|
12
|
-
import fs from "fs/promises";
|
|
13
|
-
import path from "path";
|
|
14
11
|
import { fileURLToPath } from "url";
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
readyToCommit: false,
|
|
34
|
-
readyCheckCompleted: false,
|
|
35
|
-
released: false,
|
|
36
|
-
releaseCommand: "",
|
|
37
|
-
releaseNotes: "",
|
|
38
|
-
commitAndPushCompleted: false,
|
|
39
|
-
lastCommitMessage: "",
|
|
40
|
-
lastPushBranch: "",
|
|
41
|
-
history: [],
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async load() {
|
|
46
|
-
try {
|
|
47
|
-
const data = await fs.readFile(this.stateFile, "utf-8");
|
|
48
|
-
const parsed = JSON.parse(data);
|
|
49
|
-
this.state = {
|
|
50
|
-
...this.state,
|
|
51
|
-
...parsed,
|
|
52
|
-
};
|
|
53
|
-
if (typeof this.state.testsCreated !== "boolean") {
|
|
54
|
-
this.state.testsCreated = false;
|
|
55
|
-
}
|
|
56
|
-
if (typeof this.state.testsSkipped !== "boolean") {
|
|
57
|
-
this.state.testsSkipped = false;
|
|
58
|
-
}
|
|
59
|
-
if (typeof this.state.testsSkippedReason !== "string") {
|
|
60
|
-
this.state.testsSkippedReason = "";
|
|
61
|
-
}
|
|
62
|
-
if (typeof this.state.readyCheckCompleted !== "boolean") {
|
|
63
|
-
this.state.readyCheckCompleted = false;
|
|
64
|
-
}
|
|
65
|
-
if (typeof this.state.released !== "boolean") {
|
|
66
|
-
this.state.released = false;
|
|
67
|
-
}
|
|
68
|
-
if (typeof this.state.commitAndPushCompleted !== "boolean") {
|
|
69
|
-
this.state.commitAndPushCompleted = false;
|
|
70
|
-
}
|
|
71
|
-
if (typeof this.state.releaseCommand !== "string") {
|
|
72
|
-
this.state.releaseCommand = "";
|
|
73
|
-
}
|
|
74
|
-
if (typeof this.state.releaseNotes !== "string") {
|
|
75
|
-
this.state.releaseNotes = "";
|
|
76
|
-
}
|
|
77
|
-
if (typeof this.state.lastCommitMessage !== "string") {
|
|
78
|
-
this.state.lastCommitMessage = "";
|
|
79
|
-
}
|
|
80
|
-
if (typeof this.state.lastPushBranch !== "string") {
|
|
81
|
-
this.state.lastPushBranch = "";
|
|
82
|
-
}
|
|
83
|
-
} catch (error) {
|
|
84
|
-
// File doesn't exist yet, use default state
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async save() {
|
|
89
|
-
await fs.writeFile(this.stateFile, JSON.stringify(this.state, null, 2));
|
|
90
|
-
}
|
|
12
|
+
import { exec } from "./exec.js";
|
|
13
|
+
import { normalizeRequestArgs, shellEscape } from "./utils.js";
|
|
14
|
+
import {
|
|
15
|
+
getCurrentBranch,
|
|
16
|
+
getLastCommitMessage,
|
|
17
|
+
getStagedChanges,
|
|
18
|
+
hasStagedChanges,
|
|
19
|
+
hasTestChanges,
|
|
20
|
+
hasWorkingChanges,
|
|
21
|
+
workingTreeSummary,
|
|
22
|
+
} from "./git-helpers.js";
|
|
23
|
+
import {
|
|
24
|
+
createCommitMessageParts,
|
|
25
|
+
determineReleaseTypeFromCommit,
|
|
26
|
+
} from "./commit-helpers.js";
|
|
27
|
+
import { WorkflowState } from "./workflow-state.js";
|
|
28
|
+
import { getToolList } from "./tools/definitions.js";
|
|
29
|
+
import { handleToolCall } from "./tools/handlers.js";
|
|
91
30
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
taskDescription: "",
|
|
96
|
-
bugFixed: false,
|
|
97
|
-
testsCreated: false,
|
|
98
|
-
testsPassed: false,
|
|
99
|
-
testsSkipped: false,
|
|
100
|
-
testsSkippedReason: "",
|
|
101
|
-
documentationCreated: false,
|
|
102
|
-
readyToCommit: false,
|
|
103
|
-
readyCheckCompleted: false,
|
|
104
|
-
released: false,
|
|
105
|
-
releaseCommand: "",
|
|
106
|
-
releaseNotes: "",
|
|
107
|
-
commitAndPushCompleted: false,
|
|
108
|
-
lastCommitMessage: "",
|
|
109
|
-
lastPushBranch: "",
|
|
110
|
-
history: this.state.history,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
addToHistory(entry) {
|
|
115
|
-
this.state.history.push({
|
|
116
|
-
...entry,
|
|
117
|
-
timestamp: new Date().toISOString(),
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
}
|
|
31
|
+
export { createCommitMessageParts, determineReleaseTypeFromCommit } from "./commit-helpers.js";
|
|
32
|
+
export { containsTestFilesInStatus, workingTreeSummary } from "./git-helpers.js";
|
|
33
|
+
export { WorkflowState } from "./workflow-state.js";
|
|
121
34
|
|
|
122
35
|
export const workflowState = new WorkflowState();
|
|
123
36
|
await workflowState.load();
|
|
124
37
|
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
const isWindows = process.platform === "win32";
|
|
128
|
-
|
|
129
|
-
function shellEscape(value) {
|
|
130
|
-
const stringValue = String(value);
|
|
131
|
-
if (isWindows) {
|
|
132
|
-
// Use PowerShell-friendly single quotes, escape embedded single quotes by doubling
|
|
133
|
-
return `'${stringValue.replace(/'/g, "''")}'`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return `'${stringValue.replace(/'/g, "'\\''")}'`;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function normalizeRequestArgs(rawArgs) {
|
|
140
|
-
if (rawArgs === undefined || rawArgs === null) {
|
|
141
|
-
return { args: {}, error: null };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (typeof rawArgs === "string") {
|
|
145
|
-
try {
|
|
146
|
-
const parsed = JSON.parse(rawArgs);
|
|
147
|
-
if (parsed && typeof parsed === "object") {
|
|
148
|
-
return { args: parsed, error: null };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
args: {},
|
|
153
|
-
error:
|
|
154
|
-
"⚠️ Tool arguments must be a JSON object. Please provide key/value pairs.",
|
|
155
|
-
};
|
|
156
|
-
} catch (parseError) {
|
|
157
|
-
return {
|
|
158
|
-
args: {},
|
|
159
|
-
error:
|
|
160
|
-
"⚠️ Unable to parse tool arguments. Please provide valid JSON-formatted data.",
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (typeof rawArgs === "object") {
|
|
166
|
-
return { args: rawArgs, error: null };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
args: {},
|
|
171
|
-
error: "⚠️ Unsupported tool arguments format. Expected a JSON object.",
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async function hasStagedChanges() {
|
|
176
|
-
try {
|
|
177
|
-
const { stdout } = await exec("git status --porcelain");
|
|
178
|
-
return stdout
|
|
179
|
-
.split("\n")
|
|
180
|
-
.filter(Boolean)
|
|
181
|
-
.some((line) => {
|
|
182
|
-
const indexStatus = line[0];
|
|
183
|
-
return indexStatus && indexStatus !== " " && indexStatus !== "?";
|
|
184
|
-
});
|
|
185
|
-
} catch (error) {
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async function hasWorkingChanges() {
|
|
191
|
-
try {
|
|
192
|
-
const { stdout } = await exec("git status --porcelain");
|
|
193
|
-
return stdout.split("\n").some((line) => line && line.trim().length > 0);
|
|
194
|
-
} catch (error) {
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export function workingTreeSummary(statusOutput) {
|
|
200
|
-
if (typeof statusOutput !== "string" || statusOutput.trim() === "") {
|
|
201
|
-
return {
|
|
202
|
-
hasChanges: false,
|
|
203
|
-
lines: [],
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const lines = statusOutput
|
|
208
|
-
.split("\n")
|
|
209
|
-
.map((line) => line.replace(/\s+/g, " ").trim())
|
|
210
|
-
.filter(Boolean);
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
hasChanges: lines.length > 0,
|
|
214
|
-
lines,
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function isTestFilePath(filePath) {
|
|
219
|
-
if (!filePath) {
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (/\.(test|spec)\.[^.]+$/i.test(filePath)) {
|
|
224
|
-
return true;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return /(\/|^)(__tests__|tests?|spec)\//i.test(filePath);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function extractPathFromStatusLine(line) {
|
|
231
|
-
if (!line || line.length < 4) {
|
|
232
|
-
return "";
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const rawPath = line.slice(3).trim();
|
|
236
|
-
if (!rawPath) {
|
|
237
|
-
return "";
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (rawPath.includes(" -> ")) {
|
|
241
|
-
const parts = rawPath.split(" -> ");
|
|
242
|
-
return parts[parts.length - 1].trim();
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return rawPath;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export function containsTestFilesInStatus(statusOutput) {
|
|
249
|
-
if (typeof statusOutput !== "string" || statusOutput.trim() === "") {
|
|
250
|
-
return false;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return statusOutput
|
|
254
|
-
.split("\n")
|
|
255
|
-
.map((line) => extractPathFromStatusLine(line))
|
|
256
|
-
.some((path) => isTestFilePath(path));
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function isDocumentationFilePath(filePath) {
|
|
260
|
-
if (!filePath) {
|
|
261
|
-
return false;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const normalized = filePath.toLowerCase();
|
|
265
|
-
return (
|
|
266
|
-
normalized.startsWith("docs/") ||
|
|
267
|
-
normalized.startsWith("documentation/") ||
|
|
268
|
-
normalized.endsWith(".md") ||
|
|
269
|
-
normalized.endsWith(".rst") ||
|
|
270
|
-
normalized.endsWith(".adoc") ||
|
|
271
|
-
normalized.includes("readme")
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function parseNameStatusLine(line) {
|
|
276
|
-
if (!line) {
|
|
277
|
-
return null;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const parts = line.split("\t").map((part) => part.trim());
|
|
281
|
-
const status = parts[0];
|
|
282
|
-
if (!status) {
|
|
283
|
-
return null;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (status.startsWith("R") || status.startsWith("C")) {
|
|
287
|
-
const originalPath = parts[1] || "";
|
|
288
|
-
const newPath = parts[2] || originalPath;
|
|
289
|
-
return {
|
|
290
|
-
status,
|
|
291
|
-
path: newPath,
|
|
292
|
-
originalPath,
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return {
|
|
297
|
-
status,
|
|
298
|
-
path: parts[1] || "",
|
|
299
|
-
originalPath: "",
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function formatFileList(paths) {
|
|
304
|
-
const uniquePaths = [...new Set(paths.filter(Boolean))];
|
|
305
|
-
|
|
306
|
-
if (uniquePaths.length === 0) {
|
|
307
|
-
return "updates";
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (uniquePaths.length === 1) {
|
|
311
|
-
return uniquePaths[0];
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (uniquePaths.length === 2) {
|
|
315
|
-
return `${uniquePaths[0]} and ${uniquePaths[1]}`;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (uniquePaths.length === 3) {
|
|
319
|
-
return `${uniquePaths[0]}, ${uniquePaths[1]}, and ${uniquePaths[2]}`;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const remaining = uniquePaths.length - 3;
|
|
323
|
-
return `${uniquePaths[0]}, ${uniquePaths[1]}, ${uniquePaths[2]} +${remaining} more`;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function determineCommitType(changes) {
|
|
327
|
-
const hasTestChange = changes.some((change) =>
|
|
328
|
-
[change.path, change.originalPath].some((p) => isTestFilePath(p))
|
|
329
|
-
);
|
|
330
|
-
const hasDocsChange = changes.some((change) =>
|
|
331
|
-
[change.path, change.originalPath].some((p) => isDocumentationFilePath(p))
|
|
332
|
-
);
|
|
333
|
-
|
|
334
|
-
if (hasTestChange) {
|
|
335
|
-
return "test";
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (hasDocsChange) {
|
|
339
|
-
return "docs";
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
return "chore";
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function describeChange(change) {
|
|
346
|
-
const statusCode = change.status || "";
|
|
347
|
-
const path = change.path || change.originalPath || "";
|
|
348
|
-
|
|
349
|
-
if (statusCode.startsWith("R")) {
|
|
350
|
-
return `renamed ${change.originalPath} -> ${change.path}`;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (statusCode.startsWith("C")) {
|
|
354
|
-
return `copied ${change.originalPath} -> ${change.path}`;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
switch (statusCode[0]) {
|
|
358
|
-
case "A":
|
|
359
|
-
return `added ${path}`;
|
|
360
|
-
case "M":
|
|
361
|
-
return `modified ${path}`;
|
|
362
|
-
case "D":
|
|
363
|
-
return `removed ${path}`;
|
|
364
|
-
case "U":
|
|
365
|
-
return `updated ${path}`;
|
|
366
|
-
default:
|
|
367
|
-
return `${statusCode} ${path}`.trim();
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
export function createCommitMessageParts(changes, providedSummary = "") {
|
|
372
|
-
const summaryInput = typeof providedSummary === "string" ? providedSummary.trim() : "";
|
|
373
|
-
|
|
374
|
-
if (!Array.isArray(changes) || changes.length === 0) {
|
|
375
|
-
return {
|
|
376
|
-
summary: summaryInput,
|
|
377
|
-
body: "",
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const commitType = determineCommitType(changes);
|
|
382
|
-
const summaryPaths = changes
|
|
383
|
-
.map((change) => change.path || change.originalPath)
|
|
384
|
-
.filter(Boolean);
|
|
385
|
-
const generatedSummary = `${commitType}: update ${formatFileList(summaryPaths)}`;
|
|
386
|
-
const summary = summaryInput || generatedSummary;
|
|
387
|
-
|
|
388
|
-
const detailLines = changes
|
|
389
|
-
.map((change) => describeChange(change))
|
|
390
|
-
.filter((line) => line && line.trim().length > 0)
|
|
391
|
-
.map((line) => `- ${line}`);
|
|
392
|
-
|
|
393
|
-
return {
|
|
394
|
-
summary,
|
|
395
|
-
body: detailLines.join("\n"),
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
async function getStagedChanges() {
|
|
400
|
-
try {
|
|
401
|
-
const { stdout } = await exec("git diff --cached --name-status");
|
|
402
|
-
return stdout
|
|
403
|
-
.split("\n")
|
|
404
|
-
.map((line) => parseNameStatusLine(line))
|
|
405
|
-
.filter(Boolean);
|
|
406
|
-
} catch (error) {
|
|
407
|
-
return [];
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
async function hasTestChanges() {
|
|
412
|
-
try {
|
|
413
|
-
const { stdout } = await exec("git status --porcelain");
|
|
414
|
-
return containsTestFilesInStatus(stdout);
|
|
415
|
-
} catch (error) {
|
|
416
|
-
return false;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
function normalizeCommitHeader(message) {
|
|
421
|
-
if (typeof message !== "string") {
|
|
422
|
-
return "";
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
const header = message.split("\n")[0] || "";
|
|
426
|
-
return header.trim();
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function containsBreakingChange(message) {
|
|
430
|
-
if (typeof message !== "string") {
|
|
431
|
-
return false;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
return /breaking change/i.test(message);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
export function determineReleaseTypeFromCommit(message) {
|
|
438
|
-
const header = normalizeCommitHeader(message).toLowerCase();
|
|
439
|
-
const hasBreaking = containsBreakingChange(message) || /!/.test(header.split(":")[0] || "");
|
|
440
|
-
|
|
441
|
-
if (hasBreaking) {
|
|
442
|
-
return "major";
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
const typeMatch = header.match(/^([a-z]+)(\(|:)/i);
|
|
446
|
-
const commitType = typeMatch ? typeMatch[1] : "";
|
|
447
|
-
|
|
448
|
-
if (commitType === "feat") {
|
|
449
|
-
return "minor";
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
if (commitType === "fix" || commitType === "perf") {
|
|
453
|
-
return "patch";
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (!commitType && containsBreakingChange(message)) {
|
|
457
|
-
return "major";
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
return "patch";
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
async function getLastCommitMessage() {
|
|
464
|
-
try {
|
|
465
|
-
const { stdout } = await exec("git log -1 --pretty=%B");
|
|
466
|
-
return stdout.trim();
|
|
467
|
-
} catch (error) {
|
|
468
|
-
return "";
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
async function getCurrentBranch() {
|
|
473
|
-
try {
|
|
474
|
-
const { stdout } = await exec("git rev-parse --abbrev-ref HEAD");
|
|
475
|
-
return stdout.trim();
|
|
476
|
-
} catch (error) {
|
|
477
|
-
return "";
|
|
478
|
-
}
|
|
479
|
-
}
|
|
38
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
480
39
|
|
|
481
40
|
// Create MCP server
|
|
482
41
|
export const server = new Server(
|
|
@@ -493,844 +52,29 @@ export const server = new Server(
|
|
|
493
52
|
);
|
|
494
53
|
|
|
495
54
|
// Define tools
|
|
496
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
{
|
|
500
|
-
name: "start_task",
|
|
501
|
-
description:
|
|
502
|
-
"Start a new coding task. This is the first step - be conscious about what you're coding.",
|
|
503
|
-
inputSchema: {
|
|
504
|
-
type: "object",
|
|
505
|
-
properties: {
|
|
506
|
-
description: {
|
|
507
|
-
type: "string",
|
|
508
|
-
description: "Clear description of what you're going to code",
|
|
509
|
-
},
|
|
510
|
-
type: {
|
|
511
|
-
type: "string",
|
|
512
|
-
enum: ["feature", "bugfix", "refactor", "other"],
|
|
513
|
-
description: "Type of task",
|
|
514
|
-
},
|
|
515
|
-
},
|
|
516
|
-
required: ["description", "type"],
|
|
517
|
-
},
|
|
518
|
-
},
|
|
519
|
-
{
|
|
520
|
-
name: "mark_bug_fixed",
|
|
521
|
-
description:
|
|
522
|
-
"Mark that the bug/feature is fixed. Reminder: Now you MUST create tests!",
|
|
523
|
-
inputSchema: {
|
|
524
|
-
type: "object",
|
|
525
|
-
properties: {
|
|
526
|
-
summary: {
|
|
527
|
-
type: "string",
|
|
528
|
-
description: "Brief summary of what was fixed/implemented",
|
|
529
|
-
},
|
|
530
|
-
},
|
|
531
|
-
required: ["summary"],
|
|
532
|
-
},
|
|
533
|
-
},
|
|
534
|
-
{
|
|
535
|
-
name: "create_tests",
|
|
536
|
-
description: "Mark that tests covering the change have been created.",
|
|
537
|
-
inputSchema: {
|
|
538
|
-
type: "object",
|
|
539
|
-
properties: {},
|
|
540
|
-
},
|
|
541
|
-
},
|
|
542
|
-
{
|
|
543
|
-
name: "skip_tests",
|
|
544
|
-
description:
|
|
545
|
-
"Record that tests were intentionally skipped, with a justification.",
|
|
546
|
-
inputSchema: {
|
|
547
|
-
type: "object",
|
|
548
|
-
properties: {
|
|
549
|
-
reason: {
|
|
550
|
-
type: "string",
|
|
551
|
-
description: "Why tests cannot be created or executed",
|
|
552
|
-
},
|
|
553
|
-
},
|
|
554
|
-
required: ["reason"],
|
|
555
|
-
},
|
|
556
|
-
},
|
|
557
|
-
{
|
|
558
|
-
name: "run_tests",
|
|
559
|
-
description:
|
|
560
|
-
"Record test results. NEVER commit if tests fail! Only proceed if all tests are green.",
|
|
561
|
-
inputSchema: {
|
|
562
|
-
type: "object",
|
|
563
|
-
properties: {
|
|
564
|
-
passed: {
|
|
565
|
-
type: "boolean",
|
|
566
|
-
description: "Did all tests pass?",
|
|
567
|
-
},
|
|
568
|
-
testCommand: {
|
|
569
|
-
type: "string",
|
|
570
|
-
description: "The test command that was run",
|
|
571
|
-
},
|
|
572
|
-
details: {
|
|
573
|
-
type: "string",
|
|
574
|
-
description: "Test results details",
|
|
575
|
-
},
|
|
576
|
-
},
|
|
577
|
-
required: ["passed", "testCommand"],
|
|
578
|
-
},
|
|
579
|
-
},
|
|
580
|
-
{
|
|
581
|
-
name: "create_documentation",
|
|
582
|
-
description:
|
|
583
|
-
"Mark that documentation has been created/updated. This is required before committing.",
|
|
584
|
-
inputSchema: {
|
|
585
|
-
type: "object",
|
|
586
|
-
properties: {
|
|
587
|
-
documentationType: {
|
|
588
|
-
type: "string",
|
|
589
|
-
enum: ["README", "inline-comments", "API-docs", "changelog", "other"],
|
|
590
|
-
description: "Type of documentation created",
|
|
591
|
-
},
|
|
592
|
-
summary: {
|
|
593
|
-
type: "string",
|
|
594
|
-
description: "What was documented",
|
|
595
|
-
},
|
|
596
|
-
},
|
|
597
|
-
required: ["documentationType", "summary"],
|
|
598
|
-
},
|
|
599
|
-
},
|
|
600
|
-
{
|
|
601
|
-
name: "check_ready_to_commit",
|
|
602
|
-
description:
|
|
603
|
-
"Check if all workflow steps are completed and you're ready to commit & push.",
|
|
604
|
-
inputSchema: {
|
|
605
|
-
type: "object",
|
|
606
|
-
properties: {},
|
|
607
|
-
},
|
|
608
|
-
},
|
|
609
|
-
{
|
|
610
|
-
name: "commit_and_push",
|
|
611
|
-
description:
|
|
612
|
-
"Automatically run git add, commit, and push once the ready check passes.",
|
|
613
|
-
inputSchema: {
|
|
614
|
-
type: "object",
|
|
615
|
-
properties: {
|
|
616
|
-
commitMessage: {
|
|
617
|
-
type: "string",
|
|
618
|
-
description: "Commit message to use",
|
|
619
|
-
},
|
|
620
|
-
branch: {
|
|
621
|
-
type: "string",
|
|
622
|
-
description:
|
|
623
|
-
"Optional branch name to push to (defaults to current branch)",
|
|
624
|
-
},
|
|
625
|
-
},
|
|
626
|
-
required: [],
|
|
627
|
-
},
|
|
628
|
-
},
|
|
629
|
-
{
|
|
630
|
-
name: "perform_release",
|
|
631
|
-
description:
|
|
632
|
-
"Record release details after committing and pushing your changes.",
|
|
633
|
-
inputSchema: {
|
|
634
|
-
type: "object",
|
|
635
|
-
properties: {
|
|
636
|
-
command: {
|
|
637
|
-
type: "string",
|
|
638
|
-
description: "Release command that was executed",
|
|
639
|
-
},
|
|
640
|
-
notes: {
|
|
641
|
-
type: "string",
|
|
642
|
-
description: "Optional release notes",
|
|
643
|
-
},
|
|
644
|
-
},
|
|
645
|
-
required: ["command"],
|
|
646
|
-
},
|
|
647
|
-
},
|
|
648
|
-
{
|
|
649
|
-
name: "complete_task",
|
|
650
|
-
description:
|
|
651
|
-
"Mark the task as complete after successful commit & push. Resets workflow for next task.",
|
|
652
|
-
inputSchema: {
|
|
653
|
-
type: "object",
|
|
654
|
-
properties: {
|
|
655
|
-
commitMessage: {
|
|
656
|
-
type: "string",
|
|
657
|
-
description: "The commit message used",
|
|
658
|
-
},
|
|
659
|
-
},
|
|
660
|
-
required: ["commitMessage"],
|
|
661
|
-
},
|
|
662
|
-
},
|
|
663
|
-
{
|
|
664
|
-
name: "drop_task",
|
|
665
|
-
description:
|
|
666
|
-
"Abandon the current task and reset the workflow without completing it.",
|
|
667
|
-
inputSchema: {
|
|
668
|
-
type: "object",
|
|
669
|
-
properties: {
|
|
670
|
-
reason: {
|
|
671
|
-
type: "string",
|
|
672
|
-
description: "Optional explanation for dropping the task",
|
|
673
|
-
},
|
|
674
|
-
},
|
|
675
|
-
},
|
|
676
|
-
},
|
|
677
|
-
{
|
|
678
|
-
name: "get_workflow_status",
|
|
679
|
-
description: "Get current workflow status and what needs to be done next.",
|
|
680
|
-
inputSchema: {
|
|
681
|
-
type: "object",
|
|
682
|
-
properties: {},
|
|
683
|
-
},
|
|
684
|
-
},
|
|
685
|
-
{
|
|
686
|
-
name: "view_history",
|
|
687
|
-
description: "View workflow history of completed tasks.",
|
|
688
|
-
inputSchema: {
|
|
689
|
-
type: "object",
|
|
690
|
-
properties: {
|
|
691
|
-
limit: {
|
|
692
|
-
type: "number",
|
|
693
|
-
description: "Number of recent tasks to show (default: 10)",
|
|
694
|
-
},
|
|
695
|
-
},
|
|
696
|
-
},
|
|
697
|
-
},
|
|
698
|
-
],
|
|
699
|
-
};
|
|
700
|
-
});
|
|
55
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
56
|
+
tools: getToolList(),
|
|
57
|
+
}));
|
|
701
58
|
|
|
702
59
|
// Handle tool calls
|
|
703
|
-
server.setRequestHandler(CallToolRequestSchema,
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
switch (name) {
|
|
722
|
-
case "start_task": {
|
|
723
|
-
workflowState.reset();
|
|
724
|
-
workflowState.state.currentPhase = "coding";
|
|
725
|
-
workflowState.state.taskDescription = args.description;
|
|
726
|
-
workflowState.state.taskType = args.type;
|
|
727
|
-
workflowState.state.testsSkipped = false;
|
|
728
|
-
workflowState.state.testsSkippedReason = "";
|
|
729
|
-
workflowState.state.commitAndPushCompleted = false;
|
|
730
|
-
workflowState.state.lastCommitMessage = "";
|
|
731
|
-
workflowState.state.lastPushBranch = "";
|
|
732
|
-
await workflowState.save();
|
|
733
|
-
|
|
734
|
-
return {
|
|
735
|
-
content: [
|
|
736
|
-
{
|
|
737
|
-
type: "text",
|
|
738
|
-
text: `✅ Task Started: ${args.description}\n\n🎯 Be conscious about what you're coding!\n\nWorkflow Steps:\n1. ✓ Start task (current)\n2. ⏳ Fix/implement the feature\n3. ⏳ Create tests\n4. ⏳ Run tests (must pass!)\n5. ⏳ Create documentation\n6. ⏳ Run 'check_ready_to_commit'\n7. ⏳ Commit & push, then run 'perform_release'\n8. ⏳ Complete task\n\nReminder: Focus on writing clean, maintainable code!`,
|
|
739
|
-
},
|
|
740
|
-
],
|
|
741
|
-
};
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
case "mark_bug_fixed": {
|
|
745
|
-
if (workflowState.state.currentPhase === "idle") {
|
|
746
|
-
return {
|
|
747
|
-
content: [
|
|
748
|
-
{
|
|
749
|
-
type: "text",
|
|
750
|
-
text: "⚠️ Please start a task first using 'start_task'!",
|
|
751
|
-
},
|
|
752
|
-
],
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
workflowState.state.bugFixed = true;
|
|
757
|
-
workflowState.state.testsCreated = false;
|
|
758
|
-
workflowState.state.currentPhase = "testing";
|
|
759
|
-
workflowState.state.testsSkipped = false;
|
|
760
|
-
workflowState.state.testsSkippedReason = "";
|
|
761
|
-
workflowState.state.fixSummary = args.summary;
|
|
762
|
-
workflowState.state.readyCheckCompleted = false;
|
|
763
|
-
workflowState.state.released = false;
|
|
764
|
-
workflowState.state.releaseCommand = "";
|
|
765
|
-
workflowState.state.releaseNotes = "";
|
|
766
|
-
workflowState.state.commitAndPushCompleted = false;
|
|
767
|
-
workflowState.state.lastCommitMessage = "";
|
|
768
|
-
workflowState.state.lastPushBranch = "";
|
|
769
|
-
await workflowState.save();
|
|
770
|
-
|
|
771
|
-
return {
|
|
772
|
-
content: [
|
|
773
|
-
{
|
|
774
|
-
type: "text",
|
|
775
|
-
text: `✅ Feature/Bug marked as fixed!\n\n⚠️ CRITICAL REMINDER: You MUST create tests now!\n\nNext Steps:\n1. ✓ Fix/implement feature\n2. ⏳ Create tests for: ${args.summary}\n3. ⏳ Run tests (must be green!)\n4. ⏳ Create documentation\n5. ⏳ Run 'check_ready_to_commit'\n6. ⏳ Commit & push, then run 'perform_release'\n7. ⏳ Complete task\n\n🚫 DO NOT SKIP TESTING!`,
|
|
776
|
-
},
|
|
777
|
-
],
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
case "create_tests": {
|
|
782
|
-
if (!workflowState.state.bugFixed) {
|
|
783
|
-
return {
|
|
784
|
-
content: [
|
|
785
|
-
{
|
|
786
|
-
type: "text",
|
|
787
|
-
text: "⚠️ Please mark your feature/bug as fixed first using 'mark_bug_fixed'!",
|
|
788
|
-
},
|
|
789
|
-
],
|
|
790
|
-
};
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
workflowState.state.testsCreated = true;
|
|
794
|
-
workflowState.state.testsSkipped = false;
|
|
795
|
-
workflowState.state.testsSkippedReason = "";
|
|
796
|
-
workflowState.state.readyCheckCompleted = false;
|
|
797
|
-
workflowState.state.released = false;
|
|
798
|
-
workflowState.state.commitAndPushCompleted = false;
|
|
799
|
-
workflowState.state.lastCommitMessage = "";
|
|
800
|
-
workflowState.state.lastPushBranch = "";
|
|
801
|
-
await workflowState.save();
|
|
802
|
-
|
|
803
|
-
return {
|
|
804
|
-
content: [
|
|
805
|
-
{
|
|
806
|
-
type: "text",
|
|
807
|
-
text: "✅ Tests recorded!\n\nNext Steps:\n1. ✓ Fix/implement feature\n2. ✓ Create tests\n3. ⏳ Run tests (must be green!)\n4. ⏳ Create documentation\n5. ⏳ Run 'check_ready_to_commit'\n6. ⏳ Commit & push, then run 'perform_release'\n7. ⏳ Complete task\n\n🧪 Run your test command and record the results using 'run_tests'.",
|
|
808
|
-
},
|
|
809
|
-
],
|
|
810
|
-
};
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
case "skip_tests": {
|
|
814
|
-
if (workflowState.state.currentPhase === "idle") {
|
|
815
|
-
return {
|
|
816
|
-
content: [
|
|
817
|
-
{
|
|
818
|
-
type: "text",
|
|
819
|
-
text: "⚠️ No active task. Use 'start_task' before skipping tests.",
|
|
820
|
-
},
|
|
821
|
-
],
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
if (!workflowState.state.bugFixed) {
|
|
826
|
-
return {
|
|
827
|
-
content: [
|
|
828
|
-
{
|
|
829
|
-
type: "text",
|
|
830
|
-
text: "⚠️ Please mark your feature/bug as fixed first using 'mark_bug_fixed'!",
|
|
831
|
-
},
|
|
832
|
-
],
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
const reason =
|
|
837
|
-
typeof args.reason === "string" ? args.reason.trim() : "";
|
|
838
|
-
|
|
839
|
-
if (!reason) {
|
|
840
|
-
return {
|
|
841
|
-
content: [
|
|
842
|
-
{
|
|
843
|
-
type: "text",
|
|
844
|
-
text: "⚠️ Provide a non-empty justification when skipping tests.",
|
|
845
|
-
},
|
|
846
|
-
],
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
workflowState.state.testsCreated = true;
|
|
851
|
-
workflowState.state.testsPassed = true;
|
|
852
|
-
workflowState.state.testsSkipped = true;
|
|
853
|
-
workflowState.state.testsSkippedReason = reason;
|
|
854
|
-
workflowState.state.currentPhase = "documentation";
|
|
855
|
-
workflowState.state.readyToCommit = false;
|
|
856
|
-
workflowState.state.readyCheckCompleted = false;
|
|
857
|
-
workflowState.state.released = false;
|
|
858
|
-
workflowState.state.commitAndPushCompleted = false;
|
|
859
|
-
workflowState.state.lastCommitMessage = "";
|
|
860
|
-
workflowState.state.lastPushBranch = "";
|
|
861
|
-
await workflowState.save();
|
|
862
|
-
|
|
863
|
-
return {
|
|
864
|
-
content: [
|
|
865
|
-
{
|
|
866
|
-
type: "text",
|
|
867
|
-
text: `⚠️ Tests skipped for this task.
|
|
868
|
-
Reason: ${reason}
|
|
869
|
-
|
|
870
|
-
Manual verification is now required.
|
|
871
|
-
|
|
872
|
-
Next Steps:
|
|
873
|
-
1. ✓ Fix/implement feature
|
|
874
|
-
2. ⚠️ Tests skipped (ensure manual QA)
|
|
875
|
-
3. ⏳ Create/update documentation
|
|
876
|
-
4. ⏳ Run 'check_ready_to_commit'
|
|
877
|
-
5. ⏳ Commit & push
|
|
878
|
-
6. ⏳ Run 'perform_release'
|
|
879
|
-
7. ⏳ Complete task`,
|
|
880
|
-
},
|
|
881
|
-
],
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
case "run_tests": {
|
|
886
|
-
if (!workflowState.state.bugFixed) {
|
|
887
|
-
return {
|
|
888
|
-
content: [
|
|
889
|
-
{
|
|
890
|
-
type: "text",
|
|
891
|
-
text: "⚠️ Please mark your feature/bug as fixed first using 'mark_bug_fixed'!",
|
|
892
|
-
},
|
|
893
|
-
],
|
|
894
|
-
};
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
if (!workflowState.state.testsCreated) {
|
|
898
|
-
return {
|
|
899
|
-
content: [
|
|
900
|
-
{
|
|
901
|
-
type: "text",
|
|
902
|
-
text: "⚠️ Please create tests first using 'create_tests' before recording test results!",
|
|
903
|
-
},
|
|
904
|
-
],
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
if (
|
|
909
|
-
typeof args.passed !== "boolean" ||
|
|
910
|
-
typeof args.testCommand !== "string" ||
|
|
911
|
-
args.testCommand.trim() === ""
|
|
912
|
-
) {
|
|
913
|
-
return {
|
|
914
|
-
content: [
|
|
915
|
-
{
|
|
916
|
-
type: "text",
|
|
917
|
-
text: "⚠️ Please provide both 'passed' (boolean) and 'testCommand' (non-empty string) when recording test results.",
|
|
918
|
-
},
|
|
919
|
-
],
|
|
920
|
-
};
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
workflowState.state.testsPassed = args.passed;
|
|
924
|
-
workflowState.state.testCommand = args.testCommand;
|
|
925
|
-
workflowState.state.testDetails =
|
|
926
|
-
typeof args.details === "string" ? args.details : "";
|
|
927
|
-
workflowState.state.testsSkipped = false;
|
|
928
|
-
workflowState.state.testsSkippedReason = "";
|
|
929
|
-
workflowState.state.readyCheckCompleted = false;
|
|
930
|
-
workflowState.state.released = false;
|
|
931
|
-
workflowState.state.commitAndPushCompleted = false;
|
|
932
|
-
workflowState.state.lastCommitMessage = "";
|
|
933
|
-
workflowState.state.lastPushBranch = "";
|
|
934
|
-
|
|
935
|
-
if (!args.passed) {
|
|
936
|
-
workflowState.state.currentPhase = "testing";
|
|
937
|
-
await workflowState.save();
|
|
938
|
-
|
|
939
|
-
return {
|
|
940
|
-
content: [
|
|
941
|
-
{
|
|
942
|
-
type: "text",
|
|
943
|
-
text: `❌ TESTS FAILED!\n\n🚫 STOP! DO NOT COMMIT OR PUSH!\n\nYou must:\n1. Fix the failing tests\n2. Run tests again until they pass\n\nTest command: ${args.testCommand}\n\nNever skip or ignore failing tests!`,
|
|
944
|
-
},
|
|
945
|
-
],
|
|
946
|
-
};
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
workflowState.state.currentPhase = "documentation";
|
|
950
|
-
await workflowState.save();
|
|
951
|
-
|
|
952
|
-
return {
|
|
953
|
-
content: [
|
|
954
|
-
{
|
|
955
|
-
type: "text",
|
|
956
|
-
text: `✅ All tests passed! 🎉\n\nTest command: ${args.testCommand}\n\nNext Steps:\n1. ✓ Fix/implement feature\n2. ✓ Create tests\n3. ✓ Run tests (GREEN!)\n4. ⏳ Create/update documentation\n5. ⏳ Run 'check_ready_to_commit'\n6. ⏳ Commit & push, then run 'perform_release'\n7. ⏳ Complete task\n\nReminder: Document what you did before committing!`,
|
|
957
|
-
},
|
|
958
|
-
],
|
|
959
|
-
};
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
case "create_documentation": {
|
|
963
|
-
if (!workflowState.state.testsPassed) {
|
|
964
|
-
return {
|
|
965
|
-
content: [
|
|
966
|
-
{
|
|
967
|
-
type: "text",
|
|
968
|
-
text: "⚠️ Please ensure tests are passing first! Run 'run_tests' with passed=true.",
|
|
969
|
-
},
|
|
970
|
-
],
|
|
971
|
-
};
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
workflowState.state.documentationCreated = true;
|
|
975
|
-
workflowState.state.documentationType = args.documentationType;
|
|
976
|
-
workflowState.state.documentationSummary = args.summary;
|
|
977
|
-
workflowState.state.currentPhase = "ready";
|
|
978
|
-
workflowState.state.readyToCommit = true;
|
|
979
|
-
workflowState.state.readyCheckCompleted = false;
|
|
980
|
-
workflowState.state.released = false;
|
|
981
|
-
workflowState.state.commitAndPushCompleted = false;
|
|
982
|
-
workflowState.state.lastCommitMessage = "";
|
|
983
|
-
workflowState.state.lastPushBranch = "";
|
|
984
|
-
await workflowState.save();
|
|
985
|
-
|
|
986
|
-
return {
|
|
987
|
-
content: [
|
|
988
|
-
{
|
|
989
|
-
type: "text",
|
|
990
|
-
text: `✅ Documentation created!\n\nType: ${args.documentationType}\nSummary: ${args.summary}\n\n🎉 You're ready to verify your work!\n\nNext Steps:\n1. ✓ Fix/implement feature\n2. ✓ Create tests\n3. ✓ Run tests (GREEN!)\n4. ✓ Create documentation\n5. ⏳ Run 'check_ready_to_commit' to verify\n6. ⏳ Commit & push, then run 'perform_release'\n7. ⏳ Mark as complete with 'complete_task'\n\nRemember: git add . && git commit && git push`,
|
|
991
|
-
},
|
|
992
|
-
],
|
|
993
|
-
};
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
case "check_ready_to_commit": {
|
|
997
|
-
const status = workflowState.state;
|
|
998
|
-
const testsCreatedDone = status.testsSkipped || status.testsCreated;
|
|
999
|
-
const testsPassedDone = status.testsSkipped || status.testsPassed;
|
|
1000
|
-
const checks = [
|
|
1001
|
-
{ name: "Task started", done: status.currentPhase !== "idle" },
|
|
1002
|
-
{ name: "Feature/bug fixed", done: status.bugFixed },
|
|
1003
|
-
{
|
|
1004
|
-
name: status.testsSkipped
|
|
1005
|
-
? "Tests skipped (manual QA documented)"
|
|
1006
|
-
: "Tests created",
|
|
1007
|
-
done: testsCreatedDone,
|
|
1008
|
-
},
|
|
1009
|
-
{
|
|
1010
|
-
name: status.testsSkipped ? "Tests skipped acknowledged" : "Tests passed",
|
|
1011
|
-
done: testsPassedDone,
|
|
1012
|
-
},
|
|
1013
|
-
{ name: "Documentation created", done: status.documentationCreated },
|
|
1014
|
-
];
|
|
1015
|
-
|
|
1016
|
-
const allDone = checks.every((c) => c.done);
|
|
1017
|
-
const checkList = checks
|
|
1018
|
-
.map((c) => `${c.done ? "✅" : "❌"} ${c.name}`)
|
|
1019
|
-
.join("\n");
|
|
1020
|
-
|
|
1021
|
-
workflowState.state.readyCheckCompleted = allDone;
|
|
1022
|
-
if (allDone) {
|
|
1023
|
-
workflowState.state.currentPhase = workflowState.state.commitAndPushCompleted
|
|
1024
|
-
? workflowState.state.released
|
|
1025
|
-
? "ready_to_complete"
|
|
1026
|
-
: "release"
|
|
1027
|
-
: "commit";
|
|
1028
|
-
}
|
|
1029
|
-
await workflowState.save();
|
|
1030
|
-
|
|
1031
|
-
if (allDone) {
|
|
1032
|
-
return {
|
|
1033
|
-
content: [
|
|
1034
|
-
{
|
|
1035
|
-
type: "text",
|
|
1036
|
-
text: `🎉 ALL CHECKS PASSED!\n\n${checkList}\n\n✅ Next actions:\n1. Run 'commit_and_push' with your commit message\n2. Run 'perform_release' to record the release\n3. Finish with 'complete_task'\n\nTip: Provide the optional 'branch' argument to push to a non-default branch.\n\nTask: ${status.taskDescription}`,
|
|
1037
|
-
},
|
|
1038
|
-
],
|
|
1039
|
-
};
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
return {
|
|
1043
|
-
content: [
|
|
1044
|
-
{
|
|
1045
|
-
type: "text",
|
|
1046
|
-
text: `⚠️ NOT READY TO COMMIT!\n\n${checkList}\n\nPlease complete all steps before committing.`,
|
|
1047
|
-
},
|
|
1048
|
-
],
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
case "commit_and_push": {
|
|
1053
|
-
if (!workflowState.state.readyToCommit) {
|
|
1054
|
-
return {
|
|
1055
|
-
content: [
|
|
1056
|
-
{
|
|
1057
|
-
type: "text",
|
|
1058
|
-
text: "⚠️ Not ready yet! Run 'check_ready_to_commit' and complete all steps first.",
|
|
1059
|
-
},
|
|
1060
|
-
],
|
|
1061
|
-
};
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
if (!workflowState.state.readyCheckCompleted) {
|
|
1065
|
-
return {
|
|
1066
|
-
content: [
|
|
1067
|
-
{
|
|
1068
|
-
type: "text",
|
|
1069
|
-
text: "⚠️ Please run 'check_ready_to_commit' and resolve any outstanding items before committing!",
|
|
1070
|
-
},
|
|
1071
|
-
],
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
const providedCommitMessage =
|
|
1076
|
-
typeof args.commitMessage === "string" ? args.commitMessage.trim() : "";
|
|
1077
|
-
|
|
1078
|
-
if (!(await hasWorkingChanges())) {
|
|
1079
|
-
return {
|
|
1080
|
-
content: [
|
|
1081
|
-
{
|
|
1082
|
-
type: "text",
|
|
1083
|
-
text: "⚠️ No changes detected. Make sure you have modifications to commit before running 'commit_and_push'.",
|
|
1084
|
-
},
|
|
1085
|
-
],
|
|
1086
|
-
};
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
if (!(await hasTestChanges()) && !workflowState.state.testsSkipped) {
|
|
1090
|
-
return {
|
|
1091
|
-
content: [
|
|
1092
|
-
{
|
|
1093
|
-
type: "text",
|
|
1094
|
-
text: "⚠️ Please include test updates in your changes before running 'commit_and_push'. Ensure at least one test file is modified.",
|
|
1095
|
-
},
|
|
1096
|
-
],
|
|
1097
|
-
};
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
try {
|
|
1101
|
-
await exec("git add .");
|
|
1102
|
-
} catch (error) {
|
|
1103
|
-
return {
|
|
1104
|
-
content: [
|
|
1105
|
-
{
|
|
1106
|
-
type: "text",
|
|
1107
|
-
text: `❌ Failed to stage files:\n\n${error.stderr || error.stdout || error.message}`,
|
|
1108
|
-
},
|
|
1109
|
-
],
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
const stagedChanges = await getStagedChanges();
|
|
1114
|
-
const { summary: generatedSummary, body: commitBody } =
|
|
1115
|
-
createCommitMessageParts(stagedChanges, providedCommitMessage);
|
|
1116
|
-
|
|
1117
|
-
if (!generatedSummary) {
|
|
1118
|
-
return {
|
|
1119
|
-
content: [
|
|
1120
|
-
{
|
|
1121
|
-
type: "text",
|
|
1122
|
-
text: "⚠️ Unable to generate commit message. Please provide a 'commitMessage' manually.",
|
|
1123
|
-
},
|
|
1124
|
-
],
|
|
1125
|
-
};
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
try {
|
|
1129
|
-
if (commitBody) {
|
|
1130
|
-
await exec(
|
|
1131
|
-
`git commit -m ${shellEscape(generatedSummary)} -m ${shellEscape(commitBody)}`
|
|
1132
|
-
);
|
|
1133
|
-
} else {
|
|
1134
|
-
await exec(`git commit -m ${shellEscape(generatedSummary)}`);
|
|
1135
|
-
}
|
|
1136
|
-
} catch (error) {
|
|
1137
|
-
const output = error.stderr || error.stdout || error.message || "Unknown error";
|
|
1138
|
-
if (output.includes("nothing to commit")) {
|
|
1139
|
-
return {
|
|
1140
|
-
content: [
|
|
1141
|
-
{
|
|
1142
|
-
type: "text",
|
|
1143
|
-
text: "⚠️ Nothing to commit. Make additional changes before running 'commit_and_push'.",
|
|
1144
|
-
},
|
|
1145
|
-
],
|
|
1146
|
-
};
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
return {
|
|
1150
|
-
content: [
|
|
1151
|
-
{
|
|
1152
|
-
type: "text",
|
|
1153
|
-
text: `❌ git commit failed:\n\n${output}`,
|
|
1154
|
-
},
|
|
1155
|
-
],
|
|
1156
|
-
};
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
const requestedBranch =
|
|
1160
|
-
typeof args.branch === "string" ? args.branch.trim() : "";
|
|
1161
|
-
const currentBranch = await getCurrentBranch();
|
|
1162
|
-
const pushCommand = requestedBranch
|
|
1163
|
-
? `git push origin ${shellEscape(requestedBranch)}`
|
|
1164
|
-
: "git push";
|
|
1165
|
-
|
|
1166
|
-
try {
|
|
1167
|
-
await exec(pushCommand);
|
|
1168
|
-
} catch (error) {
|
|
1169
|
-
return {
|
|
1170
|
-
content: [
|
|
1171
|
-
{
|
|
1172
|
-
type: "text",
|
|
1173
|
-
text: `❌ git push failed:\n\n${error.stderr || error.stdout || error.message}`,
|
|
1174
|
-
},
|
|
1175
|
-
],
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
workflowState.state.commitAndPushCompleted = true;
|
|
1180
|
-
workflowState.state.lastCommitMessage = generatedSummary;
|
|
1181
|
-
workflowState.state.lastPushBranch =
|
|
1182
|
-
requestedBranch || currentBranch || "";
|
|
1183
|
-
workflowState.state.currentPhase = workflowState.state.released
|
|
1184
|
-
? "ready_to_complete"
|
|
1185
|
-
: "release";
|
|
1186
|
-
await workflowState.save();
|
|
1187
|
-
|
|
1188
|
-
return {
|
|
1189
|
-
content: [
|
|
1190
|
-
{
|
|
1191
|
-
type: "text",
|
|
1192
|
-
text: `✅ Commit & push completed!\n\nCommit message: ${generatedSummary}\nPushed to: ${
|
|
1193
|
-
requestedBranch || currentBranch || "(default upstream)"
|
|
1194
|
-
}\n\nNext: Run 'perform_release' to record the release.`,
|
|
1195
|
-
},
|
|
1196
|
-
],
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
case "perform_release": {
|
|
1201
|
-
if (workflowState.state.currentPhase === "idle") {
|
|
1202
|
-
return {
|
|
1203
|
-
content: [
|
|
1204
|
-
{
|
|
1205
|
-
type: "text",
|
|
1206
|
-
text: "⚠️ Please start a task first using 'start_task'!",
|
|
1207
|
-
},
|
|
1208
|
-
],
|
|
1209
|
-
};
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
if (!workflowState.state.commitAndPushCompleted) {
|
|
1213
|
-
return {
|
|
1214
|
-
content: [
|
|
1215
|
-
{
|
|
1216
|
-
type: "text",
|
|
1217
|
-
text: "⚠️ Please run 'commit_and_push' after the ready check before recording a release!",
|
|
1218
|
-
},
|
|
1219
|
-
],
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
if (!workflowState.state.readyCheckCompleted) {
|
|
1224
|
-
return {
|
|
1225
|
-
content: [
|
|
1226
|
-
{
|
|
1227
|
-
type: "text",
|
|
1228
|
-
text: "⚠️ Please run 'check_ready_to_commit' and ensure all checks pass before releasing!",
|
|
1229
|
-
},
|
|
1230
|
-
],
|
|
1231
|
-
};
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
const releaseCommand =
|
|
1235
|
-
typeof args.command === "string" ? args.command.trim() : "";
|
|
1236
|
-
|
|
1237
|
-
if (!releaseCommand) {
|
|
1238
|
-
return {
|
|
1239
|
-
content: [
|
|
1240
|
-
{
|
|
1241
|
-
type: "text",
|
|
1242
|
-
text: "⚠️ Please provide a non-empty 'command' when using 'perform_release'.",
|
|
1243
|
-
},
|
|
1244
|
-
],
|
|
1245
|
-
};
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
if (await hasWorkingChanges()) {
|
|
1249
|
-
return {
|
|
1250
|
-
content: [
|
|
1251
|
-
{
|
|
1252
|
-
type: "text",
|
|
1253
|
-
text: "⚠️ Working tree not clean! Please commit or stash all changes before running 'perform_release'.",
|
|
1254
|
-
},
|
|
1255
|
-
],
|
|
1256
|
-
};
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
const lastCommitMessage = await getLastCommitMessage();
|
|
1260
|
-
const releaseType = determineReleaseTypeFromCommit(
|
|
1261
|
-
lastCommitMessage || workflowState.state.lastCommitMessage
|
|
1262
|
-
);
|
|
1263
|
-
|
|
1264
|
-
const finalCommand = releaseCommand.includes("--release-as")
|
|
1265
|
-
? releaseCommand
|
|
1266
|
-
: `${releaseCommand} -- --release-as ${releaseType}`;
|
|
1267
|
-
|
|
1268
|
-
try {
|
|
1269
|
-
await exec(finalCommand);
|
|
1270
|
-
} catch (error) {
|
|
1271
|
-
return {
|
|
1272
|
-
content: [
|
|
1273
|
-
{
|
|
1274
|
-
type: "text",
|
|
1275
|
-
text: `❌ Release command failed:\n\n${error.stderr || error.stdout || error.message}`,
|
|
1276
|
-
},
|
|
1277
|
-
],
|
|
1278
|
-
};
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
workflowState.state.released = true;
|
|
1282
|
-
workflowState.state.releaseCommand = finalCommand;
|
|
1283
|
-
workflowState.state.releaseNotes =
|
|
1284
|
-
typeof args.notes === "string" ? args.notes : "";
|
|
1285
|
-
workflowState.state.currentPhase = "ready_to_complete";
|
|
1286
|
-
await workflowState.save();
|
|
1287
|
-
|
|
1288
|
-
const limit = args.limit || 10;
|
|
1289
|
-
const history = workflowState.state.history.slice(-limit).reverse();
|
|
1290
|
-
|
|
1291
|
-
if (history.length === 0) {
|
|
1292
|
-
return {
|
|
1293
|
-
content: [
|
|
1294
|
-
{
|
|
1295
|
-
type: "text",
|
|
1296
|
-
text: "📜 No workflow history yet.",
|
|
1297
|
-
},
|
|
1298
|
-
],
|
|
1299
|
-
};
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
const historyText = history
|
|
1303
|
-
.map(
|
|
1304
|
-
(entry, index) =>
|
|
1305
|
-
`${index + 1}. [${new Date(entry.timestamp).toLocaleString()}]\n Task: ${entry.taskDescription}\n Type: ${entry.taskType}\n Commit: ${entry.commitMessage}\n`
|
|
1306
|
-
)
|
|
1307
|
-
.join("\n");
|
|
1308
|
-
|
|
1309
|
-
return {
|
|
1310
|
-
content: [
|
|
1311
|
-
{
|
|
1312
|
-
type: "text",
|
|
1313
|
-
text: `📜 Workflow History (last ${history.length} tasks)\n\n${historyText}`,
|
|
1314
|
-
},
|
|
1315
|
-
],
|
|
1316
|
-
};
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
default:
|
|
1320
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
1321
|
-
}
|
|
1322
|
-
} catch (error) {
|
|
1323
|
-
console.error("Tool handler error:", error);
|
|
1324
|
-
return {
|
|
1325
|
-
content: [
|
|
1326
|
-
{
|
|
1327
|
-
type: "text",
|
|
1328
|
-
text: "⚠️ Internal server error while processing the tool request. Please try again.",
|
|
1329
|
-
},
|
|
1330
|
-
],
|
|
1331
|
-
};
|
|
1332
|
-
}
|
|
1333
|
-
});
|
|
60
|
+
server.setRequestHandler(CallToolRequestSchema, (request) =>
|
|
61
|
+
handleToolCall({
|
|
62
|
+
request,
|
|
63
|
+
normalizeRequestArgs,
|
|
64
|
+
workflowState,
|
|
65
|
+
exec,
|
|
66
|
+
git: {
|
|
67
|
+
hasWorkingChanges,
|
|
68
|
+
hasTestChanges,
|
|
69
|
+
hasStagedChanges,
|
|
70
|
+
getStagedChanges,
|
|
71
|
+
getCurrentBranch,
|
|
72
|
+
getLastCommitMessage,
|
|
73
|
+
workingTreeSummary,
|
|
74
|
+
},
|
|
75
|
+
utils: { shellEscape, determineReleaseTypeFromCommit, createCommitMessageParts },
|
|
76
|
+
})
|
|
77
|
+
);
|
|
1334
78
|
|
|
1335
79
|
// Helper function
|
|
1336
80
|
export function getNextStep(status) {
|
|
@@ -1395,7 +139,8 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
1395
139
|
|
|
1396
140
|
6. 🚀 COMMIT & PUSH
|
|
1397
141
|
- Use 'check_ready_to_commit' to verify
|
|
1398
|
-
-
|
|
142
|
+
- Use 'commit_and_push' (stages, commits, and pushes)
|
|
143
|
+
- Then use 'perform_release' to handle versioning and tags
|
|
1399
144
|
- Use 'complete_task' to finish
|
|
1400
145
|
|
|
1401
146
|
Remember: No shortcuts! Each step is important for code quality.`,
|