@programinglive/dev-workflow-mcp-server 1.0.11 → 1.1.0

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,25 @@
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.1.0](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.0.11...v1.1.0) (2025-10-25)
6
+
7
+
8
+ ### šŸ“ Documentation
9
+
10
+ * clarify commit and push step ([c741f61](https://github.com/programinglive/dev-workflow-mcp-server/commit/c741f618813479c5df2bcca94c3dffc8881e97c8))
11
+
12
+
13
+ ### šŸ› Bug Fixes
14
+
15
+ * allow commit_and_push on clean tree ([a831974](https://github.com/programinglive/dev-workflow-mcp-server/commit/a831974f66313ebe8752d416752076df63902bea))
16
+ * auto-detect commit in perform_release when tree is clean ([0db7038](https://github.com/programinglive/dev-workflow-mcp-server/commit/0db7038148f68963f53e52a5f73623dea7ad3462))
17
+
18
+
19
+ ### ✨ Features
20
+
21
+ * check staged changes when resetting workflow to commit phase ([05d02fd](https://github.com/programinglive/dev-workflow-mcp-server/commit/05d02fd7ec6177af3fb1fd86ec093e1e37b56cbd))
22
+ * continue_workflow auto-executes next step in commit phase ([a5b49ed](https://github.com/programinglive/dev-workflow-mcp-server/commit/a5b49edaae7cb51ea3aa49d9b1b64587843441ce))
23
+
5
24
  ### [1.0.11](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.0.10...v1.0.11) (2025-10-25)
6
25
 
7
26
 
package/README.md CHANGED
@@ -11,8 +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** - Let the server drive the git commands once verification passes
15
- 7. **Release** - Record the release details before closing out the task
14
+ 6. **Commit & Push** - Let the server stage, commit, **and push** your changes once verification passes (if you make new edits afterward, the workflow moves back to this step automatically)
15
+ 7. **Release** - After the push succeeds, record the release details before closing out the task
16
16
 
17
17
  ## šŸš€ Installation
18
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@programinglive/dev-workflow-mcp-server",
3
- "version": "1.0.11",
3
+ "version": "1.1.0",
4
4
  "description": "MCP server to enforce development workflow discipline",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -58,6 +58,7 @@ test("commit_and_push commits changes and pushes", async () => {
58
58
 
59
59
  const git = {
60
60
  hasWorkingChanges: async () => true,
61
+ hasStagedChanges: async () => false,
61
62
  hasTestChanges: async () => true,
62
63
  getStagedChanges: async () => [{ status: "M", path: "src/index.js" }],
63
64
  getCurrentBranch: async () => "main",
@@ -95,10 +96,56 @@ test("commit_and_push commits changes and pushes", async () => {
95
96
  });
96
97
  });
97
98
 
99
+ test("perform_release blocks when new changes detected", async () => {
100
+ await withWorkflowState(async (workflowState) => {
101
+ workflowState.state.currentPhase = "release";
102
+ workflowState.state.commitAndPushCompleted = true;
103
+ workflowState.state.readyCheckCompleted = true;
104
+ await workflowState.save();
105
+
106
+ const commands = [];
107
+ const execStub = async (command) => {
108
+ commands.push(command);
109
+ if (command === "git status --porcelain") {
110
+ return { stdout: "?? new-file.js" };
111
+ }
112
+
113
+ return { stdout: "" };
114
+ };
115
+
116
+ const git = {
117
+ hasWorkingChanges: async () => true,
118
+ hasStagedChanges: async () => false,
119
+ hasTestChanges: async () => true,
120
+ getStagedChanges: async () => [],
121
+ getCurrentBranch: async () => "main",
122
+ getLastCommitMessage: async () => "fix: adjust layout",
123
+ workingTreeSummary: () => ({ hasChanges: true, lines: ["?? new-file.js"] }),
124
+ };
125
+
126
+ const response = await handleToolCall({
127
+ request: createRequest("perform_release", { command: "patch" }),
128
+ normalizeRequestArgs,
129
+ workflowState,
130
+ exec: execStub,
131
+ git,
132
+ utils,
133
+ });
134
+
135
+ assert.ok(
136
+ response.content[0].text.includes("run 'commit_and_push' again"),
137
+ "response should instruct to recommit"
138
+ );
139
+ assert.equal(workflowState.state.currentPhase, "commit");
140
+ assert.equal(workflowState.state.commitAndPushCompleted, false);
141
+ });
142
+ });
143
+
98
144
  test("continue_workflow warns when workflow is idle", async () => {
99
145
  await withWorkflowState(async (workflowState) => {
100
146
  const git = {
101
147
  hasWorkingChanges: async () => false,
148
+ hasStagedChanges: async () => false,
102
149
  hasTestChanges: async () => false,
103
150
  getStagedChanges: async () => [],
104
151
  getCurrentBranch: async () => "main",
@@ -122,6 +169,87 @@ test("continue_workflow warns when workflow is idle", async () => {
122
169
  });
123
170
  });
124
171
 
172
+ test("continue_workflow resets to commit when new changes detected", async () => {
173
+ await withWorkflowState(async (workflowState) => {
174
+ workflowState.state.currentPhase = "release";
175
+ workflowState.state.commitAndPushCompleted = true;
176
+ workflowState.state.readyCheckCompleted = true;
177
+ await workflowState.save();
178
+
179
+ const git = {
180
+ hasWorkingChanges: async () => true,
181
+ hasStagedChanges: async () => false,
182
+ hasTestChanges: async () => true,
183
+ getStagedChanges: async () => [],
184
+ getCurrentBranch: async () => "main",
185
+ getLastCommitMessage: async () => "fix: adjust layout",
186
+ workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
187
+ };
188
+
189
+ const response = await handleToolCall({
190
+ request: createRequest("continue_workflow", {}),
191
+ normalizeRequestArgs,
192
+ workflowState,
193
+ exec: async () => ({ stdout: "" }),
194
+ git,
195
+ utils,
196
+ });
197
+
198
+ assert.ok(
199
+ response.content[0].text.includes("Workflow moved back to the commit phase"),
200
+ "response should warn that workflow reset to commit"
201
+ );
202
+ assert.equal(workflowState.state.currentPhase, "commit");
203
+ assert.equal(workflowState.state.commitAndPushCompleted, false);
204
+ });
205
+ });
206
+
207
+ test("commit_and_push recognizes already committed work", async () => {
208
+ await withWorkflowState(async (workflowState) => {
209
+ workflowState.state.readyToCommit = true;
210
+ workflowState.state.readyCheckCompleted = true;
211
+ workflowState.state.testsSkipped = false;
212
+ workflowState.state.testsCreated = true;
213
+ workflowState.state.testsPassed = true;
214
+ workflowState.state.currentPhase = "commit";
215
+ await workflowState.save();
216
+
217
+ const commands = [];
218
+ const execStub = async (command) => {
219
+ commands.push(command);
220
+ return { stdout: "" };
221
+ };
222
+
223
+ const git = {
224
+ hasWorkingChanges: async () => false,
225
+ hasStagedChanges: async () => false,
226
+ hasTestChanges: async () => true,
227
+ getStagedChanges: async () => [],
228
+ getCurrentBranch: async () => "main",
229
+ getLastCommitMessage: async () => "fix: previous work",
230
+ workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
231
+ };
232
+
233
+ const response = await handleToolCall({
234
+ request: createRequest("commit_and_push", { commitMessage: "" }),
235
+ normalizeRequestArgs,
236
+ workflowState,
237
+ exec: execStub,
238
+ git,
239
+ utils,
240
+ });
241
+
242
+ assert.equal(commands.length, 0, "no git commands should run when work is already committed");
243
+ assert.ok(
244
+ response.content[0].text.includes("No changes detected"),
245
+ "response should note that no changes were detected"
246
+ );
247
+ assert.equal(workflowState.state.commitAndPushCompleted, true);
248
+ assert.equal(workflowState.state.lastCommitMessage, "fix: previous work");
249
+ assert.equal(workflowState.state.currentPhase, "release");
250
+ });
251
+ });
252
+
125
253
  test("perform_release runs release command before pushing with tags", async () => {
126
254
  await withWorkflowState(async (workflowState) => {
127
255
  workflowState.state.currentPhase = "release";
@@ -143,6 +271,7 @@ test("perform_release runs release command before pushing with tags", async () =
143
271
 
144
272
  const git = {
145
273
  hasWorkingChanges: async () => false,
274
+ hasStagedChanges: async () => false,
146
275
  hasTestChanges: async () => true,
147
276
  getStagedChanges: async () => [],
148
277
  getCurrentBranch: async () => "main",
@@ -186,3 +315,146 @@ test("perform_release runs release command before pushing with tags", async () =
186
315
  );
187
316
  });
188
317
  });
318
+
319
+ test("perform_release accepts shorthand patch command", async () => {
320
+ await withWorkflowState(async (workflowState) => {
321
+ workflowState.state.currentPhase = "release";
322
+ workflowState.state.commitAndPushCompleted = true;
323
+ workflowState.state.readyCheckCompleted = true;
324
+ workflowState.state.lastCommitMessage = "fix: adjust labels";
325
+ await workflowState.save();
326
+
327
+ const commands = [];
328
+ const execStub = async (command) => {
329
+ commands.push(command);
330
+ if (command === "git status --porcelain") {
331
+ return { stdout: "" };
332
+ }
333
+
334
+ return { stdout: "" };
335
+ };
336
+
337
+ const git = {
338
+ hasWorkingChanges: async () => false,
339
+ hasStagedChanges: async () => false,
340
+ hasTestChanges: async () => true,
341
+ getStagedChanges: async () => [],
342
+ getCurrentBranch: async () => "main",
343
+ getLastCommitMessage: async () => "fix: adjust labels",
344
+ workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
345
+ };
346
+
347
+ await handleToolCall({
348
+ request: createRequest("perform_release", { command: "patch" }),
349
+ normalizeRequestArgs,
350
+ workflowState,
351
+ exec: execStub,
352
+ git,
353
+ utils,
354
+ });
355
+
356
+ const releaseCommand = commands.find((command) => command.startsWith("npm run release:patch"));
357
+ assert.ok(releaseCommand, "shorthand patch command should map to release:patch script");
358
+ const pushCommand = commands.find((command) => command.startsWith("git push --follow-tags"));
359
+ assert.ok(pushCommand, "git push with tags should run after shorthand release");
360
+ });
361
+ });
362
+
363
+ test("perform_release respects explicit releaseType option", async () => {
364
+ await withWorkflowState(async (workflowState) => {
365
+ workflowState.state.currentPhase = "release";
366
+ workflowState.state.commitAndPushCompleted = true;
367
+ workflowState.state.readyCheckCompleted = true;
368
+ await workflowState.save();
369
+
370
+ const commands = [];
371
+ const execStub = async (command) => {
372
+ commands.push(command);
373
+ if (command === "git status --porcelain") {
374
+ return { stdout: "" };
375
+ }
376
+
377
+ return { stdout: "" };
378
+ };
379
+
380
+ const git = {
381
+ hasWorkingChanges: async () => false,
382
+ hasStagedChanges: async () => false,
383
+ hasTestChanges: async () => true,
384
+ getStagedChanges: async () => [],
385
+ getCurrentBranch: async () => "main",
386
+ getLastCommitMessage: async () => "feat: import improvements",
387
+ workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
388
+ };
389
+
390
+ await handleToolCall({
391
+ request: createRequest("perform_release", {
392
+ command: "npm run release",
393
+ releaseType: "minor",
394
+ }),
395
+ normalizeRequestArgs,
396
+ workflowState,
397
+ exec: execStub,
398
+ git,
399
+ utils,
400
+ });
401
+
402
+ const releaseCommand = commands.find((command) => command.startsWith("npm run release"));
403
+ assert.ok(releaseCommand, "release command should run");
404
+ assert.ok(
405
+ releaseCommand.includes("-- --release-as minor"),
406
+ "release command should append provided releaseType"
407
+ );
408
+ });
409
+ });
410
+
411
+ test("perform_release auto-detects commit when tree is clean", async () => {
412
+ await withWorkflowState(async (workflowState) => {
413
+ workflowState.state.currentPhase = "release";
414
+ workflowState.state.commitAndPushCompleted = false;
415
+ workflowState.state.readyCheckCompleted = true;
416
+ workflowState.state.lastPushBranch = "";
417
+ workflowState.state.lastCommitMessage = "";
418
+ await workflowState.save();
419
+
420
+ const commands = [];
421
+ const execStub = async (command) => {
422
+ commands.push(command);
423
+ if (command === "git status --porcelain") {
424
+ return { stdout: "" };
425
+ }
426
+
427
+ return { stdout: "" };
428
+ };
429
+
430
+ const git = {
431
+ hasWorkingChanges: async () => false,
432
+ hasStagedChanges: async () => false,
433
+ hasTestChanges: async () => true,
434
+ getStagedChanges: async () => [],
435
+ getCurrentBranch: async () => "main",
436
+ getLastCommitMessage: async () => "chore: already pushed",
437
+ workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
438
+ };
439
+
440
+ const response = await handleToolCall({
441
+ request: createRequest("perform_release", { command: "npm run release" }),
442
+ normalizeRequestArgs,
443
+ workflowState,
444
+ exec: execStub,
445
+ git,
446
+ utils,
447
+ });
448
+
449
+ const pushCommand = commands.find((command) => command.startsWith("git push --follow-tags"));
450
+ assert.ok(pushCommand, "git push with tags should still run");
451
+ assert.equal(workflowState.state.commitAndPushCompleted, true);
452
+ assert.equal(workflowState.state.lastCommitMessage, "chore: already pushed");
453
+ assert.equal(workflowState.state.lastPushBranch, "main");
454
+ assert.equal(workflowState.state.currentPhase, "ready_to_complete");
455
+ assert.ok(
456
+ response.content[0].text.includes("Detected clean working tree"),
457
+ "response should mention auto-detected commit"
458
+ );
459
+ });
460
+ });
package/tools/handlers.js CHANGED
@@ -7,6 +7,27 @@ const textResponse = (text) => ({
7
7
  ],
8
8
  });
9
9
 
10
+ async function resetToCommitIfWorkingChanges(workflowState, git) {
11
+ if (!workflowState.state.commitAndPushCompleted) {
12
+ return false;
13
+ }
14
+
15
+ const hasWorkingChanges = await git.hasWorkingChanges();
16
+ const hasStagedChanges = await git.hasStagedChanges();
17
+
18
+ if (!hasWorkingChanges && !hasStagedChanges) {
19
+ return false;
20
+ }
21
+
22
+ workflowState.state.commitAndPushCompleted = false;
23
+ workflowState.state.released = false;
24
+ workflowState.state.currentPhase = "commit";
25
+ workflowState.state.releaseCommand = "";
26
+ workflowState.state.releaseNotes = "";
27
+ await workflowState.save();
28
+ return true;
29
+ }
30
+
10
31
  function formatChecklist(checks) {
11
32
  return checks.map((c) => `${c.done ? "āœ…" : "āŒ"} ${c.name}`).join("\n");
12
33
  }
@@ -68,17 +89,33 @@ function getContinueGuidance(status) {
68
89
  }
69
90
  }
70
91
 
71
- async function handleContinueWorkflow(workflowState) {
92
+ async function handleContinueWorkflow(workflowState, context) {
72
93
  const status = workflowState.state;
73
94
 
74
95
  if (!status || status.currentPhase === "idle") {
75
96
  return textResponse("āš ļø No active workflow. Use 'start_task' to kick off a new task before continuing.");
76
97
  }
77
98
 
99
+ if (await resetToCommitIfWorkingChanges(workflowState, context.git)) {
100
+ return textResponse(
101
+ "āš ļø Detected new changes after the last commit. Workflow moved back to the commit phase. Run 'commit_and_push' to capture the latest updates before continuing."
102
+ );
103
+ }
104
+
78
105
  if (status.currentPhase === "ready") {
79
106
  return handleReadyCheck(workflowState);
80
107
  }
81
108
 
109
+ if (status.currentPhase === "commit") {
110
+ return handleCommitAndPush({}, context);
111
+ }
112
+
113
+ if (status.currentPhase === "release") {
114
+ return textResponse(
115
+ "āš ļø Release phase requires explicit command. Run 'perform_release' with your release command (e.g., 'npm run release')."
116
+ );
117
+ }
118
+
82
119
  const sections = [
83
120
  `šŸ“Š Current Phase: ${status.currentPhase}`,
84
121
  `šŸ“ Task: ${status.taskDescription || "(none)"}`,
@@ -334,9 +371,26 @@ async function handleCommitAndPush(args, context) {
334
371
  const providedCommitMessage =
335
372
  typeof args.commitMessage === "string" ? args.commitMessage.trim() : "";
336
373
 
337
- if (!(await git.hasWorkingChanges())) {
374
+ const requestedBranch = typeof args.branch === "string" ? args.branch.trim() : "";
375
+ const currentBranch = await git.getCurrentBranch();
376
+ const branchForPush = requestedBranch || currentBranch || "";
377
+
378
+ const hasWorkingChanges = await git.hasWorkingChanges();
379
+
380
+ if (!hasWorkingChanges) {
381
+ const lastCommitMessage = await git.getLastCommitMessage();
382
+ const effectiveCommitMessage = lastCommitMessage || providedCommitMessage;
383
+
384
+ workflowState.state.commitAndPushCompleted = true;
385
+ workflowState.state.lastCommitMessage = effectiveCommitMessage || "";
386
+ workflowState.state.lastPushBranch = branchForPush;
387
+ workflowState.state.currentPhase = workflowState.state.released ? "ready_to_complete" : "release";
388
+ await workflowState.save();
389
+
338
390
  return textResponse(
339
- "āš ļø No changes detected. Make sure you have modifications to commit before running 'commit_and_push'."
391
+ `ā„¹ļø No changes detected. Assuming commit and push already completed.\nLast commit: ${
392
+ effectiveCommitMessage || "(unavailable)"
393
+ }\n\nNext: Run 'perform_release' to handle the release and push tags.`
340
394
  );
341
395
  }
342
396
 
@@ -383,10 +437,6 @@ async function handleCommitAndPush(args, context) {
383
437
  return textResponse(`āŒ git commit failed:\n\n${output}`);
384
438
  }
385
439
 
386
- const requestedBranch = typeof args.branch === "string" ? args.branch.trim() : "";
387
- const currentBranch = await git.getCurrentBranch();
388
- const branchForPush = requestedBranch || currentBranch || "";
389
-
390
440
  try {
391
441
  const pushCommand = branchForPush
392
442
  ? `git push origin ${utils.shellEscape(branchForPush)}`
@@ -418,9 +468,9 @@ async function handlePerformRelease(args, context) {
418
468
  return textResponse("āš ļø Please start a task first using 'start_task'!");
419
469
  }
420
470
 
421
- if (!workflowState.state.commitAndPushCompleted) {
471
+ if (await resetToCommitIfWorkingChanges(workflowState, git)) {
422
472
  return textResponse(
423
- "āš ļø Please run 'commit_and_push' after the ready check before recording a release!"
473
+ "āš ļø Detected new changes after the last commit. Please run 'commit_and_push' again before recording a release."
424
474
  );
425
475
  }
426
476
 
@@ -430,7 +480,67 @@ async function handlePerformRelease(args, context) {
430
480
  );
431
481
  }
432
482
 
433
- const releaseCommand = typeof args.command === "string" ? args.command.trim() : "";
483
+ let autoDetectedCommit = false;
484
+
485
+ if (!workflowState.state.commitAndPushCompleted) {
486
+ const hasWorkingChanges = await git.hasWorkingChanges();
487
+
488
+ if (hasWorkingChanges) {
489
+ return textResponse(
490
+ "āš ļø Please run 'commit_and_push' after the ready check before recording a release!"
491
+ );
492
+ }
493
+
494
+ const lastCommitMessage = await git.getLastCommitMessage();
495
+ const currentBranch = await git.getCurrentBranch();
496
+
497
+ workflowState.state.commitAndPushCompleted = true;
498
+ workflowState.state.lastCommitMessage =
499
+ lastCommitMessage || workflowState.state.lastCommitMessage || "";
500
+ workflowState.state.lastPushBranch = currentBranch || workflowState.state.lastPushBranch || "";
501
+ workflowState.state.currentPhase = "release";
502
+ autoDetectedCommit = true;
503
+ await workflowState.save();
504
+ }
505
+
506
+ const rawReleaseCommand = typeof args.command === "string" ? args.command.trim() : "";
507
+ const releaseTypeArg =
508
+ typeof args.releaseType === "string" ? args.releaseType.trim().toLowerCase() : "";
509
+ const presetArg = typeof args.preset === "string" ? args.preset.trim().toLowerCase() : "";
510
+ const recognizedTypes = new Set(["major", "minor", "patch"]);
511
+
512
+ const tokens = rawReleaseCommand
513
+ .toLowerCase()
514
+ .split(/\s+/)
515
+ .map((token) => token.trim())
516
+ .filter(Boolean);
517
+
518
+ let releaseType = "";
519
+ if (recognizedTypes.has(releaseTypeArg)) {
520
+ releaseType = releaseTypeArg;
521
+ } else if (recognizedTypes.has(presetArg)) {
522
+ releaseType = presetArg;
523
+ } else if (tokens.length > 0) {
524
+ const possibleType = tokens[tokens.length - 1];
525
+ if (recognizedTypes.has(possibleType)) {
526
+ releaseType = possibleType;
527
+ }
528
+ }
529
+
530
+ let releaseCommand = rawReleaseCommand;
531
+ if (releaseCommand && recognizedTypes.has(releaseCommand.toLowerCase())) {
532
+ releaseType = releaseCommand.toLowerCase();
533
+ releaseCommand = "";
534
+ }
535
+
536
+ const scriptTypeMatch = /release:(major|minor|patch)/i.exec(releaseCommand);
537
+ if (scriptTypeMatch) {
538
+ releaseType = scriptTypeMatch[1].toLowerCase();
539
+ }
540
+
541
+ if (!releaseCommand && releaseType) {
542
+ releaseCommand = `npm run release:${releaseType}`;
543
+ }
434
544
 
435
545
  if (!releaseCommand) {
436
546
  return textResponse("āš ļø Please provide a non-empty 'command' when using 'perform_release'.");
@@ -460,13 +570,16 @@ async function handlePerformRelease(args, context) {
460
570
  }
461
571
 
462
572
  const lastCommitMessage = await git.getLastCommitMessage();
463
- const releaseType = utils.determineReleaseTypeFromCommit(
464
- lastCommitMessage || workflowState.state.lastCommitMessage
465
- );
573
+ if (!releaseType) {
574
+ releaseType = utils.determineReleaseTypeFromCommit(
575
+ lastCommitMessage || workflowState.state.lastCommitMessage
576
+ );
577
+ }
466
578
 
467
- const finalCommand = releaseCommand.includes("--release-as")
468
- ? releaseCommand
469
- : `${releaseCommand} -- --release-as ${releaseType}`;
579
+ const finalCommand =
580
+ releaseCommand.includes("--release-as") || /release:(major|minor|patch)/i.test(releaseCommand)
581
+ ? releaseCommand
582
+ : `${releaseCommand} -- --release-as ${releaseType}`;
470
583
 
471
584
  try {
472
585
  await exec(finalCommand);
@@ -497,10 +610,16 @@ async function handlePerformRelease(args, context) {
497
610
 
498
611
  const message = summarizeHistory(workflowState.state.history, args.limit || 10);
499
612
  if (!message) {
500
- return textResponse("šŸ“œ No workflow history yet.");
613
+ const autoNote = autoDetectedCommit
614
+ ? "\n\nā„¹ļø Detected clean working tree and treated commit/push as already completed."
615
+ : "";
616
+ return textResponse(`šŸ“œ No workflow history yet.${autoNote}`);
501
617
  }
502
618
 
503
- return textResponse(message);
619
+ const autoNote = autoDetectedCommit
620
+ ? "\n\nā„¹ļø Detected clean working tree and treated commit/push as already completed."
621
+ : "";
622
+ return textResponse(`${message}${autoNote}`);
504
623
  }
505
624
 
506
625
  async function handleCompleteTask(args, workflowState) {
@@ -607,7 +726,7 @@ export async function handleToolCall({
607
726
  case "view_history":
608
727
  return handleViewHistory(args, workflowState);
609
728
  case "continue_workflow":
610
- return handleContinueWorkflow(workflowState);
729
+ return handleContinueWorkflow(workflowState, { workflowState, exec, git, utils });
611
730
  default:
612
731
  throw new Error(`Unknown tool: ${name}`);
613
732
  }