@programinglive/dev-workflow-mcp-server 1.0.2 โ†’ 1.0.4

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,30 @@
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.4](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.0.3...v1.0.4) (2025-10-20)
6
+
7
+
8
+ ### โœจ Features
9
+
10
+ * enforce release step after ready check ([0725496](https://github.com/programinglive/dev-workflow-mcp-server/commit/07254967becd515cb2a4261d62af01dfeb842175))
11
+
12
+
13
+ ### ๐Ÿ› Bug Fixes
14
+
15
+ * support windows-friendly commit quoting ([47881fd](https://github.com/programinglive/dev-workflow-mcp-server/commit/47881fd0db54da3fabe368b1a135902e47ef873e))
16
+
17
+ ### [1.0.3](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.0.2...v1.0.3) (2025-10-20)
18
+
19
+
20
+ ### ๐Ÿงน Chores
21
+
22
+ * add gitignore ([5f695fb](https://github.com/programinglive/dev-workflow-mcp-server/commit/5f695fbc71515c7f1ffbc44fd74b52314c966ee6))
23
+
24
+
25
+ ### ๐Ÿ“ Documentation
26
+
27
+ * clarify cross-platform installation ([52b5ee7](https://github.com/programinglive/dev-workflow-mcp-server/commit/52b5ee7061e8f968ff94e6c226586fc1b7e2a057))
28
+
5
29
  ### [1.0.2](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.0.1...v1.0.2) (2025-10-19)
6
30
 
7
31
 
package/README.md CHANGED
@@ -11,7 +11,8 @@ This MCP server guides you through a disciplined development workflow:
11
11
  3. **Create Tests** - Always test your changes
12
12
  4. **Run Tests** - Tests must pass (GREEN)
13
13
  5. **Document** - Update documentation
14
- 6. **Commit & Push** - Only when all steps are complete
14
+ 6. **Commit & Push** - Let the server drive the git commands once verification passes
15
+ 7. **Release** - Record the release details before closing out the task
15
16
 
16
17
  ## ๐Ÿš€ Installation
17
18
 
@@ -23,34 +24,94 @@ npm install
23
24
 
24
25
  ### 2. Configure in Windsurf/Claude Desktop
25
26
 
26
- Add to your MCP settings file:
27
+ Point your MCP client to the server entry point. Replace `<PROJECT_ROOT>` with the absolute path to this repository on your machine.
27
28
 
28
- **For Windsurf** (`~/Library/Application Support/Windsurf/config.json`):
29
+ #### macOS
30
+
31
+ - **Windsurf** (`~/Library/Application Support/Windsurf/config.json`):
32
+
33
+ ```json
34
+ {
35
+ "mcpServers": {
36
+ "dev-workflow": {
37
+ "command": "node",
38
+ "args": ["<PROJECT_ROOT>/index.js"]
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ - **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
45
+
46
+ ```json
47
+ {
48
+ "mcpServers": {
49
+ "dev-workflow": {
50
+ "command": "node",
51
+ "args": ["<PROJECT_ROOT>/index.js"]
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ #### Windows
58
+
59
+ - **Windsurf** (`%APPDATA%\Windsurf\config.json`):
60
+
61
+ ```json
62
+ {
63
+ "mcpServers": {
64
+ "dev-workflow": {
65
+ "command": "node",
66
+ "args": ["<PROJECT_ROOT>\\index.js"]
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ - **Claude Desktop** (`%APPDATA%\Claude\claude_desktop_config.json`):
73
+
74
+ ```json
75
+ {
76
+ "mcpServers": {
77
+ "dev-workflow": {
78
+ "command": "node",
79
+ "args": ["<PROJECT_ROOT>\\index.js"]
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ #### Linux
86
+
87
+ - **Windsurf** (`~/.config/windsurf/config.json`):
29
88
 
30
89
  ```json
31
90
  {
32
91
  "mcpServers": {
33
92
  "dev-workflow": {
34
93
  "command": "node",
35
- "args": ["/Users/mahardhika/code/project/mine/app/CascadeProjects/windsurf-project/index.js"]
94
+ "args": ["<PROJECT_ROOT>/index.js"]
36
95
  }
37
96
  }
38
97
  }
39
98
  ```
40
99
 
41
- **For Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
100
+ - **Claude Desktop** (`~/.config/claude/claude_desktop_config.json`):
42
101
 
43
102
  ```json
44
103
  {
45
104
  "mcpServers": {
46
105
  "dev-workflow": {
47
106
  "command": "node",
48
- "args": ["/Users/mahardhika/code/project/mine/app/CascadeProjects/windsurf-project/index.js"]
107
+ "args": ["<PROJECT_ROOT>/index.js"]
49
108
  }
50
109
  }
51
110
  }
52
111
  ```
53
112
 
113
+ > **Note:** On Windows paths in JSON require escaped backslashes (e.g., `"C:\\path\\to\\project"`).
114
+
54
115
  ### 3. Restart Windsurf/Claude Desktop
55
116
 
56
117
  After adding the configuration, restart the application to load the MCP server.
@@ -92,6 +153,11 @@ Mark that the bug/feature is fixed. **Reminder: Now you MUST create tests!**
92
153
  **Parameters:**
93
154
  - `summary` (string, required): Brief summary of what was fixed/implemented
94
155
 
156
+ ### `create_tests`
157
+ Confirm that you've created the necessary tests covering your change. Required before recording test results.
158
+
159
+ **Parameters:** _none_
160
+
95
161
  ### `run_tests`
96
162
  Record test results. **NEVER commit if tests fail!** Only proceed if all tests are green.
97
163
 
@@ -118,6 +184,20 @@ Mark that documentation has been created/updated. This is required before commit
118
184
  ### `check_ready_to_commit`
119
185
  Check if all workflow steps are completed and you're ready to commit & push.
120
186
 
187
+ ### `commit_and_push`
188
+ Automatically run `git add`, `git commit`, and `git push` after the ready check passes.
189
+
190
+ **Parameters:**
191
+ - `commitMessage` (string, required): Conventional commit message to use
192
+ - `branch` (string, optional): Target branch to push (defaults to current branch)
193
+
194
+ ### `perform_release`
195
+ Record the release after you've committed and pushed. Required before you can complete the task.
196
+
197
+ **Parameters:**
198
+ - `command` (string, required): Release command that was executed (e.g., `npm run release`)
199
+ - `notes` (string, optional): Additional release notes
200
+
121
201
  ### `complete_task`
122
202
  Mark the task as complete after successful commit & push. Resets workflow for next task.
123
203
 
@@ -180,13 +260,16 @@ Here's how you'd use this MCP server in a typical coding session:
180
260
  ```
181
261
 
182
262
  8. **Commit & Push:**
183
- ```bash
184
- git add .
185
- git commit -m "feat: add user profile page with tests and docs"
186
- git push
263
+ ```
264
+ "Commit and push: commitMessage='feat: add user profile page with tests and docs'"
265
+ ```
266
+
267
+ 9. **Record release:**
268
+ ```
269
+ "Record release: command='npm run release', notes='v1.2.3'"
187
270
  ```
188
271
 
189
- 9. **Complete:**
272
+ 10. **Complete:**
190
273
  ```
191
274
  "Complete the task with commit message: 'feat: add user profile page'"
192
275
  ```
package/index.js CHANGED
@@ -25,9 +25,17 @@ export class WorkflowState {
25
25
  currentPhase: "idle",
26
26
  taskDescription: "",
27
27
  bugFixed: false,
28
+ testsCreated: false,
28
29
  testsPassed: false,
29
30
  documentationCreated: false,
30
31
  readyToCommit: false,
32
+ readyCheckCompleted: false,
33
+ released: false,
34
+ releaseCommand: "",
35
+ releaseNotes: "",
36
+ commitAndPushCompleted: false,
37
+ lastCommitMessage: "",
38
+ lastPushBranch: "",
31
39
  history: [],
32
40
  };
33
41
  }
@@ -35,7 +43,35 @@ export class WorkflowState {
35
43
  async load() {
36
44
  try {
37
45
  const data = await fs.readFile(this.stateFile, "utf-8");
38
- this.state = JSON.parse(data);
46
+ const parsed = JSON.parse(data);
47
+ this.state = {
48
+ ...this.state,
49
+ ...parsed,
50
+ };
51
+ if (typeof this.state.testsCreated !== "boolean") {
52
+ this.state.testsCreated = false;
53
+ }
54
+ if (typeof this.state.readyCheckCompleted !== "boolean") {
55
+ this.state.readyCheckCompleted = false;
56
+ }
57
+ if (typeof this.state.released !== "boolean") {
58
+ this.state.released = false;
59
+ }
60
+ if (typeof this.state.commitAndPushCompleted !== "boolean") {
61
+ this.state.commitAndPushCompleted = false;
62
+ }
63
+ if (typeof this.state.releaseCommand !== "string") {
64
+ this.state.releaseCommand = "";
65
+ }
66
+ if (typeof this.state.releaseNotes !== "string") {
67
+ this.state.releaseNotes = "";
68
+ }
69
+ if (typeof this.state.lastCommitMessage !== "string") {
70
+ this.state.lastCommitMessage = "";
71
+ }
72
+ if (typeof this.state.lastPushBranch !== "string") {
73
+ this.state.lastPushBranch = "";
74
+ }
39
75
  } catch (error) {
40
76
  // File doesn't exist yet, use default state
41
77
  }
@@ -50,9 +86,17 @@ export class WorkflowState {
50
86
  currentPhase: "idle",
51
87
  taskDescription: "",
52
88
  bugFixed: false,
89
+ testsCreated: false,
53
90
  testsPassed: false,
54
91
  documentationCreated: false,
55
92
  readyToCommit: false,
93
+ readyCheckCompleted: false,
94
+ released: false,
95
+ releaseCommand: "",
96
+ releaseNotes: "",
97
+ commitAndPushCompleted: false,
98
+ lastCommitMessage: "",
99
+ lastPushBranch: "",
56
100
  history: this.state.history,
57
101
  };
58
102
  }
@@ -70,6 +114,18 @@ await workflowState.load();
70
114
 
71
115
  const exec = promisify(execCallback);
72
116
 
117
+ const isWindows = process.platform === "win32";
118
+
119
+ function shellEscape(value) {
120
+ const stringValue = String(value);
121
+ if (isWindows) {
122
+ // Use PowerShell-friendly single quotes, escape embedded single quotes by doubling
123
+ return `'${stringValue.replace(/'/g, "''")}'`;
124
+ }
125
+
126
+ return `'${stringValue.replace(/'/g, "'\\''")}'`;
127
+ }
128
+
73
129
  function normalizeRequestArgs(rawArgs) {
74
130
  if (rawArgs === undefined || rawArgs === null) {
75
131
  return { args: {}, error: null };
@@ -121,6 +177,24 @@ async function hasStagedChanges() {
121
177
  }
122
178
  }
123
179
 
180
+ async function hasWorkingChanges() {
181
+ try {
182
+ const { stdout } = await exec("git status --porcelain");
183
+ return stdout.split("\n").some((line) => line && line.trim().length > 0);
184
+ } catch (error) {
185
+ return false;
186
+ }
187
+ }
188
+
189
+ async function getCurrentBranch() {
190
+ try {
191
+ const { stdout } = await exec("git rev-parse --abbrev-ref HEAD");
192
+ return stdout.trim();
193
+ } catch (error) {
194
+ return "";
195
+ }
196
+ }
197
+
124
198
  // Create MCP server
125
199
  export const server = new Server(
126
200
  {
@@ -174,6 +248,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
174
248
  required: ["summary"],
175
249
  },
176
250
  },
251
+ {
252
+ name: "create_tests",
253
+ description: "Mark that tests covering the change have been created.",
254
+ inputSchema: {
255
+ type: "object",
256
+ properties: {},
257
+ },
258
+ },
177
259
  {
178
260
  name: "run_tests",
179
261
  description:
@@ -226,6 +308,45 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
226
308
  properties: {},
227
309
  },
228
310
  },
311
+ {
312
+ name: "commit_and_push",
313
+ description:
314
+ "Automatically run git add, commit, and push once the ready check passes.",
315
+ inputSchema: {
316
+ type: "object",
317
+ properties: {
318
+ commitMessage: {
319
+ type: "string",
320
+ description: "Commit message to use",
321
+ },
322
+ branch: {
323
+ type: "string",
324
+ description:
325
+ "Optional branch name to push to (defaults to current branch)",
326
+ },
327
+ },
328
+ required: ["commitMessage"],
329
+ },
330
+ },
331
+ {
332
+ name: "perform_release",
333
+ description:
334
+ "Record release details after committing and pushing your changes.",
335
+ inputSchema: {
336
+ type: "object",
337
+ properties: {
338
+ command: {
339
+ type: "string",
340
+ description: "Release command that was executed",
341
+ },
342
+ notes: {
343
+ type: "string",
344
+ description: "Optional release notes",
345
+ },
346
+ },
347
+ required: ["command"],
348
+ },
349
+ },
229
350
  {
230
351
  name: "complete_task",
231
352
  description:
@@ -291,13 +412,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
291
412
  workflowState.state.currentPhase = "coding";
292
413
  workflowState.state.taskDescription = args.description;
293
414
  workflowState.state.taskType = args.type;
415
+ workflowState.state.commitAndPushCompleted = false;
416
+ workflowState.state.lastCommitMessage = "";
417
+ workflowState.state.lastPushBranch = "";
294
418
  await workflowState.save();
295
419
 
296
420
  return {
297
421
  content: [
298
422
  {
299
423
  type: "text",
300
- text: `โœ… Task Started: ${args.description}\n\n๐ŸŽฏ Be conscious about what you're coding!\n\nWorkflow Steps:\n1. โœ“ Start task (current)\n2. โณ Fix/implement the feature\n3. โณ Create tests\n4. โณ Run tests (must pass!)\n5. โณ Create documentation\n6. โณ Commit & push\n\nReminder: Focus on writing clean, maintainable code!`,
424
+ text: `โœ… Task Started: ${args.description}\n\n๐ŸŽฏ Be conscious about what you're coding!\n\nWorkflow Steps:\n1. โœ“ Start task (current)\n2. โณ Fix/implement the feature\n3. โณ Create tests\n4. โณ Run tests (must pass!)\n5. โณ Create documentation\n6. โณ Run 'check_ready_to_commit'\n7. โณ Commit & push, then run 'perform_release'\n8. โณ Complete task\n\nReminder: Focus on writing clean, maintainable code!`,
301
425
  },
302
426
  ],
303
427
  };
@@ -316,15 +440,54 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
316
440
  }
317
441
 
318
442
  workflowState.state.bugFixed = true;
443
+ workflowState.state.testsCreated = false;
319
444
  workflowState.state.currentPhase = "testing";
320
445
  workflowState.state.fixSummary = args.summary;
446
+ workflowState.state.readyCheckCompleted = false;
447
+ workflowState.state.released = false;
448
+ workflowState.state.releaseCommand = "";
449
+ workflowState.state.releaseNotes = "";
450
+ workflowState.state.readyToCommit = false;
451
+ workflowState.state.commitAndPushCompleted = false;
452
+ workflowState.state.lastCommitMessage = "";
453
+ workflowState.state.lastPushBranch = "";
454
+ await workflowState.save();
455
+
456
+ return {
457
+ content: [
458
+ {
459
+ type: "text",
460
+ text: `โœ… Feature/Bug marked as fixed!\n\nโš ๏ธ CRITICAL REMINDER: You MUST create tests now!\n\nNext Steps:\n1. โœ“ Fix/implement feature\n2. โณ Create tests for: ${args.summary}\n3. โณ Run tests (must be green!)\n4. โณ Create documentation\n5. โณ Run 'check_ready_to_commit'\n6. โณ Commit & push, then run 'perform_release'\n7. โณ Complete task\n\n๐Ÿšซ DO NOT SKIP TESTING!`,
461
+ },
462
+ ],
463
+ };
464
+ }
465
+
466
+ case "create_tests": {
467
+ if (!workflowState.state.bugFixed) {
468
+ return {
469
+ content: [
470
+ {
471
+ type: "text",
472
+ text: "โš ๏ธ Please mark your feature/bug as fixed first using 'mark_bug_fixed'!",
473
+ },
474
+ ],
475
+ };
476
+ }
477
+
478
+ workflowState.state.testsCreated = true;
479
+ workflowState.state.readyCheckCompleted = false;
480
+ workflowState.state.released = false;
481
+ workflowState.state.commitAndPushCompleted = false;
482
+ workflowState.state.lastCommitMessage = "";
483
+ workflowState.state.lastPushBranch = "";
321
484
  await workflowState.save();
322
485
 
323
486
  return {
324
487
  content: [
325
488
  {
326
489
  type: "text",
327
- text: `โœ… Feature/Bug marked as fixed!\n\nโš ๏ธ CRITICAL REMINDER: You MUST create tests now!\n\nNext Steps:\n1. โœ“ Fix/implement feature\n2. โณ Create tests for: ${args.summary}\n3. โณ Run tests (must be green!)\n4. โณ Create documentation\n5. โณ Commit & push\n\n๐Ÿšซ DO NOT SKIP TESTING!`,
490
+ text: "โœ… Tests recorded!\n\nNext Steps:\n1. โœ“ Fix/implement feature\n2. โœ“ Create tests\n3. โณ Run tests (must be green!)\n4. โณ Create documentation\n5. โณ Run 'check_ready_to_commit'\n6. โณ Commit & push, then run 'perform_release'\n7. โณ Complete task\n\n๐Ÿงช Run your test command and record the results using 'run_tests'.",
328
491
  },
329
492
  ],
330
493
  };
@@ -342,6 +505,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
342
505
  };
343
506
  }
344
507
 
508
+ if (!workflowState.state.testsCreated) {
509
+ return {
510
+ content: [
511
+ {
512
+ type: "text",
513
+ text: "โš ๏ธ Please create tests first using 'create_tests' before recording test results!",
514
+ },
515
+ ],
516
+ };
517
+ }
518
+
345
519
  if (
346
520
  typeof args.passed !== "boolean" ||
347
521
  typeof args.testCommand !== "string" ||
@@ -361,6 +535,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
361
535
  workflowState.state.testCommand = args.testCommand;
362
536
  workflowState.state.testDetails =
363
537
  typeof args.details === "string" ? args.details : "";
538
+ workflowState.state.readyCheckCompleted = false;
539
+ workflowState.state.released = false;
540
+ workflowState.state.commitAndPushCompleted = false;
541
+ workflowState.state.lastCommitMessage = "";
542
+ workflowState.state.lastPushBranch = "";
364
543
 
365
544
  if (!args.passed) {
366
545
  workflowState.state.currentPhase = "testing";
@@ -383,7 +562,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
383
562
  content: [
384
563
  {
385
564
  type: "text",
386
- text: `โœ… All tests passed! ๐ŸŽ‰\n\nTest command: ${args.testCommand}\n\nNext Steps:\n1. โœ“ Fix/implement feature\n2. โœ“ Create tests\n3. โœ“ Run tests (GREEN!)\n4. โณ Create/update documentation\n5. โณ Commit & push\n\nReminder: Document what you did before committing!`,
565
+ text: `โœ… All tests passed! ๐ŸŽ‰\n\nTest command: ${args.testCommand}\n\nNext Steps:\n1. โœ“ Fix/implement feature\n2. โœ“ Create tests\n3. โœ“ Run tests (GREEN!)\n4. โณ Create/update documentation\n5. โณ Run 'check_ready_to_commit'\n6. โณ Commit & push, then run 'perform_release'\n7. โณ Complete task\n\nReminder: Document what you did before committing!`,
387
566
  },
388
567
  ],
389
568
  };
@@ -406,13 +585,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
406
585
  workflowState.state.documentationSummary = args.summary;
407
586
  workflowState.state.currentPhase = "ready";
408
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 = "";
409
593
  await workflowState.save();
410
594
 
411
595
  return {
412
596
  content: [
413
597
  {
414
598
  type: "text",
415
- text: `โœ… Documentation created!\n\nType: ${args.documentationType}\nSummary: ${args.summary}\n\n๐ŸŽ‰ You're ready to commit!\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\n7. โณ Mark as complete with 'complete_task'\n\nRemember: git add . && git commit && git push`,
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`,
416
600
  },
417
601
  ],
418
602
  };
@@ -423,6 +607,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
423
607
  const checks = [
424
608
  { name: "Task started", done: status.currentPhase !== "idle" },
425
609
  { name: "Feature/bug fixed", done: status.bugFixed },
610
+ { name: "Tests created", done: status.testsCreated },
426
611
  { name: "Tests created and passed", done: status.testsPassed },
427
612
  { name: "Documentation created", done: status.documentationCreated },
428
613
  ];
@@ -432,12 +617,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
432
617
  .map((c) => `${c.done ? "โœ…" : "โŒ"} ${c.name}`)
433
618
  .join("\n");
434
619
 
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();
629
+
435
630
  if (allDone) {
436
631
  return {
437
632
  content: [
438
633
  {
439
634
  type: "text",
440
- text: `๐ŸŽ‰ ALL CHECKS PASSED! You're ready to commit!\n\n${checkList}\n\nโœ… You can now:\n1. git add .\n2. git commit -m "your message"\n3. git push\n4. Run 'complete_task' to finish\n\nTask: ${status.taskDescription}`,
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}`,
441
636
  },
442
637
  ],
443
638
  };
@@ -453,6 +648,182 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
453
648
  }
454
649
  }
455
650
 
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
+ }
687
+
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
+ }
698
+
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
+ }
711
+
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")) {
717
+ return {
718
+ content: [
719
+ {
720
+ type: "text",
721
+ text: "โš ๏ธ Nothing to commit. Make additional changes before running 'commit_and_push'.",
722
+ },
723
+ ],
724
+ };
725
+ }
726
+
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
+ }
757
+
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();
765
+
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
+ }
775
+
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
+ }
787
+
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
+ }
798
+
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
+ }
809
+
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();
816
+
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
+ }
826
+
456
827
  case "complete_task": {
457
828
  if (!workflowState.state.readyToCommit) {
458
829
  return {
@@ -465,6 +836,39 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
465
836
  };
466
837
  }
467
838
 
839
+ if (!workflowState.state.readyCheckCompleted) {
840
+ return {
841
+ content: [
842
+ {
843
+ type: "text",
844
+ text: "โš ๏ธ Please run 'check_ready_to_commit' and ensure all checks pass before completing the task!",
845
+ },
846
+ ],
847
+ };
848
+ }
849
+
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
+ }
860
+
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
+ }
871
+
468
872
  if (await hasStagedChanges()) {
469
873
  return {
470
874
  content: [
@@ -483,6 +887,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
483
887
  testCommand: workflowState.state.testCommand,
484
888
  documentationType: workflowState.state.documentationType,
485
889
  commitMessage: args.commitMessage,
890
+ releaseCommand: workflowState.state.releaseCommand,
891
+ releaseNotes: workflowState.state.releaseNotes,
486
892
  });
487
893
 
488
894
  const completedTask = workflowState.state.taskDescription;
@@ -518,14 +924,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
518
924
  coding: "๐Ÿ”จ Currently coding",
519
925
  testing: "๐Ÿงช Ready for testing",
520
926
  documentation: "๐Ÿ“ Ready for documentation",
521
- ready: "โœ… Ready to commit",
927
+ ready: "โœ… Ready for verification",
928
+ commit: "๐Ÿ“ฆ Ready to commit & push",
929
+ release: "๐Ÿš€ Ready to release",
930
+ ready_to_complete: "โœ… Release completed",
522
931
  };
523
932
 
524
933
  return {
525
934
  content: [
526
935
  {
527
936
  type: "text",
528
- text: `๐Ÿ“‹ Current Workflow Status\n\nTask: ${status.taskDescription}\nPhase: ${phaseMessages[status.currentPhase]}\n\nProgress:\n${status.bugFixed ? "โœ…" : "โณ"} Feature/bug fixed\n${status.testsPassed ? "โœ…" : "โณ"} Tests passed\n${status.documentationCreated ? "โœ…" : "โณ"} Documentation created\n${status.readyToCommit ? "โœ…" : "โณ"} Ready to commit\n\nNext: ${getNextStep(status)}`,
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)}`,
529
938
  },
530
939
  ],
531
940
  };
@@ -582,10 +991,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
582
991
  // Helper function
583
992
  export function getNextStep(status) {
584
993
  if (!status.bugFixed) return "Mark feature/bug as fixed";
585
- if (!status.testsPassed) return "Create and run tests";
994
+ if (!status.testsCreated) return "Create tests";
995
+ if (!status.testsPassed) return "Run tests";
586
996
  if (!status.documentationCreated) return "Create documentation";
587
- if (!status.readyToCommit) return "Check if ready to commit";
588
- return "Commit and push!";
997
+ if (!status.readyCheckCompleted) return "Run 'check_ready_to_commit'";
998
+ if (!status.commitAndPushCompleted) return "Run 'commit_and_push'";
999
+ if (!status.released) return "Run 'perform_release'";
1000
+ return "Complete the task";
589
1001
  }
590
1002
 
591
1003
  // Define prompts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@programinglive/dev-workflow-mcp-server",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "MCP server to enforce development workflow discipline",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -23,6 +23,14 @@ test("WorkflowState persists data across save/load", async () => {
23
23
  state.state.currentPhase = "testing";
24
24
  state.state.taskDescription = "Test persistence";
25
25
  state.state.bugFixed = true;
26
+ state.state.testsCreated = true;
27
+ state.state.readyCheckCompleted = true;
28
+ state.state.released = true;
29
+ state.state.releaseCommand = "npm run release";
30
+ state.state.releaseNotes = "Automated release";
31
+ state.state.commitAndPushCompleted = true;
32
+ state.state.lastCommitMessage = "test: persistence";
33
+ state.state.lastPushBranch = "main";
26
34
  await state.save();
27
35
 
28
36
  const reloaded = new WorkflowState(state.stateFile);
@@ -31,21 +39,37 @@ test("WorkflowState persists data across save/load", async () => {
31
39
  assert.equal(reloaded.state.currentPhase, "testing");
32
40
  assert.equal(reloaded.state.taskDescription, "Test persistence");
33
41
  assert.equal(reloaded.state.bugFixed, true);
42
+ assert.equal(reloaded.state.testsCreated, true);
43
+ assert.equal(reloaded.state.readyCheckCompleted, true);
44
+ assert.equal(reloaded.state.released, true);
45
+ assert.equal(reloaded.state.releaseCommand, "npm run release");
46
+ assert.equal(reloaded.state.releaseNotes, "Automated release");
47
+ assert.equal(reloaded.state.commitAndPushCompleted, true);
48
+ assert.equal(reloaded.state.lastCommitMessage, "test: persistence");
49
+ assert.equal(reloaded.state.lastPushBranch, "main");
34
50
  });
35
51
  });
36
52
 
37
53
  test("WorkflowState reset keeps history but clears progress", async () => {
38
54
  await withWorkflowState(async (state) => {
39
55
  state.state.currentPhase = "ready";
56
+ state.state.testsCreated = true;
40
57
  state.state.testsPassed = true;
41
58
  state.state.documentationCreated = true;
59
+ state.state.readyCheckCompleted = true;
60
+ state.state.released = true;
61
+ state.state.commitAndPushCompleted = true;
42
62
  state.addToHistory({ taskDescription: "Sample task" });
43
63
 
44
64
  state.reset();
45
65
 
46
66
  assert.equal(state.state.currentPhase, "idle");
67
+ assert.equal(state.state.testsCreated, false);
47
68
  assert.equal(state.state.testsPassed, false);
48
69
  assert.equal(state.state.documentationCreated, false);
70
+ assert.equal(state.state.readyCheckCompleted, false);
71
+ assert.equal(state.state.released, false);
72
+ assert.equal(state.state.commitAndPushCompleted, false);
49
73
  assert.equal(state.state.history.length, 1);
50
74
  assert.equal(state.state.history[0].taskDescription, "Sample task");
51
75
  });
@@ -54,9 +78,12 @@ test("WorkflowState reset keeps history but clears progress", async () => {
54
78
  test("getNextStep guides workflow progression", () => {
55
79
  const baseState = {
56
80
  bugFixed: false,
81
+ testsCreated: false,
57
82
  testsPassed: false,
58
83
  documentationCreated: false,
59
- readyToCommit: false,
84
+ readyCheckCompleted: false,
85
+ commitAndPushCompleted: false,
86
+ released: false,
60
87
  };
61
88
 
62
89
  assert.equal(
@@ -67,21 +94,27 @@ test("getNextStep guides workflow progression", () => {
67
94
  const afterFix = { ...baseState, bugFixed: true };
68
95
  assert.equal(
69
96
  getNextStep(afterFix),
70
- "Create and run tests"
97
+ "Create tests"
71
98
  );
72
99
 
73
- const afterTests = { ...afterFix, testsPassed: true };
100
+ const afterTestsCreated = { ...afterFix, testsCreated: true };
101
+ assert.equal(getNextStep(afterTestsCreated), "Run tests");
102
+
103
+ const afterTests = { ...afterTestsCreated, testsPassed: true };
74
104
  assert.equal(
75
105
  getNextStep(afterTests),
76
106
  "Create documentation"
77
107
  );
78
108
 
79
109
  const afterDocs = { ...afterTests, documentationCreated: true };
80
- assert.equal(
81
- getNextStep(afterDocs),
82
- "Check if ready to commit"
83
- );
110
+ assert.equal(getNextStep(afterDocs), "Run 'check_ready_to_commit'");
111
+
112
+ const afterReadyCheck = { ...afterDocs, readyCheckCompleted: true };
113
+ assert.equal(getNextStep(afterReadyCheck), "Run 'commit_and_push'");
114
+
115
+ const afterCommit = { ...afterReadyCheck, commitAndPushCompleted: true };
116
+ assert.equal(getNextStep(afterCommit), "Run 'perform_release'");
84
117
 
85
- const ready = { ...afterDocs, readyToCommit: true };
86
- assert.equal(getNextStep(ready), "Commit and push!");
118
+ const afterRelease = { ...afterCommit, released: true };
119
+ assert.equal(getNextStep(afterRelease), "Complete the task");
87
120
  });