@deplens/mcp 0.1.6 → 0.1.8

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.
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deplens/mcp",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "main": "./src/server.mjs",
6
6
  "bin": {
@@ -17,8 +17,11 @@
17
17
  "node": ">=18"
18
18
  },
19
19
  "dependencies": {
20
- "@deplens/core": "0.1.5",
21
- "@modelcontextprotocol/sdk": "^1.25.1"
20
+ "@deplens/core": "0.1.6",
21
+ "@modelcontextprotocol/sdk": "^1.25.1",
22
+ "fast-glob": "^3.3.2",
23
+ "typescript": "^5.0.0",
24
+ "import-meta-resolve": "^4.0.0"
22
25
  },
23
26
  "publishConfig": {
24
27
  "access": "public"
@@ -0,0 +1,313 @@
1
+ /**
2
+ * changelog-parser.mjs - Parse CHANGELOG.md files to extract version notes
3
+ */
4
+
5
+ import fs from "fs";
6
+ import path from "path";
7
+
8
+ /**
9
+ * Common changelog file names
10
+ */
11
+ const CHANGELOG_NAMES = [
12
+ "CHANGELOG.md",
13
+ "CHANGELOG",
14
+ "changelog.md",
15
+ "Changelog.md",
16
+ "HISTORY.md",
17
+ "HISTORY",
18
+ "history.md",
19
+ "CHANGES.md",
20
+ "CHANGES",
21
+ "changes.md",
22
+ "NEWS.md",
23
+ "RELEASES.md",
24
+ ];
25
+
26
+ /**
27
+ * Find changelog file in package directory
28
+ */
29
+ export function findChangelog(packageDir) {
30
+ for (const name of CHANGELOG_NAMES) {
31
+ const fullPath = path.join(packageDir, name);
32
+ if (fs.existsSync(fullPath)) {
33
+ return fullPath;
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+
39
+ /**
40
+ * Parse version header patterns
41
+ */
42
+ const VERSION_PATTERNS = [
43
+ // ## [1.2.3] - 2024-01-01
44
+ /^##\s*\[?v?(\d+\.\d+\.\d+(?:-[\w.]+)?)\]?(?:\s*[-–—]\s*(.+))?$/i,
45
+ // ## 1.2.3
46
+ /^##\s*v?(\d+\.\d+\.\d+(?:-[\w.]+)?)(?:\s+(.+))?$/i,
47
+ // # Version 1.2.3
48
+ /^#\s*(?:Version\s+)?v?(\d+\.\d+\.\d+(?:-[\w.]+)?)(?:\s+(.+))?$/i,
49
+ // ### 1.2.3
50
+ /^###\s*v?(\d+\.\d+\.\d+(?:-[\w.]+)?)(?:\s+(.+))?$/i,
51
+ ];
52
+
53
+ /**
54
+ * Categorize changelog entry
55
+ */
56
+ function categorizeEntry(line) {
57
+ const lowerLine = line.toLowerCase();
58
+
59
+ if (/breaking|removed|deprecated/i.test(lowerLine)) {
60
+ return "breaking";
61
+ }
62
+ if (/fix|bug|patch|resolved|corrected/i.test(lowerLine)) {
63
+ return "fixed";
64
+ }
65
+ if (/add|new|feature|implement/i.test(lowerLine)) {
66
+ return "added";
67
+ }
68
+ if (/change|update|improve|enhance|refactor/i.test(lowerLine)) {
69
+ return "changed";
70
+ }
71
+ if (/security|vulnerability|cve/i.test(lowerLine)) {
72
+ return "security";
73
+ }
74
+
75
+ return "other";
76
+ }
77
+
78
+ /**
79
+ * Parse a single changelog entry line
80
+ */
81
+ function parseEntryLine(line) {
82
+ // Remove bullet points and clean up
83
+ const cleaned = line
84
+ .replace(/^[\s*\-•·]+/, "")
85
+ .replace(/\[#\d+\].*$/, "") // Remove issue links
86
+ .replace(/\(#\d+\)/, "")
87
+ .replace(/by @[\w-]+/i, "")
88
+ .trim();
89
+
90
+ if (!cleaned || cleaned.length < 3) return null;
91
+
92
+ return {
93
+ text: cleaned,
94
+ category: categorizeEntry(cleaned),
95
+ raw: line,
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Parse changelog content
101
+ */
102
+ export function parseChangelog(content) {
103
+ const lines = content.split("\n");
104
+ const versions = {};
105
+ let currentVersion = null;
106
+ let currentSection = null;
107
+
108
+ for (const line of lines) {
109
+ // Check for version header
110
+ let versionMatch = null;
111
+ for (const pattern of VERSION_PATTERNS) {
112
+ const match = line.match(pattern);
113
+ if (match) {
114
+ versionMatch = match;
115
+ break;
116
+ }
117
+ }
118
+
119
+ if (versionMatch) {
120
+ currentVersion = versionMatch[1];
121
+ const date = versionMatch[2] || null;
122
+
123
+ versions[currentVersion] = {
124
+ version: currentVersion,
125
+ date,
126
+ sections: {
127
+ breaking: [],
128
+ added: [],
129
+ changed: [],
130
+ fixed: [],
131
+ security: [],
132
+ other: [],
133
+ },
134
+ raw: [],
135
+ };
136
+ currentSection = null;
137
+ continue;
138
+ }
139
+
140
+ if (!currentVersion) continue;
141
+
142
+ // Check for section headers (### Added, ### Fixed, etc.)
143
+ const sectionMatch = line.match(
144
+ /^###\s*(Added|Changed|Deprecated|Removed|Fixed|Security|Breaking)/i,
145
+ );
146
+ if (sectionMatch) {
147
+ const section = sectionMatch[1].toLowerCase();
148
+ if (section === "removed" || section === "deprecated") {
149
+ currentSection = "breaking";
150
+ } else {
151
+ currentSection = section;
152
+ }
153
+ continue;
154
+ }
155
+
156
+ // Parse entry lines
157
+ if (line.match(/^[\s*\-•·]+/)) {
158
+ const entry = parseEntryLine(line);
159
+ if (entry) {
160
+ const section = currentSection || entry.category;
161
+ if (versions[currentVersion].sections[section]) {
162
+ versions[currentVersion].sections[section].push(entry);
163
+ } else {
164
+ versions[currentVersion].sections.other.push(entry);
165
+ }
166
+ versions[currentVersion].raw.push(line);
167
+ }
168
+ }
169
+ }
170
+
171
+ return versions;
172
+ }
173
+
174
+ /**
175
+ * Parse changelog file
176
+ */
177
+ export function parseChangelogFile(filePath) {
178
+ if (!fs.existsSync(filePath)) {
179
+ return { error: "Changelog not found", versions: {} };
180
+ }
181
+
182
+ const content = fs.readFileSync(filePath, "utf-8");
183
+ return {
184
+ file: filePath,
185
+ versions: parseChangelog(content),
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Get changelog entries between two versions
191
+ */
192
+ export function getChangesBetweenVersions(changelog, fromVersion, toVersion) {
193
+ const versions = changelog.versions || {};
194
+ const versionList = Object.keys(versions);
195
+
196
+ // Sort versions (semver-like)
197
+ versionList.sort((a, b) => {
198
+ const aParts = a.split(".").map(Number);
199
+ const bParts = b.split(".").map(Number);
200
+ for (let i = 0; i < 3; i++) {
201
+ if ((aParts[i] || 0) !== (bParts[i] || 0)) {
202
+ return (aParts[i] || 0) - (bParts[i] || 0);
203
+ }
204
+ }
205
+ return 0;
206
+ });
207
+
208
+ // Find versions in range
209
+ const fromIndex = versionList.indexOf(fromVersion);
210
+ const toIndex = versionList.indexOf(toVersion);
211
+
212
+ if (fromIndex === -1 || toIndex === -1) {
213
+ // Try to find closest versions
214
+ return {
215
+ exact: false,
216
+ versions: [],
217
+ note: `Could not find exact versions. Available: ${versionList.slice(-5).join(", ")}`,
218
+ };
219
+ }
220
+
221
+ const inRange = versionList.slice(fromIndex + 1, toIndex + 1);
222
+
223
+ const combined = {
224
+ breaking: [],
225
+ added: [],
226
+ changed: [],
227
+ fixed: [],
228
+ security: [],
229
+ };
230
+
231
+ for (const v of inRange) {
232
+ const vData = versions[v];
233
+ if (!vData) continue;
234
+
235
+ for (const [section, entries] of Object.entries(vData.sections)) {
236
+ if (combined[section]) {
237
+ combined[section].push(
238
+ ...entries.map((e) => ({
239
+ ...e,
240
+ version: v,
241
+ })),
242
+ );
243
+ }
244
+ }
245
+ }
246
+
247
+ return {
248
+ exact: true,
249
+ from: fromVersion,
250
+ to: toVersion,
251
+ versionsIncluded: inRange,
252
+ changes: combined,
253
+ summary: {
254
+ breaking: combined.breaking.length,
255
+ added: combined.added.length,
256
+ changed: combined.changed.length,
257
+ fixed: combined.fixed.length,
258
+ security: combined.security.length,
259
+ },
260
+ };
261
+ }
262
+
263
+ /**
264
+ * Format changelog diff as text
265
+ */
266
+ export function formatChangelogDiff(changelogDiff, options = {}) {
267
+ const { maxPerSection = 5 } = options;
268
+ const lines = [];
269
+
270
+ if (!changelogDiff.exact) {
271
+ lines.push(`⚠️ ${changelogDiff.note}`);
272
+ return lines.join("\n");
273
+ }
274
+
275
+ lines.push(`📜 Changelog: ${changelogDiff.from} → ${changelogDiff.to}`);
276
+ lines.push(
277
+ ` Versions included: ${changelogDiff.versionsIncluded.join(", ")}`,
278
+ );
279
+ lines.push("");
280
+
281
+ const sections = [
282
+ { key: "breaking", icon: "🔴", title: "Breaking Changes" },
283
+ { key: "added", icon: "🟢", title: "Added" },
284
+ { key: "changed", icon: "🟡", title: "Changed" },
285
+ { key: "fixed", icon: "🔧", title: "Fixed" },
286
+ { key: "security", icon: "🔒", title: "Security" },
287
+ ];
288
+
289
+ for (const { key, icon, title } of sections) {
290
+ const entries = changelogDiff.changes[key] || [];
291
+ if (entries.length === 0) continue;
292
+
293
+ lines.push(`${icon} ${title} (${entries.length}):`);
294
+ const shown = entries.slice(0, maxPerSection);
295
+ for (const entry of shown) {
296
+ lines.push(` • ${entry.text} (${entry.version})`);
297
+ }
298
+ if (entries.length > maxPerSection) {
299
+ lines.push(` ... and ${entries.length - maxPerSection} more`);
300
+ }
301
+ lines.push("");
302
+ }
303
+
304
+ return lines.join("\n");
305
+ }
306
+
307
+ export default {
308
+ findChangelog,
309
+ parseChangelog,
310
+ parseChangelogFile,
311
+ getChangesBetweenVersions,
312
+ formatChangelogDiff,
313
+ };