@programinglive/dev-workflow-mcp-server 1.1.11 → 1.1.18
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/workflows/npm-publish.yml +47 -0
- package/CHANGELOG.md +46 -0
- package/README.md +55 -1
- package/docs/product/PRD.md +64 -0
- package/docs/web-dashboard.md +163 -0
- package/git-helpers.js +25 -0
- package/index.js +69 -4
- package/package.json +5 -1
- package/release-wrapper.js +26 -1
- package/tests/exec.test.js +7 -4
- package/tests/handlers.test.js +217 -7
- package/tests/release-wrapper.test.js +11 -3
- package/tests/workflowState.test.js +54 -2
- package/tools/handlers.js +9 -2
- package/web/index.html +520 -80
- package/web/server.js +34 -1
- package/workflow-state.js +86 -23
package/web/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createServer } from "http";
|
|
2
|
-
import { readFile } from "fs/promises";
|
|
2
|
+
import { readFile, access } from "fs/promises";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
4
|
import { dirname, join } from "path";
|
|
5
5
|
import getPort from "get-port";
|
|
@@ -7,6 +7,12 @@ import { getHistoryForUser, getSummaryForUser, getHistorySummary } from "../db/i
|
|
|
7
7
|
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
9
|
const __dirname = dirname(__filename);
|
|
10
|
+
const projectRoot = join(__dirname, "..");
|
|
11
|
+
|
|
12
|
+
const allowedDocs = {
|
|
13
|
+
"README.md": join(projectRoot, "README.md"),
|
|
14
|
+
"web-dashboard.md": join(projectRoot, "docs", "web-dashboard.md"),
|
|
15
|
+
};
|
|
10
16
|
|
|
11
17
|
async function start() {
|
|
12
18
|
const PORT = process.env.DEV_WORKFLOW_WEB_PORT || await getPort({ port: 3111 });
|
|
@@ -39,6 +45,33 @@ async function start() {
|
|
|
39
45
|
const summary = getHistorySummary(userId, { startDate, endDate, frequency });
|
|
40
46
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
41
47
|
res.end(JSON.stringify({ summary }));
|
|
48
|
+
} else if (req.method === "GET" && url.pathname === "/api/docs") {
|
|
49
|
+
const rawDocKey = url.searchParams.get("doc");
|
|
50
|
+
if (!rawDocKey) {
|
|
51
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
52
|
+
res.end(JSON.stringify({ docs: Object.keys(allowedDocs) }));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const docKey = rawDocKey.trim();
|
|
57
|
+
const resolvedKey = Object.keys(allowedDocs).find((key) => key.toLowerCase() === docKey.toLowerCase());
|
|
58
|
+
if (!resolvedKey) {
|
|
59
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
60
|
+
res.end(JSON.stringify({ error: "Document not found", requested: docKey, available: Object.keys(allowedDocs) }));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const docPath = allowedDocs[resolvedKey];
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
await access(docPath);
|
|
68
|
+
const content = await readFile(docPath, "utf-8");
|
|
69
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
70
|
+
res.end(JSON.stringify({ doc: resolvedKey, content }));
|
|
71
|
+
} catch (error) {
|
|
72
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
73
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
74
|
+
}
|
|
42
75
|
} else {
|
|
43
76
|
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
44
77
|
res.end("Not found");
|
package/workflow-state.js
CHANGED
|
@@ -107,21 +107,88 @@ function hasProjectMarkers(candidate) {
|
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
function
|
|
110
|
+
function normalizeAndStoreCandidate(list, candidate) {
|
|
111
|
+
if (!candidate) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const resolved = path.resolve(candidate);
|
|
116
|
+
if (!list.includes(resolved)) {
|
|
117
|
+
list.push(resolved);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function addAncestorCandidates(list, start) {
|
|
122
|
+
if (!start) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let current = path.resolve(start);
|
|
127
|
+
while (!isRootPath(current)) {
|
|
128
|
+
const parent = path.dirname(current);
|
|
129
|
+
if (parent === current) {
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
if (!isRootPath(parent)) {
|
|
133
|
+
normalizeAndStoreCandidate(list, parent);
|
|
134
|
+
}
|
|
135
|
+
current = parent;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function selectProjectRoot(candidates, moduleRoot) {
|
|
140
|
+
const cwdResolved = path.resolve(process.cwd());
|
|
141
|
+
const prioritized = [];
|
|
142
|
+
|
|
111
143
|
for (const candidate of candidates) {
|
|
112
144
|
if (!candidate) {
|
|
113
145
|
continue;
|
|
114
146
|
}
|
|
115
147
|
|
|
116
148
|
const resolved = path.resolve(candidate);
|
|
117
|
-
if (
|
|
149
|
+
if (prioritized.some((entry) => entry.resolved === resolved)) {
|
|
118
150
|
continue;
|
|
119
151
|
}
|
|
120
152
|
|
|
121
|
-
|
|
153
|
+
prioritized.push({
|
|
154
|
+
resolved,
|
|
155
|
+
hasMarkers: hasProjectMarkers(resolved),
|
|
156
|
+
isRoot: isRootPath(resolved),
|
|
157
|
+
isCwdAncestor: cwdResolved === resolved || cwdResolved.startsWith(`${resolved}${path.sep}`),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const ancestorWithMarkers = prioritized.find((entry) => entry.isCwdAncestor && entry.hasMarkers);
|
|
162
|
+
if (ancestorWithMarkers) {
|
|
163
|
+
return ancestorWithMarkers.resolved;
|
|
122
164
|
}
|
|
123
165
|
|
|
124
|
-
|
|
166
|
+
const ancestorWithoutMarkers = prioritized
|
|
167
|
+
.filter((entry) => entry.isCwdAncestor && !entry.hasMarkers && !entry.isRoot)
|
|
168
|
+
.sort((a, b) => b.resolved.length - a.resolved.length);
|
|
169
|
+
if (ancestorWithoutMarkers.length > 0) {
|
|
170
|
+
return ancestorWithoutMarkers[0].resolved;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (moduleRoot) {
|
|
174
|
+
const moduleResolved = path.resolve(moduleRoot);
|
|
175
|
+
const moduleEntry = prioritized.find((entry) => entry.resolved === moduleResolved && !entry.isRoot);
|
|
176
|
+
if (moduleEntry) {
|
|
177
|
+
return moduleEntry.resolved;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const withMarkers = prioritized.find((entry) => entry.hasMarkers);
|
|
182
|
+
if (withMarkers) {
|
|
183
|
+
return withMarkers.resolved;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const nonRoot = prioritized.find((entry) => !entry.isRoot);
|
|
187
|
+
if (nonRoot) {
|
|
188
|
+
return nonRoot.resolved;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return prioritized.length > 0 ? prioritized[0].resolved : null;
|
|
125
192
|
}
|
|
126
193
|
|
|
127
194
|
function getUserScopedDir(baseDir) {
|
|
@@ -339,33 +406,29 @@ export function resolveStateFile() {
|
|
|
339
406
|
const candidateRoots = [];
|
|
340
407
|
|
|
341
408
|
const initProjectRoot = findProjectRoot(resolvedInitCwd);
|
|
342
|
-
if (initProjectRoot) {
|
|
343
|
-
candidateRoots.push(initProjectRoot);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const moduleProjectRoot = findProjectRoot(moduleRoot);
|
|
347
|
-
if (moduleProjectRoot) {
|
|
348
|
-
candidateRoots.push(moduleProjectRoot);
|
|
349
|
-
} else if (!isRootPath(moduleRoot)) {
|
|
350
|
-
candidateRoots.push(moduleRoot);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
409
|
const cwdProjectRoot = findProjectRoot(cwdResolved);
|
|
354
|
-
|
|
355
|
-
candidateRoots.push(cwdProjectRoot);
|
|
356
|
-
} else if (!isRootPath(cwdResolved)) {
|
|
357
|
-
candidateRoots.push(cwdResolved);
|
|
358
|
-
}
|
|
410
|
+
const moduleProjectRoot = findProjectRoot(moduleRoot);
|
|
359
411
|
|
|
412
|
+
normalizeAndStoreCandidate(candidateRoots, initProjectRoot);
|
|
413
|
+
normalizeAndStoreCandidate(candidateRoots, cwdProjectRoot);
|
|
360
414
|
if (!isRootPath(resolvedInitCwd)) {
|
|
361
|
-
candidateRoots
|
|
415
|
+
normalizeAndStoreCandidate(candidateRoots, resolvedInitCwd);
|
|
416
|
+
addAncestorCandidates(candidateRoots, resolvedInitCwd);
|
|
417
|
+
}
|
|
418
|
+
if (!isRootPath(cwdResolved)) {
|
|
419
|
+
normalizeAndStoreCandidate(candidateRoots, cwdResolved);
|
|
420
|
+
addAncestorCandidates(candidateRoots, cwdResolved);
|
|
362
421
|
}
|
|
363
422
|
|
|
423
|
+
normalizeAndStoreCandidate(candidateRoots, moduleProjectRoot);
|
|
424
|
+
if (!isRootPath(moduleRoot)) {
|
|
425
|
+
normalizeAndStoreCandidate(candidateRoots, moduleRoot);
|
|
426
|
+
}
|
|
364
427
|
if (!isRootPath(__dirname)) {
|
|
365
|
-
candidateRoots
|
|
428
|
+
normalizeAndStoreCandidate(candidateRoots, __dirname);
|
|
366
429
|
}
|
|
367
430
|
|
|
368
|
-
let projectRoot = selectProjectRoot(candidateRoots);
|
|
431
|
+
let projectRoot = selectProjectRoot(candidateRoots, moduleRoot);
|
|
369
432
|
|
|
370
433
|
if (!projectRoot) {
|
|
371
434
|
projectRoot = path.resolve(__dirname);
|