@programinglive/dev-workflow-mcp-server 1.0.4 → 1.0.8
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 +100 -77
- package/CODE_OF_CONDUCT.md +79 -79
- package/CONTRIBUTING.md +60 -60
- package/LICENSE +21 -21
- package/README.md +86 -69
- package/SECURITY.md +20 -20
- package/commitlint.config.js +3 -3
- package/index.js +656 -312
- package/package.json +91 -91
- package/tests/workflowState.test.js +231 -120
package/index.js
CHANGED
|
@@ -27,6 +27,8 @@ export class WorkflowState {
|
|
|
27
27
|
bugFixed: false,
|
|
28
28
|
testsCreated: false,
|
|
29
29
|
testsPassed: false,
|
|
30
|
+
testsSkipped: false,
|
|
31
|
+
testsSkippedReason: "",
|
|
30
32
|
documentationCreated: false,
|
|
31
33
|
readyToCommit: false,
|
|
32
34
|
readyCheckCompleted: false,
|
|
@@ -51,6 +53,12 @@ export class WorkflowState {
|
|
|
51
53
|
if (typeof this.state.testsCreated !== "boolean") {
|
|
52
54
|
this.state.testsCreated = false;
|
|
53
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
|
+
}
|
|
54
62
|
if (typeof this.state.readyCheckCompleted !== "boolean") {
|
|
55
63
|
this.state.readyCheckCompleted = false;
|
|
56
64
|
}
|
|
@@ -88,6 +96,8 @@ export class WorkflowState {
|
|
|
88
96
|
bugFixed: false,
|
|
89
97
|
testsCreated: false,
|
|
90
98
|
testsPassed: false,
|
|
99
|
+
testsSkipped: false,
|
|
100
|
+
testsSkippedReason: "",
|
|
91
101
|
documentationCreated: false,
|
|
92
102
|
readyToCommit: false,
|
|
93
103
|
readyCheckCompleted: false,
|
|
@@ -186,6 +196,279 @@ async function hasWorkingChanges() {
|
|
|
186
196
|
}
|
|
187
197
|
}
|
|
188
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
|
+
|
|
189
472
|
async function getCurrentBranch() {
|
|
190
473
|
try {
|
|
191
474
|
const { stdout } = await exec("git rev-parse --abbrev-ref HEAD");
|
|
@@ -256,6 +539,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
256
539
|
properties: {},
|
|
257
540
|
},
|
|
258
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
|
+
},
|
|
259
557
|
{
|
|
260
558
|
name: "run_tests",
|
|
261
559
|
description:
|
|
@@ -325,7 +623,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
325
623
|
"Optional branch name to push to (defaults to current branch)",
|
|
326
624
|
},
|
|
327
625
|
},
|
|
328
|
-
required: [
|
|
626
|
+
required: [],
|
|
329
627
|
},
|
|
330
628
|
},
|
|
331
629
|
{
|
|
@@ -362,6 +660,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
362
660
|
required: ["commitMessage"],
|
|
363
661
|
},
|
|
364
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
|
+
},
|
|
365
677
|
{
|
|
366
678
|
name: "get_workflow_status",
|
|
367
679
|
description: "Get current workflow status and what needs to be done next.",
|
|
@@ -412,6 +724,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
412
724
|
workflowState.state.currentPhase = "coding";
|
|
413
725
|
workflowState.state.taskDescription = args.description;
|
|
414
726
|
workflowState.state.taskType = args.type;
|
|
727
|
+
workflowState.state.testsSkipped = false;
|
|
728
|
+
workflowState.state.testsSkippedReason = "";
|
|
415
729
|
workflowState.state.commitAndPushCompleted = false;
|
|
416
730
|
workflowState.state.lastCommitMessage = "";
|
|
417
731
|
workflowState.state.lastPushBranch = "";
|
|
@@ -442,12 +756,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
442
756
|
workflowState.state.bugFixed = true;
|
|
443
757
|
workflowState.state.testsCreated = false;
|
|
444
758
|
workflowState.state.currentPhase = "testing";
|
|
759
|
+
workflowState.state.testsSkipped = false;
|
|
760
|
+
workflowState.state.testsSkippedReason = "";
|
|
445
761
|
workflowState.state.fixSummary = args.summary;
|
|
446
762
|
workflowState.state.readyCheckCompleted = false;
|
|
447
763
|
workflowState.state.released = false;
|
|
448
764
|
workflowState.state.releaseCommand = "";
|
|
449
765
|
workflowState.state.releaseNotes = "";
|
|
450
|
-
workflowState.state.readyToCommit = false;
|
|
451
766
|
workflowState.state.commitAndPushCompleted = false;
|
|
452
767
|
workflowState.state.lastCommitMessage = "";
|
|
453
768
|
workflowState.state.lastPushBranch = "";
|
|
@@ -476,6 +791,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
476
791
|
}
|
|
477
792
|
|
|
478
793
|
workflowState.state.testsCreated = true;
|
|
794
|
+
workflowState.state.testsSkipped = false;
|
|
795
|
+
workflowState.state.testsSkippedReason = "";
|
|
479
796
|
workflowState.state.readyCheckCompleted = false;
|
|
480
797
|
workflowState.state.released = false;
|
|
481
798
|
workflowState.state.commitAndPushCompleted = false;
|
|
@@ -493,6 +810,78 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
493
810
|
};
|
|
494
811
|
}
|
|
495
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
|
+
|
|
496
885
|
case "run_tests": {
|
|
497
886
|
if (!workflowState.state.bugFixed) {
|
|
498
887
|
return {
|
|
@@ -535,6 +924,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
535
924
|
workflowState.state.testCommand = args.testCommand;
|
|
536
925
|
workflowState.state.testDetails =
|
|
537
926
|
typeof args.details === "string" ? args.details : "";
|
|
927
|
+
workflowState.state.testsSkipped = false;
|
|
928
|
+
workflowState.state.testsSkippedReason = "";
|
|
538
929
|
workflowState.state.readyCheckCompleted = false;
|
|
539
930
|
workflowState.state.released = false;
|
|
540
931
|
workflowState.state.commitAndPushCompleted = false;
|
|
@@ -568,75 +959,86 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
568
959
|
};
|
|
569
960
|
}
|
|
570
961
|
|
|
571
|
-
|
|
572
|
-
|
|
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
|
+
|
|
573
986
|
return {
|
|
574
987
|
content: [
|
|
575
988
|
{
|
|
576
989
|
type: "text",
|
|
577
|
-
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`,
|
|
578
991
|
},
|
|
579
992
|
],
|
|
580
993
|
};
|
|
581
994
|
}
|
|
582
995
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
workflowState.state.commitAndPushCompleted = false;
|
|
591
|
-
workflowState.state.lastCommitMessage = "";
|
|
592
|
-
workflowState.state.lastPushBranch = "";
|
|
593
|
-
await workflowState.save();
|
|
594
|
-
|
|
595
|
-
return {
|
|
596
|
-
content: [
|
|
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 },
|
|
597
1003
|
{
|
|
598
|
-
|
|
599
|
-
|
|
1004
|
+
name: status.testsSkipped
|
|
1005
|
+
? "Tests skipped (manual QA documented)"
|
|
1006
|
+
: "Tests created",
|
|
1007
|
+
done: testsCreatedDone,
|
|
600
1008
|
},
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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();
|
|
619
1030
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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
|
+
}
|
|
629
1041
|
|
|
630
|
-
if (allDone) {
|
|
631
|
-
return {
|
|
632
|
-
content: [
|
|
633
|
-
{
|
|
634
|
-
type: "text",
|
|
635
|
-
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}`,
|
|
636
|
-
},
|
|
637
|
-
],
|
|
638
|
-
};
|
|
639
|
-
} else {
|
|
640
1042
|
return {
|
|
641
1043
|
content: [
|
|
642
1044
|
{
|
|
@@ -646,301 +1048,243 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
646
1048
|
],
|
|
647
1049
|
};
|
|
648
1050
|
}
|
|
649
|
-
}
|
|
650
1051
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
if (!workflowState.state.readyCheckCompleted) {
|
|
664
|
-
return {
|
|
665
|
-
content: [
|
|
666
|
-
{
|
|
667
|
-
type: "text",
|
|
668
|
-
text: "⚠️ Please run 'check_ready_to_commit' and resolve any outstanding items before committing!",
|
|
669
|
-
},
|
|
670
|
-
],
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
const commitMessage =
|
|
675
|
-
typeof args.commitMessage === "string" ? args.commitMessage.trim() : "";
|
|
676
|
-
|
|
677
|
-
if (!commitMessage) {
|
|
678
|
-
return {
|
|
679
|
-
content: [
|
|
680
|
-
{
|
|
681
|
-
type: "text",
|
|
682
|
-
text: "⚠️ Please provide a non-empty 'commitMessage' when using 'commit_and_push'.",
|
|
683
|
-
},
|
|
684
|
-
],
|
|
685
|
-
};
|
|
686
|
-
}
|
|
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
|
+
}
|
|
687
1063
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
+
}
|
|
698
1074
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
} catch (error) {
|
|
702
|
-
return {
|
|
703
|
-
content: [
|
|
704
|
-
{
|
|
705
|
-
type: "text",
|
|
706
|
-
text: `❌ Failed to stage files:\n\n${error.stderr || error.stdout || error.message}`,
|
|
707
|
-
},
|
|
708
|
-
],
|
|
709
|
-
};
|
|
710
|
-
}
|
|
1075
|
+
const providedCommitMessage =
|
|
1076
|
+
typeof args.commitMessage === "string" ? args.commitMessage.trim() : "";
|
|
711
1077
|
|
|
712
|
-
|
|
713
|
-
await exec(`git commit -m ${shellEscape(commitMessage)}`);
|
|
714
|
-
} catch (error) {
|
|
715
|
-
const output = error.stderr || error.stdout || error.message || "Unknown error";
|
|
716
|
-
if (output.includes("nothing to commit")) {
|
|
1078
|
+
if (!(await hasWorkingChanges())) {
|
|
717
1079
|
return {
|
|
718
1080
|
content: [
|
|
719
1081
|
{
|
|
720
1082
|
type: "text",
|
|
721
|
-
text: "⚠️
|
|
1083
|
+
text: "⚠️ No changes detected. Make sure you have modifications to commit before running 'commit_and_push'.",
|
|
722
1084
|
},
|
|
723
1085
|
],
|
|
724
1086
|
};
|
|
725
1087
|
}
|
|
726
1088
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
const requestedBranch =
|
|
738
|
-
typeof args.branch === "string" ? args.branch.trim() : "";
|
|
739
|
-
const currentBranch = await getCurrentBranch();
|
|
740
|
-
const pushBranch = requestedBranch || currentBranch;
|
|
741
|
-
const pushCommand = pushBranch
|
|
742
|
-
? `git push origin ${shellEscape(pushBranch)}`
|
|
743
|
-
: "git push";
|
|
744
|
-
|
|
745
|
-
try {
|
|
746
|
-
await exec(pushCommand);
|
|
747
|
-
} catch (error) {
|
|
748
|
-
return {
|
|
749
|
-
content: [
|
|
750
|
-
{
|
|
751
|
-
type: "text",
|
|
752
|
-
text: `❌ git push failed:\n\n${error.stderr || error.stdout || error.message}`,
|
|
753
|
-
},
|
|
754
|
-
],
|
|
755
|
-
};
|
|
756
|
-
}
|
|
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
|
+
}
|
|
757
1099
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
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
|
+
}
|
|
765
1112
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
type: "text",
|
|
770
|
-
text: `✅ Commit & push completed!\n\nCommit message: ${commitMessage}\nPushed to: ${pushBranch || currentBranch || "(default upstream)"}\n\nNext: Run 'perform_release' to record the release.`,
|
|
771
|
-
},
|
|
772
|
-
],
|
|
773
|
-
};
|
|
774
|
-
}
|
|
1113
|
+
const stagedChanges = await getStagedChanges();
|
|
1114
|
+
const { summary: generatedSummary, body: commitBody } =
|
|
1115
|
+
createCommitMessageParts(stagedChanges, providedCommitMessage);
|
|
775
1116
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
}
|
|
786
|
-
}
|
|
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
|
+
}
|
|
787
1127
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
}
|
|
797
|
-
|
|
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
|
+
}
|
|
798
1148
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
}
|
|
808
|
-
}
|
|
1149
|
+
return {
|
|
1150
|
+
content: [
|
|
1151
|
+
{
|
|
1152
|
+
type: "text",
|
|
1153
|
+
text: `❌ git commit failed:\n\n${output}`,
|
|
1154
|
+
},
|
|
1155
|
+
],
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
809
1158
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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";
|
|
816
1165
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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
|
+
}
|
|
826
1178
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
],
|
|
836
|
-
};
|
|
837
|
-
}
|
|
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();
|
|
838
1187
|
|
|
839
|
-
if (!workflowState.state.readyCheckCompleted) {
|
|
840
1188
|
return {
|
|
841
1189
|
content: [
|
|
842
1190
|
{
|
|
843
1191
|
type: "text",
|
|
844
|
-
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.`,
|
|
845
1195
|
},
|
|
846
1196
|
],
|
|
847
1197
|
};
|
|
848
1198
|
}
|
|
849
1199
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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
|
+
}
|
|
860
1211
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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
|
+
}
|
|
871
1222
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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
|
+
}
|
|
882
1233
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
taskType: workflowState.state.taskType,
|
|
886
|
-
fixSummary: workflowState.state.fixSummary,
|
|
887
|
-
testCommand: workflowState.state.testCommand,
|
|
888
|
-
documentationType: workflowState.state.documentationType,
|
|
889
|
-
commitMessage: args.commitMessage,
|
|
890
|
-
releaseCommand: workflowState.state.releaseCommand,
|
|
891
|
-
releaseNotes: workflowState.state.releaseNotes,
|
|
892
|
-
});
|
|
1234
|
+
const releaseCommand =
|
|
1235
|
+
typeof args.command === "string" ? args.command.trim() : "";
|
|
893
1236
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
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
|
+
}
|
|
898
1247
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
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
|
+
}
|
|
908
1258
|
|
|
909
|
-
|
|
910
|
-
|
|
1259
|
+
const lastCommitMessage = await getLastCommitMessage();
|
|
1260
|
+
const releaseType = determineReleaseTypeFromCommit(
|
|
1261
|
+
lastCommitMessage || workflowState.state.lastCommitMessage
|
|
1262
|
+
);
|
|
911
1263
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
{
|
|
916
|
-
type: "text",
|
|
917
|
-
text: "📋 No active task.\n\nUse 'start_task' to begin a new coding session.",
|
|
918
|
-
},
|
|
919
|
-
],
|
|
920
|
-
};
|
|
921
|
-
}
|
|
1264
|
+
const finalCommand = releaseCommand.includes("--release-as")
|
|
1265
|
+
? releaseCommand
|
|
1266
|
+
: `${releaseCommand} -- --release-as ${releaseType}`;
|
|
922
1267
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
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
|
+
}
|
|
932
1280
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
],
|
|
940
|
-
};
|
|
941
|
-
}
|
|
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();
|
|
942
1287
|
|
|
943
|
-
case "view_history": {
|
|
944
1288
|
const limit = args.limit || 10;
|
|
945
1289
|
const history = workflowState.state.history.slice(-limit).reverse();
|
|
946
1290
|
|