-
Notifications
You must be signed in to change notification settings - Fork 0
AI Commit Message Generation
AI commit message generation is implemented in an Electron-free core module, src/core/commit-message.ts. The renderer asks for a message from the source-control panel; the core builds a conventional-commit prompt around the staged diff, detects an available local agent CLI, runs it read-only, and parses a single subject line from its output.
A key implementation note: in the captured source, generation is not routed through the chat model-provider adapters. It is a BYO local agent CLI flow: prefer claude, then fall back to codex, otherwise fail with an installation hint. The same core is reusable by the Server Edition because it has no Electron dependency.
| File | Responsibility |
|---|---|
src/core/commit-message.ts |
Prompt construction, diff bounding, CLI detection, CLI execution, output parsing, orchestration, result shaping. |
src/core/commit-message.test.ts |
TDD coverage for prompt content, truncation, parser cleanup, and CLI preference order. |
src/shared/ipc.ts |
Defines the shared channel name git:commit-message. |
src/renderer/src/components/SourceControlPanel.tsx |
UI trigger and state: calls window.termsprawl.git.commitMessage(target), sets the commit input, surfaces busy/error/status. |
src/preload/index.ts |
Declares the narrow window.termsprawl context-bridge surface; the renderer does not touch ipcRenderer directly. |
sequenceDiagram
participant SC as SourceControlPanel
participant API as window.termsprawl.git
participant Main as main git:commit-message handler
participant Core as core/commit-message
participant Git as stagedDiff
participant CLI as claude/codex
SC->>SC: setAiBusy(true); clear error/status
SC->>API: commitMessage(target)
API->>Main: IPC.gitCommitMessage = git:commit-message
alt local target
Main->>Core: generateCommitMessage(repoRoot)
Core->>Git: stagedDiff(repoRoot)
Git-->>Core: staged diff
else remote target
Main->>Main: fetch staged diff over ssh first (documented in core)
Main->>Core: generateCommitMessageFromDiff(diff, cwd)
end
Core->>Core: buildCommitPrompt(diff), cap at 6000 chars
Core->>Core: detectAgentCli(binOnPath)
Core->>CLI: spawn(tool, argv, cwd)
CLI-->>Core: stdout / stderr / exit status
Core->>Core: parseCommitMessage(stdout)
Core-->>Main: CommitMessageResult
Main-->>API: result
API-->>SC: result
SC->>SC: setAiBusy(false); setMsg(message) or setError(error)
Key nodes:
- The renderer owns the review flow.
SourceControlPanel.generateCommitMsgsetsaiBusy, clears previous feedback, calls the git API, and on success writes into the commit-message input rather than committing immediately. -
targetis derived from the project kind: a local folder sends{ cwd }, while a remote project sends{ remote }. - The IPC contract is centralized as
IPC.gitCommitMessage = 'git:commit-message'. - The core exposes two entry points.
generateCommitMessage(repoRoot)reads the local staged diff viastagedDiff;generateCommitMessageFromDiff(diff, cwd)is the seam for callers that already have the diff, including the documented remote path where the main handler fetches the diff over SSH first. -
buildCommitPrompt,detectAgentCli,runAgentCli, andparseCommitMessageform the actual generation pipeline.
buildCommitPrompt(diff) is deliberately narrow. The only repository context included is the staged diff. It does not include branch names, status output, recent commits, project settings, or chat context.
The prompt asks for:
- A conventional commit message.
-
type(scope): subjectformat. - Allowed types:
feat/fix/chore/docs/refactor/test/style/perf/build/ci. - Only the message as the response: one-line subject, optional short body, no explanations and no code fences.
- The diff wrapped in explicit
<diff>/</diff>tags.
The diff is bounded by MAX_DIFF_CHARS = 6000. If the diff is longer, it is sliced to the first 6000 characters and suffixed with …(truncated). This protects the argv-passed prompt from unbounded growth and keeps the CLI invocation predictable.
One boundary to note: the prompt allows an optional body, but parseCommitMessage returns only cleaned[0], so only the first usable line becomes the result message. In practice, the UI receives the subject line.
detectAgentCli(probe) implements a fixed preference order:
claudecodex'none'
The production probe is binOnPath, which runs spawnSync('which', [bin], { stdio: 'ignore' }) and checks only the exit status. No shell string is used.
runAgentCli(tool, prompt, cwd) builds arguments per tool:
-
claude:['-p', prompt] -
codex:['exec', prompt]
The prompt is passed as a single argv argument. This is the clean-room boundary documented at the top of the module: argv-array spawns only, never shell interpolation.
Execution details:
spawn(tool, args, { cwd, stdio: ['pipe', 'pipe', 'pipe'] })- stdout and stderr are accumulated as strings.
-
child.stdin.end()is called immediately because the CLI should not wait for stdin. - Spawn errors resolve as
{ error: '<tool> failed to spawn: ...' }. - Non-zero exits resolve as
{ error: '<tool> exited <code>: <stderr first 500 chars>' }. - A
120_000ms timeout resolves as{ error: '<tool> timed out' }. - A
settledflag ensures the promise resolves only once.
parseCommitMessage(output) cleans possibly chatty CLI output:
- Splits on newlines and trims each line.
- Ignores blank lines.
- Ignores markdown fence lines beginning with
```. - Ignores conversational prefixes such as
here is,here's,commit message,suggested,the commit, andbased on. - Strips surrounding straight and curly quotes via
stripWrap. - Returns the first remaining cleaned line, or
''.
The result type is shaped by literals in this module:
- Failure:
{ ok: false, error, tool? } - Success:
{ ok: true, message, tool }
Failure cases include:
- Empty staged diff:
nothing staged to commit - No CLI available:
no agent CLI found; install claude or codex - Spawn failure, non-zero exit, or timeout
- CLI output with no usable message:
agent returned no commit message
The core has no persistent state. State is per invocation:
-
settledprevents double resolution. -
timerenforces the timeout. -
outputandstderrbuffer process output. - The detected
toolis included in both success and most failure results.
The renderer state is in SourceControlPanel:
-
msg: the commit-message input, filled by generation but not auto-committed. -
aiBusy: separate busy flag for AI generation. -
busy: general git operation busy flag. -
error/status: feedback surfaces; success sets a status likemessage from ${res.tool}.
The user-facing flow intentionally stops at filling the input. Committing remains a separate window.termsprawl.git.commit(target, text) action.
- Generation requires a non-empty staged diff.
- The prompt contains only the staged diff, not broader project context.
- Diff context is truncated at 6000 characters.
- CLI availability is PATH-based; no settings or provider configuration is consulted in this core.
-
claudealways wins overcodexwhen both are present. - The process timeout is 120 seconds.
- The parser returns only one cleaned subject line and may drop an optional body.
- The spawn path never uses a shell, and the
whichprobe uses status only.
- Add another agent CLI by extending the detection order in
detectAgentCli, adding its argv shape inrunAgentCli, and updating the sharedCommitAgentClitype. - Adjust prompt wording, allowed conventional types, or diff wrapping in
buildCommitPrompt. - Tune
MAX_DIFF_CHARSandAGENT_TIMEOUT_MS. - Change output cleanup rules in
parseCommitMessage/stripWrap. - Reuse
generateCommitMessageFromDiffas the stable seam for remote projects or any caller that already has a diff. - If generation should later go through a configured model provider instead of a local CLI,
runAgentCliandgenerateCommitMessageFromDiffare the natural replacement points while preserving theCommitMessageResultcontract used by the renderer.
src/core/commit-message.test.ts covers the pure and injectable parts:
- Prompt includes the staged diff and asks for conventional format.
- Very large diffs are truncated below the test threshold.
- Parser extracts a subject from fenced output, strips quotes, and returns empty when nothing usable exists.
-
detectAgentCliprefersclaude, falls back tocodex, and returnsnone.
The tests inject the probe function, so CLI detection is covered without requiring either executable to be installed.
Sources: src/core/commit-message.ts, src/core/commit-message.test.ts, src/shared/ipc.ts, src/renderer/src/components/SourceControlPanel.tsx, src/preload/index.ts
Generated from termsprawl at 0d4393be54c6200beedd91bb636e5296c30472c5.
App Shell & Platform Foundations
- Electron Main Process & Window Lifecycle
- Preload Bridge & IPC Contract
- Shared Domain Types and File/URL Helpers
- Renderer Bootstrap & App Composition
- Build Targets & TypeScript Configuration
Canvas, Nodes & Renderer State
- Infinite Canvas Surface & Viewport Interaction
- Workspace, Project & Tab State
- Node Links, Edges & Link Inspector
- Sticky, Group, Editor & Diff Nodes
- Keyboard Canvas Navigation & Cross-Panel Requests
- Theme, Accent & Visual Language
- Boot Overlay, Onboarding & Shared UI Kit
Terminals & Session Continuity
- PTY Lifecycle & Terminal Sessions
- tmux Session Naming & Reattach
- Scrollback Snapshots & Cold Replay
- Terminal Node Rendering (xterm.js)
- SSH Remote Projects, Terminals & Files
Persistence, Projects & Files
- Workspace Store & Project File Layout
- Project Scope, Deletion & Worktree Registry
- Workspace Bundle Export/Import
- File Service & File Tree UI
Agent Runtime & Tooling
- Agent Status Model & Hook Normalization
- Hook Server & CLI Hook Installers
- Agent Launch, CLI Probing & Managed Accounts
- Agent Tool Protocol & In-Process Server
- Agent Tool Client, CLI & MCP Entry
- Transcripts, Context Discovery & Context CLI
- Agent Canvas State & Status Badges
Chat Nodes & Model Providers
- Chat Runtime, Conversation & Cost
- Model Provider Adapters & Streaming
- Chat Tool Calling & Project Tools
- Chat Node UI
Git & Source Control
Embedded Browser Nodes
- Browser Manager & Guest Runtime
- CDP Facade & Browser Agent Server
- Browser Navigation Policy & Node UI
Server Edition
- Server Bootstrap & HTTP/WebSocket Entry
- RPC Dispatch, Handlers & Service Bridges
- Renderer Shim & Server Boundary
- Server Auth & Security Boundary
Relay & Remote Access
- Relay Hub & WebSocket Frame Routing
- Relay End-to-End Cryptography
- Relay Auth, Invites, Store & Admin API
- Relay Client, Pairing & Terminal Tunneling
- Relay Trust UI
Integrations & Secondary Surfaces
- Telegram Bot, Commands & Pairing
- A2A Peers: Protocol, Client & Server
- Node Link Engine, Registry & Scheduler
- Cloud Spaces, Snapshots & Sync
Settings, Updates & Maintenance