Compare commits

...

2 Commits

Author SHA1 Message Date
github-actions[bot]
9362020835 chore(release): v0.0.5-nightly.5 2025-08-08 00:54:13 +00:00
Fan
ffc2d27ca3 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>
2025-08-07 17:07:56 +08:00
7 changed files with 189 additions and 11 deletions

8
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@qwen-code/qwen-code",
"version": "0.0.4",
"version": "0.0.5-nightly.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@qwen-code/qwen-code",
"version": "0.0.4",
"version": "0.0.5-nightly.5",
"workspaces": [
"packages/*"
],
@@ -11801,7 +11801,7 @@
},
"packages/cli": {
"name": "@qwen-code/qwen-code",
"version": "0.0.4",
"version": "0.0.5-nightly.5",
"dependencies": {
"@google/genai": "1.9.0",
"@iarna/toml": "^2.2.5",
@@ -11877,7 +11877,7 @@
},
"packages/core": {
"name": "@qwen-code/qwen-code-core",
"version": "0.0.4",
"version": "0.0.5-nightly.5",
"dependencies": {
"@google/genai": "1.9.0",
"@modelcontextprotocol/sdk": "^1.11.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@qwen-code/qwen-code",
"version": "0.0.4",
"version": "0.0.5-nightly.5",
"engines": {
"node": ">=20.0.0"
},
@@ -13,7 +13,7 @@
"url": "git+https://github.com/QwenLM/qwen-code.git"
},
"config": {
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.0.4"
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.0.5-nightly.5"
},
"scripts": {
"start": "node scripts/start.js",

View File

@@ -1,6 +1,6 @@
{
"name": "@qwen-code/qwen-code",
"version": "0.0.4",
"version": "0.0.5-nightly.5",
"description": "Qwen Code",
"repository": {
"type": "git",
@@ -25,7 +25,7 @@
"dist"
],
"config": {
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.0.4"
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.0.5-nightly.5"
},
"dependencies": {
"@qwen-code/qwen-code-core": "file:../core",

View File

@@ -1,6 +1,6 @@
{
"name": "@qwen-code/qwen-code-core",
"version": "0.0.4",
"version": "0.0.5-nightly.5",
"description": "Qwen Code Core",
"repository": {
"type": "git",

View File

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

View File

@@ -56,6 +56,11 @@ describe('ShellTool', () => {
getSummarizeToolOutputConfig: vi.fn().mockReturnValue(undefined),
getWorkspaceContext: () => createMockWorkspaceContext('.'),
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);
@@ -386,6 +391,123 @@ 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 <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', () => {

View File

@@ -205,12 +205,15 @@ export class ShellTool extends BaseTool<ShellToolParams, ToolResult> {
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;`;
})();
@@ -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;
}
}