Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,65 @@ That's it. No configuration, no setup wizards, no complexity.

## Extension Settings

This extension keeps it simple with just two optional settings:
This extension provides several optional settings to customize your experience:

* `claude-commit.claudePath`: Custom path to Claude CLI executable (auto-detects by default)
* `claude-commit.debugMode`: Enable debug output for troubleshooting
- `claude-commit.claudePath`: Custom path to Claude CLI executable (auto-detects by default)
- `claude-commit.debugMode`: Enable debug output for troubleshooting
- `claude-commit.customInstructions`: Custom instructions for commit message generation
- `claude-commit.instructionsFilePath`: Path to a file containing custom commit message instructions

## Configuration Examples

### Using a custom Claude path

```json
{
"claude-commit.claudePath": "/usr/local/bin/claude"
}
```

### Debug mode for troubleshooting

```json
{
"claude-commit.debugMode": true
}
```

### Custom instructions (inline)

Provide custom rules directly in your settings:

```json
{
"claude-commit.customInstructions": "Summarize changes into a single sentence suitable for release notes. Focus on user-facing impact rather than technical details."
}
```

### Custom instructions (from file)

Use a file to share commit message conventions across your team:

```json
{
"claude-commit.instructionsFilePath": ".vscode/copilot/commit-message-instructions.md"
}
```

Then create `.vscode/copilot/commit-message-instructions.md`:

```markdown
# Commit Message Instructions

- Summarize changes into a sentence for release notes
- Focus on the "why" rather than the "what"
- Use imperative mood (e.g., "Add feature" not "Added feature")
- Maximum 50 characters for the subject line
- Include ticket number if applicable: [TICKET-123] Subject
```

The file path can be relative (from workspace root) or absolute. If both file and inline instructions are provided, the file takes precedence.

## Troubleshooting

### Claude CLI not found
Expand Down
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@
"type": "boolean",
"default": false,
"description": "Enable debug mode to show CLI commands being executed"
},
"claude-commit.customInstructions": {
"type": "string",
"default": "",
"description": "Custom instructions for commit message generation (e.g., 'Summarize changes into a sentence for release notes')"
},
"claude-commit.instructionsFilePath": {
"type": "string",
"default": "",
"description": "Path to a file containing custom commit message instructions (e.g., '.vscode/copilot/commit-message-instructions.md')"
}
}
}
Expand Down
90 changes: 80 additions & 10 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import * as vscode from 'vscode';
import { exec } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs';
import * as path from 'path';

const execAsync = promisify(exec);

Expand Down Expand Up @@ -558,16 +560,69 @@ class CommitMessageGenerator {
constructor() {
this.config = vscode.workspace.getConfiguration('claude-commit');
this.debugMode = this.config.get<boolean>('debugMode') || false;

if (this.debugMode) {
outputChannel.appendLine('\n=== CONFIGURATION ===');
outputChannel.appendLine(`claudePath: ${this.config.get<string>('claudePath') || 'not set'}`);
outputChannel.appendLine(`debugMode: ${this.debugMode}`);
outputChannel.appendLine(`customInstructions: ${this.config.get<string>('customInstructions') || 'not set'}`);
outputChannel.appendLine(`instructionsFilePath: ${this.config.get<string>('instructionsFilePath') || 'not set'}`);
}

this.cliExecutor = new ClaudeCLIExecutor(this.debugMode);
}

private async loadCustomInstructions(workspacePath: string): Promise<string> {
if (this.debugMode) {
outputChannel.appendLine('\n[INSTRUCTIONS] Loading custom instructions...');
}

let customInstructions = '';

// First, try to load from file
const instructionsFilePath = this.config.get<string>('instructionsFilePath');
if (instructionsFilePath && instructionsFilePath.trim() !== '') {
try {
// Resolve relative paths from workspace root
const resolvedPath = path.isAbsolute(instructionsFilePath)
? instructionsFilePath
: path.join(workspacePath, instructionsFilePath);

if (this.debugMode) {
outputChannel.appendLine(`[INSTRUCTIONS] Reading from file: ${resolvedPath}`);
}

if (fs.existsSync(resolvedPath)) {
customInstructions = fs.readFileSync(resolvedPath, 'utf8').trim();
if (this.debugMode) {
outputChannel.appendLine(`[INSTRUCTIONS] Loaded ${customInstructions.length} chars from file`);
}
} else {
if (this.debugMode) {
outputChannel.appendLine(`[INSTRUCTIONS] File not found: ${resolvedPath}`);
}
}
} catch (error: any) {
if (this.debugMode) {
outputChannel.appendLine(`[INSTRUCTIONS] Error reading file: ${error.message}`);
}
}
}

// If no file instructions, use inline custom instructions
if (!customInstructions) {
const inlineInstructions = this.config.get<string>('customInstructions');
if (inlineInstructions && inlineInstructions.trim() !== '') {
customInstructions = inlineInstructions.trim();
if (this.debugMode) {
outputChannel.appendLine(`[INSTRUCTIONS] Using inline instructions: ${customInstructions.length} chars`);
}
}
}

return customInstructions;
}

async generateCommitMessage(repositoryPath?: string): Promise<string> {
if (this.debugMode) {
outputChannel.appendLine('\n=== GENERATE COMMIT MESSAGE START ===');
Expand Down Expand Up @@ -637,9 +692,28 @@ class CommitMessageGenerator {
if (this.debugMode) {
outputChannel.appendLine(`\n[PROMPT] Creating prompt for ${fileType}...`);
}

// Build prompt with hardcoded conventional format
const prompt = `Generate a git commit message for the following changes.

// Load custom instructions
const customInstructions = await this.loadCustomInstructions(cwd || '');

// Build prompt with default rules
let rulesSection = `Rules:
- Use conventional commit format
- Keep under 72 characters for the first line
- Be specific and clear
- Common types: feat, fix, docs, style, refactor, test, chore`;

// If custom instructions exist, use them instead
if (customInstructions) {
rulesSection = `Custom Instructions:
${customInstructions}`;

if (this.debugMode) {
outputChannel.appendLine(`[PROMPT] Using custom instructions (${customInstructions.length} chars)`);
}
}

const prompt = `Generate a git commit message for the following changes.

IMPORTANT: Return ONLY the commit message text itself. Do not include:
- Any explanatory text like "Based on...", "Here's...", or "Here is..."
Expand All @@ -652,11 +726,7 @@ Just return the raw commit message text that will be used directly in git commit
Git diff:
${diff}

Rules:
- Use conventional commit format
- Keep under 72 characters for the first line
- Be specific and clear
- Common types: feat, fix, docs, style, refactor, test, chore
${rulesSection}

Remember: Return ONLY the commit message text, nothing else.`;

Expand Down