@programinglive/dev-workflow-mcp-server 1.5.1 → 1.5.2

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.
Files changed (78) hide show
  1. package/.agent/workflows/dev-workflow-bugfix.md +250 -250
  2. package/.agent/workflows/dev-workflow-feature.md +201 -201
  3. package/.agent/workflows/dev-workflow-full-cycle.md +400 -400
  4. package/.agent/workflows/dev-workflow-refactor.md +238 -238
  5. package/.github/ISSUE_TEMPLATE/bug_report.md +34 -34
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +25 -25
  7. package/.github/pull_request_template.md +22 -22
  8. package/.github/workflows/npm-publish.yml +50 -50
  9. package/.husky/commit-msg +0 -0
  10. package/CHANGELOG.md +749 -739
  11. package/CODE_OF_CONDUCT.md +79 -79
  12. package/CONTRIBUTING.md +60 -60
  13. package/LICENSE +21 -21
  14. package/README.md +632 -632
  15. package/SECURITY.md +20 -20
  16. package/db/index.js +204 -204
  17. package/docs/NETLIFY_DEPLOYMENT.md +125 -125
  18. package/docs/PLESK_DEPLOYMENT.md +170 -170
  19. package/docs/WORKFLOW-BEST-PRACTICES.md +58 -58
  20. package/docs/antigravity/EXAMPLES.md +535 -535
  21. package/docs/antigravity/GETTING-STARTED.md +292 -292
  22. package/docs/antigravity/TROUBLESHOOTING.md +742 -742
  23. package/docs/antigravity/WALKTHROUGH.md +25 -25
  24. package/docs/antigravity/WORKFLOW-TEMPLATES.md +670 -670
  25. package/docs/product/PRD.md +67 -67
  26. package/docs/release-notes/RELEASE_NOTES.md +226 -226
  27. package/docs/web-dashboard.md +175 -175
  28. package/exec.js +49 -49
  29. package/index.js +279 -279
  30. package/install.js +73 -73
  31. package/package.json +4 -4
  32. package/release-wrapper.js +192 -192
  33. package/scripts/build-docs.js +63 -63
  34. package/scripts/hello-world.js +1 -1
  35. package/scripts/release.cjs +252 -252
  36. package/scripts/update-release-notes.cjs +178 -178
  37. package/server.json +23 -23
  38. package/tailwind.config.js +8 -8
  39. package/tests/build.test.js +83 -91
  40. package/tests/exec.test.js +35 -35
  41. package/tests/handlers.test.js +1058 -1058
  42. package/tests/hello-world.test.js +13 -13
  43. package/tests/mcpRegistry.test.js +65 -65
  44. package/tests/release-wrapper.test.js +107 -107
  45. package/tests/workflowState.test.js +505 -505
  46. package/tools/definitions.js +332 -332
  47. package/tools/handlers.js +1019 -1019
  48. package/vite.config.js +43 -43
  49. package/vite.web.config.js +23 -23
  50. package/web/README.md +36 -36
  51. package/web/app/docs/page.tsx +202 -202
  52. package/web/app/globals.css +90 -90
  53. package/web/app/layout.tsx +36 -36
  54. package/web/app/page.tsx +15 -15
  55. package/web/components/ApiReference.tsx +20 -20
  56. package/web/components/Features.tsx +61 -61
  57. package/web/components/Footer.tsx +9 -9
  58. package/web/components/Hero.tsx +96 -97
  59. package/web/components/History.tsx +143 -143
  60. package/web/components/Navbar.tsx +64 -64
  61. package/web/components/Terminal.tsx +114 -114
  62. package/web/eslint.config.mjs +18 -18
  63. package/web/lib/db.js +131 -131
  64. package/web/lib/docs.js +73 -73
  65. package/web/netlify.toml +3 -6
  66. package/web/next.config.ts +12 -7
  67. package/web/package-lock.json +6560 -6560
  68. package/web/package.json +28 -28
  69. package/web/postcss.config.mjs +9 -9
  70. package/web/server.js +31 -31
  71. package/web/tailwind.config.ts +19 -19
  72. package/web/tsconfig.json +42 -42
  73. package/workflow-state.js +612 -612
  74. package/web/app/api/docs/route.ts +0 -32
  75. package/web/app/api/history/route.ts +0 -12
  76. package/web/app/api/npm-downloads/route.ts +0 -21
  77. package/web/app/api/summary/route.ts +0 -13
  78. package/web/app/api/version/route.ts +0 -8
package/tools/handlers.js CHANGED
@@ -1,1019 +1,1019 @@
1
- import fs from "fs/promises";
2
- import { existsSync } from "fs";
3
- import path from "path";
4
- import { fileURLToPath } from "url";
5
-
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = path.dirname(__filename);
8
- const PROJECT_SUMMARY_FILENAME = "project-summary.json";
9
-
10
- const textResponse = (text) => ({
11
- content: [
12
- {
13
- type: "text",
14
- text,
15
- },
16
- ],
17
- });
18
-
19
- async function resetToCommitIfWorkingChanges(workflowState, git) {
20
- if (!workflowState.state.commitAndPushCompleted) {
21
- return false;
22
- }
23
-
24
- const hasWorkingChanges = await git.hasWorkingChanges();
25
- const hasStagedChanges = await git.hasStagedChanges();
26
-
27
- if (!hasWorkingChanges && !hasStagedChanges) {
28
- return false;
29
- }
30
-
31
- workflowState.state.commitAndPushCompleted = false;
32
- workflowState.state.released = false;
33
- workflowState.state.currentPhase = "commit";
34
- workflowState.state.releaseCommand = "";
35
- workflowState.state.releaseNotes = "";
36
- await workflowState.save();
37
- return true;
38
- }
39
-
40
- function formatChecklist(checks) {
41
- return checks.map((c) => `${c.done ? "✅" : "❌"} ${c.name}`).join("\n");
42
- }
43
-
44
- function summarizeHistory(history, limit) {
45
- const recent = history.slice(-limit).reverse();
46
- if (recent.length === 0) {
47
- return null;
48
- }
49
-
50
- const historyText = recent
51
- .map(
52
- (entry, index) =>
53
- `${index + 1}. [${new Date(entry.timestamp).toLocaleString()}]\n Task: ${entry.taskDescription}\n Type: ${entry.taskType}\n Commit: ${entry.commitMessage}\n`
54
- )
55
- .join("\n");
56
-
57
- return `📜 Workflow History (last ${recent.length} tasks)\n\n${historyText}`;
58
- }
59
-
60
- function generateProjectSummary(history) {
61
- if (!history || history.length === 0) {
62
- return "No workflow history available.";
63
- }
64
-
65
- const taskTypes = {};
66
- const recentTasks = history.slice(-20);
67
-
68
- for (const entry of recentTasks) {
69
- const type = entry.taskType || "other";
70
- taskTypes[type] = (taskTypes[type] || 0) + 1;
71
- }
72
-
73
- const typeSummary = Object.entries(taskTypes)
74
- .map(([type, count]) => `${type}: ${count}`)
75
- .join(", ");
76
-
77
- const totalTasks = history.length;
78
- const lastTask = history[history.length - 1];
79
- const lastActive = lastTask ? new Date(lastTask.timestamp).toLocaleDateString() : "Never";
80
-
81
- return `🧠 Project Knowledge Summary\n\n📊 Stats\n- Total tasks completed: ${totalTasks}\n- Recent task mix: ${typeSummary}\n- Last active: ${lastActive}\n\n📝 Recent tasks (last 5)\n${history.slice(-5).reverse().map((e, i) => `${i + 1}. ${e.taskDescription} (${e.taskType})`).join("\n")}`;
82
- }
83
-
84
- function getContinueGuidance(status) {
85
- switch (status.currentPhase) {
86
- case "coding": {
87
- const task = status.taskDescription ? ` "${status.taskDescription}"` : "";
88
- return `Finish implementing${task} and then run 'mark_bug_fixed' with a brief summary of the change.`;
89
- }
90
- case "testing":
91
- if (!status.testsCreated) {
92
- return "Create automated coverage now using 'create_tests' before recording any results.";
93
- }
94
-
95
- if (status.testsPassed) {
96
- return "With tests green, move on to 'create_documentation' and capture what changed.";
97
- }
98
-
99
- if (status.testsSkipped) {
100
- return "Document why tests were skipped using 'create_documentation' so the workflow can proceed.";
101
- }
102
-
103
- return status.testCommand
104
- ? `Fix any failing tests and re-run '${status.testCommand}'. Once they pass, record the results with 'run_tests'.`
105
- : "Run your automated tests and record the outcome with 'run_tests'. Fix any failures before moving forward.";
106
- case "documentation":
107
- return status.testsSkipped
108
- ? `Document the change (including manual QA for skipped tests) with 'create_documentation'. Reason recorded: ${
109
- status.testsSkippedReason || "(none provided)"
110
- }.`
111
- : "Capture your updates with 'create_documentation' (provide the documentation type and a short summary).";
112
- case "ready":
113
- return "Run 'check_ready_to_commit' to verify all prerequisite steps before committing.";
114
- case "commit":
115
- return "All checks passed—run 'commit_and_push' with your commit message (and optional branch).";
116
- case "release":
117
- return "Commit recorded. Execute 'perform_release' with the release command you ran to publish changes.";
118
- case "ready_to_complete":
119
- return "Release recorded. Finish up by calling 'complete_task' with the commit message you used.";
120
- default:
121
- return "Review your workflow progress and complete the next required step.";
122
- }
123
- }
124
-
125
- async function handleContinueWorkflow(workflowState, context) {
126
- const status = workflowState.state;
127
-
128
- if (!status || status.currentPhase === "idle") {
129
- return textResponse("⚠️ No active workflow. Use 'start_task' to kick off a new task before continuing.");
130
- }
131
-
132
- if (await resetToCommitIfWorkingChanges(workflowState, context.git)) {
133
- return textResponse(
134
- "⚠️ 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."
135
- );
136
- }
137
-
138
- if (status.currentPhase === "ready") {
139
- return handleReadyCheck(workflowState);
140
- }
141
-
142
- if (status.currentPhase === "commit") {
143
- return handleCommitAndPush({}, context);
144
- }
145
-
146
- if (status.currentPhase === "release") {
147
- return textResponse(
148
- "⚠️ Release phase requires explicit command. Run 'perform_release' with your release command (e.g., 'npm run release')."
149
- );
150
- }
151
-
152
- const sections = [
153
- `📊 Current Phase: ${status.currentPhase}`,
154
- `📝 Task: ${status.taskDescription || "(none)"}`,
155
- ];
156
-
157
- if (status.testsSkipped) {
158
- sections.push(`⚠️ Tests skipped: ${status.testsSkippedReason || "(no reason provided)"}`);
159
- }
160
-
161
- if (
162
- status.currentPhase === "testing" &&
163
- status.testsCreated &&
164
- !status.testsPassed &&
165
- typeof status.testCommand === "string" &&
166
- status.testCommand.trim() !== ""
167
- ) {
168
- sections.push(`🧪 Last test command: ${status.testCommand}`);
169
- }
170
-
171
- sections.push(`➡️ ${getContinueGuidance(status)}`);
172
-
173
- return textResponse(sections.join("\n\n"));
174
- }
175
-
176
- async function handleStartTask(args, workflowState) {
177
- workflowState.reset();
178
- workflowState.state.currentPhase = "coding";
179
- workflowState.state.taskDescription = args.description;
180
- workflowState.state.taskType = args.type;
181
- workflowState.state.testsSkipped = false;
182
- workflowState.state.testsSkippedReason = "";
183
- workflowState.state.commitAndPushCompleted = false;
184
- workflowState.state.lastCommitMessage = "";
185
- workflowState.state.lastPushBranch = "";
186
- await workflowState.save();
187
-
188
- return textResponse(
189
- `✅ 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. ⏳ Run 'commit_and_push' (commits and pushes)\n8. ⏳ Run 'perform_release' (handles versioning and tags)\n9. ⏳ Complete task\n\nReminder: Focus on writing clean, maintainable code!`
190
- );
191
- }
192
-
193
- async function handleMarkBugFixed(args, workflowState) {
194
- if (workflowState.state.currentPhase === "idle") {
195
- return textResponse("⚠️ Please start a task first using 'start_task'!");
196
- }
197
-
198
- // Idempotent behavior: if already marked fixed, do not reset downstream flags.
199
- if (workflowState.state.bugFixed) {
200
- const summary = typeof args.summary === "string" ? args.summary : "";
201
- if (summary && summary.trim() !== "") {
202
- workflowState.state.fixSummary = summary;
203
- }
204
-
205
- // Preserve existing progress and set an appropriate phase.
206
- if (workflowState.state.testsPassed && workflowState.state.documentationCreated) {
207
- workflowState.state.currentPhase = "ready";
208
- workflowState.state.readyToCommit = true;
209
- } else if (workflowState.state.testsCreated) {
210
- workflowState.state.currentPhase = "testing";
211
- }
212
- workflowState.state.readyCheckCompleted = false;
213
- workflowState.state.commitAndPushCompleted = false;
214
- await workflowState.save();
215
-
216
- return textResponse(
217
- "ℹ️ Already marked as fixed; kept existing tests/documentation. Continue with the next step."
218
- );
219
- }
220
-
221
- workflowState.state.bugFixed = true;
222
- workflowState.state.testsCreated = false;
223
- workflowState.state.currentPhase = "testing";
224
- workflowState.state.testsSkipped = false;
225
- workflowState.state.testsSkippedReason = "";
226
- workflowState.state.fixSummary = args.summary;
227
- workflowState.state.readyCheckCompleted = false;
228
- workflowState.state.released = false;
229
- workflowState.state.releaseCommand = "";
230
- workflowState.state.releaseNotes = "";
231
- workflowState.state.commitAndPushCompleted = false;
232
- workflowState.state.lastCommitMessage = "";
233
- workflowState.state.lastPushBranch = "";
234
- await workflowState.save();
235
-
236
- return textResponse(
237
- `✅ 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. ⏳ Run 'commit_and_push'\n7. ⏳ Run 'perform_release'\n8. ⏳ Complete task\n\n🚫 DO NOT SKIP TESTING!`
238
- );
239
- }
240
-
241
- async function handleCreateTests(workflowState) {
242
- if (!workflowState.state.bugFixed) {
243
- return textResponse("⚠️ Please mark your feature/bug as fixed first using 'mark_bug_fixed'!");
244
- }
245
-
246
- workflowState.state.testsCreated = true;
247
- workflowState.state.testsSkipped = false;
248
- workflowState.state.testsSkippedReason = "";
249
- workflowState.state.readyCheckCompleted = false;
250
- workflowState.state.released = false;
251
- workflowState.state.commitAndPushCompleted = false;
252
- workflowState.state.lastCommitMessage = "";
253
- workflowState.state.lastPushBranch = "";
254
- await workflowState.save();
255
-
256
- return textResponse(
257
- "✅ 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. ⏳ Run 'commit_and_push'\n7. ⏳ Run 'perform_release'\n8. ⏳ Complete task\n\n🧪 Run your test command and record the results using 'run_tests'."
258
- );
259
- }
260
-
261
- async function handleSkipTests(args, workflowState) {
262
- if (workflowState.state.currentPhase === "idle") {
263
- return textResponse("⚠️ No active task. Use 'start_task' before skipping tests.");
264
- }
265
-
266
- if (!workflowState.state.bugFixed) {
267
- return textResponse("⚠️ Please mark your feature/bug as fixed first using 'mark_bug_fixed'!");
268
- }
269
-
270
- const reason = typeof args.reason === "string" ? args.reason.trim() : "";
271
-
272
- if (!reason) {
273
- return textResponse("⚠️ Provide a non-empty justification when skipping tests.");
274
- }
275
-
276
- workflowState.state.testsCreated = true;
277
- workflowState.state.testsPassed = true;
278
- workflowState.state.testsSkipped = true;
279
- workflowState.state.testsSkippedReason = reason;
280
- workflowState.state.currentPhase = "documentation";
281
- workflowState.state.readyToCommit = false;
282
- workflowState.state.readyCheckCompleted = false;
283
- workflowState.state.released = false;
284
- workflowState.state.commitAndPushCompleted = false;
285
- workflowState.state.lastCommitMessage = "";
286
- workflowState.state.lastPushBranch = "";
287
- await workflowState.save();
288
-
289
- return textResponse(
290
- `⚠️ Tests skipped for this task.
291
- Reason: ${reason}
292
-
293
- Manual verification is now required.
294
-
295
- Next Steps:
296
- 1. ✓ Fix/implement feature
297
- 2. ⚠️ Tests skipped (ensure manual QA)
298
- 3. ⏳ Create/update documentation
299
- 4. ⏳ Run 'check_ready_to_commit'
300
- 5. ⏳ Run 'commit_and_push'
301
- 6. ⏳ Run 'perform_release'
302
- 7. ⏳ Complete task`
303
- );
304
- }
305
-
306
- async function handleRunTests(args, workflowState) {
307
- if (!workflowState.state.bugFixed) {
308
- return textResponse("⚠️ Please mark your feature/bug as fixed first using 'mark_bug_fixed'!");
309
- }
310
-
311
- if (!workflowState.state.testsCreated) {
312
- return textResponse("⚠️ Please create tests first using 'create_tests' before recording test results!");
313
- }
314
-
315
- if (
316
- typeof args.passed !== "boolean" ||
317
- typeof args.testCommand !== "string" ||
318
- args.testCommand.trim() === ""
319
- ) {
320
- return textResponse(
321
- "⚠️ Please provide both 'passed' (boolean) and 'testCommand' (non-empty string) when recording test results."
322
- );
323
- }
324
-
325
- workflowState.state.testsPassed = args.passed;
326
- workflowState.state.testCommand = args.testCommand;
327
- workflowState.state.testDetails = typeof args.details === "string" ? args.details : "";
328
- workflowState.state.testsSkipped = false;
329
- workflowState.state.testsSkippedReason = "";
330
- workflowState.state.readyCheckCompleted = false;
331
- workflowState.state.released = false;
332
- workflowState.state.commitAndPushCompleted = false;
333
- workflowState.state.lastCommitMessage = "";
334
- workflowState.state.lastPushBranch = "";
335
-
336
- if (!args.passed) {
337
- workflowState.state.currentPhase = "testing";
338
- await workflowState.save();
339
-
340
- return textResponse(
341
- `❌ TESTS FAILED!\n\n🚫 STOP! DO NOT COMMIT OR PUSH!\n\nYou must:\n1. Fix the failing tests\n2. Run tests again until they pass\n\nTest command: ${args.testCommand}\n\nNever skip or ignore failing tests!`
342
- );
343
- }
344
-
345
- workflowState.state.currentPhase = "documentation";
346
- await workflowState.save();
347
-
348
- return textResponse(
349
- `✅ All tests passed! 🎉\n\nTest command: ${args.testCommand}\n\n📝 Now create or update documentation using 'create_documentation' with:\n- documentationType: "PRD", "README", "RELEASE_NOTES", "inline-comments", "API-docs", "changelog", or "other"\n- summary: Brief description of what was documented\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. ⏳ Run 'commit_and_push'\n7. ⏳ Run 'perform_release'\n8. ⏳ Complete task`
350
- );
351
- }
352
-
353
- async function handleCreateDocumentation(args, workflowState) {
354
- if (!workflowState.state.testsPassed) {
355
- return textResponse("⚠️ Please ensure tests are passing first! Run 'run_tests' with passed=true.");
356
- }
357
-
358
- workflowState.state.documentationCreated = true;
359
- workflowState.state.documentationType = args.documentationType;
360
- workflowState.state.documentationSummary = args.summary;
361
- workflowState.state.currentPhase = "ready";
362
- workflowState.state.readyToCommit = true;
363
- workflowState.state.readyCheckCompleted = false;
364
- workflowState.state.released = false;
365
- workflowState.state.commitAndPushCompleted = false;
366
- workflowState.state.lastCommitMessage = "";
367
- workflowState.state.lastPushBranch = "";
368
- await workflowState.save();
369
-
370
- return textResponse(
371
- `✅ Documentation created/updated!\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/update documentation\n5. ⏳ Run 'check_ready_to_commit' to verify\n6. ⏳ Run 'commit_and_push'\n7. ⏳ Run 'perform_release'\n8. ⏳ Mark as complete with 'complete_task'`
372
- );
373
- }
374
-
375
- async function handleReadyCheck(workflowState) {
376
- const status = workflowState.state;
377
- const testsCreatedDone = status.testsSkipped || status.testsCreated;
378
- const testsPassedDone = status.testsSkipped || status.testsPassed;
379
- const checks = [
380
- { name: "Task started", done: status.currentPhase !== "idle" },
381
- { name: "Feature/bug fixed", done: status.bugFixed },
382
- {
383
- name: status.testsSkipped ? "Tests skipped (manual QA documented)" : "Tests created",
384
- done: testsCreatedDone,
385
- },
386
- {
387
- name: status.testsSkipped ? "Tests skipped acknowledged" : "Tests passed",
388
- done: testsPassedDone,
389
- },
390
- { name: "Documentation created", done: status.documentationCreated },
391
- ];
392
-
393
- const allDone = checks.every((c) => c.done);
394
- const checkList = formatChecklist(checks);
395
-
396
- workflowState.state.readyCheckCompleted = allDone;
397
- if (allDone) {
398
- workflowState.state.currentPhase = workflowState.state.commitAndPushCompleted
399
- ? workflowState.state.released
400
- ? "ready_to_complete"
401
- : "release"
402
- : "commit";
403
- }
404
- await workflowState.save();
405
-
406
- if (allDone) {
407
- return textResponse(
408
- `🎉 ALL CHECKS PASSED!\n\n${checkList}\n\n✅ Next actions:\n1. Run 'commit_and_push' (commits and pushes your changes)\n2. Run 'perform_release' (handles versioning, tags, and final push)\n3. Finish with 'complete_task'\n\nTip: Provide the optional 'branch' argument to 'commit_and_push' to push to a non-default branch.\n\nTask: ${status.taskDescription}`
409
- );
410
- }
411
-
412
- return textResponse(`⚠️ NOT READY TO COMMIT!\n\n${checkList}\n\nPlease complete all steps before committing.`);
413
- }
414
-
415
- async function handleCommitAndPush(args, context) {
416
- const { workflowState, exec, git, utils } = context;
417
- if (!workflowState.state.readyToCommit || !workflowState.state.readyCheckCompleted) {
418
- // Be smart: auto-run the ready check and proceed if it passes.
419
- const readyResponse = await handleReadyCheck(workflowState);
420
- if (workflowState.state.currentPhase !== "commit") {
421
- return readyResponse;
422
- }
423
- }
424
-
425
- const providedCommitMessage =
426
- typeof args.commitMessage === "string" ? args.commitMessage.trim() : "";
427
-
428
- const requestedBranch = typeof args.branch === "string" ? args.branch.trim() : "";
429
- const primaryBranch = await git.getPrimaryBranch();
430
- const currentBranch = await git.getCurrentBranch();
431
- const branchForPush = requestedBranch || primaryBranch || currentBranch || "";
432
-
433
- const hasWorkingChanges = await git.hasWorkingChanges();
434
-
435
- if (!hasWorkingChanges) {
436
- const lastCommitMessage = await git.getLastCommitMessage();
437
- const effectiveCommitMessage = lastCommitMessage || providedCommitMessage;
438
-
439
- workflowState.state.commitAndPushCompleted = true;
440
- workflowState.state.lastCommitMessage = effectiveCommitMessage || "";
441
- workflowState.state.lastPushBranch = branchForPush;
442
- workflowState.state.currentPhase = workflowState.state.released ? "ready_to_complete" : "release";
443
- await workflowState.save();
444
-
445
- return textResponse(
446
- `ℹ️ No changes detected. Assuming commit and push already completed.\nLast commit: ${
447
- effectiveCommitMessage || "(unavailable)"
448
- }\n\nNext: Run 'perform_release' to handle the release and push tags.`
449
- );
450
- }
451
-
452
- if (!(await git.hasTestChanges()) && !workflowState.state.testsSkipped) {
453
- return textResponse(
454
- "⚠️ Please include test updates in your changes before running 'commit_and_push'. Ensure at least one test file is modified."
455
- );
456
- }
457
-
458
- try {
459
- await exec("git add .");
460
- } catch (error) {
461
- return textResponse(
462
- `❌ Failed to stage files:\n\n${error.stderr || error.stdout || error.message}`
463
- );
464
- }
465
-
466
- const stagedChanges = await git.getStagedChanges();
467
- const { summary: generatedSummary, body: commitBody } = utils.createCommitMessageParts(
468
- stagedChanges,
469
- providedCommitMessage
470
- );
471
-
472
- if (!generatedSummary) {
473
- return textResponse("⚠️ Unable to generate commit message. Please provide a 'commitMessage' manually.");
474
- }
475
-
476
- try {
477
- if (commitBody) {
478
- await exec(
479
- `git commit -m ${utils.shellEscape(generatedSummary)} -m ${utils.shellEscape(commitBody)}`
480
- );
481
- } else {
482
- await exec(`git commit -m ${utils.shellEscape(generatedSummary)}`);
483
- }
484
- } catch (error) {
485
- const output = error.stderr || error.stdout || error.message || "Unknown error";
486
- if (output.includes("nothing to commit")) {
487
- return textResponse(
488
- "⚠️ Nothing to commit. Make additional changes before running 'commit_and_push'."
489
- );
490
- }
491
-
492
- return textResponse(`❌ git commit failed:\n\n${output}`);
493
- }
494
-
495
- try {
496
- const pushCommand = branchForPush
497
- ? `git push origin ${utils.shellEscape(branchForPush)}`
498
- : "git push";
499
- await exec(pushCommand);
500
- } catch (error) {
501
- return textResponse(
502
- `❌ git push failed:\n\n${error.stderr || error.stdout || error.message}`
503
- );
504
- }
505
-
506
- workflowState.state.commitAndPushCompleted = true;
507
- workflowState.state.lastCommitMessage = generatedSummary;
508
- workflowState.state.lastPushBranch = branchForPush;
509
- workflowState.state.currentPhase = workflowState.state.released ? "ready_to_complete" : "release";
510
- await workflowState.save();
511
-
512
- return textResponse(
513
- `✅ Commit and push completed!\n\nCommit message: ${generatedSummary}\nPushed to: ${
514
- branchForPush || "(default upstream)"
515
- }\n\nNext: Run 'perform_release' to handle the release and push tags.`
516
- );
517
- }
518
-
519
- async function handlePerformRelease(args, context) {
520
- const { workflowState, exec, git, utils } = context;
521
-
522
- if (workflowState.state.currentPhase === "idle") {
523
- return textResponse("⚠️ Please start a task first using 'start_task'!");
524
- }
525
-
526
- if (await resetToCommitIfWorkingChanges(workflowState, git)) {
527
- return textResponse(
528
- "⚠️ Detected new changes after the last commit. Please run 'commit_and_push' again before recording a release."
529
- );
530
- }
531
-
532
- if (!workflowState.state.readyCheckCompleted) {
533
- return textResponse(
534
- "⚠️ Please run 'check_ready_to_commit' and ensure all checks pass before releasing!"
535
- );
536
- }
537
-
538
- if (!workflowState.state.commitAndPushCompleted) {
539
- const hasWorkingChanges = await git.hasWorkingChanges();
540
-
541
- if (hasWorkingChanges) {
542
- return textResponse(
543
- "⚠️ Please run 'commit_and_push' after the ready check before recording a release!"
544
- );
545
- }
546
-
547
- workflowState.state.currentPhase = "commit";
548
- await workflowState.save();
549
-
550
- return textResponse(
551
- "⚠️ Please run 'commit_and_push' after the ready check before recording a release!"
552
- );
553
- }
554
-
555
- const rawReleaseCommand = typeof args.command === "string" ? args.command.trim() : "";
556
- const releaseTypeArg =
557
- typeof args.releaseType === "string" ? args.releaseType.trim().toLowerCase() : "";
558
- const presetArg = typeof args.preset === "string" ? args.preset.trim().toLowerCase() : "";
559
- const recognizedTypes = new Set(["major", "minor", "patch"]);
560
-
561
- const tokens = rawReleaseCommand
562
- .toLowerCase()
563
- .split(/\s+/)
564
- .map((token) => token.trim())
565
- .filter(Boolean);
566
-
567
- let releaseType = "";
568
- if (recognizedTypes.has(releaseTypeArg)) {
569
- releaseType = releaseTypeArg;
570
- } else if (recognizedTypes.has(presetArg)) {
571
- releaseType = presetArg;
572
- } else if (tokens.length > 0) {
573
- const possibleType = tokens[tokens.length - 1];
574
- if (recognizedTypes.has(possibleType)) {
575
- releaseType = possibleType;
576
- }
577
- }
578
-
579
- let releaseCommand = rawReleaseCommand;
580
- if (releaseCommand && recognizedTypes.has(releaseCommand.toLowerCase())) {
581
- releaseType = releaseCommand.toLowerCase();
582
- releaseCommand = "";
583
- }
584
-
585
- const scriptTypeMatch = /release:(major|minor|patch)/i.exec(releaseCommand);
586
- if (scriptTypeMatch) {
587
- releaseType = scriptTypeMatch[1].toLowerCase();
588
- }
589
-
590
- if (!releaseCommand && releaseType) {
591
- releaseCommand = `npm run release:${releaseType}`;
592
- }
593
-
594
- if (!releaseCommand) {
595
- return textResponse("⚠️ Please provide a non-empty 'command' when using 'perform_release'.");
596
- }
597
-
598
- let statusOutput;
599
- try {
600
- const { stdout } = await exec("git status --porcelain");
601
- statusOutput = stdout;
602
- } catch (error) {
603
- const details = error.stderr || error.stdout || error.message || "Unknown error";
604
- return textResponse(
605
- `⚠️ Unable to verify Git status. Refusing to run the release command until the repository state can be checked.\n\nDetails:\n${details}`
606
- );
607
- }
608
-
609
- const statusSummary = git.workingTreeSummary(statusOutput);
610
- if (statusSummary.hasChanges) {
611
- const changeDetails =
612
- statusSummary.lines.length > 0
613
- ? statusSummary.lines.map((line) => `• ${line}`).join("\n")
614
- : "• (unable to list changes)";
615
-
616
- return textResponse(
617
- `⚠️ Working tree not clean! Please commit or stash all changes before running 'perform_release'.\n\nDetected changes:\n${changeDetails}`
618
- );
619
- }
620
-
621
- const lastCommitMessage = await git.getLastCommitMessage();
622
- if (!releaseType) {
623
- releaseType = utils.determineReleaseTypeFromCommit(
624
- lastCommitMessage || workflowState.state.lastCommitMessage
625
- );
626
- }
627
-
628
- const finalCommand =
629
- releaseCommand.includes("--release-as") || /release:(major|minor|patch)/i.test(releaseCommand)
630
- ? releaseCommand
631
- : `${releaseCommand} -- --release-as ${releaseType}`;
632
-
633
- try {
634
- await exec(finalCommand);
635
- } catch (error) {
636
- return textResponse(
637
- `❌ Release command failed:\n\n${error.stderr || error.stdout || error.message}`
638
- );
639
- }
640
-
641
- const branchForPush = workflowState.state.lastPushBranch || (await git.getCurrentBranch()) || "";
642
- const pushCommand = branchForPush
643
- ? `git push --follow-tags origin ${utils.shellEscape(branchForPush)}`
644
- : "git push --follow-tags";
645
-
646
- try {
647
- await exec(pushCommand);
648
- } catch (error) {
649
- return textResponse(
650
- `❌ git push (with tags) failed:\n\n${error.stderr || error.stdout || error.message}`
651
- );
652
- }
653
-
654
- workflowState.state.released = true;
655
- workflowState.state.releaseCommand = finalCommand;
656
- workflowState.state.releaseNotes = typeof args.notes === "string" ? args.notes : "";
657
- workflowState.state.currentPhase = "ready_to_complete";
658
- await workflowState.save();
659
-
660
- const message = summarizeHistory(workflowState.state.history, args.limit || 10);
661
- if (!message) {
662
- return textResponse("📜 No workflow history yet.");
663
- }
664
-
665
- return textResponse(message);
666
- }
667
-
668
- async function handleCompleteTask(args, workflowState) {
669
- const entry = {
670
- taskDescription: workflowState.state.taskDescription,
671
- taskType: workflowState.state.taskType,
672
- commitMessage: args.commitMessage,
673
- };
674
-
675
- workflowState.addToHistory(entry);
676
- workflowState.reset();
677
- await workflowState.save();
678
-
679
- return textResponse(
680
- `✅ Task completed!\n\nCommit message: ${args.commitMessage}\nHistory recorded.`
681
- );
682
- }
683
-
684
- async function handleForceCompleteTask(args, workflowState) {
685
- const commitMessageArg =
686
- typeof args.commitMessage === "string" ? args.commitMessage.trim() : "";
687
- const reason = typeof args.reason === "string" ? args.reason.trim() : "";
688
-
689
- const commitMessage =
690
- commitMessageArg || workflowState.state.lastCommitMessage || "(no commit recorded)";
691
-
692
- workflowState.addToHistory({
693
- taskDescription: workflowState.state.taskDescription,
694
- taskType: workflowState.state.taskType,
695
- commitMessage,
696
- forced: true,
697
- forceReason: reason,
698
- });
699
-
700
- workflowState.reset();
701
- await workflowState.save();
702
-
703
- const reasonText = reason ? reason : "(none provided)";
704
-
705
- return textResponse(
706
- `⚠️ Task force-completed.\n\nRecorded commit message: ${commitMessage}\nReason: ${reasonText}\nWorkflow state reset. Start a new task when ready.`
707
- );
708
- }
709
-
710
- async function handleDropTask(args, workflowState) {
711
- workflowState.addToHistory({
712
- taskDescription: workflowState.state.taskDescription,
713
- taskType: workflowState.state.taskType,
714
- commitMessage: workflowState.state.lastCommitMessage,
715
- dropped: true,
716
- dropReason: typeof args.reason === "string" ? args.reason : "",
717
- });
718
- workflowState.reset();
719
- await workflowState.save();
720
-
721
- return textResponse("⚠️ Task dropped. Workflow state reset.");
722
- }
723
-
724
- function handleGetWorkflowStatus(workflowState) {
725
- const status = workflowState.state;
726
- const nextStep = status ? status.currentPhase : "idle";
727
-
728
- return textResponse(
729
- `📊 Current Workflow Status:
730
-
731
- Phase: ${status.currentPhase}
732
- Task: ${status.taskDescription || "(none)"}
733
- Bug fixed: ${status.bugFixed ? "✅" : "❌"}
734
- Tests created: ${status.testsCreated ? "✅" : "❌"}
735
- Tests passed: ${status.testsPassed ? "✅" : "❌"}
736
- Documentation: ${status.documentationCreated ? "✅" : "❌"}
737
- Ready check: ${status.readyCheckCompleted ? "✅" : "❌"}
738
- Commit & push: ${status.commitAndPushCompleted ? "✅" : "❌"}
739
- Released: ${status.released ? "✅" : "❌"}
740
-
741
- Next step: ${nextStep}`
742
- );
743
- }
744
-
745
- function handleViewHistory(args, workflowState) {
746
- const message = summarizeHistory(workflowState.state.history, args.limit || 10);
747
- if (!message) {
748
- return textResponse("📜 No workflow history yet.");
749
- }
750
-
751
- return textResponse(message);
752
- }
753
-
754
- function handleProjectSummary(workflowState) {
755
- const summary = generateProjectSummary(workflowState.state.history);
756
- return textResponse(summary);
757
- }
758
-
759
- async function handleProjectSummaryData(workflowState) {
760
- const summaryPath = path.join(path.dirname(workflowState.stateFile), PROJECT_SUMMARY_FILENAME);
761
- try {
762
- const data = await fs.readFile(summaryPath, "utf-8");
763
- const parsed = JSON.parse(data);
764
- const formatted = `🧠 Project Knowledge Summary (from persisted data)\n\n📊 Stats\n- Total tasks completed: ${parsed.totalTasks}\n- Recent task mix: ${Object.entries(parsed.taskTypes).map(([t, c]) => `${t}: ${c}`).join(", ")}\n- Last active: ${parsed.lastActive ? new Date(parsed.lastActive).toLocaleDateString() : "Never"}\n- Updated: ${new Date(parsed.updatedAt).toLocaleString()}\n\n📝 Recent tasks (last 5)\n${parsed.recentTasks.map((e, i) => `${i + 1}. ${e.description} (${e.type})`).join("\n")}`;
765
- return textResponse(formatted);
766
- } catch (error) {
767
- // Fallback to in-memory generation if file doesn't exist
768
- return handleProjectSummary(workflowState);
769
- }
770
- }
771
-
772
- async function handleProjectSummaryDb(workflowState) {
773
- const { getSummaryForUser } = await import("../db/index.js");
774
- const userId = process.env.DEV_WORKFLOW_USER_ID || "default";
775
- const summary = getSummaryForUser(userId);
776
- if (!summary) {
777
- return textResponse("📜 No project summary in database yet.");
778
- }
779
- const formatted = `🧠 Project Knowledge Summary (from SQLite)\n\n📊 Stats\n- Total tasks completed: ${summary.totalTasks}\n- Recent task mix: ${Object.entries(summary.taskTypes).map(([t, c]) => `${t}: ${c}`).join(", ")}\n- Last active: ${summary.lastActive ? new Date(summary.lastActive).toLocaleDateString() : "Never"}\n- Updated: ${new Date(summary.updatedAt).toLocaleString()}\n\n📝 Recent tasks (last 5)\n${summary.recentTasks.map((e, i) => `${i + 1}. ${e.description} (${e.type})`).join("\n")}`;
780
- return textResponse(formatted);
781
- }
782
-
783
- async function handleRerunWorkflow(workflowState) {
784
- // Capture task context BEFORE reset to avoid losing it
785
- const currentDescription = workflowState.state.taskDescription;
786
- const currentType = workflowState.state.taskType;
787
-
788
- if (!currentDescription) {
789
- return textResponse("⚠️ No active task to rerun. Use 'start_task' to begin a new workflow.");
790
- }
791
-
792
- // Reset all progress flags but preserve history
793
- workflowState.reset();
794
-
795
- // Restore task context after reset
796
- workflowState.state.currentPhase = "coding";
797
- workflowState.state.taskDescription = currentDescription;
798
- workflowState.state.taskType = currentType;
799
- await workflowState.save();
800
-
801
- return textResponse(
802
- `🔄 Rerunning workflow from the start!\n\nTask: ${currentDescription}\nType: ${currentType}\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. ⏳ Run 'commit_and_push' (commits and pushes)\n8. ⏳ Run 'perform_release' (handles versioning and tags)\n9. ⏳ Complete task\n\n🎯 Be conscious about what you're coding!`
803
- );
804
- }
805
-
806
- function isErrorResponse(response) {
807
- const text = response?.content?.[0]?.text || "";
808
- return /^\s*[⚠️❌]/u.test(text);
809
- }
810
-
811
- function summarizeResponse(label, response) {
812
- const text = response?.content?.[0]?.text || "";
813
- const firstLine = text.split("\n")[0] || "";
814
- return `${label}: ${firstLine}`;
815
- }
816
-
817
- async function handleRunFullWorkflow(args, { workflowState, exec, git, utils }) {
818
- const description = workflowState.state.taskDescription;
819
- if (!description) {
820
- return textResponse("⚠️ Please start a task first using 'start_task' before running the full workflow.");
821
- }
822
-
823
- await workflowState.ensurePrimaryFile();
824
-
825
- const requestedBranch = typeof args.branch === "string" ? args.branch.trim() : "";
826
- if (requestedBranch) {
827
- workflowState.state.lastPushBranch = requestedBranch;
828
- await workflowState.save();
829
- }
830
-
831
- const requiredStrings = [
832
- { key: "summary", message: "Provide a non-empty 'summary' for mark_bug_fixed." },
833
- { key: "testCommand", message: "Provide a non-empty 'testCommand' for run_tests." },
834
- { key: "documentationType", message: "Provide 'documentationType' for create_documentation." },
835
- { key: "documentationSummary", message: "Provide a non-empty 'documentationSummary'." },
836
- { key: "commitMessage", message: "Provide a non-empty 'commitMessage'." },
837
- { key: "releaseCommand", message: "Provide a non-empty 'releaseCommand'." },
838
- ];
839
-
840
- for (const { key, message } of requiredStrings) {
841
- const value = args[key];
842
- if (typeof value !== "string" || value.trim() === "") {
843
- return textResponse(`⚠️ ${message}`);
844
- }
845
- }
846
-
847
- const steps = [];
848
- const testsPassed = typeof args.testsPassed === "boolean" ? args.testsPassed : true;
849
- const testDetails = typeof args.testDetails === "string" ? args.testDetails : "";
850
- const releaseArgs = {
851
- command: args.releaseCommand,
852
- };
853
- if (typeof args.releaseNotes === "string" && args.releaseNotes.trim() !== "") {
854
- releaseArgs.notes = args.releaseNotes;
855
- }
856
- if (typeof args.releaseType === "string" && args.releaseType.trim() !== "") {
857
- releaseArgs.releaseType = args.releaseType;
858
- }
859
- if (typeof args.preset === "string" && args.preset.trim() !== "") {
860
- releaseArgs.preset = args.preset;
861
- }
862
-
863
- // Smart flow: detect current phase and run only remaining steps
864
- async function executeStep(name, handlerFn, handlerArgs = []) {
865
- if (isErrorResponse(handlerFn)) {
866
- return handlerFn;
867
- }
868
- const response = await handlerFn(...handlerArgs);
869
- if (isErrorResponse(response)) {
870
- return response;
871
- }
872
- steps.push(summarizeResponse(name, response));
873
- await workflowState.load();
874
- return response;
875
- }
876
-
877
- while (true) {
878
- const state = workflowState.state;
879
-
880
- if (!state.bugFixed) {
881
- const resp = await executeStep("mark_bug_fixed", handleMarkBugFixed, [{ summary: args.summary }, workflowState]);
882
- if (isErrorResponse(resp)) return resp;
883
- continue;
884
- }
885
-
886
- if (!state.testsCreated) {
887
- const resp = await executeStep("create_tests", handleCreateTests, [workflowState]);
888
- if (isErrorResponse(resp)) return resp;
889
- continue;
890
- }
891
-
892
- if (!state.testsPassed && !state.testsSkipped) {
893
- const resp = await executeStep("run_tests", handleRunTests, [
894
- { passed: testsPassed, testCommand: args.testCommand, details: testDetails },
895
- workflowState,
896
- ]);
897
- if (isErrorResponse(resp)) return resp;
898
- continue;
899
- }
900
-
901
- if (!state.documentationCreated) {
902
- const resp = await executeStep("create_documentation", handleCreateDocumentation, [
903
- { documentationType: args.documentationType, summary: args.documentationSummary },
904
- workflowState,
905
- ]);
906
- if (isErrorResponse(resp)) return resp;
907
- continue;
908
- }
909
-
910
- if (!state.readyCheckCompleted) {
911
- const resp = await executeStep("check_ready_to_commit", handleReadyCheck, [workflowState]);
912
- if (isErrorResponse(resp)) return resp;
913
- continue;
914
- }
915
-
916
- if (!state.commitAndPushCompleted) {
917
- const resp = await executeStep("commit_and_push", handleCommitAndPush, [
918
- {
919
- commitMessage: args.commitMessage,
920
- branch: requestedBranch,
921
- },
922
- { workflowState, exec, git, utils },
923
- ]);
924
- if (isErrorResponse(resp)) return resp;
925
- continue;
926
- }
927
-
928
- if (!state.released) {
929
- const resp = await executeStep("perform_release", handlePerformRelease, [
930
- releaseArgs,
931
- { workflowState, exec, git, utils },
932
- ]);
933
- if (isErrorResponse(resp)) return resp;
934
- continue;
935
- }
936
-
937
- const completeResponse = await handleCompleteTask({ commitMessage: args.commitMessage }, workflowState);
938
- if (isErrorResponse(completeResponse)) {
939
- return completeResponse;
940
- }
941
- steps.push(summarizeResponse("complete_task", completeResponse));
942
- break;
943
- }
944
-
945
- const summary = steps.map((line, index) => `${index + 1}. ${line}`).join("\n");
946
- const celebration = workflowState.state.history?.length
947
- ? "🎉 Cheers! We solved another issue and captured it in the knowledge base."
948
- : "";
949
- const message = [`✅ Full workflow completed successfully!`, celebration, summary].filter(Boolean).join("\n\n");
950
- return textResponse(message);
951
- }
952
-
953
- export async function handleToolCall({
954
- request,
955
- normalizeRequestArgs,
956
- workflowState,
957
- exec,
958
- git,
959
- utils,
960
- }) {
961
- try {
962
- const { name } = request.params;
963
- const { args, error: argumentsError } = normalizeRequestArgs(request.params.arguments);
964
-
965
- if (argumentsError) {
966
- return textResponse(argumentsError);
967
- }
968
-
969
- switch (name) {
970
- case "start_task":
971
- return handleStartTask(args, workflowState);
972
- case "mark_bug_fixed":
973
- return handleMarkBugFixed(args, workflowState);
974
- case "create_tests":
975
- return handleCreateTests(workflowState);
976
- case "skip_tests":
977
- return handleSkipTests(args, workflowState);
978
- case "run_tests":
979
- return handleRunTests(args, workflowState);
980
- case "create_documentation":
981
- return handleCreateDocumentation(args, workflowState);
982
- case "check_ready_to_commit":
983
- return handleReadyCheck(workflowState);
984
- case "commit_and_push":
985
- return handleCommitAndPush(args, { workflowState, exec, git, utils });
986
- case "perform_release":
987
- return handlePerformRelease(args, { workflowState, exec, git, utils });
988
- case "complete_task":
989
- return handleCompleteTask(args, workflowState);
990
- case "force_complete_task":
991
- return handleForceCompleteTask(args, workflowState);
992
- case "drop_task":
993
- return handleDropTask(args, workflowState);
994
- case "get_workflow_status":
995
- return handleGetWorkflowStatus(workflowState);
996
- case "view_history":
997
- return handleViewHistory(args, workflowState);
998
- case "project_summary":
999
- return handleProjectSummary(workflowState);
1000
- case "project_summary_data":
1001
- return handleProjectSummaryData(workflowState);
1002
- case "project_summary_db":
1003
- return handleProjectSummaryDb(workflowState);
1004
- case "continue_workflow":
1005
- return handleContinueWorkflow(workflowState, { workflowState, exec, git, utils });
1006
- case "rerun_workflow":
1007
- return handleRerunWorkflow(workflowState);
1008
- case "run_full_workflow":
1009
- return handleRunFullWorkflow(args, { workflowState, exec, git, utils });
1010
- default:
1011
- throw new Error(`Unknown tool: ${name}`);
1012
- }
1013
- } catch (error) {
1014
- console.error("Tool handler error:", error);
1015
- return textResponse(
1016
- "⚠️ Internal server error while processing the tool request. Please try again."
1017
- );
1018
- }
1019
- }
1
+ import fs from "fs/promises";
2
+ import { existsSync } from "fs";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ const PROJECT_SUMMARY_FILENAME = "project-summary.json";
9
+
10
+ const textResponse = (text) => ({
11
+ content: [
12
+ {
13
+ type: "text",
14
+ text,
15
+ },
16
+ ],
17
+ });
18
+
19
+ async function resetToCommitIfWorkingChanges(workflowState, git) {
20
+ if (!workflowState.state.commitAndPushCompleted) {
21
+ return false;
22
+ }
23
+
24
+ const hasWorkingChanges = await git.hasWorkingChanges();
25
+ const hasStagedChanges = await git.hasStagedChanges();
26
+
27
+ if (!hasWorkingChanges && !hasStagedChanges) {
28
+ return false;
29
+ }
30
+
31
+ workflowState.state.commitAndPushCompleted = false;
32
+ workflowState.state.released = false;
33
+ workflowState.state.currentPhase = "commit";
34
+ workflowState.state.releaseCommand = "";
35
+ workflowState.state.releaseNotes = "";
36
+ await workflowState.save();
37
+ return true;
38
+ }
39
+
40
+ function formatChecklist(checks) {
41
+ return checks.map((c) => `${c.done ? "✅" : "❌"} ${c.name}`).join("\n");
42
+ }
43
+
44
+ function summarizeHistory(history, limit) {
45
+ const recent = history.slice(-limit).reverse();
46
+ if (recent.length === 0) {
47
+ return null;
48
+ }
49
+
50
+ const historyText = recent
51
+ .map(
52
+ (entry, index) =>
53
+ `${index + 1}. [${new Date(entry.timestamp).toLocaleString()}]\n Task: ${entry.taskDescription}\n Type: ${entry.taskType}\n Commit: ${entry.commitMessage}\n`
54
+ )
55
+ .join("\n");
56
+
57
+ return `📜 Workflow History (last ${recent.length} tasks)\n\n${historyText}`;
58
+ }
59
+
60
+ function generateProjectSummary(history) {
61
+ if (!history || history.length === 0) {
62
+ return "No workflow history available.";
63
+ }
64
+
65
+ const taskTypes = {};
66
+ const recentTasks = history.slice(-20);
67
+
68
+ for (const entry of recentTasks) {
69
+ const type = entry.taskType || "other";
70
+ taskTypes[type] = (taskTypes[type] || 0) + 1;
71
+ }
72
+
73
+ const typeSummary = Object.entries(taskTypes)
74
+ .map(([type, count]) => `${type}: ${count}`)
75
+ .join(", ");
76
+
77
+ const totalTasks = history.length;
78
+ const lastTask = history[history.length - 1];
79
+ const lastActive = lastTask ? new Date(lastTask.timestamp).toLocaleDateString() : "Never";
80
+
81
+ return `🧠 Project Knowledge Summary\n\n📊 Stats\n- Total tasks completed: ${totalTasks}\n- Recent task mix: ${typeSummary}\n- Last active: ${lastActive}\n\n📝 Recent tasks (last 5)\n${history.slice(-5).reverse().map((e, i) => `${i + 1}. ${e.taskDescription} (${e.taskType})`).join("\n")}`;
82
+ }
83
+
84
+ function getContinueGuidance(status) {
85
+ switch (status.currentPhase) {
86
+ case "coding": {
87
+ const task = status.taskDescription ? ` "${status.taskDescription}"` : "";
88
+ return `Finish implementing${task} and then run 'mark_bug_fixed' with a brief summary of the change.`;
89
+ }
90
+ case "testing":
91
+ if (!status.testsCreated) {
92
+ return "Create automated coverage now using 'create_tests' before recording any results.";
93
+ }
94
+
95
+ if (status.testsPassed) {
96
+ return "With tests green, move on to 'create_documentation' and capture what changed.";
97
+ }
98
+
99
+ if (status.testsSkipped) {
100
+ return "Document why tests were skipped using 'create_documentation' so the workflow can proceed.";
101
+ }
102
+
103
+ return status.testCommand
104
+ ? `Fix any failing tests and re-run '${status.testCommand}'. Once they pass, record the results with 'run_tests'.`
105
+ : "Run your automated tests and record the outcome with 'run_tests'. Fix any failures before moving forward.";
106
+ case "documentation":
107
+ return status.testsSkipped
108
+ ? `Document the change (including manual QA for skipped tests) with 'create_documentation'. Reason recorded: ${
109
+ status.testsSkippedReason || "(none provided)"
110
+ }.`
111
+ : "Capture your updates with 'create_documentation' (provide the documentation type and a short summary).";
112
+ case "ready":
113
+ return "Run 'check_ready_to_commit' to verify all prerequisite steps before committing.";
114
+ case "commit":
115
+ return "All checks passed—run 'commit_and_push' with your commit message (and optional branch).";
116
+ case "release":
117
+ return "Commit recorded. Execute 'perform_release' with the release command you ran to publish changes.";
118
+ case "ready_to_complete":
119
+ return "Release recorded. Finish up by calling 'complete_task' with the commit message you used.";
120
+ default:
121
+ return "Review your workflow progress and complete the next required step.";
122
+ }
123
+ }
124
+
125
+ async function handleContinueWorkflow(workflowState, context) {
126
+ const status = workflowState.state;
127
+
128
+ if (!status || status.currentPhase === "idle") {
129
+ return textResponse("⚠️ No active workflow. Use 'start_task' to kick off a new task before continuing.");
130
+ }
131
+
132
+ if (await resetToCommitIfWorkingChanges(workflowState, context.git)) {
133
+ return textResponse(
134
+ "⚠️ 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."
135
+ );
136
+ }
137
+
138
+ if (status.currentPhase === "ready") {
139
+ return handleReadyCheck(workflowState);
140
+ }
141
+
142
+ if (status.currentPhase === "commit") {
143
+ return handleCommitAndPush({}, context);
144
+ }
145
+
146
+ if (status.currentPhase === "release") {
147
+ return textResponse(
148
+ "⚠️ Release phase requires explicit command. Run 'perform_release' with your release command (e.g., 'npm run release')."
149
+ );
150
+ }
151
+
152
+ const sections = [
153
+ `📊 Current Phase: ${status.currentPhase}`,
154
+ `📝 Task: ${status.taskDescription || "(none)"}`,
155
+ ];
156
+
157
+ if (status.testsSkipped) {
158
+ sections.push(`⚠️ Tests skipped: ${status.testsSkippedReason || "(no reason provided)"}`);
159
+ }
160
+
161
+ if (
162
+ status.currentPhase === "testing" &&
163
+ status.testsCreated &&
164
+ !status.testsPassed &&
165
+ typeof status.testCommand === "string" &&
166
+ status.testCommand.trim() !== ""
167
+ ) {
168
+ sections.push(`🧪 Last test command: ${status.testCommand}`);
169
+ }
170
+
171
+ sections.push(`➡️ ${getContinueGuidance(status)}`);
172
+
173
+ return textResponse(sections.join("\n\n"));
174
+ }
175
+
176
+ async function handleStartTask(args, workflowState) {
177
+ workflowState.reset();
178
+ workflowState.state.currentPhase = "coding";
179
+ workflowState.state.taskDescription = args.description;
180
+ workflowState.state.taskType = args.type;
181
+ workflowState.state.testsSkipped = false;
182
+ workflowState.state.testsSkippedReason = "";
183
+ workflowState.state.commitAndPushCompleted = false;
184
+ workflowState.state.lastCommitMessage = "";
185
+ workflowState.state.lastPushBranch = "";
186
+ await workflowState.save();
187
+
188
+ return textResponse(
189
+ `✅ 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. ⏳ Run 'commit_and_push' (commits and pushes)\n8. ⏳ Run 'perform_release' (handles versioning and tags)\n9. ⏳ Complete task\n\nReminder: Focus on writing clean, maintainable code!`
190
+ );
191
+ }
192
+
193
+ async function handleMarkBugFixed(args, workflowState) {
194
+ if (workflowState.state.currentPhase === "idle") {
195
+ return textResponse("⚠️ Please start a task first using 'start_task'!");
196
+ }
197
+
198
+ // Idempotent behavior: if already marked fixed, do not reset downstream flags.
199
+ if (workflowState.state.bugFixed) {
200
+ const summary = typeof args.summary === "string" ? args.summary : "";
201
+ if (summary && summary.trim() !== "") {
202
+ workflowState.state.fixSummary = summary;
203
+ }
204
+
205
+ // Preserve existing progress and set an appropriate phase.
206
+ if (workflowState.state.testsPassed && workflowState.state.documentationCreated) {
207
+ workflowState.state.currentPhase = "ready";
208
+ workflowState.state.readyToCommit = true;
209
+ } else if (workflowState.state.testsCreated) {
210
+ workflowState.state.currentPhase = "testing";
211
+ }
212
+ workflowState.state.readyCheckCompleted = false;
213
+ workflowState.state.commitAndPushCompleted = false;
214
+ await workflowState.save();
215
+
216
+ return textResponse(
217
+ "ℹ️ Already marked as fixed; kept existing tests/documentation. Continue with the next step."
218
+ );
219
+ }
220
+
221
+ workflowState.state.bugFixed = true;
222
+ workflowState.state.testsCreated = false;
223
+ workflowState.state.currentPhase = "testing";
224
+ workflowState.state.testsSkipped = false;
225
+ workflowState.state.testsSkippedReason = "";
226
+ workflowState.state.fixSummary = args.summary;
227
+ workflowState.state.readyCheckCompleted = false;
228
+ workflowState.state.released = false;
229
+ workflowState.state.releaseCommand = "";
230
+ workflowState.state.releaseNotes = "";
231
+ workflowState.state.commitAndPushCompleted = false;
232
+ workflowState.state.lastCommitMessage = "";
233
+ workflowState.state.lastPushBranch = "";
234
+ await workflowState.save();
235
+
236
+ return textResponse(
237
+ `✅ 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. ⏳ Run 'commit_and_push'\n7. ⏳ Run 'perform_release'\n8. ⏳ Complete task\n\n🚫 DO NOT SKIP TESTING!`
238
+ );
239
+ }
240
+
241
+ async function handleCreateTests(workflowState) {
242
+ if (!workflowState.state.bugFixed) {
243
+ return textResponse("⚠️ Please mark your feature/bug as fixed first using 'mark_bug_fixed'!");
244
+ }
245
+
246
+ workflowState.state.testsCreated = true;
247
+ workflowState.state.testsSkipped = false;
248
+ workflowState.state.testsSkippedReason = "";
249
+ workflowState.state.readyCheckCompleted = false;
250
+ workflowState.state.released = false;
251
+ workflowState.state.commitAndPushCompleted = false;
252
+ workflowState.state.lastCommitMessage = "";
253
+ workflowState.state.lastPushBranch = "";
254
+ await workflowState.save();
255
+
256
+ return textResponse(
257
+ "✅ 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. ⏳ Run 'commit_and_push'\n7. ⏳ Run 'perform_release'\n8. ⏳ Complete task\n\n🧪 Run your test command and record the results using 'run_tests'."
258
+ );
259
+ }
260
+
261
+ async function handleSkipTests(args, workflowState) {
262
+ if (workflowState.state.currentPhase === "idle") {
263
+ return textResponse("⚠️ No active task. Use 'start_task' before skipping tests.");
264
+ }
265
+
266
+ if (!workflowState.state.bugFixed) {
267
+ return textResponse("⚠️ Please mark your feature/bug as fixed first using 'mark_bug_fixed'!");
268
+ }
269
+
270
+ const reason = typeof args.reason === "string" ? args.reason.trim() : "";
271
+
272
+ if (!reason) {
273
+ return textResponse("⚠️ Provide a non-empty justification when skipping tests.");
274
+ }
275
+
276
+ workflowState.state.testsCreated = true;
277
+ workflowState.state.testsPassed = true;
278
+ workflowState.state.testsSkipped = true;
279
+ workflowState.state.testsSkippedReason = reason;
280
+ workflowState.state.currentPhase = "documentation";
281
+ workflowState.state.readyToCommit = false;
282
+ workflowState.state.readyCheckCompleted = false;
283
+ workflowState.state.released = false;
284
+ workflowState.state.commitAndPushCompleted = false;
285
+ workflowState.state.lastCommitMessage = "";
286
+ workflowState.state.lastPushBranch = "";
287
+ await workflowState.save();
288
+
289
+ return textResponse(
290
+ `⚠️ Tests skipped for this task.
291
+ Reason: ${reason}
292
+
293
+ Manual verification is now required.
294
+
295
+ Next Steps:
296
+ 1. ✓ Fix/implement feature
297
+ 2. ⚠️ Tests skipped (ensure manual QA)
298
+ 3. ⏳ Create/update documentation
299
+ 4. ⏳ Run 'check_ready_to_commit'
300
+ 5. ⏳ Run 'commit_and_push'
301
+ 6. ⏳ Run 'perform_release'
302
+ 7. ⏳ Complete task`
303
+ );
304
+ }
305
+
306
+ async function handleRunTests(args, workflowState) {
307
+ if (!workflowState.state.bugFixed) {
308
+ return textResponse("⚠️ Please mark your feature/bug as fixed first using 'mark_bug_fixed'!");
309
+ }
310
+
311
+ if (!workflowState.state.testsCreated) {
312
+ return textResponse("⚠️ Please create tests first using 'create_tests' before recording test results!");
313
+ }
314
+
315
+ if (
316
+ typeof args.passed !== "boolean" ||
317
+ typeof args.testCommand !== "string" ||
318
+ args.testCommand.trim() === ""
319
+ ) {
320
+ return textResponse(
321
+ "⚠️ Please provide both 'passed' (boolean) and 'testCommand' (non-empty string) when recording test results."
322
+ );
323
+ }
324
+
325
+ workflowState.state.testsPassed = args.passed;
326
+ workflowState.state.testCommand = args.testCommand;
327
+ workflowState.state.testDetails = typeof args.details === "string" ? args.details : "";
328
+ workflowState.state.testsSkipped = false;
329
+ workflowState.state.testsSkippedReason = "";
330
+ workflowState.state.readyCheckCompleted = false;
331
+ workflowState.state.released = false;
332
+ workflowState.state.commitAndPushCompleted = false;
333
+ workflowState.state.lastCommitMessage = "";
334
+ workflowState.state.lastPushBranch = "";
335
+
336
+ if (!args.passed) {
337
+ workflowState.state.currentPhase = "testing";
338
+ await workflowState.save();
339
+
340
+ return textResponse(
341
+ `❌ TESTS FAILED!\n\n🚫 STOP! DO NOT COMMIT OR PUSH!\n\nYou must:\n1. Fix the failing tests\n2. Run tests again until they pass\n\nTest command: ${args.testCommand}\n\nNever skip or ignore failing tests!`
342
+ );
343
+ }
344
+
345
+ workflowState.state.currentPhase = "documentation";
346
+ await workflowState.save();
347
+
348
+ return textResponse(
349
+ `✅ All tests passed! 🎉\n\nTest command: ${args.testCommand}\n\n📝 Now create or update documentation using 'create_documentation' with:\n- documentationType: "PRD", "README", "RELEASE_NOTES", "inline-comments", "API-docs", "changelog", or "other"\n- summary: Brief description of what was documented\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. ⏳ Run 'commit_and_push'\n7. ⏳ Run 'perform_release'\n8. ⏳ Complete task`
350
+ );
351
+ }
352
+
353
+ async function handleCreateDocumentation(args, workflowState) {
354
+ if (!workflowState.state.testsPassed) {
355
+ return textResponse("⚠️ Please ensure tests are passing first! Run 'run_tests' with passed=true.");
356
+ }
357
+
358
+ workflowState.state.documentationCreated = true;
359
+ workflowState.state.documentationType = args.documentationType;
360
+ workflowState.state.documentationSummary = args.summary;
361
+ workflowState.state.currentPhase = "ready";
362
+ workflowState.state.readyToCommit = true;
363
+ workflowState.state.readyCheckCompleted = false;
364
+ workflowState.state.released = false;
365
+ workflowState.state.commitAndPushCompleted = false;
366
+ workflowState.state.lastCommitMessage = "";
367
+ workflowState.state.lastPushBranch = "";
368
+ await workflowState.save();
369
+
370
+ return textResponse(
371
+ `✅ Documentation created/updated!\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/update documentation\n5. ⏳ Run 'check_ready_to_commit' to verify\n6. ⏳ Run 'commit_and_push'\n7. ⏳ Run 'perform_release'\n8. ⏳ Mark as complete with 'complete_task'`
372
+ );
373
+ }
374
+
375
+ async function handleReadyCheck(workflowState) {
376
+ const status = workflowState.state;
377
+ const testsCreatedDone = status.testsSkipped || status.testsCreated;
378
+ const testsPassedDone = status.testsSkipped || status.testsPassed;
379
+ const checks = [
380
+ { name: "Task started", done: status.currentPhase !== "idle" },
381
+ { name: "Feature/bug fixed", done: status.bugFixed },
382
+ {
383
+ name: status.testsSkipped ? "Tests skipped (manual QA documented)" : "Tests created",
384
+ done: testsCreatedDone,
385
+ },
386
+ {
387
+ name: status.testsSkipped ? "Tests skipped acknowledged" : "Tests passed",
388
+ done: testsPassedDone,
389
+ },
390
+ { name: "Documentation created", done: status.documentationCreated },
391
+ ];
392
+
393
+ const allDone = checks.every((c) => c.done);
394
+ const checkList = formatChecklist(checks);
395
+
396
+ workflowState.state.readyCheckCompleted = allDone;
397
+ if (allDone) {
398
+ workflowState.state.currentPhase = workflowState.state.commitAndPushCompleted
399
+ ? workflowState.state.released
400
+ ? "ready_to_complete"
401
+ : "release"
402
+ : "commit";
403
+ }
404
+ await workflowState.save();
405
+
406
+ if (allDone) {
407
+ return textResponse(
408
+ `🎉 ALL CHECKS PASSED!\n\n${checkList}\n\n✅ Next actions:\n1. Run 'commit_and_push' (commits and pushes your changes)\n2. Run 'perform_release' (handles versioning, tags, and final push)\n3. Finish with 'complete_task'\n\nTip: Provide the optional 'branch' argument to 'commit_and_push' to push to a non-default branch.\n\nTask: ${status.taskDescription}`
409
+ );
410
+ }
411
+
412
+ return textResponse(`⚠️ NOT READY TO COMMIT!\n\n${checkList}\n\nPlease complete all steps before committing.`);
413
+ }
414
+
415
+ async function handleCommitAndPush(args, context) {
416
+ const { workflowState, exec, git, utils } = context;
417
+ if (!workflowState.state.readyToCommit || !workflowState.state.readyCheckCompleted) {
418
+ // Be smart: auto-run the ready check and proceed if it passes.
419
+ const readyResponse = await handleReadyCheck(workflowState);
420
+ if (workflowState.state.currentPhase !== "commit") {
421
+ return readyResponse;
422
+ }
423
+ }
424
+
425
+ const providedCommitMessage =
426
+ typeof args.commitMessage === "string" ? args.commitMessage.trim() : "";
427
+
428
+ const requestedBranch = typeof args.branch === "string" ? args.branch.trim() : "";
429
+ const primaryBranch = await git.getPrimaryBranch();
430
+ const currentBranch = await git.getCurrentBranch();
431
+ const branchForPush = requestedBranch || primaryBranch || currentBranch || "";
432
+
433
+ const hasWorkingChanges = await git.hasWorkingChanges();
434
+
435
+ if (!hasWorkingChanges) {
436
+ const lastCommitMessage = await git.getLastCommitMessage();
437
+ const effectiveCommitMessage = lastCommitMessage || providedCommitMessage;
438
+
439
+ workflowState.state.commitAndPushCompleted = true;
440
+ workflowState.state.lastCommitMessage = effectiveCommitMessage || "";
441
+ workflowState.state.lastPushBranch = branchForPush;
442
+ workflowState.state.currentPhase = workflowState.state.released ? "ready_to_complete" : "release";
443
+ await workflowState.save();
444
+
445
+ return textResponse(
446
+ `ℹ️ No changes detected. Assuming commit and push already completed.\nLast commit: ${
447
+ effectiveCommitMessage || "(unavailable)"
448
+ }\n\nNext: Run 'perform_release' to handle the release and push tags.`
449
+ );
450
+ }
451
+
452
+ if (!(await git.hasTestChanges()) && !workflowState.state.testsSkipped) {
453
+ return textResponse(
454
+ "⚠️ Please include test updates in your changes before running 'commit_and_push'. Ensure at least one test file is modified."
455
+ );
456
+ }
457
+
458
+ try {
459
+ await exec("git add .");
460
+ } catch (error) {
461
+ return textResponse(
462
+ `❌ Failed to stage files:\n\n${error.stderr || error.stdout || error.message}`
463
+ );
464
+ }
465
+
466
+ const stagedChanges = await git.getStagedChanges();
467
+ const { summary: generatedSummary, body: commitBody } = utils.createCommitMessageParts(
468
+ stagedChanges,
469
+ providedCommitMessage
470
+ );
471
+
472
+ if (!generatedSummary) {
473
+ return textResponse("⚠️ Unable to generate commit message. Please provide a 'commitMessage' manually.");
474
+ }
475
+
476
+ try {
477
+ if (commitBody) {
478
+ await exec(
479
+ `git commit -m ${utils.shellEscape(generatedSummary)} -m ${utils.shellEscape(commitBody)}`
480
+ );
481
+ } else {
482
+ await exec(`git commit -m ${utils.shellEscape(generatedSummary)}`);
483
+ }
484
+ } catch (error) {
485
+ const output = error.stderr || error.stdout || error.message || "Unknown error";
486
+ if (output.includes("nothing to commit")) {
487
+ return textResponse(
488
+ "⚠️ Nothing to commit. Make additional changes before running 'commit_and_push'."
489
+ );
490
+ }
491
+
492
+ return textResponse(`❌ git commit failed:\n\n${output}`);
493
+ }
494
+
495
+ try {
496
+ const pushCommand = branchForPush
497
+ ? `git push origin ${utils.shellEscape(branchForPush)}`
498
+ : "git push";
499
+ await exec(pushCommand);
500
+ } catch (error) {
501
+ return textResponse(
502
+ `❌ git push failed:\n\n${error.stderr || error.stdout || error.message}`
503
+ );
504
+ }
505
+
506
+ workflowState.state.commitAndPushCompleted = true;
507
+ workflowState.state.lastCommitMessage = generatedSummary;
508
+ workflowState.state.lastPushBranch = branchForPush;
509
+ workflowState.state.currentPhase = workflowState.state.released ? "ready_to_complete" : "release";
510
+ await workflowState.save();
511
+
512
+ return textResponse(
513
+ `✅ Commit and push completed!\n\nCommit message: ${generatedSummary}\nPushed to: ${
514
+ branchForPush || "(default upstream)"
515
+ }\n\nNext: Run 'perform_release' to handle the release and push tags.`
516
+ );
517
+ }
518
+
519
+ async function handlePerformRelease(args, context) {
520
+ const { workflowState, exec, git, utils } = context;
521
+
522
+ if (workflowState.state.currentPhase === "idle") {
523
+ return textResponse("⚠️ Please start a task first using 'start_task'!");
524
+ }
525
+
526
+ if (await resetToCommitIfWorkingChanges(workflowState, git)) {
527
+ return textResponse(
528
+ "⚠️ Detected new changes after the last commit. Please run 'commit_and_push' again before recording a release."
529
+ );
530
+ }
531
+
532
+ if (!workflowState.state.readyCheckCompleted) {
533
+ return textResponse(
534
+ "⚠️ Please run 'check_ready_to_commit' and ensure all checks pass before releasing!"
535
+ );
536
+ }
537
+
538
+ if (!workflowState.state.commitAndPushCompleted) {
539
+ const hasWorkingChanges = await git.hasWorkingChanges();
540
+
541
+ if (hasWorkingChanges) {
542
+ return textResponse(
543
+ "⚠️ Please run 'commit_and_push' after the ready check before recording a release!"
544
+ );
545
+ }
546
+
547
+ workflowState.state.currentPhase = "commit";
548
+ await workflowState.save();
549
+
550
+ return textResponse(
551
+ "⚠️ Please run 'commit_and_push' after the ready check before recording a release!"
552
+ );
553
+ }
554
+
555
+ const rawReleaseCommand = typeof args.command === "string" ? args.command.trim() : "";
556
+ const releaseTypeArg =
557
+ typeof args.releaseType === "string" ? args.releaseType.trim().toLowerCase() : "";
558
+ const presetArg = typeof args.preset === "string" ? args.preset.trim().toLowerCase() : "";
559
+ const recognizedTypes = new Set(["major", "minor", "patch"]);
560
+
561
+ const tokens = rawReleaseCommand
562
+ .toLowerCase()
563
+ .split(/\s+/)
564
+ .map((token) => token.trim())
565
+ .filter(Boolean);
566
+
567
+ let releaseType = "";
568
+ if (recognizedTypes.has(releaseTypeArg)) {
569
+ releaseType = releaseTypeArg;
570
+ } else if (recognizedTypes.has(presetArg)) {
571
+ releaseType = presetArg;
572
+ } else if (tokens.length > 0) {
573
+ const possibleType = tokens[tokens.length - 1];
574
+ if (recognizedTypes.has(possibleType)) {
575
+ releaseType = possibleType;
576
+ }
577
+ }
578
+
579
+ let releaseCommand = rawReleaseCommand;
580
+ if (releaseCommand && recognizedTypes.has(releaseCommand.toLowerCase())) {
581
+ releaseType = releaseCommand.toLowerCase();
582
+ releaseCommand = "";
583
+ }
584
+
585
+ const scriptTypeMatch = /release:(major|minor|patch)/i.exec(releaseCommand);
586
+ if (scriptTypeMatch) {
587
+ releaseType = scriptTypeMatch[1].toLowerCase();
588
+ }
589
+
590
+ if (!releaseCommand && releaseType) {
591
+ releaseCommand = `npm run release:${releaseType}`;
592
+ }
593
+
594
+ if (!releaseCommand) {
595
+ return textResponse("⚠️ Please provide a non-empty 'command' when using 'perform_release'.");
596
+ }
597
+
598
+ let statusOutput;
599
+ try {
600
+ const { stdout } = await exec("git status --porcelain");
601
+ statusOutput = stdout;
602
+ } catch (error) {
603
+ const details = error.stderr || error.stdout || error.message || "Unknown error";
604
+ return textResponse(
605
+ `⚠️ Unable to verify Git status. Refusing to run the release command until the repository state can be checked.\n\nDetails:\n${details}`
606
+ );
607
+ }
608
+
609
+ const statusSummary = git.workingTreeSummary(statusOutput);
610
+ if (statusSummary.hasChanges) {
611
+ const changeDetails =
612
+ statusSummary.lines.length > 0
613
+ ? statusSummary.lines.map((line) => `• ${line}`).join("\n")
614
+ : "• (unable to list changes)";
615
+
616
+ return textResponse(
617
+ `⚠️ Working tree not clean! Please commit or stash all changes before running 'perform_release'.\n\nDetected changes:\n${changeDetails}`
618
+ );
619
+ }
620
+
621
+ const lastCommitMessage = await git.getLastCommitMessage();
622
+ if (!releaseType) {
623
+ releaseType = utils.determineReleaseTypeFromCommit(
624
+ lastCommitMessage || workflowState.state.lastCommitMessage
625
+ );
626
+ }
627
+
628
+ const finalCommand =
629
+ releaseCommand.includes("--release-as") || /release:(major|minor|patch)/i.test(releaseCommand)
630
+ ? releaseCommand
631
+ : `${releaseCommand} -- --release-as ${releaseType}`;
632
+
633
+ try {
634
+ await exec(finalCommand);
635
+ } catch (error) {
636
+ return textResponse(
637
+ `❌ Release command failed:\n\n${error.stderr || error.stdout || error.message}`
638
+ );
639
+ }
640
+
641
+ const branchForPush = workflowState.state.lastPushBranch || (await git.getCurrentBranch()) || "";
642
+ const pushCommand = branchForPush
643
+ ? `git push --follow-tags origin ${utils.shellEscape(branchForPush)}`
644
+ : "git push --follow-tags";
645
+
646
+ try {
647
+ await exec(pushCommand);
648
+ } catch (error) {
649
+ return textResponse(
650
+ `❌ git push (with tags) failed:\n\n${error.stderr || error.stdout || error.message}`
651
+ );
652
+ }
653
+
654
+ workflowState.state.released = true;
655
+ workflowState.state.releaseCommand = finalCommand;
656
+ workflowState.state.releaseNotes = typeof args.notes === "string" ? args.notes : "";
657
+ workflowState.state.currentPhase = "ready_to_complete";
658
+ await workflowState.save();
659
+
660
+ const message = summarizeHistory(workflowState.state.history, args.limit || 10);
661
+ if (!message) {
662
+ return textResponse("📜 No workflow history yet.");
663
+ }
664
+
665
+ return textResponse(message);
666
+ }
667
+
668
+ async function handleCompleteTask(args, workflowState) {
669
+ const entry = {
670
+ taskDescription: workflowState.state.taskDescription,
671
+ taskType: workflowState.state.taskType,
672
+ commitMessage: args.commitMessage,
673
+ };
674
+
675
+ workflowState.addToHistory(entry);
676
+ workflowState.reset();
677
+ await workflowState.save();
678
+
679
+ return textResponse(
680
+ `✅ Task completed!\n\nCommit message: ${args.commitMessage}\nHistory recorded.`
681
+ );
682
+ }
683
+
684
+ async function handleForceCompleteTask(args, workflowState) {
685
+ const commitMessageArg =
686
+ typeof args.commitMessage === "string" ? args.commitMessage.trim() : "";
687
+ const reason = typeof args.reason === "string" ? args.reason.trim() : "";
688
+
689
+ const commitMessage =
690
+ commitMessageArg || workflowState.state.lastCommitMessage || "(no commit recorded)";
691
+
692
+ workflowState.addToHistory({
693
+ taskDescription: workflowState.state.taskDescription,
694
+ taskType: workflowState.state.taskType,
695
+ commitMessage,
696
+ forced: true,
697
+ forceReason: reason,
698
+ });
699
+
700
+ workflowState.reset();
701
+ await workflowState.save();
702
+
703
+ const reasonText = reason ? reason : "(none provided)";
704
+
705
+ return textResponse(
706
+ `⚠️ Task force-completed.\n\nRecorded commit message: ${commitMessage}\nReason: ${reasonText}\nWorkflow state reset. Start a new task when ready.`
707
+ );
708
+ }
709
+
710
+ async function handleDropTask(args, workflowState) {
711
+ workflowState.addToHistory({
712
+ taskDescription: workflowState.state.taskDescription,
713
+ taskType: workflowState.state.taskType,
714
+ commitMessage: workflowState.state.lastCommitMessage,
715
+ dropped: true,
716
+ dropReason: typeof args.reason === "string" ? args.reason : "",
717
+ });
718
+ workflowState.reset();
719
+ await workflowState.save();
720
+
721
+ return textResponse("⚠️ Task dropped. Workflow state reset.");
722
+ }
723
+
724
+ function handleGetWorkflowStatus(workflowState) {
725
+ const status = workflowState.state;
726
+ const nextStep = status ? status.currentPhase : "idle";
727
+
728
+ return textResponse(
729
+ `📊 Current Workflow Status:
730
+
731
+ Phase: ${status.currentPhase}
732
+ Task: ${status.taskDescription || "(none)"}
733
+ Bug fixed: ${status.bugFixed ? "✅" : "❌"}
734
+ Tests created: ${status.testsCreated ? "✅" : "❌"}
735
+ Tests passed: ${status.testsPassed ? "✅" : "❌"}
736
+ Documentation: ${status.documentationCreated ? "✅" : "❌"}
737
+ Ready check: ${status.readyCheckCompleted ? "✅" : "❌"}
738
+ Commit & push: ${status.commitAndPushCompleted ? "✅" : "❌"}
739
+ Released: ${status.released ? "✅" : "❌"}
740
+
741
+ Next step: ${nextStep}`
742
+ );
743
+ }
744
+
745
+ function handleViewHistory(args, workflowState) {
746
+ const message = summarizeHistory(workflowState.state.history, args.limit || 10);
747
+ if (!message) {
748
+ return textResponse("📜 No workflow history yet.");
749
+ }
750
+
751
+ return textResponse(message);
752
+ }
753
+
754
+ function handleProjectSummary(workflowState) {
755
+ const summary = generateProjectSummary(workflowState.state.history);
756
+ return textResponse(summary);
757
+ }
758
+
759
+ async function handleProjectSummaryData(workflowState) {
760
+ const summaryPath = path.join(path.dirname(workflowState.stateFile), PROJECT_SUMMARY_FILENAME);
761
+ try {
762
+ const data = await fs.readFile(summaryPath, "utf-8");
763
+ const parsed = JSON.parse(data);
764
+ const formatted = `🧠 Project Knowledge Summary (from persisted data)\n\n📊 Stats\n- Total tasks completed: ${parsed.totalTasks}\n- Recent task mix: ${Object.entries(parsed.taskTypes).map(([t, c]) => `${t}: ${c}`).join(", ")}\n- Last active: ${parsed.lastActive ? new Date(parsed.lastActive).toLocaleDateString() : "Never"}\n- Updated: ${new Date(parsed.updatedAt).toLocaleString()}\n\n📝 Recent tasks (last 5)\n${parsed.recentTasks.map((e, i) => `${i + 1}. ${e.description} (${e.type})`).join("\n")}`;
765
+ return textResponse(formatted);
766
+ } catch (error) {
767
+ // Fallback to in-memory generation if file doesn't exist
768
+ return handleProjectSummary(workflowState);
769
+ }
770
+ }
771
+
772
+ async function handleProjectSummaryDb(workflowState) {
773
+ const { getSummaryForUser } = await import("../db/index.js");
774
+ const userId = process.env.DEV_WORKFLOW_USER_ID || "default";
775
+ const summary = getSummaryForUser(userId);
776
+ if (!summary) {
777
+ return textResponse("📜 No project summary in database yet.");
778
+ }
779
+ const formatted = `🧠 Project Knowledge Summary (from SQLite)\n\n📊 Stats\n- Total tasks completed: ${summary.totalTasks}\n- Recent task mix: ${Object.entries(summary.taskTypes).map(([t, c]) => `${t}: ${c}`).join(", ")}\n- Last active: ${summary.lastActive ? new Date(summary.lastActive).toLocaleDateString() : "Never"}\n- Updated: ${new Date(summary.updatedAt).toLocaleString()}\n\n📝 Recent tasks (last 5)\n${summary.recentTasks.map((e, i) => `${i + 1}. ${e.description} (${e.type})`).join("\n")}`;
780
+ return textResponse(formatted);
781
+ }
782
+
783
+ async function handleRerunWorkflow(workflowState) {
784
+ // Capture task context BEFORE reset to avoid losing it
785
+ const currentDescription = workflowState.state.taskDescription;
786
+ const currentType = workflowState.state.taskType;
787
+
788
+ if (!currentDescription) {
789
+ return textResponse("⚠️ No active task to rerun. Use 'start_task' to begin a new workflow.");
790
+ }
791
+
792
+ // Reset all progress flags but preserve history
793
+ workflowState.reset();
794
+
795
+ // Restore task context after reset
796
+ workflowState.state.currentPhase = "coding";
797
+ workflowState.state.taskDescription = currentDescription;
798
+ workflowState.state.taskType = currentType;
799
+ await workflowState.save();
800
+
801
+ return textResponse(
802
+ `🔄 Rerunning workflow from the start!\n\nTask: ${currentDescription}\nType: ${currentType}\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. ⏳ Run 'commit_and_push' (commits and pushes)\n8. ⏳ Run 'perform_release' (handles versioning and tags)\n9. ⏳ Complete task\n\n🎯 Be conscious about what you're coding!`
803
+ );
804
+ }
805
+
806
+ function isErrorResponse(response) {
807
+ const text = response?.content?.[0]?.text || "";
808
+ return /^\s*[⚠️❌]/u.test(text);
809
+ }
810
+
811
+ function summarizeResponse(label, response) {
812
+ const text = response?.content?.[0]?.text || "";
813
+ const firstLine = text.split("\n")[0] || "";
814
+ return `${label}: ${firstLine}`;
815
+ }
816
+
817
+ async function handleRunFullWorkflow(args, { workflowState, exec, git, utils }) {
818
+ const description = workflowState.state.taskDescription;
819
+ if (!description) {
820
+ return textResponse("⚠️ Please start a task first using 'start_task' before running the full workflow.");
821
+ }
822
+
823
+ await workflowState.ensurePrimaryFile();
824
+
825
+ const requestedBranch = typeof args.branch === "string" ? args.branch.trim() : "";
826
+ if (requestedBranch) {
827
+ workflowState.state.lastPushBranch = requestedBranch;
828
+ await workflowState.save();
829
+ }
830
+
831
+ const requiredStrings = [
832
+ { key: "summary", message: "Provide a non-empty 'summary' for mark_bug_fixed." },
833
+ { key: "testCommand", message: "Provide a non-empty 'testCommand' for run_tests." },
834
+ { key: "documentationType", message: "Provide 'documentationType' for create_documentation." },
835
+ { key: "documentationSummary", message: "Provide a non-empty 'documentationSummary'." },
836
+ { key: "commitMessage", message: "Provide a non-empty 'commitMessage'." },
837
+ { key: "releaseCommand", message: "Provide a non-empty 'releaseCommand'." },
838
+ ];
839
+
840
+ for (const { key, message } of requiredStrings) {
841
+ const value = args[key];
842
+ if (typeof value !== "string" || value.trim() === "") {
843
+ return textResponse(`⚠️ ${message}`);
844
+ }
845
+ }
846
+
847
+ const steps = [];
848
+ const testsPassed = typeof args.testsPassed === "boolean" ? args.testsPassed : true;
849
+ const testDetails = typeof args.testDetails === "string" ? args.testDetails : "";
850
+ const releaseArgs = {
851
+ command: args.releaseCommand,
852
+ };
853
+ if (typeof args.releaseNotes === "string" && args.releaseNotes.trim() !== "") {
854
+ releaseArgs.notes = args.releaseNotes;
855
+ }
856
+ if (typeof args.releaseType === "string" && args.releaseType.trim() !== "") {
857
+ releaseArgs.releaseType = args.releaseType;
858
+ }
859
+ if (typeof args.preset === "string" && args.preset.trim() !== "") {
860
+ releaseArgs.preset = args.preset;
861
+ }
862
+
863
+ // Smart flow: detect current phase and run only remaining steps
864
+ async function executeStep(name, handlerFn, handlerArgs = []) {
865
+ if (isErrorResponse(handlerFn)) {
866
+ return handlerFn;
867
+ }
868
+ const response = await handlerFn(...handlerArgs);
869
+ if (isErrorResponse(response)) {
870
+ return response;
871
+ }
872
+ steps.push(summarizeResponse(name, response));
873
+ await workflowState.load();
874
+ return response;
875
+ }
876
+
877
+ while (true) {
878
+ const state = workflowState.state;
879
+
880
+ if (!state.bugFixed) {
881
+ const resp = await executeStep("mark_bug_fixed", handleMarkBugFixed, [{ summary: args.summary }, workflowState]);
882
+ if (isErrorResponse(resp)) return resp;
883
+ continue;
884
+ }
885
+
886
+ if (!state.testsCreated) {
887
+ const resp = await executeStep("create_tests", handleCreateTests, [workflowState]);
888
+ if (isErrorResponse(resp)) return resp;
889
+ continue;
890
+ }
891
+
892
+ if (!state.testsPassed && !state.testsSkipped) {
893
+ const resp = await executeStep("run_tests", handleRunTests, [
894
+ { passed: testsPassed, testCommand: args.testCommand, details: testDetails },
895
+ workflowState,
896
+ ]);
897
+ if (isErrorResponse(resp)) return resp;
898
+ continue;
899
+ }
900
+
901
+ if (!state.documentationCreated) {
902
+ const resp = await executeStep("create_documentation", handleCreateDocumentation, [
903
+ { documentationType: args.documentationType, summary: args.documentationSummary },
904
+ workflowState,
905
+ ]);
906
+ if (isErrorResponse(resp)) return resp;
907
+ continue;
908
+ }
909
+
910
+ if (!state.readyCheckCompleted) {
911
+ const resp = await executeStep("check_ready_to_commit", handleReadyCheck, [workflowState]);
912
+ if (isErrorResponse(resp)) return resp;
913
+ continue;
914
+ }
915
+
916
+ if (!state.commitAndPushCompleted) {
917
+ const resp = await executeStep("commit_and_push", handleCommitAndPush, [
918
+ {
919
+ commitMessage: args.commitMessage,
920
+ branch: requestedBranch,
921
+ },
922
+ { workflowState, exec, git, utils },
923
+ ]);
924
+ if (isErrorResponse(resp)) return resp;
925
+ continue;
926
+ }
927
+
928
+ if (!state.released) {
929
+ const resp = await executeStep("perform_release", handlePerformRelease, [
930
+ releaseArgs,
931
+ { workflowState, exec, git, utils },
932
+ ]);
933
+ if (isErrorResponse(resp)) return resp;
934
+ continue;
935
+ }
936
+
937
+ const completeResponse = await handleCompleteTask({ commitMessage: args.commitMessage }, workflowState);
938
+ if (isErrorResponse(completeResponse)) {
939
+ return completeResponse;
940
+ }
941
+ steps.push(summarizeResponse("complete_task", completeResponse));
942
+ break;
943
+ }
944
+
945
+ const summary = steps.map((line, index) => `${index + 1}. ${line}`).join("\n");
946
+ const celebration = workflowState.state.history?.length
947
+ ? "🎉 Cheers! We solved another issue and captured it in the knowledge base."
948
+ : "";
949
+ const message = [`✅ Full workflow completed successfully!`, celebration, summary].filter(Boolean).join("\n\n");
950
+ return textResponse(message);
951
+ }
952
+
953
+ export async function handleToolCall({
954
+ request,
955
+ normalizeRequestArgs,
956
+ workflowState,
957
+ exec,
958
+ git,
959
+ utils,
960
+ }) {
961
+ try {
962
+ const { name } = request.params;
963
+ const { args, error: argumentsError } = normalizeRequestArgs(request.params.arguments);
964
+
965
+ if (argumentsError) {
966
+ return textResponse(argumentsError);
967
+ }
968
+
969
+ switch (name) {
970
+ case "start_task":
971
+ return handleStartTask(args, workflowState);
972
+ case "mark_bug_fixed":
973
+ return handleMarkBugFixed(args, workflowState);
974
+ case "create_tests":
975
+ return handleCreateTests(workflowState);
976
+ case "skip_tests":
977
+ return handleSkipTests(args, workflowState);
978
+ case "run_tests":
979
+ return handleRunTests(args, workflowState);
980
+ case "create_documentation":
981
+ return handleCreateDocumentation(args, workflowState);
982
+ case "check_ready_to_commit":
983
+ return handleReadyCheck(workflowState);
984
+ case "commit_and_push":
985
+ return handleCommitAndPush(args, { workflowState, exec, git, utils });
986
+ case "perform_release":
987
+ return handlePerformRelease(args, { workflowState, exec, git, utils });
988
+ case "complete_task":
989
+ return handleCompleteTask(args, workflowState);
990
+ case "force_complete_task":
991
+ return handleForceCompleteTask(args, workflowState);
992
+ case "drop_task":
993
+ return handleDropTask(args, workflowState);
994
+ case "get_workflow_status":
995
+ return handleGetWorkflowStatus(workflowState);
996
+ case "view_history":
997
+ return handleViewHistory(args, workflowState);
998
+ case "project_summary":
999
+ return handleProjectSummary(workflowState);
1000
+ case "project_summary_data":
1001
+ return handleProjectSummaryData(workflowState);
1002
+ case "project_summary_db":
1003
+ return handleProjectSummaryDb(workflowState);
1004
+ case "continue_workflow":
1005
+ return handleContinueWorkflow(workflowState, { workflowState, exec, git, utils });
1006
+ case "rerun_workflow":
1007
+ return handleRerunWorkflow(workflowState);
1008
+ case "run_full_workflow":
1009
+ return handleRunFullWorkflow(args, { workflowState, exec, git, utils });
1010
+ default:
1011
+ throw new Error(`Unknown tool: ${name}`);
1012
+ }
1013
+ } catch (error) {
1014
+ console.error("Tool handler error:", error);
1015
+ return textResponse(
1016
+ "⚠️ Internal server error while processing the tool request. Please try again."
1017
+ );
1018
+ }
1019
+ }