@programinglive/dev-workflow-mcp-server 1.0.7 โ†’ 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [1.0.9](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.0.8...v1.0.9) (2025-10-23)
6
+
7
+
8
+ ### ๐Ÿ“ Documentation
9
+
10
+ * explain local workflow state ([cb6e333](https://github.com/programinglive/dev-workflow-mcp-server/commit/cb6e33353b4e1c0f693146f6cb07c9437a7f5aa8))
11
+
12
+ ### [1.0.8](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.0.7...v1.0.8) (2025-10-21)
13
+
14
+
15
+ ### โœจ Features
16
+
17
+ * allow skipping tests and dropping workflow tasks ([2b8b27c](https://github.com/programinglive/dev-workflow-mcp-server/commit/2b8b27cca611d2fe266c1b814c21b6fec2472856))
18
+
5
19
  ### [1.0.7](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.0.6...v1.0.7) (2025-10-20)
6
20
 
7
21
  ### [1.0.6](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.0.5...v1.0.6) (2025-10-20)
package/README.md CHANGED
@@ -158,6 +158,12 @@ Confirm that you've created the necessary tests covering your change. Required b
158
158
 
159
159
  **Parameters:** _none_
160
160
 
161
+ ### `skip_tests`
162
+ Record an explicit justification when automated tests aren't feasible. Marks testing as satisfied so you can proceed with documentation and verification, while flagging the task for manual QA.
163
+
164
+ **Parameters:**
165
+ - `reason` (string, required): Why automated tests were skipped
166
+
161
167
  ### `run_tests`
162
168
  Record test results. **NEVER commit if tests fail!** Only proceed if all tests are green.
163
169
 
@@ -204,6 +210,12 @@ Mark the task as complete after successful commit & push. Resets workflow for ne
204
210
  **Parameters:**
205
211
  - `commitMessage` (string, required): The commit message used
206
212
 
213
+ ### `drop_task`
214
+ Abandon the current task without completing the workflow. Preserves an audit entry with context, then resets the state so you can start fresh.
215
+
216
+ **Parameters:**
217
+ - `reason` (string, optional): Additional detail about why the task was dropped
218
+
207
219
  ### `get_workflow_status`
208
220
  Get current workflow status and what needs to be done next.
209
221
 
@@ -249,31 +261,36 @@ Here's how you'd use this MCP server in a typical coding session:
249
261
  ```
250
262
  - If tests fail, the server will **block** you from proceeding!
251
263
 
252
- 6. **Document:**
264
+ 7. **Document:**
253
265
  ```
254
266
  "Create documentation: type=README, summary='Added user profile section to docs'"
255
267
  ```
256
268
 
257
- 7. **Check readiness:**
269
+ 8. **Check readiness:**
258
270
  ```
259
271
  "Check if I'm ready to commit"
260
272
  ```
261
273
 
262
- 8. **Commit & Push:**
274
+ 9. **Commit & Push:**
263
275
  ```
264
276
  "Commit and push: commitMessage='feat: add user profile page with tests and docs'"
265
277
  ```
266
278
 
267
- 9. **Record release:**
279
+ 10. **Record release:**
268
280
  ```
269
281
  "Record release: command='npm run release', notes='v1.2.3'"
270
282
  ```
271
283
 
272
- 10. **Complete:**
284
+ 11. **Complete:**
273
285
  ```
274
286
  "Complete the task with commit message: 'feat: add user profile page'"
275
287
  ```
276
288
 
289
+ 12. **Drop task (optional):**
290
+ ```
291
+ "Drop task: reason='Switching to a different feature'"
292
+ ```
293
+
277
294
  ## ๐ŸŽฏ Key Features
278
295
 
279
296
  - **Enforces discipline**: Won't let you skip steps
@@ -294,7 +311,7 @@ Here's how you'd use this MCP server in a typical coding session:
294
311
  ## ๐Ÿ’ก Tips
295
312
 
296
313
  1. **Always start with `start_task`** - This sets your intention
297
- 2. **Never skip tests** - The server will remind you!
314
+ 2. **Never skip tests without justification** - Use `skip_tests` only when absolutely necessary and document the reason for manual QA
298
315
  3. **Use `get_workflow_status`** - Check where you are anytime
299
316
  4. **Review history** - Learn from your past tasks
300
317
  5. **Follow the prompts** - They contain best practices
@@ -316,6 +333,7 @@ The server maintains state in `.workflow-state.json`:
316
333
  - History of completed tasks
317
334
 
318
335
  This file is automatically created and managed by the server.
336
+ It contains local, machine-specific progress and is ignored by git so each environment can manage its own workflow history without cross-contamination.
319
337
 
320
338
  ## ๐Ÿค Integration with Your Rules
321
339
 
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,
@@ -529,6 +539,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
529
539
  properties: {},
530
540
  },
531
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
+ },
532
557
  {
533
558
  name: "run_tests",
534
559
  description:
@@ -635,6 +660,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
635
660
  required: ["commitMessage"],
636
661
  },
637
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
+ },
638
677
  {
639
678
  name: "get_workflow_status",
640
679
  description: "Get current workflow status and what needs to be done next.",
@@ -685,6 +724,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
685
724
  workflowState.state.currentPhase = "coding";
686
725
  workflowState.state.taskDescription = args.description;
687
726
  workflowState.state.taskType = args.type;
727
+ workflowState.state.testsSkipped = false;
728
+ workflowState.state.testsSkippedReason = "";
688
729
  workflowState.state.commitAndPushCompleted = false;
689
730
  workflowState.state.lastCommitMessage = "";
690
731
  workflowState.state.lastPushBranch = "";
@@ -715,12 +756,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
715
756
  workflowState.state.bugFixed = true;
716
757
  workflowState.state.testsCreated = false;
717
758
  workflowState.state.currentPhase = "testing";
759
+ workflowState.state.testsSkipped = false;
760
+ workflowState.state.testsSkippedReason = "";
718
761
  workflowState.state.fixSummary = args.summary;
719
762
  workflowState.state.readyCheckCompleted = false;
720
763
  workflowState.state.released = false;
721
764
  workflowState.state.releaseCommand = "";
722
765
  workflowState.state.releaseNotes = "";
723
- workflowState.state.readyToCommit = false;
724
766
  workflowState.state.commitAndPushCompleted = false;
725
767
  workflowState.state.lastCommitMessage = "";
726
768
  workflowState.state.lastPushBranch = "";
@@ -749,6 +791,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
749
791
  }
750
792
 
751
793
  workflowState.state.testsCreated = true;
794
+ workflowState.state.testsSkipped = false;
795
+ workflowState.state.testsSkippedReason = "";
752
796
  workflowState.state.readyCheckCompleted = false;
753
797
  workflowState.state.released = false;
754
798
  workflowState.state.commitAndPushCompleted = false;
@@ -766,6 +810,78 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
766
810
  };
767
811
  }
768
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
+
769
885
  case "run_tests": {
770
886
  if (!workflowState.state.bugFixed) {
771
887
  return {
@@ -808,6 +924,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
808
924
  workflowState.state.testCommand = args.testCommand;
809
925
  workflowState.state.testDetails =
810
926
  typeof args.details === "string" ? args.details : "";
927
+ workflowState.state.testsSkipped = false;
928
+ workflowState.state.testsSkippedReason = "";
811
929
  workflowState.state.readyCheckCompleted = false;
812
930
  workflowState.state.released = false;
813
931
  workflowState.state.commitAndPushCompleted = false;
@@ -841,75 +959,86 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
841
959
  };
842
960
  }
843
961
 
844
- case "create_documentation": {
845
- 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
+
846
986
  return {
847
987
  content: [
848
988
  {
849
989
  type: "text",
850
- 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`,
851
991
  },
852
992
  ],
853
993
  };
854
994
  }
855
995
 
856
- workflowState.state.documentationCreated = true;
857
- workflowState.state.documentationType = args.documentationType;
858
- workflowState.state.documentationSummary = args.summary;
859
- workflowState.state.currentPhase = "ready";
860
- workflowState.state.readyToCommit = true;
861
- workflowState.state.readyCheckCompleted = false;
862
- workflowState.state.released = false;
863
- workflowState.state.commitAndPushCompleted = false;
864
- workflowState.state.lastCommitMessage = "";
865
- workflowState.state.lastPushBranch = "";
866
- await workflowState.save();
867
-
868
- return {
869
- 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 },
870
1003
  {
871
- type: "text",
872
- 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,
873
1008
  },
874
- ],
875
- };
876
- }
877
-
878
- case "check_ready_to_commit": {
879
- const status = workflowState.state;
880
- const checks = [
881
- { name: "Task started", done: status.currentPhase !== "idle" },
882
- { name: "Feature/bug fixed", done: status.bugFixed },
883
- { name: "Tests created", done: status.testsCreated },
884
- { name: "Tests created and passed", done: status.testsPassed },
885
- { name: "Documentation created", done: status.documentationCreated },
886
- ];
887
-
888
- const allDone = checks.every((c) => c.done);
889
- const checkList = checks
890
- .map((c) => `${c.done ? "โœ…" : "โŒ"} ${c.name}`)
891
- .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();
892
1030
 
893
- workflowState.state.readyCheckCompleted = allDone;
894
- if (allDone) {
895
- workflowState.state.currentPhase = workflowState.state.commitAndPushCompleted
896
- ? workflowState.state.released
897
- ? "ready_to_complete"
898
- : "release"
899
- : "commit";
900
- }
901
- 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
+ }
902
1041
 
903
- if (allDone) {
904
- return {
905
- content: [
906
- {
907
- type: "text",
908
- 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}`,
909
- },
910
- ],
911
- };
912
- } else {
913
1042
  return {
914
1043
  content: [
915
1044
  {
@@ -919,371 +1048,243 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
919
1048
  ],
920
1049
  };
921
1050
  }
922
- }
923
-
924
- case "commit_and_push": {
925
- if (!workflowState.state.readyToCommit) {
926
- return {
927
- content: [
928
- {
929
- type: "text",
930
- text: "โš ๏ธ Not ready yet! Run 'check_ready_to_commit' and complete all steps first.",
931
- },
932
- ],
933
- };
934
- }
935
-
936
- if (!workflowState.state.readyCheckCompleted) {
937
- return {
938
- content: [
939
- {
940
- type: "text",
941
- text: "โš ๏ธ Please run 'check_ready_to_commit' and resolve any outstanding items before committing!",
942
- },
943
- ],
944
- };
945
- }
946
-
947
- const providedCommitMessage =
948
- typeof args.commitMessage === "string" ? args.commitMessage.trim() : "";
949
-
950
- if (!(await hasWorkingChanges())) {
951
- return {
952
- content: [
953
- {
954
- type: "text",
955
- text: "โš ๏ธ No changes detected. Make sure you have modifications to commit before running 'commit_and_push'.",
956
- },
957
- ],
958
- };
959
- }
960
1051
 
961
- if (!(await hasTestChanges())) {
962
- return {
963
- content: [
964
- {
965
- type: "text",
966
- text: "โš ๏ธ Please include test updates in your changes before running 'commit_and_push'. Ensure at least one test file is modified.",
967
- },
968
- ],
969
- };
970
- }
971
-
972
- try {
973
- await exec("git add .");
974
- } catch (error) {
975
- return {
976
- content: [
977
- {
978
- type: "text",
979
- text: `โŒ Failed to stage files:\n\n${error.stderr || error.stdout || error.message}`,
980
- },
981
- ],
982
- };
983
- }
984
-
985
- const stagedChanges = await getStagedChanges();
986
- const { summary: generatedSummary, body: commitBody } =
987
- createCommitMessageParts(stagedChanges, providedCommitMessage);
988
-
989
- if (!generatedSummary) {
990
- return {
991
- content: [
992
- {
993
- type: "text",
994
- text: "โš ๏ธ Unable to generate commit message. Please provide a 'commitMessage' manually.",
995
- },
996
- ],
997
- };
998
- }
999
-
1000
- try {
1001
- if (commitBody) {
1002
- await exec(
1003
- `git commit -m ${shellEscape(generatedSummary)} -m ${shellEscape(commitBody)}`
1004
- );
1005
- } else {
1006
- await exec(`git commit -m ${shellEscape(generatedSummary)}`);
1007
- }
1008
- } catch (error) {
1009
- const output = error.stderr || error.stdout || error.message || "Unknown error";
1010
- if (output.includes("nothing to commit")) {
1052
+ case "commit_and_push": {
1053
+ if (!workflowState.state.readyToCommit) {
1011
1054
  return {
1012
1055
  content: [
1013
1056
  {
1014
1057
  type: "text",
1015
- text: "โš ๏ธ Nothing to commit. Make additional changes before running 'commit_and_push'.",
1058
+ text: "โš ๏ธ Not ready yet! Run 'check_ready_to_commit' and complete all steps first.",
1016
1059
  },
1017
1060
  ],
1018
1061
  };
1019
1062
  }
1020
1063
 
1021
- return {
1022
- content: [
1023
- {
1024
- type: "text",
1025
- text: `โŒ git commit failed:\n\n${output}`,
1026
- },
1027
- ],
1028
- };
1029
- }
1030
-
1031
- const requestedBranch =
1032
- typeof args.branch === "string" ? args.branch.trim() : "";
1033
- const currentBranch = await getCurrentBranch();
1034
- const pushCommand = requestedBranch
1035
- ? `git push origin ${shellEscape(requestedBranch)}`
1036
- : "git push";
1037
-
1038
- try {
1039
- await exec(pushCommand);
1040
- } catch (error) {
1041
- return {
1042
- content: [
1043
- {
1044
- type: "text",
1045
- text: `โŒ git push failed:\n\n${error.stderr || error.stdout || error.message}`,
1046
- },
1047
- ],
1048
- };
1049
- }
1050
-
1051
- workflowState.state.commitAndPushCompleted = true;
1052
- workflowState.state.lastCommitMessage = generatedSummary;
1053
- workflowState.state.lastPushBranch =
1054
- requestedBranch || currentBranch || "";
1055
- workflowState.state.currentPhase = workflowState.state.released
1056
- ? "ready_to_complete"
1057
- : "release";
1058
- await workflowState.save();
1059
-
1060
- return {
1061
- content: [
1062
- {
1063
- type: "text",
1064
- text: `โœ… Commit & push completed!\n\nCommit message: ${generatedSummary}\nPushed to: ${
1065
- requestedBranch || currentBranch || "(default upstream)"
1066
- }\n\nNext: Run 'perform_release' to record the release.`,
1067
- },
1068
- ],
1069
- };
1070
- }
1071
-
1072
- case "perform_release": {
1073
- if (workflowState.state.currentPhase === "idle") {
1074
- return {
1075
- content: [
1076
- {
1077
- type: "text",
1078
- text: "โš ๏ธ Please start a task first using 'start_task'!",
1079
- },
1080
- ],
1081
- };
1082
- }
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
+ }
1083
1074
 
1084
- if (!workflowState.state.commitAndPushCompleted) {
1085
- return {
1086
- content: [
1087
- {
1088
- type: "text",
1089
- text: "โš ๏ธ Please run 'commit_and_push' after the ready check before recording a release!",
1090
- },
1091
- ],
1092
- };
1093
- }
1075
+ const providedCommitMessage =
1076
+ typeof args.commitMessage === "string" ? args.commitMessage.trim() : "";
1094
1077
 
1095
- if (!workflowState.state.readyCheckCompleted) {
1096
- return {
1097
- content: [
1098
- {
1099
- type: "text",
1100
- text: "โš ๏ธ Please run 'check_ready_to_commit' and ensure all checks pass before releasing!",
1101
- },
1102
- ],
1103
- };
1104
- }
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
+ }
1105
1088
 
1106
- const releaseCommand =
1107
- typeof args.command === "string" ? args.command.trim() : "";
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
+ }
1108
1099
 
1109
- if (!releaseCommand) {
1110
- return {
1111
- content: [
1112
- {
1113
- type: "text",
1114
- text: "โš ๏ธ Please provide a non-empty 'command' when using 'perform_release'.",
1115
- },
1116
- ],
1117
- };
1118
- }
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
+ }
1119
1112
 
1120
- if (await hasWorkingChanges()) {
1121
- return {
1122
- content: [
1123
- {
1124
- type: "text",
1125
- text: "โš ๏ธ Working tree not clean! Please commit or stash all changes before running 'perform_release'.",
1126
- },
1127
- ],
1128
- };
1129
- }
1113
+ const stagedChanges = await getStagedChanges();
1114
+ const { summary: generatedSummary, body: commitBody } =
1115
+ createCommitMessageParts(stagedChanges, providedCommitMessage);
1130
1116
 
1131
- const lastCommitMessage = await getLastCommitMessage();
1132
- const releaseType = determineReleaseTypeFromCommit(
1133
- lastCommitMessage || workflowState.state.lastCommitMessage
1134
- );
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
+ }
1135
1127
 
1136
- const finalCommand = releaseCommand.includes("--release-as")
1137
- ? releaseCommand
1138
- : `${releaseCommand} -- --release-as ${releaseType}`;
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
+ }
1139
1148
 
1140
- try {
1141
- await exec(finalCommand);
1142
- } catch (error) {
1143
- return {
1144
- content: [
1145
- {
1146
- type: "text",
1147
- text: `โŒ Release command failed:\n\n${error.stderr || error.stdout || error.message}`,
1148
- },
1149
- ],
1150
- };
1151
- }
1149
+ return {
1150
+ content: [
1151
+ {
1152
+ type: "text",
1153
+ text: `โŒ git commit failed:\n\n${output}`,
1154
+ },
1155
+ ],
1156
+ };
1157
+ }
1152
1158
 
1153
- workflowState.state.released = true;
1154
- workflowState.state.releaseCommand = finalCommand;
1155
- workflowState.state.releaseNotes =
1156
- typeof args.notes === "string" ? args.notes : "";
1157
- workflowState.state.currentPhase = "ready_to_complete";
1158
- 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";
1159
1165
 
1160
- return {
1161
- content: [
1162
- {
1163
- type: "text",
1164
- text: `๐Ÿš€ Release recorded!\n\nCommand: ${finalCommand}\nRelease type: ${releaseType}\n${workflowState.state.releaseNotes ? `Notes: ${workflowState.state.releaseNotes}\n` : ""}\nโœ… Next: Run 'complete_task' to wrap up.`,
1165
- },
1166
- ],
1167
- };
1168
- }
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
+ }
1169
1178
 
1170
- case "complete_task": {
1171
- if (!workflowState.state.readyToCommit) {
1172
- return {
1173
- content: [
1174
- {
1175
- type: "text",
1176
- text: "โš ๏ธ Not ready to complete! Run 'check_ready_to_commit' first.",
1177
- },
1178
- ],
1179
- };
1180
- }
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();
1181
1187
 
1182
- if (!workflowState.state.readyCheckCompleted) {
1183
1188
  return {
1184
1189
  content: [
1185
1190
  {
1186
1191
  type: "text",
1187
- 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.`,
1188
1195
  },
1189
1196
  ],
1190
1197
  };
1191
1198
  }
1192
1199
 
1193
- if (!workflowState.state.commitAndPushCompleted) {
1194
- return {
1195
- content: [
1196
- {
1197
- type: "text",
1198
- text: "โš ๏ธ Commit and push not completed! Run 'commit_and_push' first.",
1199
- },
1200
- ],
1201
- };
1202
- }
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
+ }
1203
1211
 
1204
- if (!workflowState.state.released) {
1205
- return {
1206
- content: [
1207
- {
1208
- type: "text",
1209
- text: "โš ๏ธ Release not recorded! Run 'perform_release' after committing and pushing.",
1210
- },
1211
- ],
1212
- };
1213
- }
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
+ }
1214
1222
 
1215
- if (await hasStagedChanges()) {
1216
- return {
1217
- content: [
1218
- {
1219
- type: "text",
1220
- text: "โš ๏ธ Staged files detected! Please commit or unstage all files before pushing. Run 'git status' to confirm a clean working tree, then try again.",
1221
- },
1222
- ],
1223
- };
1224
- }
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
+ }
1225
1233
 
1226
- workflowState.addToHistory({
1227
- taskDescription: workflowState.state.taskDescription,
1228
- taskType: workflowState.state.taskType,
1229
- fixSummary: workflowState.state.fixSummary,
1230
- testCommand: workflowState.state.testCommand,
1231
- documentationType: workflowState.state.documentationType,
1232
- commitMessage: args.commitMessage,
1233
- releaseCommand: workflowState.state.releaseCommand,
1234
- releaseNotes: workflowState.state.releaseNotes,
1235
- });
1234
+ const releaseCommand =
1235
+ typeof args.command === "string" ? args.command.trim() : "";
1236
1236
 
1237
- const completedTask = workflowState.state.taskDescription;
1238
- workflowState.reset();
1239
- workflowState.state.currentPhase = "idle";
1240
- 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
+ }
1241
1247
 
1242
- return {
1243
- content: [
1244
- {
1245
- type: "text",
1246
- 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.`,
1247
- },
1248
- ],
1249
- };
1250
- }
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
+ }
1251
1258
 
1252
- case "get_workflow_status": {
1253
- const status = workflowState.state;
1259
+ const lastCommitMessage = await getLastCommitMessage();
1260
+ const releaseType = determineReleaseTypeFromCommit(
1261
+ lastCommitMessage || workflowState.state.lastCommitMessage
1262
+ );
1254
1263
 
1255
- if (status.currentPhase === "idle") {
1256
- return {
1257
- content: [
1258
- {
1259
- type: "text",
1260
- text: "๐Ÿ“‹ No active task.\n\nUse 'start_task' to begin a new coding session.",
1261
- },
1262
- ],
1263
- };
1264
- }
1264
+ const finalCommand = releaseCommand.includes("--release-as")
1265
+ ? releaseCommand
1266
+ : `${releaseCommand} -- --release-as ${releaseType}`;
1265
1267
 
1266
- const phaseMessages = {
1267
- coding: "๐Ÿ”จ Currently coding",
1268
- testing: "๐Ÿงช Ready for testing",
1269
- documentation: "๐Ÿ“ Ready for documentation",
1270
- ready: "โœ… Ready for verification",
1271
- commit: "๐Ÿ“ฆ Ready to commit & push",
1272
- release: "๐Ÿš€ Ready to release",
1273
- ready_to_complete: "โœ… Release completed",
1274
- };
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
+ }
1275
1280
 
1276
- return {
1277
- content: [
1278
- {
1279
- type: "text",
1280
- 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)}`,
1281
- },
1282
- ],
1283
- };
1284
- }
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();
1285
1287
 
1286
- case "view_history": {
1287
1288
  const limit = args.limit || 10;
1288
1289
  const history = workflowState.state.history.slice(-limit).reverse();
1289
1290
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@programinglive/dev-workflow-mcp-server",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "MCP server to enforce development workflow discipline",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -38,6 +38,8 @@ test("WorkflowState persists data across save/load", async () => {
38
38
  state.state.commitAndPushCompleted = true;
39
39
  state.state.lastCommitMessage = "test: persistence";
40
40
  state.state.lastPushBranch = "main";
41
+ state.state.testsSkipped = true;
42
+ state.state.testsSkippedReason = "Legacy app uses manual QA";
41
43
  await state.save();
42
44
 
43
45
  const reloaded = new WorkflowState(state.stateFile);
@@ -54,6 +56,11 @@ test("WorkflowState persists data across save/load", async () => {
54
56
  assert.equal(reloaded.state.commitAndPushCompleted, true);
55
57
  assert.equal(reloaded.state.lastCommitMessage, "test: persistence");
56
58
  assert.equal(reloaded.state.lastPushBranch, "main");
59
+ assert.equal(reloaded.state.testsSkipped, true);
60
+ assert.equal(
61
+ reloaded.state.testsSkippedReason,
62
+ "Legacy app uses manual QA"
63
+ );
57
64
  });
58
65
  });
59
66
 
@@ -66,6 +73,8 @@ test("WorkflowState reset keeps history but clears progress", async () => {
66
73
  state.state.readyCheckCompleted = true;
67
74
  state.state.released = true;
68
75
  state.state.commitAndPushCompleted = true;
76
+ state.state.testsSkipped = true;
77
+ state.state.testsSkippedReason = "Manual QA";
69
78
  state.addToHistory({ taskDescription: "Sample task" });
70
79
 
71
80
  state.reset();
@@ -77,11 +86,29 @@ test("WorkflowState reset keeps history but clears progress", async () => {
77
86
  assert.equal(state.state.readyCheckCompleted, false);
78
87
  assert.equal(state.state.released, false);
79
88
  assert.equal(state.state.commitAndPushCompleted, false);
89
+ assert.equal(state.state.testsSkipped, false);
90
+ assert.equal(state.state.testsSkippedReason, "");
80
91
  assert.equal(state.state.history.length, 1);
81
92
  assert.equal(state.state.history[0].taskDescription, "Sample task");
82
93
  });
83
94
  });
84
95
 
96
+ test("getNextStep treats skipped tests as satisfied", () => {
97
+ const skippedState = {
98
+ bugFixed: true,
99
+ testsCreated: true,
100
+ testsPassed: true,
101
+ testsSkipped: true,
102
+ testsSkippedReason: "Legacy system",
103
+ documentationCreated: false,
104
+ readyCheckCompleted: false,
105
+ commitAndPushCompleted: false,
106
+ released: false,
107
+ };
108
+
109
+ assert.equal(getNextStep(skippedState), "Create documentation");
110
+ });
111
+
85
112
  test("getNextStep guides workflow progression", () => {
86
113
  const baseState = {
87
114
  bugFixed: false,