From 4471c2b2b2052cd7cdc94a31091a640e79c5f036 Mon Sep 17 00:00:00 2001 From: Hyko Date: Fri, 24 Apr 2026 12:22:01 -0400 Subject: [PATCH] refactor(git,commitGenerator): add git context with stat and improve prompt structure - introduce `getGitContext` returning both diff and name-status stat output - update `generateForRepository` and `performGeneration` to consume new context - prefix prompt with changed files stat block when available - replace `commitStyle` config key with `promptVersion` using `LATEST_PROMPT_VERSION` - fix first-commit diff logic to use else-branch instead of fallthrough checks - update extension icon --- images/icon.png | Bin 11655 -> 4314 bytes src/commitGenerator.ts | 13 ++++---- src/git.ts | 70 +++++++++++++++++++++++++++++++++++------ src/prompts.ts | 26 +++++++++++++-- src/providers.ts | 6 ++-- 5 files changed, 94 insertions(+), 21 deletions(-) diff --git a/images/icon.png b/images/icon.png index d8ad7ba5d4973f7df052553ccebb4a74f5a6d2bb..13bae321b3b30ff8f48fdf0513d24b5271586b75 100644 GIT binary patch delta 4243 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 delta 11641 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 DcJQSK diff --git a/src/commitGenerator.ts b/src/commitGenerator.ts index 88e3731..821790d 100644 --- a/src/commitGenerator.ts +++ b/src/commitGenerator.ts @@ -1,6 +1,7 @@ import * as path from "path" import * as vscode from "vscode" -import { getGitDiffStagedFirst } from "./git" +import { getGitContext, getGitDiffStagedFirst } from "./git" +import { LATEST_PROMPT_VERSION } from "./prompts" import { createProvider } from "./providers" let abortController: AbortController | undefined @@ -99,7 +100,7 @@ async function filterReposWithChanges(repos: unknown[]): Promise { async function generateForRepository(repository: any): Promise { const repoPath = repository.rootUri.fsPath const repoName = repoPath.split(path.sep).pop() || "repository" - const diff = await getGitDiffStagedFirst(repoPath) + const { diff, stat } = await getGitContext(repoPath) await vscode.window.withProgress( { @@ -107,26 +108,26 @@ async function generateForRepository(repository: any): Promise { title: `Zemit : Génération du message de commit pour ${repoName}...`, cancellable: true, }, - (_progress, token) => performGeneration(repository.inputBox, diff, token), + (_progress, token) => performGeneration(repository.inputBox, diff, stat, token), ) } async function performGeneration( inputBox: any, diff: string, + stat: string, token: vscode.CancellationToken, ): Promise { const config = vscode.workspace.getConfiguration("zemit") const maxDiffSize = config.get("maxDiffSize", 5000) - const style = config.get("commitStyle", "conventional") + const style = config.get("promptVersion", LATEST_PROMPT_VERSION) const truncatedDiff = diff.length > maxDiffSize ? diff.substring(0, maxDiffSize) + "\n\n[Diff truncated due to size]" : diff - // Include any existing user note in the prompt const existingNote = inputBox.value?.trim() || "" - let prompt = truncatedDiff + let prompt = stat ? `## Changed files\n${stat}\n\n## Diff\n${truncatedDiff}` : truncatedDiff if (existingNote) { prompt = `Developer note (use if relevant): ${existingNote}\n\n${prompt}` } diff --git a/src/git.ts b/src/git.ts index 0a60d05..ccc2b6a 100644 --- a/src/git.ts +++ b/src/git.ts @@ -43,17 +43,15 @@ export async function getGitDiff(cwd: string, stagedOnly = false): Promise { return await getGitDiff(cwd, false) } } + +export async function getGitContext(cwd: string): Promise<{ diff: string; stat: string }> { + if (!(await isGitInstalled())) throw new Error("Git n'est pas installé") + if (!(await isGitRepo(cwd))) throw new Error("Pas un dépôt git") + + let diff = "" + let stat = "" + let includeUntracked = false + + if (await hasCommits(cwd)) { + const { stdout: stagedDiff } = await execAsync("git --no-pager diff --staged --diff-filter=d", { cwd }) + diff = stagedDiff.trim() + + if (diff) { + const { stdout: stagedStat } = await execAsync("git --no-pager diff --staged --name-status", { cwd }) + stat = stagedStat.trim() + } else { + includeUntracked = true + const { stdout: allDiff } = await execAsync("git --no-pager diff HEAD --diff-filter=d", { cwd }) + diff = allDiff.trim() + const { stdout: allStat } = await execAsync("git --no-pager diff HEAD --name-status", { cwd }) + stat = allStat.trim() + } + } else { + // First commit — no HEAD exists yet + includeUntracked = true + const { stdout: cachedDiff } = await execAsync("git --no-pager diff --cached --diff-filter=d", { cwd }) + diff = cachedDiff.trim() + const { stdout: cachedStat } = await execAsync("git --no-pager diff --cached --name-status", { cwd }) + stat = cachedStat.trim() + } + + if (includeUntracked) { + const { stdout: untrackedList } = await execAsync("git ls-files --others --exclude-standard", { cwd }) + const untrackedFiles = untrackedList.trim().split("\n").filter(Boolean) + for (const file of untrackedFiles) { + const result = await execAsync( + `git --no-pager diff --no-index -- /dev/null ${JSON.stringify(file)}`, + { cwd }, + ).catch((e: any) => ({ stdout: e.stdout as string | undefined })) + if (result.stdout) diff += (diff ? "\n" : "") + result.stdout + stat += (stat ? "\n" : "") + `A\t${file}` + } + } + + diff = diff.trim() + stat = stat.trim() + + if (!diff) throw new Error("Aucune modification trouvée pour générer un message de commit") + + return { diff, stat } +} diff --git a/src/prompts.ts b/src/prompts.ts index 8ee5920..208328d 100644 --- a/src/prompts.ts +++ b/src/prompts.ts @@ -1,7 +1,9 @@ +export const LATEST_PROMPT_VERSION = "zemit-v2" + 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: +export const ZEMIT_V1 = `Based on the provided git diff, generate a concise and descriptive commit message following the Conventional Commits format: (): @@ -23,4 +25,24 @@ Rules: - 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).` +export const ZEMIT_V2 = `Generate a git commit message following Conventional Commits format: + +(): + +Types: feat | fix | refactor | style | docs | test | chore | perf +Scope: optional, affected module (e.g. auth, api, ui, config) +Description: lowercase, imperative mood, no trailing period + +Body rules: +- Single focused change → title only +- Multiple files or distinct concerns → title + blank line + concise bullet body listing each change +- Breaking change: add ! after type + "BREAKING CHANGE: " footer` + +const PROMPT_REGISTRY: Record = { + "zemit-v1": ZEMIT_V1, + "zemit-v2": ZEMIT_V2, +} + +export function getPrompt(version: string): string { + return PROMPT_REGISTRY[version] ?? PROMPT_REGISTRY[LATEST_PROMPT_VERSION] +} diff --git a/src/providers.ts b/src/providers.ts index 1ee9e45..f21187b 100644 --- a/src/providers.ts +++ b/src/providers.ts @@ -1,5 +1,5 @@ import * as vscode from "vscode" -import { SYSTEM_PROMPT, CONVENTIONAL_INSTRUCTION, SIMPLE_INSTRUCTION } from "./prompts" +import { SYSTEM_PROMPT, getPrompt } from "./prompts" export interface AIProvider { generateCommitMessage(diff: string, style: string, signal: AbortSignal): AsyncIterable @@ -15,7 +15,7 @@ class OpenAICompatibleProvider implements AIProvider { ) {} async *generateCommitMessage(diff: string, style: string, signal: AbortSignal): AsyncIterable { - const instruction = style === "conventional" ? CONVENTIONAL_INSTRUCTION : SIMPLE_INSTRUCTION + const instruction = getPrompt(style) const url = `${this.baseUrl}/chat/completions` const response = await fetch(url, { @@ -58,7 +58,7 @@ class AnthropicProvider implements AIProvider { ) {} async *generateCommitMessage(diff: string, style: string, signal: AbortSignal): AsyncIterable { - const instruction = style === "conventional" ? CONVENTIONAL_INSTRUCTION : SIMPLE_INSTRUCTION + const instruction = getPrompt(style) const url = `${this.baseUrl}/messages` const response = await fetch(url, {