@programinglive/dev-workflow-mcp-server 1.2.3 → 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +17 -0
- package/package.json +4 -2
- package/scripts/build-docs.js +38 -0
- package/tests/build.test.js +9 -0
- package/web/index.html +4 -1
- package/web/server.js +43 -15
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [1.2.4](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.2.3...v1.2.4) (2025-11-11)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ✨ Features
|
|
9
|
+
|
|
10
|
+
* **docs:** prebuild markdown docs ([84272b6](https://github.com/programinglive/dev-workflow-mcp-server/commit/84272b690e7da88cab60f3b29449044d875261d6))
|
|
11
|
+
|
|
5
12
|
### [1.2.3](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.2.2...v1.2.3) (2025-11-11)
|
|
6
13
|
|
|
7
14
|
|
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Development Workflow MCP Server
|
|
2
2
|
|
|
3
|
+
<p align="left">
|
|
4
|
+
<a href="https://www.npmjs.com/package/@programinglive/dev-workflow-mcp-server"><img src="https://img.shields.io/npm/v/@programinglive/dev-workflow-mcp-server.svg" alt="npm version"></a>
|
|
5
|
+
<a href="https://www.npmjs.com/package/@programinglive/dev-workflow-mcp-server"><img src="https://img.shields.io/npm/dm/@programinglive/dev-workflow-mcp-server.svg" alt="npm downloads"></a>
|
|
6
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="license: MIT"></a>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
3
9
|
An MCP (Model Context Protocol) server that helps enforce development discipline and workflow best practices. This server acts as your coding conscience, reminding you to follow proper development workflows.
|
|
4
10
|
|
|
5
11
|
## 🎯 Purpose
|
|
@@ -190,6 +196,7 @@ Open `http://localhost:3111` in a browser to view the dashboard UI (`web/index.h
|
|
|
190
196
|
Running `npm run build` generates:
|
|
191
197
|
- `dist/index.mjs` - Optimized ES module bundle
|
|
192
198
|
- Source maps and other build artifacts
|
|
199
|
+
- `dist/docs/` - Pre-rendered HTML documentation generated from Markdown via `scripts/build-docs.js`
|
|
193
200
|
|
|
194
201
|
The build bundles all source files while externalizing Node.js built-in modules and dependencies, resulting in a single file distribution.
|
|
195
202
|
|
|
@@ -200,6 +207,16 @@ For MCP server usage, point your client at `index.js` (source) to avoid stdio tr
|
|
|
200
207
|
- Performance optimization
|
|
201
208
|
- Embedding in other projects
|
|
202
209
|
|
|
210
|
+
#### PowerShell CLI tip
|
|
211
|
+
|
|
212
|
+
When invoking the lightweight CLI from PowerShell, use `--%` to prevent PowerShell from rewriting JSON arguments, for example:
|
|
213
|
+
|
|
214
|
+
```powershell
|
|
215
|
+
node --% index.js call start_task --args "{\"description\":\"Convert docs to HTML during build\",\"type\":\"feature\"}"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The `--%` prefix and escaped double quotes ensure the JSON reaches the MCP server unchanged.
|
|
219
|
+
|
|
203
220
|
## 📁 Project-Specific Workflow State
|
|
204
221
|
|
|
205
222
|
When you install this package in a project, a `.state/workflow-state.json` file is automatically created in your project root. This file:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@programinglive/dev-workflow-mcp-server",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.5",
|
|
4
4
|
"description": "MCP server to enforce development workflow discipline",
|
|
5
5
|
"homepage": "https://devworkflow.programinglive.com/",
|
|
6
6
|
"type": "module",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "vite build",
|
|
13
|
+
"postbuild": "node scripts/build-docs.js",
|
|
13
14
|
"dev": "node --watch index.js",
|
|
14
15
|
"start": "node index.js",
|
|
15
16
|
"local": "node index.js",
|
|
@@ -50,7 +51,8 @@
|
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@commitlint/cli": "^20.1.0",
|
|
52
53
|
"@commitlint/config-conventional": "^20.0.0",
|
|
53
|
-
"@programinglive/commiter": "^1.1.0"
|
|
54
|
+
"@programinglive/commiter": "^1.1.0",
|
|
55
|
+
"marked": "^12.0.2"
|
|
54
56
|
},
|
|
55
57
|
"standard-version": {
|
|
56
58
|
"releaseCommitMessageFormat": "chore(release): {{currentTag}} 🚀",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { marked } from 'marked';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
const projectRoot = path.join(__dirname, '..');
|
|
9
|
+
|
|
10
|
+
const docs = [
|
|
11
|
+
{
|
|
12
|
+
key: 'README.md',
|
|
13
|
+
source: path.join(projectRoot, 'README.md'),
|
|
14
|
+
output: 'README.html',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
key: 'web-dashboard.md',
|
|
18
|
+
source: path.join(projectRoot, 'docs', 'web-dashboard.md'),
|
|
19
|
+
output: 'web-dashboard.html',
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const distDocDir = path.join(projectRoot, 'dist', 'docs');
|
|
24
|
+
|
|
25
|
+
async function buildDocs() {
|
|
26
|
+
await mkdir(distDocDir, { recursive: true });
|
|
27
|
+
|
|
28
|
+
for (const doc of docs) {
|
|
29
|
+
const markdown = await readFile(doc.source, 'utf-8');
|
|
30
|
+
const html = marked.parse(markdown);
|
|
31
|
+
await writeFile(path.join(distDocDir, doc.output), html, 'utf-8');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
buildDocs().catch((error) => {
|
|
36
|
+
console.error('Failed to build documentation assets:', error);
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
});
|
package/tests/build.test.js
CHANGED
|
@@ -25,6 +25,15 @@ test('build generates dist/index.mjs file', async () => {
|
|
|
25
25
|
const content = await readFile(distPath, 'utf-8');
|
|
26
26
|
assert(content.includes('main'), 'Bundle should contain main function');
|
|
27
27
|
assert(content.length > 1000, 'Bundle should be substantial');
|
|
28
|
+
|
|
29
|
+
const docDir = path.join(process.cwd(), 'dist', 'docs');
|
|
30
|
+
const docFiles = ['README.html', 'web-dashboard.html'];
|
|
31
|
+
for (const file of docFiles) {
|
|
32
|
+
const fullPath = path.join(docDir, file);
|
|
33
|
+
await access(fullPath);
|
|
34
|
+
const docContent = await readFile(fullPath, 'utf-8');
|
|
35
|
+
assert(docContent.includes('<h1'), `${file} should contain HTML output`);
|
|
36
|
+
}
|
|
28
37
|
});
|
|
29
38
|
|
|
30
39
|
test('build script exists in package.json', async () => {
|
package/web/index.html
CHANGED
|
@@ -865,8 +865,11 @@
|
|
|
865
865
|
throw new Error(`Failed to load document (${response.status})`);
|
|
866
866
|
}
|
|
867
867
|
const payload = await response.json();
|
|
868
|
-
const
|
|
868
|
+
const rawContent = payload.content || '';
|
|
869
|
+
const format = (payload.format || 'markdown').toLowerCase();
|
|
870
|
+
const html = format === 'html' ? rawContent : marked.parse(rawContent);
|
|
869
871
|
docModalContent.innerHTML = html;
|
|
872
|
+
|
|
870
873
|
lucide.createIcons();
|
|
871
874
|
|
|
872
875
|
if (headingText) {
|
package/web/server.js
CHANGED
|
@@ -9,11 +9,39 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
9
9
|
const __dirname = dirname(__filename);
|
|
10
10
|
const projectRoot = join(__dirname, "..");
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
"README.md":
|
|
14
|
-
|
|
12
|
+
const docSources = {
|
|
13
|
+
"README.md": {
|
|
14
|
+
markdown: join(projectRoot, "README.md"),
|
|
15
|
+
html: join(projectRoot, "dist", "docs", "README.html"),
|
|
16
|
+
},
|
|
17
|
+
"web-dashboard.md": {
|
|
18
|
+
markdown: join(projectRoot, "docs", "web-dashboard.md"),
|
|
19
|
+
html: join(projectRoot, "dist", "docs", "web-dashboard.html"),
|
|
20
|
+
},
|
|
15
21
|
};
|
|
16
22
|
|
|
23
|
+
async function resolveDocContent(docKey) {
|
|
24
|
+
const entry = docSources[docKey];
|
|
25
|
+
if (!entry) {
|
|
26
|
+
return { exists: false };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Try HTML first, fall back to Markdown in development or if HTML missing.
|
|
30
|
+
const candidates = [entry.html, entry.markdown];
|
|
31
|
+
for (const candidate of candidates) {
|
|
32
|
+
try {
|
|
33
|
+
await access(candidate);
|
|
34
|
+
const content = await readFile(candidate, "utf-8");
|
|
35
|
+
const format = candidate.endsWith(".html") ? "html" : "markdown";
|
|
36
|
+
return { exists: true, content, format };
|
|
37
|
+
} catch {
|
|
38
|
+
// try next candidate
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { exists: false };
|
|
43
|
+
}
|
|
44
|
+
|
|
17
45
|
function getPreferredPort() {
|
|
18
46
|
const fromEnv = process.env.PORT || process.env.DEV_WORKFLOW_WEB_PORT;
|
|
19
47
|
|
|
@@ -59,31 +87,31 @@ async function start() {
|
|
|
59
87
|
res.end(JSON.stringify({ summary }));
|
|
60
88
|
} else if (req.method === "GET" && url.pathname === "/api/docs") {
|
|
61
89
|
const rawDocKey = url.searchParams.get("doc");
|
|
90
|
+
const availableDocs = Object.keys(docSources);
|
|
91
|
+
|
|
62
92
|
if (!rawDocKey) {
|
|
63
93
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
64
|
-
res.end(JSON.stringify({ docs:
|
|
94
|
+
res.end(JSON.stringify({ docs: availableDocs }));
|
|
65
95
|
return;
|
|
66
96
|
}
|
|
67
97
|
|
|
68
98
|
const docKey = rawDocKey.trim();
|
|
69
|
-
const resolvedKey =
|
|
99
|
+
const resolvedKey = availableDocs.find((key) => key.toLowerCase() === docKey.toLowerCase());
|
|
70
100
|
if (!resolvedKey) {
|
|
71
101
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
72
|
-
res.end(JSON.stringify({ error: "Document not found", requested: docKey, available:
|
|
102
|
+
res.end(JSON.stringify({ error: "Document not found", requested: docKey, available: availableDocs }));
|
|
73
103
|
return;
|
|
74
104
|
}
|
|
75
105
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
await access(docPath);
|
|
80
|
-
const content = await readFile(docPath, "utf-8");
|
|
81
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
82
|
-
res.end(JSON.stringify({ doc: resolvedKey, content }));
|
|
83
|
-
} catch (error) {
|
|
106
|
+
const result = await resolveDocContent(resolvedKey);
|
|
107
|
+
if (!result.exists) {
|
|
84
108
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
85
|
-
res.end(JSON.stringify({ error:
|
|
109
|
+
res.end(JSON.stringify({ error: "Document asset is missing" }));
|
|
110
|
+
return;
|
|
86
111
|
}
|
|
112
|
+
|
|
113
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
114
|
+
res.end(JSON.stringify({ doc: resolvedKey, content: result.content, format: result.format }));
|
|
87
115
|
} else {
|
|
88
116
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
89
117
|
res.end("Not found");
|