@programinglive/dev-workflow-mcp-server 1.0.8 โ 1.0.11
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/.github/ISSUE_TEMPLATE/bug_report.md +34 -34
- package/.github/ISSUE_TEMPLATE/feature_request.md +25 -25
- package/.github/pull_request_template.md +22 -22
- package/.husky/commit-msg +3 -3
- package/.husky/pre-commit +1 -1
- package/CHANGELOG.md +128 -97
- package/CODE_OF_CONDUCT.md +79 -79
- package/CONTRIBUTING.md +60 -60
- package/LICENSE +21 -21
- package/README.md +64 -63
- package/SECURITY.md +20 -20
- package/commit-helpers.js +166 -0
- package/commitlint.config.js +3 -3
- package/exec.js +4 -0
- package/git-helpers.js +152 -0
- package/index.js +45 -1300
- package/package.json +91 -91
- package/tests/handlers.test.js +188 -0
- package/tests/workflowState.test.js +231 -231
- package/tools/definitions.js +211 -0
- package/tools/handlers.js +620 -0
- package/utils.js +45 -0
- package/workflow-state.js +107 -0
package/package.json
CHANGED
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@programinglive/dev-workflow-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "MCP server to enforce development workflow discipline",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "index.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"dev-workflow-mcp": "index.js"
|
|
9
|
-
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"test": "node --test",
|
|
12
|
-
"check": "node --check index.js",
|
|
13
|
-
"start": "node index.js",
|
|
14
|
-
"dev": "node --watch index.js",
|
|
15
|
-
"prepare": "husky",
|
|
16
|
-
"release": "standard-version",
|
|
17
|
-
"release:major": "npm run release -- --release-as major",
|
|
18
|
-
"release:minor": "npm run release -- --release-as minor",
|
|
19
|
-
"release:patch": "npm run release -- --release-as patch"
|
|
20
|
-
},
|
|
21
|
-
"keywords": [
|
|
22
|
-
"mcp",
|
|
23
|
-
"workflow",
|
|
24
|
-
"development",
|
|
25
|
-
"discipline"
|
|
26
|
-
],
|
|
27
|
-
"author": "",
|
|
28
|
-
"license": "MIT",
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
31
|
-
},
|
|
32
|
-
"publishConfig": {
|
|
33
|
-
"access": "public"
|
|
34
|
-
},
|
|
35
|
-
"devDependencies": {
|
|
36
|
-
"@commitlint/cli": "^20.1.0",
|
|
37
|
-
"@commitlint/config-conventional": "^20.0.0",
|
|
38
|
-
"@programinglive/commiter": "^1.0.0",
|
|
39
|
-
"husky": "^9.1.7",
|
|
40
|
-
"standard-version": "^9.5.0"
|
|
41
|
-
},
|
|
42
|
-
"standard-version": {
|
|
43
|
-
"releaseCommitMessageFormat": "chore(release): {{currentTag}} ๐",
|
|
44
|
-
"types": [
|
|
45
|
-
{
|
|
46
|
-
"type": "feat",
|
|
47
|
-
"section": "โจ Features"
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"type": "fix",
|
|
51
|
-
"section": "๐ Bug Fixes"
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
"type": "perf",
|
|
55
|
-
"section": "โก Performance"
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"type": "refactor",
|
|
59
|
-
"section": "โป๏ธ Refactors"
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
"type": "docs",
|
|
63
|
-
"section": "๐ Documentation"
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
"type": "style",
|
|
67
|
-
"section": "๐ Styles"
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
"type": "test",
|
|
71
|
-
"section": "โ
Tests"
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
"type": "build",
|
|
75
|
-
"section": "๐๏ธ Build System"
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
"type": "ci",
|
|
79
|
-
"section": "๐ท Continuous Integration"
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
"type": "chore",
|
|
83
|
-
"section": "๐งน Chores"
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
"type": "revert",
|
|
87
|
-
"section": "โช Reverts"
|
|
88
|
-
}
|
|
89
|
-
]
|
|
90
|
-
}
|
|
91
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@programinglive/dev-workflow-mcp-server",
|
|
3
|
+
"version": "1.0.11",
|
|
4
|
+
"description": "MCP server to enforce development workflow discipline",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"dev-workflow-mcp": "index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node --test",
|
|
12
|
+
"check": "node --check index.js",
|
|
13
|
+
"start": "node index.js",
|
|
14
|
+
"dev": "node --watch index.js",
|
|
15
|
+
"prepare": "husky",
|
|
16
|
+
"release": "standard-version",
|
|
17
|
+
"release:major": "npm run release -- --release-as major",
|
|
18
|
+
"release:minor": "npm run release -- --release-as minor",
|
|
19
|
+
"release:patch": "npm run release -- --release-as patch"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"mcp",
|
|
23
|
+
"workflow",
|
|
24
|
+
"development",
|
|
25
|
+
"discipline"
|
|
26
|
+
],
|
|
27
|
+
"author": "",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@commitlint/cli": "^20.1.0",
|
|
37
|
+
"@commitlint/config-conventional": "^20.0.0",
|
|
38
|
+
"@programinglive/commiter": "^1.0.0",
|
|
39
|
+
"husky": "^9.1.7",
|
|
40
|
+
"standard-version": "^9.5.0"
|
|
41
|
+
},
|
|
42
|
+
"standard-version": {
|
|
43
|
+
"releaseCommitMessageFormat": "chore(release): {{currentTag}} ๐",
|
|
44
|
+
"types": [
|
|
45
|
+
{
|
|
46
|
+
"type": "feat",
|
|
47
|
+
"section": "โจ Features"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"type": "fix",
|
|
51
|
+
"section": "๐ Bug Fixes"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"type": "perf",
|
|
55
|
+
"section": "โก Performance"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"type": "refactor",
|
|
59
|
+
"section": "โป๏ธ Refactors"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"type": "docs",
|
|
63
|
+
"section": "๐ Documentation"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"type": "style",
|
|
67
|
+
"section": "๐ Styles"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"type": "test",
|
|
71
|
+
"section": "โ
Tests"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"type": "build",
|
|
75
|
+
"section": "๐๏ธ Build System"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"type": "ci",
|
|
79
|
+
"section": "๐ท Continuous Integration"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"type": "chore",
|
|
83
|
+
"section": "๐งน Chores"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"type": "revert",
|
|
87
|
+
"section": "โช Reverts"
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
import { WorkflowState } from "../workflow-state.js";
|
|
8
|
+
import { handleToolCall } from "../tools/handlers.js";
|
|
9
|
+
import { normalizeRequestArgs, shellEscape } from "../utils.js";
|
|
10
|
+
import {
|
|
11
|
+
createCommitMessageParts,
|
|
12
|
+
determineReleaseTypeFromCommit,
|
|
13
|
+
} from "../commit-helpers.js";
|
|
14
|
+
|
|
15
|
+
async function withWorkflowState(callback) {
|
|
16
|
+
const tempDir = await mkdtemp(path.join(tmpdir(), "workflow-handlers-"));
|
|
17
|
+
const stateFile = path.join(tempDir, "state.json");
|
|
18
|
+
const state = new WorkflowState(stateFile);
|
|
19
|
+
await state.load();
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await callback(state);
|
|
23
|
+
} finally {
|
|
24
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const utils = {
|
|
29
|
+
shellEscape,
|
|
30
|
+
determineReleaseTypeFromCommit,
|
|
31
|
+
createCommitMessageParts,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function createRequest(name, args) {
|
|
35
|
+
return {
|
|
36
|
+
params: {
|
|
37
|
+
name,
|
|
38
|
+
arguments: args,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
test("commit_and_push commits changes and pushes", async () => {
|
|
44
|
+
await withWorkflowState(async (workflowState) => {
|
|
45
|
+
workflowState.state.readyToCommit = true;
|
|
46
|
+
workflowState.state.readyCheckCompleted = true;
|
|
47
|
+
workflowState.state.testsSkipped = false;
|
|
48
|
+
workflowState.state.testsCreated = true;
|
|
49
|
+
workflowState.state.testsPassed = true;
|
|
50
|
+
workflowState.state.currentPhase = "commit";
|
|
51
|
+
await workflowState.save();
|
|
52
|
+
|
|
53
|
+
const commands = [];
|
|
54
|
+
const execStub = async (command) => {
|
|
55
|
+
commands.push(command);
|
|
56
|
+
return { stdout: "" };
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const git = {
|
|
60
|
+
hasWorkingChanges: async () => true,
|
|
61
|
+
hasTestChanges: async () => true,
|
|
62
|
+
getStagedChanges: async () => [{ status: "M", path: "src/index.js" }],
|
|
63
|
+
getCurrentBranch: async () => "main",
|
|
64
|
+
getLastCommitMessage: async () => "",
|
|
65
|
+
workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const response = await handleToolCall({
|
|
69
|
+
request: createRequest("commit_and_push", { commitMessage: "" }),
|
|
70
|
+
normalizeRequestArgs,
|
|
71
|
+
workflowState,
|
|
72
|
+
exec: execStub,
|
|
73
|
+
git,
|
|
74
|
+
utils,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const pushCommand = commands.find((command) => command.startsWith("git push"));
|
|
78
|
+
assert.ok(pushCommand, "git push should be executed during commit");
|
|
79
|
+
assert.ok(
|
|
80
|
+
pushCommand.startsWith("git push origin"),
|
|
81
|
+
"push command should target origin"
|
|
82
|
+
);
|
|
83
|
+
assert.equal(
|
|
84
|
+
commands.some((command) => command.startsWith("git commit")),
|
|
85
|
+
true,
|
|
86
|
+
"git commit should be executed"
|
|
87
|
+
);
|
|
88
|
+
assert.ok(
|
|
89
|
+
response.content[0].text.includes("Commit and push completed!"),
|
|
90
|
+
"response should confirm commit and push"
|
|
91
|
+
);
|
|
92
|
+
assert.equal(workflowState.state.commitAndPushCompleted, true);
|
|
93
|
+
assert.equal(workflowState.state.lastCommitMessage.length > 0, true);
|
|
94
|
+
assert.equal(workflowState.state.currentPhase, "release");
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("continue_workflow warns when workflow is idle", async () => {
|
|
99
|
+
await withWorkflowState(async (workflowState) => {
|
|
100
|
+
const git = {
|
|
101
|
+
hasWorkingChanges: async () => false,
|
|
102
|
+
hasTestChanges: async () => false,
|
|
103
|
+
getStagedChanges: async () => [],
|
|
104
|
+
getCurrentBranch: async () => "main",
|
|
105
|
+
getLastCommitMessage: async () => "",
|
|
106
|
+
workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const response = await handleToolCall({
|
|
110
|
+
request: createRequest("continue_workflow", {}),
|
|
111
|
+
normalizeRequestArgs,
|
|
112
|
+
workflowState,
|
|
113
|
+
exec: async () => ({ stdout: "" }),
|
|
114
|
+
git,
|
|
115
|
+
utils,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
assert.ok(
|
|
119
|
+
response.content[0].text.includes("No active workflow"),
|
|
120
|
+
"Idle workflow should prompt user to start a task"
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("perform_release runs release command before pushing with tags", async () => {
|
|
126
|
+
await withWorkflowState(async (workflowState) => {
|
|
127
|
+
workflowState.state.currentPhase = "release";
|
|
128
|
+
workflowState.state.commitAndPushCompleted = true;
|
|
129
|
+
workflowState.state.readyCheckCompleted = true;
|
|
130
|
+
workflowState.state.lastPushBranch = "feature/split";
|
|
131
|
+
workflowState.state.lastCommitMessage = "fix: address issue";
|
|
132
|
+
await workflowState.save();
|
|
133
|
+
|
|
134
|
+
const commands = [];
|
|
135
|
+
const execStub = async (command) => {
|
|
136
|
+
commands.push(command);
|
|
137
|
+
if (command === "git status --porcelain") {
|
|
138
|
+
return { stdout: "" };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { stdout: "" };
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const git = {
|
|
145
|
+
hasWorkingChanges: async () => false,
|
|
146
|
+
hasTestChanges: async () => true,
|
|
147
|
+
getStagedChanges: async () => [],
|
|
148
|
+
getCurrentBranch: async () => "main",
|
|
149
|
+
getLastCommitMessage: async () => "",
|
|
150
|
+
workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const response = await handleToolCall({
|
|
154
|
+
request: createRequest("perform_release", { command: "npm run release" }),
|
|
155
|
+
normalizeRequestArgs,
|
|
156
|
+
workflowState,
|
|
157
|
+
exec: execStub,
|
|
158
|
+
git,
|
|
159
|
+
utils,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const releaseCommand = commands.find((command) => command.startsWith("npm run release"));
|
|
163
|
+
assert.ok(releaseCommand, "release command should be executed");
|
|
164
|
+
assert.ok(
|
|
165
|
+
releaseCommand.includes("-- --release-as patch"),
|
|
166
|
+
"release command should append release type"
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const pushCommand = commands.find((command) => command.startsWith("git push"));
|
|
170
|
+
assert.ok(pushCommand, "git push with tags should be executed after release");
|
|
171
|
+
assert.ok(
|
|
172
|
+
pushCommand.startsWith("git push --follow-tags"),
|
|
173
|
+
"push command should include --follow-tags"
|
|
174
|
+
);
|
|
175
|
+
assert.ok(
|
|
176
|
+
pushCommand.includes("feature/split"),
|
|
177
|
+
"push command should target last recorded branch"
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
assert.equal(workflowState.state.released, true);
|
|
181
|
+
assert.equal(workflowState.state.currentPhase, "ready_to_complete");
|
|
182
|
+
assert.ok(
|
|
183
|
+
response.content[0].text.includes("No workflow history yet") ||
|
|
184
|
+
response.content[0].text.includes("Workflow History"),
|
|
185
|
+
"response should describe workflow history or absence thereof"
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
});
|