8 Commits
Author SHA1 Message Date
hykocx 954ca5bd47 docs(changelog): release version 1.1.5 2026-05-01 19:17:10 -04:00
hykocx 030a7b1472 docs: add changelog with project history 2026-05-01 19:16:10 -04:00
hykocx 05879f9171 fix(git): remove --diff-filter=d to include deleted files in diffs 2026-05-01 19:13:17 -04:00
hykocx ca73d9683f feat(providers): add Claude Code CLI provider
- implement `ClaudeCodeProvider` using `claude -p` with plain text output
- register `claudecode` as a new provider enum option in package.json
- add `enumDescriptions` for all provider choices
- update API key description to note it is not required for Claude Code
- bump version to 1.1.5
2026-05-01 14:59:26 -04:00
hykocx e309fa3acd chore: bump version to 1.1.4 2026-04-24 12:38:38 -04:00
hykocx c870350c11 chore(config): update vscodeignore to exclude additional build artifacts 2026-04-24 12:38:01 -04:00
hykocx f823dc4803 docs(readme): update banner image and replace asset file
- update README.md to reference new preview.jpg instead of git-banner.png
- add assets/preview.jpg as replacement banner image
- remove deprecated assets/git-banner.png
2026-04-24 12:32:06 -04:00
hykocx cbceea72fa docs(readme): update description and settings table to reflect new prompt version
- update diff description to mention file summary context
- replace `zemit.commitStyle` setting with `zemit.promptVersion` in settings table
2026-04-24 12:26:06 -04:00
10 changed files with 181 additions and 20 deletions
+7 -3
View File
@@ -1,6 +1,10 @@
src/**
node_modules/**
.vscode/**
.git/**
.gitignore
node_modules/**
*.vsix
assets/**
src/**
tsconfig.json
cline-main/**
**/*.map
package-lock.json
+68
View File
@@ -0,0 +1,68 @@
# Changelog
All notable changes to Zemit are documented here.
## [1.1.5] - 2026-05-01
### Added
- Claude Code CLI provider (local, no API key required)
### Fixed
- File deletions were not detected when generating commit messages (`--diff-filter=d` excluded deleted files)
---
## [1.1.4] - 2026-04-24
### Changed
- Improved prompt structure: diff is now accompanied by a `--name-status` summary of changed files
- Updated README description and settings table to reflect new prompt version
- Updated extension icon and banner image
---
## [1.1.3] - 2026-04-15
### Changed
- Refactored prompt system into a dedicated `prompts` module with enhanced commit instructions
- Updated app icon and git banner images
---
## [1.1.2] - 2026-04-15
### Changed
- Updated extension icon
- Shortened marketplace description
---
## [1.1.1] - 2026-04-15
### Changed
- Translated all UI messages to French
- Updated default model to `claude-sonnet-4-6`
---
## [1.1.0] - 2026-04-15
### Added
- Dedicated `providers` module to support multiple AI backends
- Dedicated `git` module for staged/unstaged diff handling
- VS Code extension entry point with model selection
### Changed
- Refined conventional commit prompt to limit body usage
- Extracted commit generation logic into its own module
---
## [1.0.0] - 2026-04-15
### Added
- Initial release of Zemit
- AI-powered commit message generation using the Anthropic Claude API
- Support for staged and unstaged git diffs
- Conventional commit format by default
- GNU General Public License v3.0
+18 -6
View File
@@ -2,11 +2,11 @@
Génère des messages de commit, via un modèle d'IA, directement dans VSCode.
![banner](/assets/git-banner.png)
![preview](/assets/preview.jpg)
## Ce que ça fait
Zemit lit le diff stagé dans ton dépôt Git et envoie le contenu à un modèle d'IA pour produire un message de commit. Le message apparaît directement dans le champ de saisie du panneau Source Control.
Zemit lit le diff stagé et le résumé des fichiers modifiés dans ton dépôt Git, puis envoie ce contexte à un modèle d'IA pour produire un message de commit. Le message apparaît directement dans le champ de saisie du panneau Source Control.
Tu peux interrompre la génération à tout moment depuis le même panneau.
@@ -14,7 +14,7 @@ Tu peux interrompre la génération à tout moment depuis le même panneau.
- VS Code 1.85 ou plus récent
- Un dépôt Git avec des changements stagés
- Une clé API pour le fournisseur choisi (non requise pour Ollama)
- Une clé API pour le fournisseur choisi (non requise pour Ollama et Claude Code)
## Installation
@@ -39,11 +39,11 @@ Les paramètres se trouvent dans les préférences VS Code sous **Zemit**.
| Paramètre | Description | Défaut |
|---|---|---|
| `zemit.provider` | Fournisseur d'IA : `anthropic`, `openai` ou `ollama` | `anthropic` |
| `zemit.apiKey` | Clé API du fournisseur (inutile pour Ollama) | _(vide)_ |
| `zemit.provider` | Fournisseur d'IA : `anthropic`, `openai`, `ollama` ou `claudecode` | `anthropic` |
| `zemit.apiKey` | Clé API du fournisseur (inutile pour Ollama et Claude Code) | _(vide)_ |
| `zemit.model` | Modèle à utiliser | `claude-sonnet-4-6` |
| `zemit.baseUrl` | URL de base personnalisée (ex. Ollama local) | _(vide)_ |
| `zemit.commitStyle` | Style du message : `conventional` ou `simple` | `conventional` |
| `zemit.promptVersion` | Version du prompt | `zemit-v2` |
| `zemit.maxDiffSize` | Taille maximale du diff envoyé à l'IA (en caractères) | `5000` |
Pour choisir un modèle parmi ceux disponibles chez ton fournisseur, lance la commande **Zemit: Select Model** depuis la palette de commandes.
@@ -61,6 +61,18 @@ Pour arrêter une génération en cours, clique sur l'icône d'arrêt au même e
- **Anthropic** : modèles Claude (Haiku, Sonnet, Opus)
- **OpenAI** : modèles GPT
- **Ollama** : modèles locaux, aucune clé requise
- **Claude Code** : utilise le CLI `claude` installé localement, aucune clé API requise — l'authentification est gérée par Claude Code lui-même
### Utiliser Claude Code
Assure-toi que le CLI `claude` est installé et accessible (`~/.local/bin/claude` ou dans ton `PATH`), puis configure :
```json
"zemit.provider": "claudecode",
"zemit.model": "claude-sonnet-4-6"
```
> Note : le provider Claude Code ne supporte pas le streaming — le message apparaît d'un coup à la fin de la génération, contrairement aux providers API.
## Développement
Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 KiB

+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "zemit",
"version": "1.1.3",
"version": "1.1.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "zemit",
"version": "1.1.3",
"version": "1.1.5",
"license": "GPL-3.0-only",
"devDependencies": {
"@types/node": "^18.0.0",
+10 -3
View File
@@ -1,6 +1,6 @@
{
"name": "zemit",
"version": "1.1.4",
"version": "1.1.5",
"displayName": "Zemit",
"description": "Génère des messages de commit, via un modèle d'IA, directement dans VSCode.",
"repository": {
@@ -36,7 +36,14 @@
"enum": [
"anthropic",
"openai",
"ollama"
"ollama",
"claudecode"
],
"enumDescriptions": [
"Anthropic API (clé API requise)",
"OpenAI API (clé API requise)",
"Ollama instance locale",
"Claude Code CLI installation locale, sans clé API"
],
"default": "anthropic",
"description": "Fournisseur d'IA utilisé pour générer les messages de commit."
@@ -44,7 +51,7 @@
"zemit.apiKey": {
"type": "string",
"default": "",
"markdownDescription": "Clé API du fournisseur sélectionné. Non requise pour Ollama."
"markdownDescription": "Clé API du fournisseur sélectionné. Non requise pour Ollama et Claude Code."
},
"zemit.model": {
"type": "string",
+6 -6
View File
@@ -41,16 +41,16 @@ export async function getGitDiff(cwd: string, stagedOnly = false): Promise<strin
let diff = ""
if (await hasCommits(cwd)) {
const { stdout: staged } = await execAsync("git --no-pager diff --staged --diff-filter=d", { cwd })
const { stdout: staged } = await execAsync("git --no-pager diff --staged", { cwd })
diff = staged.trim()
if (!stagedOnly && !diff) {
const { stdout: unstaged } = await execAsync("git --no-pager diff HEAD --diff-filter=d", { cwd })
const { stdout: unstaged } = await execAsync("git --no-pager diff HEAD", { cwd })
diff = unstaged.trim()
}
} else {
// First commit — no HEAD exists yet
const { stdout: cached } = await execAsync("git --no-pager diff --cached --diff-filter=d", { cwd })
const { stdout: cached } = await execAsync("git --no-pager diff --cached", { cwd })
diff = cached.trim()
}
@@ -93,7 +93,7 @@ export async function getGitContext(cwd: string): Promise<{ diff: string; stat:
let includeUntracked = false
if (await hasCommits(cwd)) {
const { stdout: stagedDiff } = await execAsync("git --no-pager diff --staged --diff-filter=d", { cwd })
const { stdout: stagedDiff } = await execAsync("git --no-pager diff --staged", { cwd })
diff = stagedDiff.trim()
if (diff) {
@@ -101,7 +101,7 @@ export async function getGitContext(cwd: string): Promise<{ diff: string; stat:
stat = stagedStat.trim()
} else {
includeUntracked = true
const { stdout: allDiff } = await execAsync("git --no-pager diff HEAD --diff-filter=d", { cwd })
const { stdout: allDiff } = await execAsync("git --no-pager diff HEAD", { cwd })
diff = allDiff.trim()
const { stdout: allStat } = await execAsync("git --no-pager diff HEAD --name-status", { cwd })
stat = allStat.trim()
@@ -109,7 +109,7 @@ export async function getGitContext(cwd: string): Promise<{ diff: string; stat:
} else {
// First commit — no HEAD exists yet
includeUntracked = true
const { stdout: cachedDiff } = await execAsync("git --no-pager diff --cached --diff-filter=d", { cwd })
const { stdout: cachedDiff } = await execAsync("git --no-pager diff --cached", { cwd })
diff = cachedDiff.trim()
const { stdout: cachedStat } = await execAsync("git --no-pager diff --cached --name-status", { cwd })
stat = cachedStat.trim()
+69
View File
@@ -1,3 +1,4 @@
import { spawn } from "child_process"
import * as vscode from "vscode"
import { SYSTEM_PROMPT, getPrompt } from "./prompts"
@@ -91,6 +92,69 @@ class AnthropicProvider implements AIProvider {
}
}
// ─── Claude Code CLI ─────────────────────────────────────────────────────────
class ClaudeCodeProvider implements AIProvider {
constructor(private readonly model: string) {}
async *generateCommitMessage(diff: string, style: string, signal: AbortSignal): AsyncIterable<string> {
const instruction = getPrompt(style)
const input = `<input>${instruction}\n\n${diff}</input>`
// ~/.local/bin may not be in PATH inside the VSCode extension host
const env = { ...process.env, PATH: `${process.env.HOME ?? ""}/.local/bin:${process.env.PATH ?? ""}` }
const proc = spawn("claude", [
"-p", "--output-format", "text",
"--model", this.model,
"--system-prompt", SYSTEM_PROMPT,
], { env })
const onAbort = () => proc.kill()
signal.addEventListener("abort", onAbort, { once: true })
proc.stdin.end(input)
let stderrData = ""
proc.stderr.on("data", (chunk: Buffer) => { stderrData += chunk.toString() })
// When spawn fails (e.g. ENOENT), 'close' never fires — resolve via 'error' too
let spawnError: Error | undefined
const closeOrError = new Promise<number | null>((resolve) => {
proc.on("close", resolve)
proc.on("error", (err: Error) => {
spawnError = err
proc.stdout.destroy() // Unblock the for-await below
resolve(null)
})
})
try {
for await (const chunk of proc.stdout) {
yield (chunk as Buffer).toString()
}
} catch {
// stdout may throw if destroyed due to a spawn error — handled below
} finally {
signal.removeEventListener("abort", onAbort)
}
await closeOrError
if (spawnError) {
const isNotFound = (spawnError as NodeJS.ErrnoException).code === "ENOENT"
throw new Error(isNotFound
? "Claude Code CLI introuvable. Assurez-vous que 'claude' est installé et accessible (ex. ~/.local/bin)."
: `Claude Code CLI: ${spawnError.message}`)
}
const exitCode = await closeOrError
if (exitCode !== 0 && !signal.aborted) {
throw new Error(`Claude Code CLI error: ${stderrData || `exit code ${exitCode}`}`)
}
}
}
// ─── SSE parsing helpers ─────────────────────────────────────────────────────
async function* parseSSEStream(
@@ -161,6 +225,9 @@ export function createProvider(config: vscode.WorkspaceConfiguration): AIProvide
if (!apiKey) throw new Error("Clé API Anthropic requise. Configurez-la dans Paramètres → Zemit AI Commit.")
return new AnthropicProvider(apiKey, model, baseUrl)
}
case "claudecode": {
return new ClaudeCodeProvider(model)
}
case "ollama": {
const baseUrl = customBaseUrl || "http://localhost:11434/v1"
return new OpenAICompatibleProvider("ollama", model, baseUrl)
@@ -207,6 +274,8 @@ export async function fetchAvailableModels(config: vscode.WorkspaceConfiguration
.filter((id) => /^(gpt-|o1|o3|chatgpt-)/.test(id))
.sort()
}
case "claudecode":
return []
case "ollama": {
const baseUrl = customBaseUrl || "http://localhost:11434/v1"
const response = await fetch(`${baseUrl}/models`)
+1
View File
@@ -3,6 +3,7 @@
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"types": ["node"],
"outDir": "dist",
"rootDir": "src",
"strict": true,