@klarkxy/dsh-git-commit 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/git.ts","../src/plan.ts","../src/service.ts","../src/index.ts"],"sourcesContent":["import { execFile } from 'node:child_process'\nimport { createHash } from 'node:crypto'\nimport { constants } from 'node:fs'\nimport { copyFile, lstat, mkdtemp, open, readFile, readlink, realpath, rm } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { isAbsolute, join, relative, resolve } from 'node:path'\nimport { GIT_TIMEOUT_MS, MAX_UNTRACKED_PREVIEW_CHARS } from './contracts.ts'\n\nexport interface GitRunOptions {\n readonly cwd: string\n readonly signal?: AbortSignal\n readonly timeoutMs?: number\n}\n\nexport class GitError extends Error {\n readonly stderr: string\n constructor(message: string, stderr = '') {\n super(message)\n this.name = 'GitError'\n this.stderr = stderr\n }\n}\n\nexport type GitRunner = (args: readonly string[], options: GitRunOptions) => Promise<string>\n\n/** NUL separator used by `git status -z` without embedding a control byte in this source file. */\nconst NUL = String.fromCharCode(0)\n\n/** Spawn git without a shell; arguments are passed literally. */\nexport const execGit: GitRunner = (args, options) => new Promise((resolve, reject) => {\n try {\n execFile('git', [...args], {\n cwd: options.cwd,\n encoding: 'utf8',\n maxBuffer: 32 * 1024 * 1024,\n timeout: options.timeoutMs ?? GIT_TIMEOUT_MS,\n signal: options.signal,\n windowsHide: true,\n env: { ...process.env, GIT_TERMINAL_PROMPT: '0', GIT_LITERAL_PATHSPECS: '1' },\n }, (error, stdout, stderr) => {\n if (error) {\n if (options.signal?.aborted) {\n reject(Object.assign(new Error('dsh-git-commit: git command cancelled'), { code: 'cancelled' }))\n return\n }\n reject(Object.assign(new GitError(stderr.trim() || error.message, stderr), { code: (error as NodeJS.ErrnoException).code }))\n return\n }\n resolve(stdout)\n })\n } catch (error) {\n reject(error)\n }\n})\n\nexport function isGitMissing(error: unknown): boolean {\n return typeof error === 'object' && error !== null && (error as { code?: unknown }).code === 'ENOENT'\n}\n\nexport type ChangeStatus = 'modified' | 'added' | 'deleted' | 'renamed' | 'copied' | 'untracked'\n\nexport interface ChangedFile {\n readonly path: string\n readonly status: ChangeStatus\n /** Rename source in porcelain -z output; committed with the destination. */\n readonly origin?: string\n /** True when the index already stages this path. */\n readonly staged: boolean\n}\n\nexport function statusLetter(kind: ChangeStatus): string {\n switch (kind) {\n case 'added': return 'A'\n case 'deleted': return 'D'\n case 'renamed': return 'R'\n case 'copied': return 'C'\n case 'untracked': return '?'\n default: return 'M'\n }\n}\n\n/**\n * Parse `git status --porcelain=v1 -z --untracked-files=all` output.\n * In -z mode rename/copy entries put the destination path first, then a NUL, then the origin path.\n */\nexport function parsePorcelain(output: string): ChangedFile[] {\n const tokens = output.split(NUL)\n const files: ChangedFile[] = []\n let index = 0\n while (index < tokens.length) {\n const token = tokens[index]\n index += 1\n if (!token) continue\n if (token.length < 4) continue\n const x = token[0] ?? ' '\n const y = token[1] ?? ' '\n const path = token.slice(3)\n if (x === '!' && y === '!') continue\n if (x === '?' && y === '?') {\n files.push({ path, status: 'untracked', staged: false })\n continue\n }\n if (x === 'R' || x === 'C' || y === 'R' || y === 'C') {\n const origin = tokens[index]\n index += 1\n if (!origin) throw new GitError('dsh-git-commit: incomplete rename status')\n files.push({ path, origin, status: x === 'C' || y === 'C' ? 'copied' : 'renamed', staged: x !== ' ' && x !== '?' })\n continue\n }\n if (x === 'U' || y === 'U' || (x === 'A' && y === 'A') || (x === 'D' && y === 'D')) {\n throw new GitError(`dsh-git-commit: resolve merge conflicts before committing (${path})`)\n }\n const status: ChangeStatus = x === 'D' || y === 'D' ? 'deleted' : x === 'A' || y === 'A' ? 'added' : 'modified'\n files.push({ path, status, staged: x !== ' ' && x !== '?' })\n }\n return files\n}\n\nexport interface RepoInfo {\n readonly root: string\n readonly branch: string\n}\n\nexport async function repoInfo(run: GitRunner, cwd: string, signal?: AbortSignal): Promise<RepoInfo> {\n const root = (await run(['rev-parse', '--show-toplevel'], { cwd, signal })).trim()\n let branch = (await run(['symbolic-ref', '--quiet', '--short', 'HEAD'], { cwd: root, signal }).catch(() => '')).trim()\n if (!branch) branch = (await run(['rev-parse', '--short', 'HEAD'], { cwd: root, signal }).catch(() => '')).trim()\n return { root, branch: branch || 'HEAD' }\n}\n\nexport async function workingTreeChanges(run: GitRunner, root: string, signal?: AbortSignal): Promise<ChangedFile[]> {\n const output = await run(['status', '--porcelain=v1', '-z', '--untracked-files=all'], { cwd: root, signal })\n return parsePorcelain(output)\n}\n\n/** Exact index/worktree content fingerprint, including untracked file bytes. */\nexport async function workspaceSnapshot(run: GitRunner, root: string, signal?: AbortSignal): Promise<{ files: ChangedFile[]; fingerprint: string }> {\n const status = await run(['status', '--porcelain=v1', '-z', '--untracked-files=all'], { cwd: root, signal })\n const files = parsePorcelain(status)\n const hash = createHash('sha256').update(status)\n hash.update(await run(['symbolic-ref', '--quiet', 'HEAD'], { cwd: root, signal }).catch(() => 'DETACHED'))\n hash.update(await run(['rev-parse', 'HEAD'], { cwd: root, signal }).catch(() => 'unborn'))\n hash.update(await run(['diff', '--cached', '--binary', '--no-ext-diff'], { cwd: root, signal }))\n hash.update(await run(['diff', '--binary', '--no-ext-diff'], { cwd: root, signal }))\n for (const file of files) {\n if (file.status !== 'untracked') continue\n hash.update(file.path)\n hash.update(await worktreePathDigest(run, root, file.path, signal))\n }\n return { files, fingerprint: hash.digest('hex') }\n}\n\n/** A worktree content check for groups waiting behind earlier commits. */\nexport async function pathVersions(run: GitRunner, root: string, paths: readonly string[], signal?: AbortSignal): Promise<string> {\n const hash = createHash('sha256')\n for (const path of paths) {\n hash.update(path)\n hash.update(await worktreePathDigest(run, root, path, signal))\n }\n return hash.digest('hex')\n}\n\nasync function worktreePathDigest(run: GitRunner, root: string, path: string, signal?: AbortSignal): Promise<string> {\n const absolute = resolve(root, path)\n let stat\n try {\n stat = await lstat(absolute)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return 'missing'\n throw error\n }\n const hash = createHash('sha256').update(String(stat.mode))\n if (stat.isSymbolicLink()) hash.update(await readlink(absolute))\n else if (stat.isFile()) {\n const handle = await open(absolute, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0))\n try {\n if (!(await handle.stat()).isFile() || !(await lstat(absolute)).isFile()) throw new GitError(`dsh-git-commit: file changed while reading (${path})`)\n for await (const chunk of handle.createReadStream({ autoClose: false })) {\n signal?.throwIfAborted()\n hash.update(chunk)\n }\n if (!(await lstat(absolute)).isFile()) throw new GitError(`dsh-git-commit: file changed while reading (${path})`)\n } finally {\n await handle.close()\n }\n } else if (stat.isDirectory()) {\n hash.update(await run(['-C', absolute, 'rev-parse', 'HEAD'], { cwd: root, signal }))\n hash.update(await run(['-C', absolute, 'status', '--porcelain=v1', '-z'], { cwd: root, signal }))\n }\n return hash.digest('hex')\n}\n\nexport async function recentSubjects(run: GitRunner, root: string, count: number, signal?: AbortSignal): Promise<string[]> {\n const output = await run(['log', `-${count}`, '--pretty=%s'], { cwd: root, signal }).catch(() => '')\n return output.split('\\n').map(line => line.trim()).filter(Boolean)\n}\n\n/** Combined staged+worktree stat and diff against HEAD for the given paths. */\nexport async function diffAgainstHead(\n run: GitRunner, root: string, paths: readonly string[], signal?: AbortSignal,\n): Promise<{ stat: string; diff: string }> {\n const args = paths.length > 0 ? ['--', ...paths] : []\n const stat = await run(['diff', 'HEAD', '--stat', '--no-color', ...args], { cwd: root, signal }).catch(() => '')\n const diff = await run(['diff', 'HEAD', '--no-color', '--no-ext-diff', ...args], { cwd: root, signal }).catch(() => '')\n return { stat, diff }\n}\n\nexport interface UntrackedPreview {\n readonly path: string\n readonly preview: string\n readonly binary: boolean\n}\n\nexport async function untrackedPreview(root: string, path: string): Promise<UntrackedPreview> {\n try {\n const absolute = resolve(root, path)\n const rootReal = await realpath(root)\n const withinRoot = async () => {\n const target = await realpath(absolute)\n const rel = relative(rootReal, target)\n return rel !== '' && rel !== '..' && !rel.startsWith(`..${String.fromCharCode(47)}`)\n && !rel.startsWith(`..${String.fromCharCode(92)}`) && !isAbsolute(rel)\n }\n if (!await withinRoot()) return { path, preview: '', binary: true }\n if (!(await lstat(absolute)).isFile()) return { path, preview: '', binary: true }\n const handle = await open(absolute, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0))\n let buffer: Buffer\n try {\n const bounded = Buffer.alloc(MAX_UNTRACKED_PREVIEW_CHARS * 4)\n const { bytesRead } = await handle.read(bounded, 0, bounded.length, 0)\n buffer = bounded.subarray(0, bytesRead)\n } finally {\n await handle.close()\n }\n if (!await withinRoot() || !(await lstat(absolute)).isFile()) return { path, preview: '', binary: true }\n if (buffer.includes(0)) return { path, preview: '', binary: true }\n const text = buffer.toString('utf8')\n return { path, preview: text.slice(0, MAX_UNTRACKED_PREVIEW_CHARS), binary: false }\n } catch {\n return { path, preview: '', binary: true }\n }\n}\n\nexport interface ExecutedCommit {\n readonly hash: string\n readonly skipped: boolean\n}\n\nasync function indexBytes(path: string): Promise<Buffer | undefined> {\n try { return await readFile(path) }\n catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined\n throw error\n }\n}\n\nasync function headOid(run: GitRunner, root: string): Promise<string> {\n try { return (await run(['rev-parse', '--verify', 'HEAD'], { cwd: root })).trim() }\n catch (error) {\n if (error instanceof GitError && /Needed a single revision|not a valid object name HEAD/u.test(error.stderr)) return ''\n throw error\n }\n}\n\n/**\n * Commit each group from the worktree. `--only` keeps other staged paths out of\n * this commit; an exact index backup protects the current group on failure.\n */\nexport async function commitGroups(\n run: GitRunner,\n root: string,\n groups: ReadonlyArray<{ message: string; files: readonly string[] }>,\n selected: readonly ChangedFile[],\n signal?: AbortSignal,\n verify?: (paths: readonly string[]) => Promise<void>,\n): Promise<ExecutedCommit[]> {\n const commits: ExecutedCommit[] = []\n for (const group of groups) {\n if (group.files.length === 0) continue\n const paths = group.files.flatMap(path => {\n const file = selected.find(file => file.path === path)\n return file?.status === 'renamed' && file.origin ? [path, file.origin] : [path]\n })\n await verify?.(paths)\n const gitPath = (await run(['rev-parse', '--git-path', 'index'], { cwd: root, signal })).trim()\n const indexPath = isAbsolute(gitPath) ? gitPath : resolve(root, gitPath)\n const backupDir = await mkdtemp(join(tmpdir(), 'dsh-git-commit-'))\n const backupPath = join(backupDir, 'index')\n let hadIndex = true\n try {\n try {\n await copyFile(indexPath, backupPath)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n hadIndex = false\n }\n const head = await headOid(run, root)\n const originalIndex = await indexBytes(indexPath)\n if (hadIndex ? !originalIndex?.equals(await readFile(backupPath)) : originalIndex !== undefined) {\n throw new GitError('dsh-git-commit: index changed before committing; retry')\n }\n let stagedIndex = originalIndex\n try {\n await run(['add', '-A', '--', ...group.files], { cwd: root, signal })\n stagedIndex = await indexBytes(indexPath)\n await run(['commit', '--quiet', '--only', '-m', group.message, '--', ...paths], { cwd: root, signal })\n const hash = (await run(['rev-parse', '--short', 'HEAD'], { cwd: root, signal })).trim()\n commits.push({ hash, skipped: false })\n } catch (error) {\n const currentHead = await headOid(run, root)\n const currentIndex = await indexBytes(indexPath)\n const indexUntouched = currentIndex === undefined ? stagedIndex === undefined : currentIndex.equals(stagedIndex ?? Buffer.alloc(0))\n if (currentHead === head && indexUntouched) {\n if (hadIndex) await copyFile(backupPath, indexPath)\n else await rm(indexPath, { force: true })\n }\n throw error\n }\n } finally {\n await rm(backupDir, { recursive: true, force: true })\n }\n }\n return commits\n}\n\n/** One deterministic commit for every selected path; used when the model plan is unavailable. */\nexport function fallbackGroup(files: readonly ChangedFile[], localeHint: 'zh' | 'en'): { message: string; files: readonly string[] } {\n const message = localeHint === 'zh'\n ? `chore: 提交 ${files.length} 个文件的修改`\n : `chore: commit changes to ${files.length} file${files.length === 1 ? '' : 's'}`\n return { message, files: files.map(file => file.path) }\n}\n","import { MAX_MANIFEST_CHARS, MAX_MESSAGE_CHARS } from './contracts.ts'\nimport { statusLetter, type ChangedFile, type UntrackedPreview } from './git.ts'\n\nexport interface PlanGroup {\n readonly message: string\n readonly files: readonly string[]\n}\n\nexport interface PlanInput {\n readonly files: readonly ChangedFile[]\n readonly recentSubjects: readonly string[]\n readonly stat: string\n readonly diff: string\n readonly untracked: readonly UntrackedPreview[]\n}\n\nexport function planSystemPrompt(): string {\n return [\n 'You split a git working-tree change set into a small number of coherent commits.',\n 'Return exactly one JSON object: {\"groups\":[{\"message\":\"<commit message>\",\"files\":[\"<path>\",...]}]}.',\n 'Rules:',\n '- Every listed file must appear in exactly one group; do not invent paths.',\n '- One to five groups. Group files that belong to the same logical change.',\n '- Order groups so earlier commits do not depend on later ones.',\n '- Each message is a single line of at most 72 characters, no trailing period.',\n '- Match the language and style of the recent commit subjects when provided; otherwise use English conventional-commit style.',\n '- Return no Markdown, code fence, or explanation.',\n ].join('\\n')\n}\n\nfunction truncateMiddle(text: string, maxChars: number): string {\n if (text.length <= maxChars) return text\n const half = Math.floor((maxChars - 40) / 2)\n return `${text.slice(0, half)}\\n… [${text.length - maxChars} chars omitted] …\\n${text.slice(-half)}`\n}\n\nexport function buildPlanInput(input: PlanInput): string {\n const sections: string[] = []\n sections.push('Scope: all current workspace changes of the repository.')\n if (input.recentSubjects.length > 0) {\n sections.push(['Recent commit subjects (style reference):', ...input.recentSubjects.map(s => `- ${s}`)].join('\\n'))\n }\n const manifest = input.files.map(file => `- ${statusLetter(file.status)} ${file.origin ? `${file.origin} ${file.status === 'copied' ? '=>' : '->'} ` : ''}${file.path}`).join('\\n')\n sections.push(`Changed files:\\n${manifest}`)\n if (input.stat.trim()) sections.push(`Diff stat:\\n${input.stat.trim()}`)\n if (input.diff.trim()) sections.push(`Diff against HEAD:\\n${input.diff}`)\n for (const untracked of input.untracked) {\n sections.push(untracked.binary\n ? `New binary file: ${untracked.path}`\n : `New file ${untracked.path}:\\n${untracked.preview}`)\n }\n return truncateMiddle(sections.join('\\n\\n'), MAX_MANIFEST_CHARS)\n}\n\nexport function sanitizeMessage(raw: unknown): string | undefined {\n if (typeof raw !== 'string') return undefined\n const line = raw.replace(/\\s+/gu, ' ').trim().replace(/\\.+$/u, '')\n if (!line) return undefined\n return line.length > MAX_MESSAGE_CHARS ? line.slice(0, MAX_MESSAGE_CHARS).trim() : line\n}\n\n/** Extract the first balanced top-level JSON object from model output. */\nexport function extractJsonObject(text: string): string | undefined {\n const fenced = /```(?:json)?\\s*([\\s\\S]*?)```/u.exec(text)\n const candidate = fenced?.[1] ?? text\n const start = candidate.indexOf('{')\n if (start === -1) return undefined\n let depth = 0\n let inString = false\n let escaped = false\n for (let index = start; index < candidate.length; index += 1) {\n const char = candidate[index]\n if (inString) {\n if (escaped) escaped = false\n else if (char === '\\\\') escaped = true\n else if (char === '\"') inString = false\n continue\n }\n if (char === '\"') inString = true\n else if (char === '{') depth += 1\n else if (char === '}') {\n depth -= 1\n if (depth === 0) return candidate.slice(start, index + 1)\n }\n }\n return undefined\n}\n\n/**\n * Validate the model plan against the exact change set. Returns undefined when\n * the output is malformed, covers the wrong files, or duplicates a file.\n */\nexport function parsePlan(text: string, files: readonly ChangedFile[]): PlanGroup[] | undefined {\n const json = extractJsonObject(text)\n if (!json) return undefined\n let parsed: unknown\n try {\n parsed = JSON.parse(json)\n } catch {\n return undefined\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return undefined\n const groups = (parsed as { groups?: unknown }).groups\n if (!Array.isArray(groups) || groups.length === 0 || groups.length > 8) return undefined\n const allowed = new Set(files.map(file => file.path))\n const seen = new Set<string>()\n const plan: PlanGroup[] = []\n for (const group of groups) {\n if (!group || typeof group !== 'object') return undefined\n const message = sanitizeMessage((group as { message?: unknown }).message)\n const paths = (group as { files?: unknown }).files\n if (!message || !Array.isArray(paths) || paths.length === 0) return undefined\n const groupFiles: string[] = []\n for (const path of paths) {\n if (typeof path !== 'string' || !allowed.has(path) || seen.has(path)) return undefined\n seen.add(path)\n groupFiles.push(path)\n }\n plan.push({ message, files: groupFiles })\n }\n if (seen.size !== allowed.size) return undefined\n return plan\n}\n","import type { AiFeatureScope, AiServices, ResolvedRoute } from '@klarkxy/dsh-ai-services/contracts'\nimport {\n CONTRACT_VERSION, DEFAULT_MAX_OUTPUT_TOKENS, PROMPT_VERSION, PURPOSE_ID, RECENT_LOG_COUNT, SCHEMA_VERSION,\n type CommitGroupResult, type CommitModelInfo, type CommitRunResult, type GitCommitStatus,\n} from './contracts.ts'\nimport {\n commitGroups, diffAgainstHead, execGit, fallbackGroup, isGitMissing, pathVersions, recentSubjects, repoInfo,\n untrackedPreview, workspaceSnapshot, workingTreeChanges, type ChangedFile, type GitRunner, type UntrackedPreview,\n} from './git.ts'\nimport { buildPlanInput, parsePlan, planSystemPrompt, type PlanGroup } from './plan.ts'\n\nexport interface SessionLike {\n readonly id: string\n readonly header?: { readonly cwd?: string }\n}\n\nexport interface SessionStoreLike {\n get(id: string): SessionLike | undefined\n}\n\nexport interface AgentStoreLike {\n get(id: string): { readonly status?: string } | undefined\n}\n\nexport interface GitCommitServiceDeps {\n readonly plugin: string\n readonly ai: AiServices\n readonly sessions: SessionStoreLike\n readonly agents?: AgentStoreLike\n readonly run?: GitRunner\n readonly preview?: (root: string, path: string) => Promise<UntrackedPreview>\n}\n\nexport class CommitRunError extends Error {\n readonly code: string\n constructor(code: string, message: string) {\n super(message)\n this.name = 'CommitRunError'\n this.code = code\n }\n}\n\nfunction modelInfo(route: ResolvedRoute | undefined): CommitModelInfo | undefined {\n if (!route?.provider || !route.model) return undefined\n return { provider: route.provider, model: route.model, source: route.source }\n}\n\nfunction looksChinese(subjects: readonly string[]): boolean {\n return subjects.some(subject => /[一-鿿]/u.test(subject))\n}\n\nfunction normalizeCwd(cwd: string | undefined): string | undefined {\n if (typeof cwd !== 'string') return undefined\n const normalized = cwd.trim().replaceAll('\\\\', '/')\n if (!normalized) return undefined\n return normalized === '/' || /^[A-Za-z]:\\/$/.test(normalized) ? normalized : normalized.replace(/\\/+$/, '')\n}\n\nexport class GitCommitService {\n private readonly deps: GitCommitServiceDeps\n private readonly run: GitRunner\n private readonly preview: (root: string, path: string) => Promise<UntrackedPreview>\n private scope: AiFeatureScope | undefined\n private disposePurpose: (() => void) | undefined\n private running: Promise<unknown> | undefined\n\n constructor(deps: GitCommitServiceDeps) {\n this.deps = deps\n this.run = deps.run ?? execGit\n this.preview = deps.preview ?? untrackedPreview\n }\n\n start(): void {\n this.scope = this.deps.ai.activate(this.deps.plugin)\n this.disposePurpose = this.scope.registerPurpose({\n id: PURPOSE_ID,\n label: 'Git 提交',\n // Configurable in advance via the AI-services purpose route; unset roles\n // inherit the normal tier and finally the default chat model.\n defaultTarget: { kind: 'role', role: 'weak' },\n maxOutputTokens: DEFAULT_MAX_OUTPUT_TOKENS,\n })\n }\n\n async dispose(): Promise<void> {\n this.disposePurpose?.()\n this.scope?.dispose()\n this.scope = undefined\n await this.running?.catch(() => {})\n }\n\n private async resolveRepo(sessionId: string): Promise<{ root: string; branch: string }> {\n const session = this.deps.sessions.get(sessionId)\n if (!session) throw new CommitRunError('no-session', 'dsh-git-commit: session is not live on this host')\n const cwd = normalizeCwd(session.header?.cwd)\n if (!cwd) throw new CommitRunError('no-cwd', 'dsh-git-commit: session has no working directory')\n try {\n return await repoInfo(this.run, cwd)\n } catch (error) {\n if (isGitMissing(error)) throw new CommitRunError('no-git', 'dsh-git-commit: git executable not found')\n throw new CommitRunError('not-a-repo', 'dsh-git-commit: session directory is not inside a git repository')\n }\n }\n\n async status(sessionId: string): Promise<GitCommitStatus> {\n let resolved\n try {\n resolved = await this.resolveRepo(sessionId)\n } catch (error) {\n if (error instanceof CommitRunError) {\n return { available: false, reason: error.code, detail: error.message, workspace: { files: 0 } }\n }\n throw error\n }\n const changes = await workingTreeChanges(this.run, resolved.root)\n let model: CommitModelInfo | undefined\n if (this.scope?.active) {\n model = await this.deps.ai.resolve(PURPOSE_ID, sessionId).then(modelInfo, () => undefined)\n }\n return {\n available: true,\n root: resolved.root,\n branch: resolved.branch,\n workspace: { files: changes.length },\n ...(model ? { model } : {}),\n }\n }\n\n async commit(sessionId: string, signal?: AbortSignal): Promise<CommitRunResult> {\n if (this.running) throw new CommitRunError('busy', 'dsh-git-commit: a commit run is already in progress')\n const run = this.runCommit(sessionId, signal)\n this.running = run\n try {\n return await run\n } finally {\n this.running = undefined\n }\n }\n\n private async runCommit(sessionId: string, signal?: AbortSignal): Promise<CommitRunResult> {\n const session = this.deps.sessions.get(sessionId)\n const sessionCwd = normalizeCwd(session?.header?.cwd)\n const resolved = await this.resolveRepo(sessionId)\n const assertReady = () => {\n signal?.throwIfAborted()\n if (!this.scope?.active) throw new CommitRunError('cancelled', 'dsh-git-commit: plugin is no longer active')\n const currentSession = this.deps.sessions.get(sessionId)\n if (currentSession !== session || normalizeCwd(currentSession?.header?.cwd) !== sessionCwd) {\n throw new CommitRunError('session-changed', 'dsh-git-commit: session workspace changed during planning')\n }\n if (this.deps.agents?.get(sessionId)?.status === 'running') {\n throw new CommitRunError('session-running', 'dsh-git-commit: wait for the session turn to finish')\n }\n }\n assertReady()\n const initial = await workspaceSnapshot(this.run, resolved.root, signal)\n const branchRef = await this.run(['symbolic-ref', '--quiet', 'HEAD'], { cwd: resolved.root, signal }).catch(() => 'DETACHED')\n const selected: ChangedFile[] = initial.files\n if (selected.length === 0) {\n throw new CommitRunError('no-changes', 'dsh-git-commit: working tree is clean')\n }\n const paths = selected.flatMap(file => file.status === 'renamed' && file.origin ? [file.path, file.origin] : [file.path])\n const originalVersions = await pathVersions(this.run, resolved.root, paths, signal)\n const [{ stat, diff }, subjects] = await Promise.all([\n diffAgainstHead(this.run, resolved.root, paths, signal),\n recentSubjects(this.run, resolved.root, RECENT_LOG_COUNT, signal),\n ])\n const untracked: UntrackedPreview[] = []\n for (const file of selected) {\n if (file.status === 'untracked') untracked.push(await this.preview(resolved.root, file.path))\n }\n\n let groups: PlanGroup[] | undefined\n let model: CommitModelInfo | undefined\n const scope = this.scope\n if (scope?.active) {\n try {\n const result = await scope.run({\n purpose: PURPOSE_ID,\n sessionId,\n input: buildPlanInput({ files: selected, recentSubjects: subjects, stat, diff, untracked }),\n system: planSystemPrompt(),\n sourceVersion: `${sessionId}:${initial.fingerprint}`,\n contractVersion: CONTRACT_VERSION,\n promptVersion: PROMPT_VERSION,\n schemaVersion: SCHEMA_VERSION,\n signal,\n priority: 'interactive',\n })\n model = modelInfo(result.receipt.route)\n if (result.receipt.status === 'cancelled') throw new CommitRunError('cancelled', 'dsh-git-commit: planning was cancelled')\n groups = parsePlan(result.text, selected)\n } catch (error) {\n if (error instanceof CommitRunError) throw error\n groups = undefined\n }\n }\n\n assertReady()\n const current = await workspaceSnapshot(this.run, resolved.root, signal)\n if (current.fingerprint !== initial.fingerprint) {\n throw new CommitRunError('workspace-changed', 'dsh-git-commit: workspace changed during planning; retry the commit')\n }\n const effective = groups ?? [fallbackGroup(selected, looksChinese(subjects) ? 'zh' : 'en')]\n const executed = await commitGroups(this.run, resolved.root, effective, selected, signal, async () => {\n assertReady()\n const currentBranchRef = await this.run(['symbolic-ref', '--quiet', 'HEAD'], { cwd: resolved.root, signal }).catch(() => 'DETACHED')\n if (currentBranchRef !== branchRef) {\n throw new CommitRunError('workspace-changed', 'dsh-git-commit: branch changed during the commit run')\n }\n if (await pathVersions(this.run, resolved.root, paths, signal) !== originalVersions) {\n throw new CommitRunError('workspace-changed', 'dsh-git-commit: selected file changed during the commit run')\n }\n // A new merge conflict must never be passed to git add or git commit.\n await workingTreeChanges(this.run, resolved.root, signal)\n })\n const commits: CommitGroupResult[] = []\n for (let index = 0; index < effective.length; index += 1) {\n const result = executed[index]\n const group = effective[index]\n if (!result || !group || result.skipped) continue\n commits.push({ hash: result.hash, message: group.message, files: group.files })\n }\n if (commits.length === 0) throw new CommitRunError('no-changes', 'dsh-git-commit: nothing was committed')\n return {\n branch: resolved.branch,\n commits,\n fallback: groups === undefined,\n ...(model ? { model } : {}),\n }\n }\n}\n","import type { Context } from '@deepseek-ai/cordis'\nimport type { AiServices, RpcResult } from '@klarkxy/dsh-ai-services/contracts'\nimport { registerHostRpc, type HostRpcContext } from '@klarkxy/dsh-ai-services/host-rpc'\nimport { PLUGIN_NAME, RPC_CHANNEL, type CommitRunResult, type GitCommitStatus } from './contracts.ts'\nimport { CommitRunError, GitCommitService, type AgentStoreLike, type SessionStoreLike } from './service.ts'\n\nexport const name = PLUGIN_NAME\nexport const inject = ['aiServices', 'sessions', 'agents', 'connection', 'webServer'] as const\nexport { GitCommitService } from './service.ts'\nexport { parsePorcelain } from './git.ts'\nexport { parsePlan, buildPlanInput } from './plan.ts'\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context { gitCommit: GitCommitService }\n}\n\ntype Host = Context & HostRpcContext & {\n aiServices: AiServices\n sessions: SessionStoreLike\n agents: AgentStoreLike\n}\n\nfunction stringField(payload: unknown, key: string): string | undefined {\n if (!payload || typeof payload !== 'object') return undefined\n const value = (payload as Record<string, unknown>)[key]\n return typeof value === 'string' && value.trim() ? value : undefined\n}\n\nfunction fail(error: unknown): RpcResult<never> {\n if (error instanceof CommitRunError) return { ok: false, error: { code: error.code, message: error.message } }\n const message = error instanceof Error ? error.message : String(error)\n return { ok: false, error: { code: 'internal', message } }\n}\n\nexport async function handleRpc(\n service: GitCommitService,\n endpoint: string,\n payload: unknown,\n signal: AbortSignal,\n): Promise<RpcResult> {\n try {\n switch (endpoint) {\n case 'status': {\n const sessionId = stringField(payload, 'sessionId')\n if (!sessionId) return { ok: false, error: { code: 'bad-request', message: 'sessionId is required' } }\n const value: GitCommitStatus = await service.status(sessionId)\n return { ok: true, value }\n }\n case 'commit': {\n const sessionId = stringField(payload, 'sessionId')\n if (!sessionId) return { ok: false, error: { code: 'bad-request', message: 'sessionId is required' } }\n const value: CommitRunResult = await service.commit(sessionId, signal)\n return { ok: true, value }\n }\n default:\n return { ok: false, error: { code: 'not-found', message: `unknown endpoint: ${endpoint}` } }\n }\n } catch (error) {\n return fail(error)\n }\n}\n\nexport function apply(ctx: Context): void {\n const host = ctx as Host\n const service = new GitCommitService({ plugin: PLUGIN_NAME, ai: host.aiServices, sessions: host.sessions, agents: host.agents })\n service.start()\n ctx.provide('gitCommit', service)\n ctx.effect(() => async () => { await service.dispose() }, 'git-commit.lifecycle')\n ctx.effect(() => registerHostRpc(host, RPC_CHANNEL, (endpoint, payload, signal) => handleRpc(service, endpoint, payload, signal)), 'git-commit.rpc')\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,108 @@
1
+ {
2
+ "name": "@klarkxy/dsh-git-commit",
3
+ "version": "0.1.3",
4
+ "description": "Git Commit: commit workspace changes from the conversation header, with AI-split commits via ai-services.",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=22"
9
+ },
10
+ "main": "./lib/index.js",
11
+ "types": "./lib/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./lib/index.d.ts",
15
+ "import": "./lib/index.js",
16
+ "default": "./lib/index.js"
17
+ },
18
+ "./contracts": {
19
+ "types": "./lib/contracts.d.ts",
20
+ "import": "./lib/contracts.js",
21
+ "default": "./lib/contracts.js"
22
+ },
23
+ "./client": {
24
+ "default": "./lib/client.js"
25
+ },
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "icon.svg",
30
+ "lib",
31
+ "cordis.patch.yml",
32
+ "README.md",
33
+ "docs/README.zh-CN.md",
34
+ "LICENSE"
35
+ ],
36
+ "dsh": {
37
+ "bundle": {
38
+ "patch": "./cordis.patch.yml"
39
+ },
40
+ "client": {
41
+ "platform": "web",
42
+ "inject": [
43
+ "@deepseek-ai/dsh-client-connection",
44
+ "@deepseek-ai/dsh-client-locale",
45
+ "@deepseek-ai/dsh-client-ui-slots",
46
+ "@deepseek-ai/dsh-client-ui-session",
47
+ "@deepseek-ai/dsh-client-ui-primitives"
48
+ ]
49
+ }
50
+ },
51
+ "dshEditor": {
52
+ "role": "feature",
53
+ "visibility": "public",
54
+ "wrapClient": true,
55
+ "entries": [
56
+ {
57
+ "id": "git-commit",
58
+ "title": "Git 提交",
59
+ "description": "整理当前仓库的全部工作区修改,AI 生成提交信息并分次提交",
60
+ "feature": "git-commit",
61
+ "service": "gitCommit"
62
+ }
63
+ ]
64
+ },
65
+ "scripts": {
66
+ "build": "tsdown && node ../../scripts/editor-plugins/wrap-client.mjs @klarkxy/dsh-git-commit",
67
+ "typecheck": "tsc --noEmit -p tsconfig.json",
68
+ "test": "vitest run --root ../.. --config vitest.editor-plugins.config.ts --maxWorkers=2 plugins/dsh-git-commit/src"
69
+ },
70
+ "dependencies": {
71
+ "@klarkxy/dsh-ai-services": "0.1.3"
72
+ },
73
+ "peerDependencies": {
74
+ "@deepseek-ai/cordis": "^4.0.4"
75
+ },
76
+ "devDependencies": {
77
+ "@deepseek-ai/cordis": "^4.0.4",
78
+ "react": "^18.2.0",
79
+ "react-dom": "^18.2.0",
80
+ "@types/react": "18.3.31",
81
+ "@types/react-dom": "^18.3.0",
82
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.2"
83
+ },
84
+ "keywords": [
85
+ "dsh",
86
+ "dsh-plugin",
87
+ "dsh-plugins",
88
+ "deepseek-harness",
89
+ "git",
90
+ "commit"
91
+ ],
92
+ "repository": {
93
+ "type": "git",
94
+ "url": "git+https://github.com/klarkxy/dsh-plugins.git",
95
+ "directory": "plugins/dsh-git-commit"
96
+ },
97
+ "homepage": "https://klarkxy.github.io/dsh-plugins/plugins/git-commit/",
98
+ "bugs": {
99
+ "url": "https://github.com/klarkxy/dsh-plugins/issues"
100
+ },
101
+ "publishConfig": {
102
+ "access": "public",
103
+ "registry": "https://registry.npmjs.org/"
104
+ },
105
+ "dshRelease": {
106
+ "contentHash": "28afe3c04f600df39921b1bbacbb13057d1f5ca56d1287014236ac8c788872ff"
107
+ }
108
+ }