From 9520660382c772ded110194cc5d444c5a88039ab Mon Sep 17 00:00:00 2001 From: "koalazf.99" Date: Tue, 5 Aug 2025 17:41:38 +0800 Subject: [PATCH] init Co-authored-by: Qwen-Coder --- packages/core/src/config/config.ts | 17 ++++ packages/core/src/tools/shell.test.ts | 122 ++++++++++++++++++++++++++ packages/core/src/tools/shell.ts | 43 ++++++++- 3 files changed, 180 insertions(+), 2 deletions(-) diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index feb67a92..03858530 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -77,6 +77,12 @@ export interface TelemetrySettings { outfile?: string; } +export interface GitCoAuthorSettings { + enabled?: boolean; + name?: string; + email?: string; +} + export interface GeminiCLIExtension { name: string; version: string; @@ -161,6 +167,7 @@ export interface ConfigParameters { contextFileName?: string | string[]; accessibility?: AccessibilitySettings; telemetry?: TelemetrySettings; + gitCoAuthor?: GitCoAuthorSettings; usageStatisticsEnabled?: boolean; fileFiltering?: { respectGitIgnore?: boolean; @@ -221,6 +228,7 @@ export class Config { private readonly showMemoryUsage: boolean; private readonly accessibility: AccessibilitySettings; private readonly telemetrySettings: TelemetrySettings; + private readonly gitCoAuthor: GitCoAuthorSettings; private readonly usageStatisticsEnabled: boolean; private geminiClient!: GeminiClient; private readonly fileFiltering: { @@ -293,6 +301,11 @@ export class Config { logPrompts: params.telemetry?.logPrompts ?? true, outfile: params.telemetry?.outfile, }; + this.gitCoAuthor = { + enabled: params.gitCoAuthor?.enabled ?? true, + name: params.gitCoAuthor?.name ?? 'Qwen-Coder', + email: params.gitCoAuthor?.email ?? 'qwen-coder@alibabacloud.com', + }; this.usageStatisticsEnabled = params.usageStatisticsEnabled ?? true; this.fileFiltering = { @@ -529,6 +542,10 @@ export class Config { return this.telemetrySettings.outfile; } + getGitCoAuthor(): GitCoAuthorSettings { + return this.gitCoAuthor; + } + getGeminiClient(): GeminiClient { return this.geminiClient; } diff --git a/packages/core/src/tools/shell.test.ts b/packages/core/src/tools/shell.test.ts index 55364197..9a09c575 100644 --- a/packages/core/src/tools/shell.test.ts +++ b/packages/core/src/tools/shell.test.ts @@ -54,6 +54,11 @@ describe('ShellTool', () => { getTargetDir: vi.fn().mockReturnValue('/test/dir'), getSummarizeToolOutputConfig: vi.fn().mockReturnValue(undefined), getGeminiClient: vi.fn(), + getGitCoAuthor: vi.fn().mockReturnValue({ + enabled: true, + name: 'Qwen-Coder', + email: 'qwen-coder@alibabacloud.com', + }), } as unknown as Config; shellTool = new ShellTool(mockConfig); @@ -384,4 +389,121 @@ describe('ShellTool', () => { expect(confirmation).toBe(false); }); }); + + describe('addCoAuthorToGitCommit', () => { + it('should add co-author to git commit with double quotes', () => { + const command = 'git commit -m "Initial commit"'; + // Use public test method + const result = ( + shellTool as unknown as { + addCoAuthorToGitCommit: (command: string) => string; + } + ).addCoAuthorToGitCommit(command); + expect(result).toBe( + `git commit -m "Initial commit + +Co-authored-by: Qwen-Coder "`, + ); + }); + + it('should add co-author to git commit with single quotes', () => { + const command = "git commit -m 'Fix bug'"; + const result = ( + shellTool as unknown as { + addCoAuthorToGitCommit: (command: string) => string; + } + ).addCoAuthorToGitCommit(command); + expect(result).toBe( + `git commit -m 'Fix bug + +Co-authored-by: Qwen-Coder '`, + ); + }); + + it('should handle git commit with additional flags', () => { + const command = 'git commit -a -m "Add feature"'; + const result = ( + shellTool as unknown as { + addCoAuthorToGitCommit: (command: string) => string; + } + ).addCoAuthorToGitCommit(command); + expect(result).toBe( + `git commit -a -m "Add feature + +Co-authored-by: Qwen-Coder "`, + ); + }); + + it('should not modify non-git commands', () => { + const command = 'npm install'; + const result = ( + shellTool as unknown as { + addCoAuthorToGitCommit: (command: string) => string; + } + ).addCoAuthorToGitCommit(command); + expect(result).toBe('npm install'); + }); + + it('should not modify git commands without -m flag', () => { + const command = 'git commit'; + const result = ( + shellTool as unknown as { + addCoAuthorToGitCommit: (command: string) => string; + } + ).addCoAuthorToGitCommit(command); + expect(result).toBe('git commit'); + }); + + it('should handle git commit with escaped quotes in message', () => { + const command = 'git commit -m "Fix \\"quoted\\" text"'; + const result = ( + shellTool as unknown as { + addCoAuthorToGitCommit: (command: string) => string; + } + ).addCoAuthorToGitCommit(command); + expect(result).toBe( + `git commit -m "Fix \\"quoted\\" text + +Co-authored-by: Qwen-Coder "`, + ); + }); + + it('should not add co-author when disabled in config', () => { + // Mock config with disabled co-author + (mockConfig.getGitCoAuthor as Mock).mockReturnValue({ + enabled: false, + name: 'Qwen-Coder', + email: 'qwen-coder@alibabacloud.com', + }); + + const command = 'git commit -m "Initial commit"'; + const result = ( + shellTool as unknown as { + addCoAuthorToGitCommit: (command: string) => string; + } + ).addCoAuthorToGitCommit(command); + expect(result).toBe('git commit -m "Initial commit"'); + }); + + it('should use custom name and email from config', () => { + // Mock config with custom co-author details + (mockConfig.getGitCoAuthor as Mock).mockReturnValue({ + enabled: true, + name: 'Custom Bot', + email: 'custom@example.com', + }); + + const command = 'git commit -m "Test commit"'; + const result = ( + shellTool as unknown as { + addCoAuthorToGitCommit: (command: string) => string; + } + ).addCoAuthorToGitCommit(command); + expect(result).toBe( + `git commit -m "Test commit + +Co-authored-by: Custom Bot "`, + ); + }); + }); }); diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts index 02fcbb7f..5e091c52 100644 --- a/packages/core/src/tools/shell.ts +++ b/packages/core/src/tools/shell.ts @@ -200,12 +200,15 @@ export class ShellTool extends BaseTool { const tempFilePath = path.join(os.tmpdir(), tempFileName); try { + // Add co-author to git commit commands + const processedCommand = this.addCoAuthorToGitCommit(strippedCommand); + // pgrep is not available on Windows, so we can't get background PIDs const commandToExecute = isWindows - ? strippedCommand + ? processedCommand : (() => { // wrap command to append subprocess pids (via pgrep) to temporary file - let command = strippedCommand.trim(); + let command = processedCommand.trim(); if (!command.endsWith('&')) command += ';'; return `{ ${command} }; __code=$?; pgrep -g 0 >${tempFilePath} 2>&1; exit $__code;`; })(); @@ -377,4 +380,40 @@ export class ShellTool extends BaseTool { } } } + + private addCoAuthorToGitCommit(command: string): string { + // Check if co-author feature is enabled + const gitCoAuthorSettings = this.config.getGitCoAuthor(); + if (!gitCoAuthorSettings.enabled) { + return command; + } + + // Check if this is a git commit command + const gitCommitPattern = /^git\s+commit/; + if (!gitCommitPattern.test(command.trim())) { + return command; + } + + // Define the co-author line using configuration + const coAuthor = ` + +Co-authored-by: ${gitCoAuthorSettings.name} <${gitCoAuthorSettings.email}>`; + + // Handle different git commit patterns + // Match -m "message" or -m 'message' + const messagePattern = /(-m\s+)(['"])((?:[^\\]|\\.)*)(\2)/; + const match = command.match(messagePattern); + + if (match) { + const [fullMatch, prefix, quote, existingMessage, closingQuote] = match; + const newMessage = existingMessage + coAuthor; + const replacement = prefix + quote + newMessage + closingQuote; + + return command.replace(fullMatch, replacement); + } + + // If no -m flag found, the command might open an editor + // In this case, we can't easily modify it, so return as-is + return command; + } }