@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/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: ["commitMessage"],
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
- case "create_documentation": {
572
- if (!workflowState.state.testsPassed) {
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: "⚠️ Please ensure tests are passing first! Run 'run_tests' with passed=true.",
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
- workflowState.state.documentationCreated = true;
584
- workflowState.state.documentationType = args.documentationType;
585
- workflowState.state.documentationSummary = args.summary;
586
- workflowState.state.currentPhase = "ready";
587
- workflowState.state.readyToCommit = true;
588
- workflowState.state.readyCheckCompleted = false;
589
- workflowState.state.released = false;
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
- type: "text",
599
- 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`,
1004
+ name: status.testsSkipped
1005
+ ? "Tests skipped (manual QA documented)"
1006
+ : "Tests created",
1007
+ done: testsCreatedDone,
600
1008
  },
601
- ],
602
- };
603
- }
604
-
605
- case "check_ready_to_commit": {
606
- const status = workflowState.state;
607
- const checks = [
608
- { name: "Task started", done: status.currentPhase !== "idle" },
609
- { name: "Feature/bug fixed", done: status.bugFixed },
610
- { name: "Tests created", done: status.testsCreated },
611
- { name: "Tests created and passed", done: status.testsPassed },
612
- { name: "Documentation created", done: status.documentationCreated },
613
- ];
614
-
615
- const allDone = checks.every((c) => c.done);
616
- const checkList = checks
617
- .map((c) => `${c.done ? "✅" : "❌"} ${c.name}`)
618
- .join("\n");
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
- workflowState.state.readyCheckCompleted = allDone;
621
- if (allDone) {
622
- workflowState.state.currentPhase = workflowState.state.commitAndPushCompleted
623
- ? workflowState.state.released
624
- ? "ready_to_complete"
625
- : "release"
626
- : "commit";
627
- }
628
- await workflowState.save();
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
- case "commit_and_push": {
652
- if (!workflowState.state.readyToCommit) {
653
- return {
654
- content: [
655
- {
656
- type: "text",
657
- text: "⚠️ Not ready yet! Run 'check_ready_to_commit' and complete all steps first.",
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
- if (!(await hasWorkingChanges())) {
689
- return {
690
- content: [
691
- {
692
- type: "text",
693
- text: "⚠️ No changes detected. Make sure you have modifications to commit before running 'commit_and_push'.",
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
- try {
700
- await exec("git add .");
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
- try {
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: "⚠️ Nothing to commit. Make additional changes before running 'commit_and_push'.",
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
- return {
728
- content: [
729
- {
730
- type: "text",
731
- text: `❌ git commit failed:\n\n${output}`,
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
- workflowState.state.commitAndPushCompleted = true;
759
- workflowState.state.lastCommitMessage = commitMessage;
760
- workflowState.state.lastPushBranch = pushBranch || currentBranch || "";
761
- workflowState.state.currentPhase = workflowState.state.released
762
- ? "ready_to_complete"
763
- : "release";
764
- await workflowState.save();
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
- return {
767
- content: [
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
- case "perform_release": {
777
- if (workflowState.state.currentPhase === "idle") {
778
- return {
779
- content: [
780
- {
781
- type: "text",
782
- text: "⚠️ Please start a task first using 'start_task'!",
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
- if (!workflowState.state.commitAndPushCompleted) {
789
- return {
790
- content: [
791
- {
792
- type: "text",
793
- text: "⚠️ Please run 'commit_and_push' after the ready check before recording a release!",
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
- if (!workflowState.state.readyCheckCompleted) {
800
- return {
801
- content: [
802
- {
803
- type: "text",
804
- text: "⚠️ Please run 'check_ready_to_commit' and ensure all checks pass before releasing!",
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
- workflowState.state.released = true;
811
- workflowState.state.releaseCommand = args.command;
812
- workflowState.state.releaseNotes =
813
- typeof args.notes === "string" ? args.notes : "";
814
- workflowState.state.currentPhase = "ready_to_complete";
815
- await workflowState.save();
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
- return {
818
- content: [
819
- {
820
- type: "text",
821
- text: `🚀 Release recorded!\n\nCommand: ${args.command}\n${workflowState.state.releaseNotes ? `Notes: ${workflowState.state.releaseNotes}\n` : ""}\n✅ Next: Run 'complete_task' to wrap up.`,
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
- case "complete_task": {
828
- if (!workflowState.state.readyToCommit) {
829
- return {
830
- content: [
831
- {
832
- type: "text",
833
- text: "⚠️ Not ready to complete! Run 'check_ready_to_commit' first.",
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: "⚠️ Please run 'check_ready_to_commit' and ensure all checks pass before completing the task!",
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
- if (!workflowState.state.commitAndPushCompleted) {
851
- return {
852
- content: [
853
- {
854
- type: "text",
855
- text: "⚠️ Commit and push not completed! Run 'commit_and_push' first.",
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
- if (!workflowState.state.released) {
862
- return {
863
- content: [
864
- {
865
- type: "text",
866
- text: "⚠️ Release not recorded! Run 'perform_release' after committing and pushing.",
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
- if (await hasStagedChanges()) {
873
- return {
874
- content: [
875
- {
876
- type: "text",
877
- text: "⚠️ Staged files detected! Please commit or unstage all files before pushing. Run 'git status' to confirm a clean working tree, then try again.",
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
- workflowState.addToHistory({
884
- taskDescription: workflowState.state.taskDescription,
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
- const completedTask = workflowState.state.taskDescription;
895
- workflowState.reset();
896
- workflowState.state.currentPhase = "idle";
897
- await workflowState.save();
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
- return {
900
- content: [
901
- {
902
- type: "text",
903
- text: `🎉 Task completed successfully!\n\nCompleted: ${completedTask}\nCommit: ${args.commitMessage}\n\n✅ Workflow reset. Ready for next task!\n\nUse 'start_task' when you begin your next coding session.`,
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
- case "get_workflow_status": {
910
- const status = workflowState.state;
1259
+ const lastCommitMessage = await getLastCommitMessage();
1260
+ const releaseType = determineReleaseTypeFromCommit(
1261
+ lastCommitMessage || workflowState.state.lastCommitMessage
1262
+ );
911
1263
 
912
- if (status.currentPhase === "idle") {
913
- return {
914
- content: [
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
- const phaseMessages = {
924
- coding: "🔨 Currently coding",
925
- testing: "🧪 Ready for testing",
926
- documentation: "📝 Ready for documentation",
927
- ready: "✅ Ready for verification",
928
- commit: "📦 Ready to commit & push",
929
- release: "🚀 Ready to release",
930
- ready_to_complete: "✅ Release completed",
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
- return {
934
- content: [
935
- {
936
- type: "text",
937
- text: `📋 Current Workflow Status\n\nTask: ${status.taskDescription}\nPhase: ${phaseMessages[status.currentPhase]}\n\nProgress:\n${status.bugFixed ? "✅" : "⏳"} Feature/bug fixed\n${status.testsCreated ? "✅" : "⏳"} Tests created\n${status.testsPassed ? "✅" : "⏳"} Tests passed\n${status.documentationCreated ? "✅" : "⏳"} Documentation created\n${status.readyCheckCompleted ? "✅" : "⏳"} Ready check completed\n${status.commitAndPushCompleted ? "✅" : "⏳"} Commit & push completed\n${status.released ? "✅" : "⏳"} Release recorded\n\nNext: ${getNextStep(status)}`,
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