Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f63b6770d2 | |||
| a90ab0eca3 | |||
| 1bdd31ae0c | |||
| 0697e279ca | |||
| 718cdf87c9 | |||
| bab40a8304 | |||
| 102861ccfa | |||
| 76aa5b0444 | |||
| 7839a27d3a | |||
| 1f9ed36194 | |||
| 4f59c77e8f | |||
| a102b0d714 | |||
| b9e0273d57 | |||
| 7c4f5a9189 |
@@ -1,6 +1,8 @@
|
||||
# Zemit
|
||||
|
||||
Génère des messages de commit dans VSCode à partir du diff en cours, via un modèle d'IA.
|
||||
Génère des messages de commit, via un modèle d'IA, directement dans VSCode.
|
||||
|
||||

|
||||
|
||||
## Ce que ça fait
|
||||
|
||||
@@ -16,8 +18,17 @@ Tu peux interrompre la génération à tout moment depuis le même panneau.
|
||||
|
||||
## Installation
|
||||
|
||||
### Via le marketplace (recommandé)
|
||||
|
||||
Recherche **Zemit** dans l'onglet Extensions de ton éditeur, ou installe directement depuis :
|
||||
|
||||
- **VS Code** : [marketplace.visualstudio.com](https://marketplace.visualstudio.com/items?itemName=hykocx.zemit)
|
||||
- **VSCodium / Open VSX** : [open-vsx.org](https://open-vsx.org/extension/hykocx/zemit)
|
||||
|
||||
### Via un fichier VSIX
|
||||
|
||||
1. Télécharge le fichier `.vsix` depuis les [releases](https://git.hyko.cx/hykocx/zemit/releases).
|
||||
2. Dans VS Code, ouvre la palette de commandes (`Ctrl+Shift+P` / `Cmd+Shift+P`).
|
||||
2. Ouvre la palette de commandes (`Ctrl+Shift+P` / `Cmd+Shift+P`).
|
||||
3. Cherche **Extensions: Installer depuis un fichier VSIX...** et sélectionne le fichier téléchargé.
|
||||
|
||||
Ou via le menu **Extensions** (icône en barre latérale) → `...` → **Installer depuis un fichier VSIX...**
|
||||
@@ -28,12 +39,12 @@ Les paramètres se trouvent dans les préférences VS Code sous **Zemit**.
|
||||
|
||||
| Paramètre | Description | Défaut |
|
||||
|---|---|---|
|
||||
| `aiCommit.provider` | Fournisseur d'IA : `anthropic`, `openai` ou `ollama` | `anthropic` |
|
||||
| `aiCommit.apiKey` | Clé API du fournisseur (inutile pour Ollama) | _(vide)_ |
|
||||
| `aiCommit.model` | Modèle à utiliser | `claude-haiku-4-5-20251001` |
|
||||
| `aiCommit.baseUrl` | URL de base personnalisée (ex. Ollama local) | _(vide)_ |
|
||||
| `aiCommit.commitStyle` | Style du message : `conventional` ou `simple` | `conventional` |
|
||||
| `aiCommit.maxDiffSize` | Taille maximale du diff envoyé à l'IA (en caractères) | `5000` |
|
||||
| `zemit.provider` | Fournisseur d'IA : `anthropic`, `openai` ou `ollama` | `anthropic` |
|
||||
| `zemit.apiKey` | Clé API du fournisseur (inutile pour Ollama) | _(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.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.
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 216 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 11 KiB |
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "zemit",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "zemit",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.3",
|
||||
"license": "GPL-3.0-only",
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.0.0",
|
||||
|
||||
+17
-17
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "zemit",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.3",
|
||||
"displayName": "Zemit",
|
||||
"description": "Génère des messages de commit dans VSCode à partir du diff en cours, via un modèle d'IA.",
|
||||
"description": "Génère des messages de commit, via un modèle d'IA, directement dans VSCode.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.hyko.cx/hykocx/zemit"
|
||||
@@ -31,7 +31,7 @@
|
||||
"configuration": {
|
||||
"title": "Zemit",
|
||||
"properties": {
|
||||
"aiCommit.provider": {
|
||||
"zemit.provider": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"anthropic",
|
||||
@@ -41,22 +41,22 @@
|
||||
"default": "anthropic",
|
||||
"description": "Fournisseur d'IA utilisé pour générer les messages de commit."
|
||||
},
|
||||
"aiCommit.apiKey": {
|
||||
"zemit.apiKey": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"markdownDescription": "Clé API du fournisseur sélectionné. Non requise pour Ollama."
|
||||
},
|
||||
"aiCommit.model": {
|
||||
"zemit.model": {
|
||||
"type": "string",
|
||||
"default": "claude-haiku-4-5-20251001",
|
||||
"default": "claude-sonnet-4-6",
|
||||
"markdownDescription": "Modèle à utiliser. Lance **Zemit : Sélectionner un modèle** pour parcourir les modèles disponibles du fournisseur configuré."
|
||||
},
|
||||
"aiCommit.baseUrl": {
|
||||
"zemit.baseUrl": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"markdownDescription": "URL de base personnalisée (ex. `http://localhost:11434/v1` pour Ollama). Laisser vide pour utiliser la valeur par défaut du fournisseur."
|
||||
},
|
||||
"aiCommit.commitStyle": {
|
||||
"zemit.commitStyle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"conventional",
|
||||
@@ -69,7 +69,7 @@
|
||||
"default": "conventional",
|
||||
"description": "Format du message de commit généré."
|
||||
},
|
||||
"aiCommit.maxDiffSize": {
|
||||
"zemit.maxDiffSize": {
|
||||
"type": "number",
|
||||
"default": 5000,
|
||||
"minimum": 500,
|
||||
@@ -80,19 +80,19 @@
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "aiCommit.generateCommitMessage",
|
||||
"command": "zemit.generateCommitMessage",
|
||||
"title": "Générer un message de commit",
|
||||
"category": "Zemit",
|
||||
"icon": "$(sparkle)"
|
||||
},
|
||||
{
|
||||
"command": "aiCommit.abortGeneration",
|
||||
"command": "zemit.abortGeneration",
|
||||
"title": "Arrêter la génération",
|
||||
"category": "Zemit",
|
||||
"icon": "$(debug-stop)"
|
||||
},
|
||||
{
|
||||
"command": "aiCommit.selectModel",
|
||||
"command": "zemit.selectModel",
|
||||
"title": "Sélectionner un modèle",
|
||||
"category": "Zemit",
|
||||
"icon": "$(list-selection)"
|
||||
@@ -101,14 +101,14 @@
|
||||
"menus": {
|
||||
"scm/title": [
|
||||
{
|
||||
"command": "aiCommit.generateCommitMessage",
|
||||
"command": "zemit.generateCommitMessage",
|
||||
"group": "navigation@1",
|
||||
"when": "config.git.enabled && scmProvider == git && !aiCommit.isGenerating"
|
||||
"when": "config.git.enabled && scmProvider == git && !zemit.isGenerating"
|
||||
},
|
||||
{
|
||||
"command": "aiCommit.abortGeneration",
|
||||
"command": "zemit.abortGeneration",
|
||||
"group": "navigation@1",
|
||||
"when": "config.git.enabled && scmProvider == git && aiCommit.isGenerating"
|
||||
"when": "config.git.enabled && scmProvider == git && zemit.isGenerating"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -116,7 +116,7 @@
|
||||
"scripts": {
|
||||
"build": "esbuild src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --platform=node --target=node18",
|
||||
"watch": "esbuild src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --platform=node --target=node18 --watch",
|
||||
"package": "npm run build && vsce package"
|
||||
"package": "npm run build && vsce package --baseContentUrl https://git.hyko.cx/hykocx/zemit/raw/branch/main --baseImagesUrl https://git.hyko.cx/hykocx/zemit/raw/branch/main"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.0.0",
|
||||
|
||||
+12
-12
@@ -9,19 +9,19 @@ export async function generateCommitMsg(scm?: vscode.SourceControl): Promise<voi
|
||||
try {
|
||||
const gitExtension = vscode.extensions.getExtension("vscode.git")?.exports
|
||||
if (!gitExtension) {
|
||||
throw new Error("Git extension not found")
|
||||
throw new Error("Extension Git introuvable")
|
||||
}
|
||||
|
||||
const git = gitExtension.getAPI(1)
|
||||
if (git.repositories.length === 0) {
|
||||
throw new Error("No Git repositories available")
|
||||
throw new Error("Aucun dépôt Git disponible")
|
||||
}
|
||||
|
||||
// If invoked from SCM panel button, a specific repo is provided
|
||||
if (scm) {
|
||||
const repository = git.getRepository(scm.rootUri)
|
||||
if (!repository) {
|
||||
throw new Error("Repository not found for the selected SCM")
|
||||
throw new Error("Dépôt introuvable pour le SCM sélectionné")
|
||||
}
|
||||
await generateForRepository(repository)
|
||||
return
|
||||
@@ -36,7 +36,7 @@ export async function generateCommitMsg(scm?: vscode.SourceControl): Promise<voi
|
||||
|
||||
export function abortGeneration(): void {
|
||||
abortController?.abort()
|
||||
vscode.commands.executeCommand("setContext", "aiCommit.isGenerating", false)
|
||||
vscode.commands.executeCommand("setContext", "zemit.isGenerating", false)
|
||||
}
|
||||
|
||||
// ─── Multi-repo orchestration ─────────────────────────────────────────────────
|
||||
@@ -45,7 +45,7 @@ async function orchestrateMultiRepo(repos: unknown[]): Promise<void> {
|
||||
const reposWithChanges = await filterReposWithChanges(repos)
|
||||
|
||||
if (reposWithChanges.length === 0) {
|
||||
vscode.window.showInformationMessage("[Zemit] No changes found in any repository.")
|
||||
vscode.window.showInformationMessage("[Zemit] Aucune modification trouvée dans les dépôts.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -62,12 +62,12 @@ async function orchestrateMultiRepo(repos: unknown[]): Promise<void> {
|
||||
|
||||
items.unshift({
|
||||
label: "$(git-commit) All repositories with changes",
|
||||
description: `Generate for ${reposWithChanges.length} repositories`,
|
||||
description: `Générer pour ${reposWithChanges.length} dépôts`,
|
||||
repo: null as any,
|
||||
})
|
||||
|
||||
const selection = await vscode.window.showQuickPick(items, {
|
||||
placeHolder: "Select a repository to generate a commit message for",
|
||||
placeHolder: "Sélectionner un dépôt pour générer un message de commit",
|
||||
})
|
||||
|
||||
if (!selection) return
|
||||
@@ -104,7 +104,7 @@ async function generateForRepository(repository: any): Promise<void> {
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.SourceControl,
|
||||
title: `Zemit: Generating commit message for ${repoName}...`,
|
||||
title: `Zemit : Génération du message de commit pour ${repoName}...`,
|
||||
cancellable: true,
|
||||
},
|
||||
(_progress, token) => performGeneration(repository.inputBox, diff, token),
|
||||
@@ -116,7 +116,7 @@ async function performGeneration(
|
||||
diff: string,
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<void> {
|
||||
const config = vscode.workspace.getConfiguration("aiCommit")
|
||||
const config = vscode.workspace.getConfiguration("zemit")
|
||||
const maxDiffSize = config.get<number>("maxDiffSize", 5000)
|
||||
const style = config.get<string>("commitStyle", "conventional")
|
||||
|
||||
@@ -135,7 +135,7 @@ async function performGeneration(
|
||||
token.onCancellationRequested(() => abortController?.abort())
|
||||
|
||||
try {
|
||||
await vscode.commands.executeCommand("setContext", "aiCommit.isGenerating", true)
|
||||
await vscode.commands.executeCommand("setContext", "zemit.isGenerating", true)
|
||||
|
||||
const provider = createProvider(config)
|
||||
let response = ""
|
||||
@@ -147,10 +147,10 @@ async function performGeneration(
|
||||
}
|
||||
|
||||
if (!inputBox.value) {
|
||||
throw new Error("The AI returned an empty response")
|
||||
throw new Error("L'IA n'a retourné aucune réponse")
|
||||
}
|
||||
} finally {
|
||||
await vscode.commands.executeCommand("setContext", "aiCommit.isGenerating", false)
|
||||
await vscode.commands.executeCommand("setContext", "zemit.isGenerating", false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-9
@@ -4,21 +4,21 @@ import { fetchAvailableModels } from "./providers"
|
||||
|
||||
export function activate(context: vscode.ExtensionContext): void {
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("aiCommit.generateCommitMessage", (scm?: vscode.SourceControl) =>
|
||||
vscode.commands.registerCommand("zemit.generateCommitMessage", (scm?: vscode.SourceControl) =>
|
||||
generateCommitMsg(scm),
|
||||
),
|
||||
vscode.commands.registerCommand("aiCommit.abortGeneration", () => abortGeneration()),
|
||||
vscode.commands.registerCommand("aiCommit.selectModel", () => selectModel()),
|
||||
vscode.commands.registerCommand("zemit.abortGeneration", () => abortGeneration()),
|
||||
vscode.commands.registerCommand("zemit.selectModel", () => selectModel()),
|
||||
)
|
||||
}
|
||||
|
||||
async function selectModel(): Promise<void> {
|
||||
const config = vscode.workspace.getConfiguration("aiCommit")
|
||||
const config = vscode.workspace.getConfiguration("zemit")
|
||||
|
||||
let models: string[]
|
||||
try {
|
||||
models = await vscode.window.withProgress(
|
||||
{ location: vscode.ProgressLocation.Notification, title: "Zemit: Fetching available models…", cancellable: false },
|
||||
{ location: vscode.ProgressLocation.Notification, title: "Zemit : Récupération des modèles disponibles…", cancellable: false },
|
||||
() => fetchAvailableModels(config),
|
||||
)
|
||||
} catch (err) {
|
||||
@@ -28,25 +28,25 @@ async function selectModel(): Promise<void> {
|
||||
}
|
||||
|
||||
if (models.length === 0) {
|
||||
vscode.window.showWarningMessage("[Zemit] No models found for the configured provider.")
|
||||
vscode.window.showWarningMessage("[Zemit] Aucun modèle trouvé pour le fournisseur configuré.")
|
||||
return
|
||||
}
|
||||
|
||||
const currentModel = config.get<string>("model", "")
|
||||
const items = models.map((id) => ({
|
||||
label: id,
|
||||
description: id === currentModel ? "current" : undefined,
|
||||
description: id === currentModel ? "actuel" : undefined,
|
||||
}))
|
||||
|
||||
const picked = await vscode.window.showQuickPick(items, {
|
||||
placeHolder: "Select a model",
|
||||
placeHolder: "Sélectionner un modèle",
|
||||
matchOnDescription: false,
|
||||
})
|
||||
|
||||
if (!picked) return
|
||||
|
||||
await config.update("model", picked.label, vscode.ConfigurationTarget.Global)
|
||||
vscode.window.showInformationMessage(`[Zemit] Model set to ${picked.label}`)
|
||||
vscode.window.showInformationMessage(`[Zemit] Modèle défini sur ${picked.label}`)
|
||||
}
|
||||
|
||||
export function deactivate(): void {}
|
||||
|
||||
+3
-3
@@ -32,10 +32,10 @@ async function hasCommits(cwd: string): Promise<boolean> {
|
||||
|
||||
export async function getGitDiff(cwd: string, stagedOnly = false): Promise<string> {
|
||||
if (!(await isGitInstalled())) {
|
||||
throw new Error("Git is not installed")
|
||||
throw new Error("Git n'est pas installé")
|
||||
}
|
||||
if (!(await isGitRepo(cwd))) {
|
||||
throw new Error("Not a git repository")
|
||||
throw new Error("Pas un dépôt git")
|
||||
}
|
||||
|
||||
let diff = ""
|
||||
@@ -72,7 +72,7 @@ export async function getGitDiff(cwd: string, stagedOnly = false): Promise<strin
|
||||
}
|
||||
|
||||
if (!diff) {
|
||||
throw new Error("No changes found to generate a commit message from")
|
||||
throw new Error("Aucune modification trouvée pour générer un message de commit")
|
||||
}
|
||||
|
||||
return diff
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
export const SYSTEM_PROMPT =
|
||||
"You are a git commit message generator. Output only the commit message — no explanation, no preamble, no backticks, no quotes. All messages must be written in English."
|
||||
|
||||
export const CONVENTIONAL_INSTRUCTION = `Based on the provided git diff, generate a concise and descriptive commit message following the Conventional Commits format:
|
||||
|
||||
<type>(<scope>): <short description>
|
||||
|
||||
Types: feat, fix, refactor, style, docs, test, chore, perf, revert
|
||||
- feat: new feature
|
||||
- fix: bug fix
|
||||
- refactor: code restructuring without behavior change
|
||||
- style: formatting only (spaces, commas, no logic change)
|
||||
- docs: documentation only
|
||||
- test: add or update tests
|
||||
- chore: maintenance, dependencies, build config
|
||||
- perf: performance improvement
|
||||
- revert: revert a previous commit (format: revert: revert "<original message>")
|
||||
|
||||
Rules:
|
||||
- Scope is optional, specifies the affected area (e.g. auth, api, storage, ui, config)
|
||||
- Description: lowercase, no trailing period, in English
|
||||
- One commit = one intention, do not mix fix and refactor
|
||||
- For breaking changes, add ! after the type and a BREAKING CHANGE: footer
|
||||
- Only include a body if there are multiple distinct changes to explain; for a single focused change, output the title only`
|
||||
|
||||
export const SIMPLE_INSTRUCTION = `Based on the provided git diff, generate a short and clear one-line commit message (50-72 characters).`
|
||||
+6
-18
@@ -1,17 +1,5 @@
|
||||
import * as vscode from "vscode"
|
||||
|
||||
const SYSTEM_PROMPT =
|
||||
"You are a helpful assistant that generates informative git commit messages based on git diffs output. Skip preamble and remove all backticks surrounding the commit message."
|
||||
|
||||
const CONVENTIONAL_INSTRUCTION = `Based on the provided git diff, generate a concise and descriptive commit message.
|
||||
|
||||
The commit message should:
|
||||
1. Have a short title (50-72 characters)
|
||||
2. Follow the Conventional Commits format (feat:, fix:, chore:, docs:, refactor:, test:, style:, etc.)
|
||||
3. Be clear and informative
|
||||
4. Only include a body description if there are multiple distinct changes to explain, if the diff represents a single focused change, output the title only`
|
||||
|
||||
const SIMPLE_INSTRUCTION = `Based on the provided git diff, generate a short and clear one-line commit message (50-72 characters).`
|
||||
import { SYSTEM_PROMPT, CONVENTIONAL_INSTRUCTION, SIMPLE_INSTRUCTION } from "./prompts"
|
||||
|
||||
export interface AIProvider {
|
||||
generateCommitMessage(diff: string, style: string, signal: AbortSignal): AsyncIterable<string>
|
||||
@@ -164,13 +152,13 @@ function extractAnthropicDelta(data: string): string | null {
|
||||
export function createProvider(config: vscode.WorkspaceConfiguration): AIProvider {
|
||||
const provider = config.get<string>("provider", "anthropic")
|
||||
const apiKey = config.get<string>("apiKey", "")
|
||||
const model = config.get<string>("model", "claude-haiku-4-5-20251001")
|
||||
const model = config.get<string>("model", "claude-sonnet-4-6")
|
||||
const customBaseUrl = config.get<string>("baseUrl", "")
|
||||
|
||||
switch (provider) {
|
||||
case "anthropic": {
|
||||
const baseUrl = customBaseUrl || "https://api.anthropic.com/v1"
|
||||
if (!apiKey) throw new Error("Anthropic API key is required. Set it in Settings → 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)
|
||||
}
|
||||
case "ollama": {
|
||||
@@ -179,7 +167,7 @@ export function createProvider(config: vscode.WorkspaceConfiguration): AIProvide
|
||||
}
|
||||
default: {
|
||||
const baseUrl = customBaseUrl || "https://api.openai.com/v1"
|
||||
if (!apiKey) throw new Error("OpenAI API key is required. Set it in Settings → Zemit AI Commit.")
|
||||
if (!apiKey) throw new Error("Clé API OpenAI requise. Configurez-la dans Paramètres → Zemit AI Commit.")
|
||||
return new OpenAICompatibleProvider(apiKey, model, baseUrl)
|
||||
}
|
||||
}
|
||||
@@ -195,7 +183,7 @@ export async function fetchAvailableModels(config: vscode.WorkspaceConfiguration
|
||||
switch (provider) {
|
||||
case "anthropic": {
|
||||
const baseUrl = customBaseUrl || "https://api.anthropic.com/v1"
|
||||
if (!apiKey) throw new Error("Anthropic API key is required. Set it in Settings → Zemit AI Commit.")
|
||||
if (!apiKey) throw new Error("Clé API Anthropic requise. Configurez-la dans Paramètres → Zemit AI Commit.")
|
||||
const response = await fetch(`${baseUrl}/models`, {
|
||||
headers: {
|
||||
"x-api-key": apiKey,
|
||||
@@ -208,7 +196,7 @@ export async function fetchAvailableModels(config: vscode.WorkspaceConfiguration
|
||||
}
|
||||
case "openai": {
|
||||
const baseUrl = customBaseUrl || "https://api.openai.com/v1"
|
||||
if (!apiKey) throw new Error("OpenAI API key is required. Set it in Settings → Zemit AI Commit.")
|
||||
if (!apiKey) throw new Error("Clé API OpenAI requise. Configurez-la dans Paramètres → Zemit AI Commit.")
|
||||
const response = await fetch(`${baseUrl}/models`, {
|
||||
headers: { Authorization: `Bearer ${apiKey}` },
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user