diff --git a/CHANGELOG.md b/CHANGELOG.md
index 67db105..22d9ea9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,23 @@
All notable changes to the "Q Commit" extension will be documented in this file.
+## [0.0.3] - 2026-01-07
+
+### Changed
+- Switch from Amazon Q CLI to Kiro CLI
+- Use claude-haiku-4.5 model by default (2x faster)
+- Simplified prompt for faster execution
+
+### Added
+- Settings panel with configurable options:
+ - Model selection (haiku-4.5, sonnet-4, sonnet-4.5)
+ - Toggle output panel visibility
+ - Custom prompt support
+- Keyboard shortcut: Ctrl+Shift+G (Cmd+Shift+G on Mac)
+- Cancellable generation (click Cancel on notification)
+- Multi-repository support (picker when multiple repos open)
+- CLI installation check with friendly error message
+
## [0.0.2] - 2025-10-31
### Changed
diff --git a/package-lock.json b/package-lock.json
index 86e437f..e9da605 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "q-commit",
- "version": "0.0.2",
+ "version": "0.0.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "q-commit",
- "version": "0.0.2",
+ "version": "0.0.3",
"license": "MIT",
"devDependencies": {
"@types/node": "^20.x",
@@ -676,6 +676,7 @@
"integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
"dev": true,
"license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "6.21.0",
"@typescript-eslint/types": "6.21.0",
@@ -845,6 +846,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1583,6 +1585,7 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -4499,6 +4502,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
diff --git a/package.json b/package.json
index caa7ba0..944b380 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
{
"name": "q-commit",
"displayName": "Q Commit",
- "description": "Generate commit messages using Amazon Q CLI",
- "version": "0.0.2",
+ "description": "Generate commit messages using Kiro CLI",
+ "version": "0.0.3",
"publisher": "codeatlasdev",
"license": "MIT",
"icon": "icon.png",
@@ -37,6 +37,14 @@
"icon": "$(sparkle)"
}
],
+ "keybindings": [
+ {
+ "command": "q-commit.generate",
+ "key": "ctrl+shift+g",
+ "mac": "cmd+shift+g",
+ "when": "scmProvider == git"
+ }
+ ],
"menus": {
"scm/title": [
{
@@ -45,6 +53,27 @@
"when": "scmProvider == git"
}
]
+ },
+ "configuration": {
+ "title": "Q Commit",
+ "properties": {
+ "q-commit.model": {
+ "type": "string",
+ "default": "claude-haiku-4.5",
+ "enum": ["claude-haiku-4.5", "claude-sonnet-4", "claude-sonnet-4.5"],
+ "description": "Model to use for generating commit messages"
+ },
+ "q-commit.showOutput": {
+ "type": "boolean",
+ "default": false,
+ "description": "Show output panel when generating commit messages"
+ },
+ "q-commit.prompt": {
+ "type": "string",
+ "default": "Run git diff --cached and reply with ONLY a conventional commit message wrapped in tags. Example: feat: add login",
+ "description": "Custom prompt for generating commit messages"
+ }
+ }
}
},
"scripts": {
diff --git a/src/extension.ts b/src/extension.ts
index ddbe90d..78281af 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -1,87 +1,143 @@
import * as vscode from 'vscode';
-import { exec } from 'child_process';
+import { exec, ChildProcess } from 'child_process';
import { promisify } from 'util';
-import { COMMIT_MESSAGE_PROMPT } from './prompts';
import { stripAnsi, extractCommitMessage } from './utils';
const execAsync = promisify(exec);
let outputChannel: vscode.OutputChannel;
+let currentProcess: ChildProcess | null = null;
+
+function getConfig() {
+ const config = vscode.workspace.getConfiguration('q-commit');
+ return {
+ model: config.get('model', 'claude-haiku-4.5'),
+ showOutput: config.get('showOutput', false),
+ prompt: config.get(
+ 'prompt',
+ 'Run git diff --cached and reply with ONLY a conventional commit message wrapped in tags. Example: feat: add login'
+ ),
+ };
+}
-async function generateCommitMessage(repoPath: string): Promise {
- const { stdout, stderr } = await execAsync(
- `q chat --no-interactive --trust-tools=fs_read,execute_bash "${COMMIT_MESSAGE_PROMPT.replace(/"/g, '\\"')}"`,
- { cwd: repoPath, maxBuffer: 10 * 1024 * 1024 }
- );
-
- if (stderr) {
- outputChannel.appendLine('[STDERR] ' + stripAnsi(stderr));
+async function checkCliInstalled(): Promise {
+ try {
+ await execAsync('kiro-cli --version');
+ return true;
+ } catch {
+ return false;
}
+}
- outputChannel.appendLine('[STDOUT] Raw output:');
- outputChannel.appendLine(stripAnsi(stdout));
+async function generateCommitMessage(
+ repoPath: string,
+ token: vscode.CancellationToken
+): Promise {
+ const { model, prompt } = getConfig();
- const message = extractCommitMessage(stdout);
+ return new Promise((resolve, reject) => {
+ const cmd = `kiro-cli chat --no-interactive --model ${model} --trust-tools=execute_bash "${prompt.replace(/"/g, '\\"')}"`;
- if (!message) {
- throw new Error('Could not extract commit message from output');
- }
+ currentProcess = exec(
+ cmd,
+ { cwd: repoPath, maxBuffer: 10 * 1024 * 1024 },
+ (error, stdout, stderr) => {
+ currentProcess = null;
+
+ if (token.isCancellationRequested) {
+ reject(new Error('Cancelled'));
+ return;
+ }
- outputChannel.appendLine(`[INFO] Extracted message: "${message}"`);
- return message;
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ if (stderr) outputChannel.appendLine('[STDERR] ' + stripAnsi(stderr));
+ outputChannel.appendLine('[STDOUT] ' + stripAnsi(stdout));
+
+ const message = extractCommitMessage(stdout);
+ if (!message) {
+ reject(new Error('Could not extract commit message from output'));
+ return;
+ }
+
+ outputChannel.appendLine(`[INFO] Extracted: "${message}"`);
+ resolve(message);
+ }
+ );
+
+ token.onCancellationRequested(() => {
+ if (currentProcess) {
+ currentProcess.kill();
+ currentProcess = null;
+ }
+ });
+ });
}
async function handleGenerateCommand() {
+ if (currentProcess) {
+ vscode.window.showWarningMessage('Already generating a commit message...');
+ return;
+ }
+
+ const { showOutput } = getConfig();
outputChannel.clear();
- outputChannel.show();
- outputChannel.appendLine('[Q Commit] Starting...');
+ if (showOutput) outputChannel.show();
+
+ if (!(await checkCliInstalled())) {
+ vscode.window.showErrorMessage('Kiro CLI not found. Please install it first: https://kiro.dev');
+ return;
+ }
const gitExtension = vscode.extensions.getExtension('vscode.git');
if (!gitExtension) {
- outputChannel.appendLine('[ERROR] Git extension not found');
vscode.window.showErrorMessage('Git extension not found');
return;
}
const git = gitExtension.exports.getAPI(1);
-
if (git.repositories.length === 0) {
- outputChannel.appendLine('[ERROR] No git repository found');
vscode.window.showErrorMessage('No git repository found');
return;
}
- const repo = git.repositories[0];
- outputChannel.appendLine(`[INFO] Repository: ${repo.rootUri.fsPath}`);
+ let repo = git.repositories[0];
+ if (git.repositories.length > 1) {
+ type RepoItem = vscode.QuickPickItem & { repo: typeof repo };
+ const items: RepoItem[] = git.repositories.map((r: { rootUri: vscode.Uri }) => ({
+ label: r.rootUri.fsPath.split('/').pop() || '',
+ description: r.rootUri.fsPath,
+ repo: r,
+ }));
+ const selected = await vscode.window.showQuickPick(items, { placeHolder: 'Select repository' });
+ if (!selected) return;
+ repo = selected.repo;
+ }
if (repo.state.indexChanges.length === 0) {
- outputChannel.appendLine('[WARN] No staged changes');
vscode.window.showWarningMessage('No staged changes');
return;
}
- outputChannel.appendLine(`[INFO] Staged changes: ${repo.state.indexChanges.length} files`);
-
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: 'Generating commit message...',
- cancellable: false,
+ cancellable: true,
},
- async () => {
+ async (_, token) => {
try {
- outputChannel.appendLine('[INFO] Executing Q CLI...');
- const message = await generateCommitMessage(repo.rootUri.fsPath);
-
+ const message = await generateCommitMessage(repo.rootUri.fsPath, token);
repo.inputBox.value = message;
- outputChannel.appendLine('[SUCCESS] Commit message generated!');
vscode.window.showInformationMessage('Commit message generated!');
} catch (error) {
- const errorMsg = error instanceof Error ? error.message : 'Unknown error';
- outputChannel.appendLine('[ERROR] ' + errorMsg);
- if (error instanceof Error && error.stack) {
- outputChannel.appendLine('[STACK] ' + error.stack);
- }
- vscode.window.showErrorMessage(`Error: ${errorMsg}`);
+ if (error instanceof Error && error.message === 'Cancelled') return;
+ const msg = error instanceof Error ? error.message : 'Unknown error';
+ outputChannel.appendLine('[ERROR] ' + msg);
+ outputChannel.show();
+ vscode.window.showErrorMessage(`Error: ${msg}`);
}
}
);
@@ -89,14 +145,13 @@ async function handleGenerateCommand() {
export function activate(context: vscode.ExtensionContext) {
outputChannel = vscode.window.createOutputChannel('Q Commit');
-
- const disposable = vscode.commands.registerCommand('q-commit.generate', handleGenerateCommand);
-
- context.subscriptions.push(disposable, outputChannel);
+ context.subscriptions.push(
+ vscode.commands.registerCommand('q-commit.generate', handleGenerateCommand),
+ outputChannel
+ );
}
export function deactivate() {
- if (outputChannel) {
- outputChannel.dispose();
- }
+ if (currentProcess) currentProcess.kill();
+ outputChannel?.dispose();
}
diff --git a/src/prompts.ts b/src/prompts.ts
deleted file mode 100644
index c6f92e9..0000000
--- a/src/prompts.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export const COMMIT_MESSAGE_PROMPT = `You are a VS Code extension that generates git commit messages.
-
-Analyze the staged changes using git diff --cached and generate a concise commit message following conventional commits format.
-
-IMPORTANT: You MUST wrap your final commit message in tags like this:
-feat: add new feature
-
-Return ONLY the commit message wrapped in tags, nothing else after the tags.`;
diff --git a/src/utils.ts b/src/utils.ts
index d433f9a..95b9db2 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -3,27 +3,6 @@ export function stripAnsi(str: string): string {
}
export function extractCommitMessage(output: string): string | null {
- const cleanOutput = stripAnsi(output);
- const commitMatch = cleanOutput.match(/(.*?)<\/commit>/s);
-
- if (commitMatch && commitMatch[1]) {
- return commitMatch[1].trim();
- }
-
- // Fallback: extract after last tool output
- const lines = cleanOutput.split('\n');
- let lastToolIndex = -1;
- for (let i = lines.length - 1; i >= 0; i--) {
- const line = lines[i];
- if (line.includes('🛠️') || line.includes('●') || line.includes('↳') || line.includes('>')) {
- lastToolIndex = i;
- break;
- }
- }
-
- const message = lines
- .slice(lastToolIndex + 1)
- .join('\n')
- .trim();
- return message || null;
+ const match = stripAnsi(output).match(/(.*?)<\/commit>/s);
+ return match?.[1]?.trim() || null;
}