Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 954ca5bd47 | |||
| 030a7b1472 | |||
| 05879f9171 | |||
| ca73d9683f |
@@ -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
|
||||||
@@ -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
|
- VS Code 1.85 ou plus récent
|
||||||
- Un dépôt Git avec des changements stagés
|
- 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
|
## Installation
|
||||||
|
|
||||||
@@ -39,8 +39,8 @@ Les paramètres se trouvent dans les préférences VS Code sous **Zemit**.
|
|||||||
|
|
||||||
| Paramètre | Description | Défaut |
|
| Paramètre | Description | Défaut |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `zemit.provider` | Fournisseur d'IA : `anthropic`, `openai` ou `ollama` | `anthropic` |
|
| `zemit.provider` | Fournisseur d'IA : `anthropic`, `openai`, `ollama` ou `claudecode` | `anthropic` |
|
||||||
| `zemit.apiKey` | Clé API du fournisseur (inutile pour Ollama) | _(vide)_ |
|
| `zemit.apiKey` | Clé API du fournisseur (inutile pour Ollama et Claude Code) | _(vide)_ |
|
||||||
| `zemit.model` | Modèle à utiliser | `claude-sonnet-4-6` |
|
| `zemit.model` | Modèle à utiliser | `claude-sonnet-4-6` |
|
||||||
| `zemit.baseUrl` | URL de base personnalisée (ex. Ollama local) | _(vide)_ |
|
| `zemit.baseUrl` | URL de base personnalisée (ex. Ollama local) | _(vide)_ |
|
||||||
| `zemit.promptVersion` | Version du prompt | `zemit-v2` |
|
| `zemit.promptVersion` | Version du prompt | `zemit-v2` |
|
||||||
@@ -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)
|
- **Anthropic** : modèles Claude (Haiku, Sonnet, Opus)
|
||||||
- **OpenAI** : modèles GPT
|
- **OpenAI** : modèles GPT
|
||||||
- **Ollama** : modèles locaux, aucune clé requise
|
- **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
|
## Développement
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "zemit",
|
"name": "zemit",
|
||||||
"version": "1.1.4",
|
"version": "1.1.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "zemit",
|
"name": "zemit",
|
||||||
"version": "1.1.4",
|
"version": "1.1.5",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.0.0",
|
"@types/node": "^18.0.0",
|
||||||
|
|||||||
+10
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "zemit",
|
"name": "zemit",
|
||||||
"version": "1.1.4",
|
"version": "1.1.5",
|
||||||
"displayName": "Zemit",
|
"displayName": "Zemit",
|
||||||
"description": "Génère des messages de commit, via un modèle d'IA, directement dans VSCode.",
|
"description": "Génère des messages de commit, via un modèle d'IA, directement dans VSCode.",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -36,7 +36,14 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"anthropic",
|
"anthropic",
|
||||||
"openai",
|
"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",
|
"default": "anthropic",
|
||||||
"description": "Fournisseur d'IA utilisé pour générer les messages de commit."
|
"description": "Fournisseur d'IA utilisé pour générer les messages de commit."
|
||||||
@@ -44,7 +51,7 @@
|
|||||||
"zemit.apiKey": {
|
"zemit.apiKey": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "",
|
"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": {
|
"zemit.model": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
+6
-6
@@ -41,16 +41,16 @@ export async function getGitDiff(cwd: string, stagedOnly = false): Promise<strin
|
|||||||
let diff = ""
|
let diff = ""
|
||||||
|
|
||||||
if (await hasCommits(cwd)) {
|
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()
|
diff = staged.trim()
|
||||||
|
|
||||||
if (!stagedOnly && !diff) {
|
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()
|
diff = unstaged.trim()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// First commit — no HEAD exists yet
|
// 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()
|
diff = cached.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ export async function getGitContext(cwd: string): Promise<{ diff: string; stat:
|
|||||||
let includeUntracked = false
|
let includeUntracked = false
|
||||||
|
|
||||||
if (await hasCommits(cwd)) {
|
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()
|
diff = stagedDiff.trim()
|
||||||
|
|
||||||
if (diff) {
|
if (diff) {
|
||||||
@@ -101,7 +101,7 @@ export async function getGitContext(cwd: string): Promise<{ diff: string; stat:
|
|||||||
stat = stagedStat.trim()
|
stat = stagedStat.trim()
|
||||||
} else {
|
} else {
|
||||||
includeUntracked = true
|
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()
|
diff = allDiff.trim()
|
||||||
const { stdout: allStat } = await execAsync("git --no-pager diff HEAD --name-status", { cwd })
|
const { stdout: allStat } = await execAsync("git --no-pager diff HEAD --name-status", { cwd })
|
||||||
stat = allStat.trim()
|
stat = allStat.trim()
|
||||||
@@ -109,7 +109,7 @@ export async function getGitContext(cwd: string): Promise<{ diff: string; stat:
|
|||||||
} else {
|
} else {
|
||||||
// First commit — no HEAD exists yet
|
// First commit — no HEAD exists yet
|
||||||
includeUntracked = true
|
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()
|
diff = cachedDiff.trim()
|
||||||
const { stdout: cachedStat } = await execAsync("git --no-pager diff --cached --name-status", { cwd })
|
const { stdout: cachedStat } = await execAsync("git --no-pager diff --cached --name-status", { cwd })
|
||||||
stat = cachedStat.trim()
|
stat = cachedStat.trim()
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { spawn } from "child_process"
|
||||||
import * as vscode from "vscode"
|
import * as vscode from "vscode"
|
||||||
import { SYSTEM_PROMPT, getPrompt } from "./prompts"
|
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 ─────────────────────────────────────────────────────
|
// ─── SSE parsing helpers ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
async function* parseSSEStream(
|
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.")
|
if (!apiKey) throw new Error("Clé API Anthropic requise. Configurez-la dans Paramètres → Zemit AI Commit.")
|
||||||
return new AnthropicProvider(apiKey, model, baseUrl)
|
return new AnthropicProvider(apiKey, model, baseUrl)
|
||||||
}
|
}
|
||||||
|
case "claudecode": {
|
||||||
|
return new ClaudeCodeProvider(model)
|
||||||
|
}
|
||||||
case "ollama": {
|
case "ollama": {
|
||||||
const baseUrl = customBaseUrl || "http://localhost:11434/v1"
|
const baseUrl = customBaseUrl || "http://localhost:11434/v1"
|
||||||
return new OpenAICompatibleProvider("ollama", model, baseUrl)
|
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))
|
.filter((id) => /^(gpt-|o1|o3|chatgpt-)/.test(id))
|
||||||
.sort()
|
.sort()
|
||||||
}
|
}
|
||||||
|
case "claudecode":
|
||||||
|
return []
|
||||||
case "ollama": {
|
case "ollama": {
|
||||||
const baseUrl = customBaseUrl || "http://localhost:11434/v1"
|
const baseUrl = customBaseUrl || "http://localhost:11434/v1"
|
||||||
const response = await fetch(`${baseUrl}/models`)
|
const response = await fetch(`${baseUrl}/models`)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"lib": ["ES2020"],
|
"lib": ["ES2020"],
|
||||||
|
"types": ["node"],
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user