@entro314labs/ai-changelog-generator 3.6.1 → 3.7.0

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/manifest.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "manifest_version": "0.3",
3
3
  "name": "ai-changelog-generator",
4
4
  "display_name": "AI Changelog Generator",
5
- "version": "3.6.1",
5
+ "version": "3.7.0",
6
6
  "description": "AI-powered changelog generator with MCP server support - works with most providers, online and local models",
7
7
  "long_description": "Generate intelligent changelogs from git commits using AI. Supports multiple providers including OpenAI, Claude, Gemini, Ollama, and LM Studio. Perfect for automating release documentation with smart commit analysis and categorization.\n\nFeatures:\n- Automatic changelog generation from git commits\n- Working directory change analysis\n- Multiple AI provider support (OpenAI, Azure, Claude, Gemini, Ollama, LM Studio)\n- Interactive and batch modes\n- Repository health analysis\n- Branch analysis and recommendations\n- Commit categorization and impact assessment",
8
8
  "author": {
@@ -56,6 +56,18 @@
56
56
  {
57
57
  "name": "configure_providers",
58
58
  "description": "Manage AI providers - list, switch, test, and configure"
59
+ },
60
+ {
61
+ "name": "analyze_stash",
62
+ "description": "List and analyze git stashed changes with AI-powered insights"
63
+ },
64
+ {
65
+ "name": "list_provider_models",
66
+ "description": "List available AI models for configured providers"
67
+ },
68
+ {
69
+ "name": "check_provider_health",
70
+ "description": "Check health status and connectivity of all AI providers"
59
71
  }
60
72
  ],
61
73
  "tools_generated": false,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@entro314labs/ai-changelog-generator",
3
3
  "displayName": "AI Changelog Generator",
4
- "version": "3.6.1",
4
+ "version": "3.7.0",
5
5
  "type": "module",
6
6
  "description": "AI-powered changelog generator with MCP server support - works with most providers, online and local models",
7
7
  "main": "src/ai-changelog-generator.js",
@@ -22,16 +22,16 @@
22
22
  "CHANGELOG.md"
23
23
  ],
24
24
  "dependencies": {
25
- "@anthropic-ai/sdk": "^0.71.0",
26
- "@aws-sdk/client-bedrock-runtime": "^3.941.0",
25
+ "@anthropic-ai/sdk": "^0.71.2",
26
+ "@aws-sdk/client-bedrock-runtime": "^3.948.0",
27
27
  "@azure/identity": "^4.13.0",
28
28
  "@clack/prompts": "^0.11.0",
29
- "@google/genai": "^1.30.0",
29
+ "@google/genai": "^1.33.0",
30
30
  "@google/generative-ai": "^0.24.1",
31
31
  "@huggingface/hub": "^2.7.1",
32
- "@huggingface/inference": "^4.13.4",
32
+ "@huggingface/inference": "^4.13.5",
33
33
  "@lmstudio/sdk": "^1.5.0",
34
- "@modelcontextprotocol/sdk": "^1.23.0",
34
+ "@modelcontextprotocol/sdk": "^1.24.3",
35
35
  "@modelcontextprotocol/server-filesystem": "2025.11.25",
36
36
  "boxen": "^8.0.1",
37
37
  "chalk": "^5.6.2",
@@ -42,13 +42,13 @@
42
42
  "gradient-string": "^3.0.0",
43
43
  "js-yaml": "^4.1.1",
44
44
  "ollama": "^0.6.3",
45
- "openai": "^6.9.1",
45
+ "openai": "^6.10.0",
46
46
  "ora": "^9.0.0",
47
47
  "yargs": "^18.0.0"
48
48
  },
49
49
  "devDependencies": {
50
- "@anthropic-ai/mcpb": "^2.0.1",
51
- "@types/node": "24.10.1",
50
+ "@anthropic-ai/mcpb": "^2.1.2",
51
+ "@types/node": "25.0.1",
52
52
  "@vitest/browser": "latest",
53
53
  "@vitest/coverage-v8": "latest",
54
54
  "@vitest/ui": "latest",
@@ -154,7 +154,7 @@ export class ChangelogOrchestrator {
154
154
  }
155
155
  }
156
156
 
157
- async generateChangelog(version, since) {
157
+ async generateChangelog(version, since, extraOptions = {}) {
158
158
  try {
159
159
  await this.ensureInitialized()
160
160
 
@@ -167,8 +167,34 @@ export class ChangelogOrchestrator {
167
167
  throw new Error('Not a git repository')
168
168
  }
169
169
 
170
+ // Handle tag range option (e.g., v1.0.0..v2.0.0)
171
+ let effectiveSince = since
172
+ let effectiveUntil = 'HEAD'
173
+ if (extraOptions.tagRange) {
174
+ const [fromTag, toTag] = extraOptions.tagRange.split('..')
175
+ if (fromTag) {
176
+ effectiveSince = fromTag
177
+ }
178
+ if (toTag) {
179
+ effectiveUntil = toTag
180
+ }
181
+ console.log(colors.infoMessage(`📦 Generating changelog between tags: ${fromTag} → ${toTag || 'HEAD'}`))
182
+ }
183
+
184
+ // Display author filter if specified
185
+ if (extraOptions.author) {
186
+ console.log(colors.infoMessage(`👤 Filtering commits by author: ${extraOptions.author}`))
187
+ }
188
+
189
+ // Merge options
190
+ const mergedOptions = {
191
+ ...this.options,
192
+ author: extraOptions.author,
193
+ until: effectiveUntil,
194
+ }
195
+
170
196
  // Generate changelog using the service
171
- const result = await this.changelogService.generateChangelog(version, since, this.options)
197
+ const result = await this.changelogService.generateChangelog(version, effectiveSince, mergedOptions)
172
198
 
173
199
  if (!result) {
174
200
  console.log(colors.warningMessage('No changelog generated'))
@@ -41,8 +41,14 @@ export class ApplicationService {
41
41
 
42
42
  async generateChangelog(options = {}) {
43
43
  try {
44
- const { version, since } = options
45
- return await this.orchestrator.generateChangelog(version, since)
44
+ const { version, since, author, tagRange, format, output, dryRun } = options
45
+ return await this.orchestrator.generateChangelog(version, since, {
46
+ author,
47
+ tagRange,
48
+ format,
49
+ output,
50
+ dryRun,
51
+ })
46
52
  } catch (error) {
47
53
  console.error(colors.errorMessage('Application service error:'), error.message)
48
54
  throw error
@@ -37,8 +37,11 @@ export class ChangelogService {
37
37
  async generateChangelog(version = null, since = null, options = {}) {
38
38
  console.log(colors.processingMessage('🤖 Analyzing changes with AI...'))
39
39
 
40
- // Get committed changes
41
- const commits = await this.gitService.getCommitsSince(since)
40
+ // Get committed changes with optional filters
41
+ const commits = await this.gitService.getCommitsSince(since, {
42
+ author: options.author,
43
+ until: options.until,
44
+ })
42
45
 
43
46
  // Get working directory changes using analysis engine
44
47
  let workingDirAnalysis = null
@@ -205,11 +205,43 @@ export class GitManager {
205
205
  * Get commits between two references
206
206
  * @param {string} from - Starting reference (commit, tag, branch)
207
207
  * @param {string} to - Ending reference (default: 'HEAD')
208
+ * @param {Object} options - Optional filters
209
+ * @param {string} options.author - Filter by author name or email
208
210
  * @returns {Array<Object>} Array of commit objects
209
211
  */
210
- getCommitsBetween(from, to = 'HEAD') {
212
+ getCommitsBetween(from, to = 'HEAD', options = {}) {
211
213
  try {
212
- const output = this.execGitSafe(`git log ${from}..${to} --oneline`)
214
+ let command = `git log ${from}..${to} --oneline`
215
+ if (options.author) {
216
+ command += ` --author="${options.author}"`
217
+ }
218
+ const output = this.execGitSafe(command)
219
+ return output
220
+ .split('\n')
221
+ .filter((line) => line.trim())
222
+ .map((line) => {
223
+ const [hash, ...messageParts] = line.split(' ')
224
+ return {
225
+ hash: hash.trim(),
226
+ message: messageParts.join(' '),
227
+ }
228
+ })
229
+ } catch {
230
+ return []
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Get commits filtered by author
236
+ * @param {string} author - Author name or email to filter by
237
+ * @param {number} limit - Maximum number of commits to return
238
+ * @returns {Array<Object>} Array of commit objects by this author
239
+ */
240
+ getCommitsByAuthor(author, limit = 50) {
241
+ try {
242
+ const output = this.execGitSafe(
243
+ `git log --author="${author}" --oneline -n ${limit}`
244
+ )
213
245
  return output
214
246
  .split('\n')
215
247
  .filter((line) => line.trim())
@@ -225,6 +257,116 @@ export class GitManager {
225
257
  }
226
258
  }
227
259
 
260
+ /**
261
+ * Get commits between two tags
262
+ * @param {string} fromTag - Starting tag
263
+ * @param {string} toTag - Ending tag (default: 'HEAD')
264
+ * @param {Object} options - Optional filters
265
+ * @returns {Array<Object>} Array of commit objects between tags
266
+ */
267
+ getCommitsBetweenTags(fromTag, toTag = 'HEAD', options = {}) {
268
+ return this.getCommitsBetween(fromTag, toTag, options)
269
+ }
270
+
271
+ /**
272
+ * Get list of all authors in repository
273
+ * @returns {Array<string>} Array of unique author names
274
+ */
275
+ getAllAuthors() {
276
+ try {
277
+ const output = this.execGitSafe('git log --format="%an" | sort -u')
278
+ return output
279
+ .split('\n')
280
+ .filter((name) => name.trim())
281
+ .sort()
282
+ } catch {
283
+ return []
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Get list of stashed changes
289
+ * @returns {Array<Object>} Array of stash entries with index, message, and date
290
+ */
291
+ getStashList() {
292
+ try {
293
+ const output = this.execGitSafe('git stash list --format="%gd|%gs|%ci"')
294
+ if (!output.trim()) {
295
+ return []
296
+ }
297
+ return output
298
+ .split('\n')
299
+ .filter((line) => line.trim())
300
+ .map((line) => {
301
+ const [index, message, date] = line.split('|')
302
+ return {
303
+ index: index.trim(),
304
+ message: message.trim(),
305
+ date: date.trim(),
306
+ }
307
+ })
308
+ } catch {
309
+ return []
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Get detailed information about a specific stash entry
315
+ * @param {string} stashRef - Stash reference (e.g., 'stash@{0}')
316
+ * @returns {Object|null} Stash details including files changed
317
+ */
318
+ getStashDetails(stashRef = 'stash@{0}') {
319
+ try {
320
+ // Get stash message
321
+ const message = this.execGitSafe(`git stash list --format="%gs" -1 ${stashRef}`).trim()
322
+
323
+ // Get files changed in stash
324
+ const statOutput = this.execGitSafe(`git stash show ${stashRef} --stat`)
325
+ const diffOutput = this.execGitSafe(`git stash show ${stashRef} -p`)
326
+
327
+ // Parse stat output for files
328
+ const files = statOutput
329
+ .split('\n')
330
+ .filter((line) => line.includes('|'))
331
+ .map((line) => {
332
+ const match = line.match(/^\s*(.+?)\s*\|\s*(\d+)/)
333
+ if (match) {
334
+ return {
335
+ path: match[1].trim(),
336
+ changes: parseInt(match[2], 10),
337
+ }
338
+ }
339
+ return null
340
+ })
341
+ .filter(Boolean)
342
+
343
+ // Get summary stats
344
+ const summaryMatch = statOutput.match(/(\d+) files? changed(?:, (\d+) insertions?)?(?:, (\d+) deletions?)?/)
345
+
346
+ return {
347
+ ref: stashRef,
348
+ message,
349
+ files,
350
+ diff: diffOutput,
351
+ stats: {
352
+ filesChanged: summaryMatch ? parseInt(summaryMatch[1], 10) : files.length,
353
+ insertions: summaryMatch && summaryMatch[2] ? parseInt(summaryMatch[2], 10) : 0,
354
+ deletions: summaryMatch && summaryMatch[3] ? parseInt(summaryMatch[3], 10) : 0,
355
+ },
356
+ }
357
+ } catch {
358
+ return null
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Check if there are any stashed changes
364
+ * @returns {boolean} True if stash has entries
365
+ */
366
+ hasStashedChanges() {
367
+ return this.getStashList().length > 0
368
+ }
369
+
228
370
  /**
229
371
  * Check if a file exists in a specific commit
230
372
  * @param {string} commitHash - The commit to check
@@ -333,9 +333,29 @@ export class GitService {
333
333
  }
334
334
  }
335
335
 
336
- async getCommitsSince(since) {
336
+ async getCommitsSince(since, options = {}) {
337
337
  try {
338
- const command = since ? `git log --oneline --since="${since}"` : 'git log --oneline -10'
338
+ let command = 'git log --oneline'
339
+
340
+ // Handle different "since" formats
341
+ if (since) {
342
+ // Check if it's a tag/ref (starts with v, contains dots, or is a commit hash)
343
+ const isRef = /^v?\d|^[a-f0-9]{6,40}$/i.test(since)
344
+ if (isRef) {
345
+ const until = options.until || 'HEAD'
346
+ command = `git log ${since}..${until} --oneline`
347
+ } else {
348
+ // Treat as date
349
+ command += ` --since="${since}"`
350
+ }
351
+ } else {
352
+ command += ' -10'
353
+ }
354
+
355
+ // Add author filter if specified
356
+ if (options.author) {
357
+ command += ` --author="${options.author}"`
358
+ }
339
359
 
340
360
  const output = this.gitManager.execGitSafe(command)
341
361
  return output