feat: add qwencoder as co-author (#207)

* init

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix shell tool regex pattern for git commit messages

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Fan
2025-08-07 17:07:56 +08:00
committed by GitHub
parent f0c60b90ea
commit ffc2d27ca3
3 changed files with 180 additions and 2 deletions

View File

@@ -79,6 +79,12 @@ export interface TelemetrySettings {
outfile?: string; outfile?: string;
} }
export interface GitCoAuthorSettings {
enabled?: boolean;
name?: string;
email?: string;
}
export interface GeminiCLIExtension { export interface GeminiCLIExtension {
name: string; name: string;
version: string; version: string;
@@ -164,6 +170,7 @@ export interface ConfigParameters {
contextFileName?: string | string[]; contextFileName?: string | string[];
accessibility?: AccessibilitySettings; accessibility?: AccessibilitySettings;
telemetry?: TelemetrySettings; telemetry?: TelemetrySettings;
gitCoAuthor?: GitCoAuthorSettings;
usageStatisticsEnabled?: boolean; usageStatisticsEnabled?: boolean;
fileFiltering?: { fileFiltering?: {
respectGitIgnore?: boolean; respectGitIgnore?: boolean;
@@ -227,6 +234,7 @@ export class Config {
private readonly showMemoryUsage: boolean; private readonly showMemoryUsage: boolean;
private readonly accessibility: AccessibilitySettings; private readonly accessibility: AccessibilitySettings;
private readonly telemetrySettings: TelemetrySettings; private readonly telemetrySettings: TelemetrySettings;
private readonly gitCoAuthor: GitCoAuthorSettings;
private readonly usageStatisticsEnabled: boolean; private readonly usageStatisticsEnabled: boolean;
private geminiClient!: GeminiClient; private geminiClient!: GeminiClient;
private readonly fileFiltering: { private readonly fileFiltering: {
@@ -304,6 +312,11 @@ export class Config {
logPrompts: params.telemetry?.logPrompts ?? true, logPrompts: params.telemetry?.logPrompts ?? true,
outfile: params.telemetry?.outfile, 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.usageStatisticsEnabled = params.usageStatisticsEnabled ?? true;
this.fileFiltering = { this.fileFiltering = {
@@ -571,6 +584,10 @@ export class Config {
return this.telemetrySettings.outfile; return this.telemetrySettings.outfile;
} }
getGitCoAuthor(): GitCoAuthorSettings {
return this.gitCoAuthor;
}
getGeminiClient(): GeminiClient { getGeminiClient(): GeminiClient {
return this.geminiClient; return this.geminiClient;
} }

View File

@@ -56,6 +56,11 @@ describe('ShellTool', () => {
getSummarizeToolOutputConfig: vi.fn().mockReturnValue(undefined), getSummarizeToolOutputConfig: vi.fn().mockReturnValue(undefined),
getWorkspaceContext: () => createMockWorkspaceContext('.'), getWorkspaceContext: () => createMockWorkspaceContext('.'),
getGeminiClient: vi.fn(), getGeminiClient: vi.fn(),
getGitCoAuthor: vi.fn().mockReturnValue({
enabled: true,
name: 'Qwen-Coder',
email: 'qwen-coder@alibabacloud.com',
}),
} as unknown as Config; } as unknown as Config;
shellTool = new ShellTool(mockConfig); shellTool = new ShellTool(mockConfig);
@@ -386,6 +391,123 @@ describe('ShellTool', () => {
expect(confirmation).toBe(false); 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 <qwen-coder@alibabacloud.com>"`,
);
});
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 <qwen-coder@alibabacloud.com>'`,
);
});
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 <qwen-coder@alibabacloud.com>"`,
);
});
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 <qwen-coder@alibabacloud.com>"`,
);
});
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 <custom@example.com>"`,
);
});
});
}); });
describe('validateToolParams', () => { describe('validateToolParams', () => {

View File

@@ -205,12 +205,15 @@ export class ShellTool extends BaseTool<ShellToolParams, ToolResult> {
const tempFilePath = path.join(os.tmpdir(), tempFileName); const tempFilePath = path.join(os.tmpdir(), tempFileName);
try { 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 // pgrep is not available on Windows, so we can't get background PIDs
const commandToExecute = isWindows const commandToExecute = isWindows
? strippedCommand ? processedCommand
: (() => { : (() => {
// wrap command to append subprocess pids (via pgrep) to temporary file // wrap command to append subprocess pids (via pgrep) to temporary file
let command = strippedCommand.trim(); let command = processedCommand.trim();
if (!command.endsWith('&')) command += ';'; if (!command.endsWith('&')) command += ';';
return `{ ${command} }; __code=$?; pgrep -g 0 >${tempFilePath} 2>&1; exit $__code;`; return `{ ${command} }; __code=$?; pgrep -g 0 >${tempFilePath} 2>&1; exit $__code;`;
})(); })();
@@ -382,4 +385,40 @@ export class ShellTool extends BaseTool<ShellToolParams, ToolResult> {
} }
} }
} }
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;
}
} }