@programinglive/dev-workflow-mcp-server 1.0.9 → 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/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
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.0.11](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.0.10...v1.0.11) (2025-10-25)
6
+
7
+
8
+ ### ✨ Features
9
+
10
+ * add continue workflow guidance ([516b33e](https://github.com/programinglive/dev-workflow-mcp-server/commit/516b33ecfc7533df5868c9c26458088d6ea591a2))
11
+
12
+
13
+ ### 🐛 Bug Fixes
14
+
15
+ * ensure commit and push happens before release ([08ee662](https://github.com/programinglive/dev-workflow-mcp-server/commit/08ee662f525431ba4a2d735ed253aad8d6720ac2))
16
+
17
+ ### [1.0.10](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.0.9...v1.0.10) (2025-10-24)
18
+
19
+
20
+ ### ♻️ Refactors
21
+
22
+ * modularize workflow server ([f0c9401](https://github.com/programinglive/dev-workflow-mcp-server/commit/f0c940123a8e6f961555cc7ba4d6dec2deb854e7))
23
+
24
+
25
+ ### ✅ Tests
26
+
27
+ * cover release push flow ([933ff11](https://github.com/programinglive/dev-workflow-mcp-server/commit/933ff1126af08a49d1ce38a4eedcc738b0064efb))
28
+
5
29
  ### [1.0.9](https://github.com/programinglive/dev-workflow-mcp-server/compare/v1.0.8...v1.0.9) (2025-10-23)
6
30
 
7
31
 
@@ -0,0 +1,166 @@
1
+ function formatFileList(paths) {
2
+ const uniquePaths = [...new Set(paths.filter(Boolean))];
3
+
4
+ if (uniquePaths.length === 0) {
5
+ return "updates";
6
+ }
7
+
8
+ if (uniquePaths.length === 1) {
9
+ return uniquePaths[0];
10
+ }
11
+
12
+ if (uniquePaths.length === 2) {
13
+ return `${uniquePaths[0]} and ${uniquePaths[1]}`;
14
+ }
15
+
16
+ if (uniquePaths.length === 3) {
17
+ return `${uniquePaths[0]}, ${uniquePaths[1]}, and ${uniquePaths[2]}`;
18
+ }
19
+
20
+ const remaining = uniquePaths.length - 3;
21
+ return `${uniquePaths[0]}, ${uniquePaths[1]}, ${uniquePaths[2]} +${remaining} more`;
22
+ }
23
+
24
+ function determineCommitType(changes) {
25
+ const hasTestChange = changes.some((change) =>
26
+ [change.path, change.originalPath].some((p) => isTestFilePath(p))
27
+ );
28
+ const hasDocsChange = changes.some((change) =>
29
+ [change.path, change.originalPath].some((p) => isDocumentationFilePath(p))
30
+ );
31
+
32
+ if (hasTestChange) {
33
+ return "test";
34
+ }
35
+
36
+ if (hasDocsChange) {
37
+ return "docs";
38
+ }
39
+
40
+ return "chore";
41
+ }
42
+
43
+ function isTestFilePath(filePath) {
44
+ if (!filePath) {
45
+ return false;
46
+ }
47
+
48
+ if (/\.(test|spec)\.[^.]+$/i.test(filePath)) {
49
+ return true;
50
+ }
51
+
52
+ return /(\/|^)(__tests__|tests?|spec)\//i.test(filePath);
53
+ }
54
+
55
+ function isDocumentationFilePath(filePath) {
56
+ if (!filePath) {
57
+ return false;
58
+ }
59
+
60
+ const normalized = filePath.toLowerCase();
61
+ return (
62
+ normalized.startsWith("docs/") ||
63
+ normalized.startsWith("documentation/") ||
64
+ normalized.endsWith(".md") ||
65
+ normalized.endsWith(".rst") ||
66
+ normalized.endsWith(".adoc") ||
67
+ normalized.includes("readme")
68
+ );
69
+ }
70
+
71
+ function describeChange(change) {
72
+ const statusCode = change.status || "";
73
+ const path = change.path || change.originalPath || "";
74
+
75
+ if (statusCode.startsWith("R")) {
76
+ return `renamed ${change.originalPath} -> ${change.path}`;
77
+ }
78
+
79
+ if (statusCode.startsWith("C")) {
80
+ return `copied ${change.originalPath} -> ${change.path}`;
81
+ }
82
+
83
+ switch (statusCode[0]) {
84
+ case "A":
85
+ return `added ${path}`;
86
+ case "M":
87
+ return `modified ${path}`;
88
+ case "D":
89
+ return `removed ${path}`;
90
+ case "U":
91
+ return `updated ${path}`;
92
+ default:
93
+ return `${statusCode} ${path}`.trim();
94
+ }
95
+ }
96
+
97
+ export function createCommitMessageParts(changes, providedSummary = "") {
98
+ const summaryInput = typeof providedSummary === "string" ? providedSummary.trim() : "";
99
+
100
+ if (!Array.isArray(changes) || changes.length === 0) {
101
+ return {
102
+ summary: summaryInput,
103
+ body: "",
104
+ };
105
+ }
106
+
107
+ const commitType = determineCommitType(changes);
108
+ const summaryPaths = changes
109
+ .map((change) => change.path || change.originalPath)
110
+ .filter(Boolean);
111
+ const generatedSummary = `${commitType}: update ${formatFileList(summaryPaths)}`;
112
+ const summary = summaryInput || generatedSummary;
113
+
114
+ const detailLines = changes
115
+ .map((change) => describeChange(change))
116
+ .filter((line) => line && line.trim().length > 0)
117
+ .map((line) => `- ${line}`);
118
+
119
+ return {
120
+ summary,
121
+ body: detailLines.join("\n"),
122
+ };
123
+ }
124
+
125
+ function normalizeCommitHeader(message) {
126
+ if (typeof message !== "string") {
127
+ return "";
128
+ }
129
+
130
+ const header = message.split("\n")[0] || "";
131
+ return header.trim();
132
+ }
133
+
134
+ function containsBreakingChange(message) {
135
+ if (typeof message !== "string") {
136
+ return false;
137
+ }
138
+
139
+ return /breaking change/i.test(message);
140
+ }
141
+
142
+ export function determineReleaseTypeFromCommit(message) {
143
+ const header = normalizeCommitHeader(message).toLowerCase();
144
+ const hasBreaking = containsBreakingChange(message) || /!/.test(header.split(":")[0] || "");
145
+
146
+ if (hasBreaking) {
147
+ return "major";
148
+ }
149
+
150
+ const typeMatch = header.match(/^([a-z]+)(\(|:)/i);
151
+ const commitType = typeMatch ? typeMatch[1] : "";
152
+
153
+ if (commitType === "feat") {
154
+ return "minor";
155
+ }
156
+
157
+ if (commitType === "fix" || commitType === "perf") {
158
+ return "patch";
159
+ }
160
+
161
+ if (!commitType && containsBreakingChange(message)) {
162
+ return "major";
163
+ }
164
+
165
+ return "patch";
166
+ }
package/exec.js ADDED
@@ -0,0 +1,4 @@
1
+ import { exec as execCallback } from "child_process";
2
+ import { promisify } from "util";
3
+
4
+ export const exec = promisify(execCallback);
package/git-helpers.js ADDED
@@ -0,0 +1,152 @@
1
+ import { exec } from "./exec.js";
2
+
3
+ export async function hasStagedChanges() {
4
+ try {
5
+ const { stdout } = await exec("git status --porcelain");
6
+ return stdout
7
+ .split("\n")
8
+ .filter(Boolean)
9
+ .some((line) => {
10
+ const indexStatus = line[0];
11
+ return indexStatus && indexStatus !== " " && indexStatus !== "?";
12
+ });
13
+ } catch (error) {
14
+ return false;
15
+ }
16
+ }
17
+
18
+ export async function hasWorkingChanges() {
19
+ try {
20
+ const { stdout } = await exec("git status --porcelain");
21
+ return stdout.split("\n").some((line) => line && line.trim().length > 0);
22
+ } catch (error) {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ export function workingTreeSummary(statusOutput) {
28
+ if (typeof statusOutput !== "string" || statusOutput.trim() === "") {
29
+ return {
30
+ hasChanges: false,
31
+ lines: [],
32
+ };
33
+ }
34
+
35
+ const lines = statusOutput
36
+ .split("\n")
37
+ .map((line) => line.replace(/\s+/g, " ").trim())
38
+ .filter(Boolean);
39
+
40
+ return {
41
+ hasChanges: lines.length > 0,
42
+ lines,
43
+ };
44
+ }
45
+
46
+ function isTestFilePath(filePath) {
47
+ if (!filePath) {
48
+ return false;
49
+ }
50
+
51
+ if (/\.(test|spec)\.[^.]+$/i.test(filePath)) {
52
+ return true;
53
+ }
54
+
55
+ return /(\/|^)(__tests__|tests?|spec)\//i.test(filePath);
56
+ }
57
+
58
+ function extractPathFromStatusLine(line) {
59
+ if (!line || line.length < 4) {
60
+ return "";
61
+ }
62
+
63
+ const rawPath = line.slice(3).trim();
64
+ if (!rawPath) {
65
+ return "";
66
+ }
67
+
68
+ if (rawPath.includes(" -> ")) {
69
+ const parts = rawPath.split(" -> ");
70
+ return parts[parts.length - 1].trim();
71
+ }
72
+
73
+ return rawPath;
74
+ }
75
+
76
+ export function containsTestFilesInStatus(statusOutput) {
77
+ if (typeof statusOutput !== "string" || statusOutput.trim() === "") {
78
+ return false;
79
+ }
80
+
81
+ return statusOutput
82
+ .split("\n")
83
+ .map((line) => extractPathFromStatusLine(line))
84
+ .some((path) => isTestFilePath(path));
85
+ }
86
+
87
+ function parseNameStatusLine(line) {
88
+ if (!line) {
89
+ return null;
90
+ }
91
+
92
+ const parts = line.split("\t").map((part) => part.trim());
93
+ const status = parts[0];
94
+ if (!status) {
95
+ return null;
96
+ }
97
+
98
+ if (status.startsWith("R") || status.startsWith("C")) {
99
+ const originalPath = parts[1] || "";
100
+ const newPath = parts[2] || originalPath;
101
+ return {
102
+ status,
103
+ path: newPath,
104
+ originalPath,
105
+ };
106
+ }
107
+
108
+ return {
109
+ status,
110
+ path: parts[1] || "",
111
+ originalPath: "",
112
+ };
113
+ }
114
+
115
+ export async function getStagedChanges() {
116
+ try {
117
+ const { stdout } = await exec("git diff --cached --name-status");
118
+ return stdout
119
+ .split("\n")
120
+ .map((line) => parseNameStatusLine(line))
121
+ .filter(Boolean);
122
+ } catch (error) {
123
+ return [];
124
+ }
125
+ }
126
+
127
+ export async function hasTestChanges() {
128
+ try {
129
+ const { stdout } = await exec("git status --porcelain");
130
+ return containsTestFilesInStatus(stdout);
131
+ } catch (error) {
132
+ return false;
133
+ }
134
+ }
135
+
136
+ export async function getLastCommitMessage() {
137
+ try {
138
+ const { stdout } = await exec("git log -1 --pretty=%B");
139
+ return stdout.trim();
140
+ } catch (error) {
141
+ return "";
142
+ }
143
+ }
144
+
145
+ export async function getCurrentBranch() {
146
+ try {
147
+ const { stdout } = await exec("git rev-parse --abbrev-ref HEAD");
148
+ return stdout.trim();
149
+ } catch (error) {
150
+ return "";
151
+ }
152
+ }