pi-committer
Conventional commit automation for the pi coding agent — auto-commits on goal completion with intelligent, subagent-generated messages
Package details
Install pi-committer from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-committer- Package
pi-committer- Version
0.15.0- Published
- Aug 8, 2026
- Downloads
- 1,027/mo · 737/wk
- Author
- tmonk
- License
- MIT
- Types
- extension
- Size
- 636.6 KB
- Dependencies
- 2 dependencies · 0 peers
Pi manifest JSON
{
"extensions": [
"./index.ts"
],
"image": "https://github.com/tmonk/pi-committer/raw/main/screenshot.png"
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-committer
Conventional commit automation for the pi coding agent.
Creates commits as you work. The extension is opt-in — enable it per-project with a config file.
Features
- Commits automatically when a pi-goal completes (
on_goaltrigger, opt-in). Other modes available. - Splits diffs into separate commits via a subagent (e.g. feature and tests in one commit, docs in another).
- Commit messages generated by a subagent using your model.
- Pick the model with
/commit-modelor thesubagent_modelconfig key. - Detects and commits in any git repositories the agent touched.
commit_changestool that agents can call to checkpoint work.- Exclusion patterns for files matching glob patterns (
*.log,build/, etc.). - Slash commands:
/commit,/commit-config,/commit-model.
Quick start
Install from npm (recommended)
pi install npm:pi-committer
This installs the package globally. Pi auto-loads it on startup.
Install from source
git clone https://github.com/tmonk/pi-committer.git
cd pi-committer && npm install
# Load it in a session
pi -e ./index.ts
Global auto-discovery (source install)
ln -s ~/projects/pi-committer ~/.pi/agent/extensions/pi-committer
Configuration
Create .pi-committer.toml (or .pi-committer.json) in your project root. The extension walks up directories to find it.
[committer]
enabled = false # off by default; set to true to enable
trigger_mode = "on_goal" # on_goal | agent_sensible | after_tool | manual
detailed_body = true
min_changes = 1
staged_commits = true
exclude_patterns = ["*.log", "node_modules/"]
# Opt-in: sample the repo's recent commit history (git log) and match its
# types/scopes/tone when generating messages. Defaults to false — no git-log
# sampling unless you enable it.
# match_repo_style = true
# When true and trigger_mode is "on_goal", the commit_changes tool will skip
# when pi-goal has an active goal and defer to the automatic commit-after-audit
# flow. Defaults to false — commit_changes always proceeds immediately unless
# you explicitly opt in to deferral.
# defer_to_goal_audit = false
# Minimum number of changed files before the subagent is called for grouping.
# Small change sets below this threshold use a single commit with a
# subagent-generated message (unless also below subagent_message_min_files).
# Default: 15 (determined empirically — see benchmark results).
# subagent_grouping_min_files = 15
# Minimum number of changed files before the subagent is called for the
# commit message. Below this threshold, the deterministic fallback is used
# directly (no LLM call at all) — but ONLY when deterministic_fallback is
# enabled (see below). Default: 3.
# subagent_message_min_files = 3
# Opt-in: allow the content-driven deterministic commit-message generator
# (small change sets below subagent_message_min_files, and regeneration of
# garbled subagent output). Default: false. With this OFF (default), the
# subagent is required for every commit: when it is unavailable, fails, or
# returns an invalid message, the commit is BLOCKED with a clear warning and
# the changes are left staged — a generic message is never committed.
# deterministic_fallback = false
# Optional: override the model used by the commit-message subagent
# subagent_model = "openai/gpt-4o-mini"
# Threshold for async (background subprocess) commits. When the number of changed
# files >= this value, /commit and commit_changes fork the commit pipeline into a
# detached subprocess so the conversation continues immediately. The widget shows
# progress and Esc cancels. Default: 10. Set to 0 to disable async (always sync).
# async_threshold = 10
# When an async (background) commit finishes, pi-committer delivers a completion
# message into the session: a custom message the agent sees (delivered once the
# agent has no more tool calls, triggering a turn if idle) summarizing the result
# (commit hashes + summaries, or the error). This lets the agent continue working
# instead of sleeping or polling for the background commit. The TUI notification
# always fires; this toggle only disables the injected session message.
# Default: true.
# notify_async_completion = true
# Optional: extend conventional commit types / restrict scopes
# custom_types = ["api", "wip"]
# allowed_scopes = ["api", "cli", "core"]
Trigger modes
| Mode | Behaviour |
|---|---|
on_goal |
Commits when a goal transitions to complete |
agent_sensible |
Commits after every agent turn |
after_tool |
Commits after each tool call |
manual (default) |
Never auto-commits; use /commit or commit_changes only |
Usage
First, enable auto-commit in your project config:
# .pi-committer.toml
[committer]
enabled = true
Auto-commit on goal completion
With enabled = true and trigger_mode = "on_goal" (default), the extension hooks into pi-goal's lifecycle. When a goal managed by pi-goal transitions to complete status, pi-committer automatically stages and commits the changes:
/goals "Add user authentication" ← start a goal via /goals
> agent implements auth logic ← work happens
> pi-goal marks the goal complete ← pi-committer auto-commits
The commit is triggered by the goal state transition — no manual /commit needed.
Manual commit
/commit
Or ask the agent to "save my progress" and it will call the commit_changes tool.
Choose the commit-message model
/commit-model
Opens an interactive selector. Defaults to your current agent model.
Reload config
/commit-config
Staged commits
When staged_commits = true (default), the subagent receives the diff and organizes changes into commit groups. Editing a source file, adding tests, and updating docs might produce:
feat(api): add user authentication endpoint
test(api): add authentication tests
docs: update API documentation
The subagent decides the grouping from the diff content, not from file extensions.
Multi-repo
If the agent edits files in multiple git repositories during a session, commit_changes finds and commits in all of them. Detection works via session tool-call history — repos where the agent created or modified files using write or edit tools are detected and added on top of the primary working directory.
Background commits & the completion notification
Large change sets (≥ async_threshold files, default 10) are committed in a
detached subprocess so the conversation continues immediately. The tool result
explicitly tells the agent never to sleep, wait, or poll for the background
commit. When the worker finishes, pi-committer:
- Injects a session message (custom type
pi-committer) summarizing the result —✓ Background commit complete: N commit(s) — <hash> <summary>…orBackground commit failed: <error>. It is delivered once the agent has no more tool calls (deliverAs: "followUp") and triggers a turn if idle, so the agent learns the commit finished without sleeping. - Fires a TUI notification with the same summary.
Set notify_async_completion = false in .pi-committer.toml to suppress only
the injected session message (the TUI notification still fires).
Commit message generation & the block gate
By default the commit-message subagent is required for every commit. When the subagent is unavailable (e.g., SDK load failure), fails, or returns an invalid message, the commit is blocked with a clear warning and the changes are left staged — a generic message is never committed.
Opt-in deterministic fallback (deterministic_fallback = true) restores the
previous behavior: when the subagent is unavailable or returns no result,
pi-committer generates a deterministic commit message from the diff itself —
no LLM calls needed:
- Content-driven description: Reads the actual added/removed lines from the diff and describes what changed (e.g.
add const retryCount = 3), never falling back to filename-only filler likeupdate file.ts. - Smart scope: Uses the longest common ancestor directory across all changed files. If files span unrelated directories, scope is omitted entirely. When
match_repo_styleis enabled, the scope is normalized to one the repo actually uses. - Structured body: Always includes a body with a summary line and per-file change details (+N/−M with the first changed line).
- Block instead of boilerplate: If the diff has no extractable content (e.g. empty diffs), the commit is skipped with a clear warning — a generic message is never committed in either mode. The old
chore: update N file(s)substitution was removed from every commit path.
Agent-specified commit messages
commit_changes accepts two optional parameters so the agent can pass what the
commit message should say:
| Parameter | Purpose |
|---|---|
message |
Short freeform summary of what the message should include / how it should be structured. Appended to the subagent prompt as a "user message request" the generated message(s) MUST cover. |
verbatim |
Exact commit message text, used as-is — generation is skipped entirely. Forces a single commit containing all changes (staged-commits grouping is bypassed). |
When the user tells the agent what the commit message should say, the agent is
directed to prefer passing it as verbatim (never silently rewrite what the
user asked for). A verbatim message is validated against the conventional
commit format (<type>(<scope>): <description>); an invalid one blocks the
commit with a clear warning and leaves changes staged — it is never edited,
prefixed, or reformatted. Both parameters work in the sync and background
(async) commit paths.
Matching the repo's commit style (opt-in)
Commit messages are detailed by default; matching the repository's own history is opt-in:
[committer]
# Sample the last 15 commits from git log and use them as style context:
# the subagent matches the repo's types/scopes/tone, and the deterministic
# fallback constrains its type/scope to ones the repo actually uses.
# Default: false — no git-log sampling happens unless you enable this.
match_repo_style = true
Every subagent fallback decision point logs a DIAG: diagnostic message to help identify root causes.
Architecture
Extension events (turn_end, tool_result, goal_event)
│
▼
commitAllRepos(dir, ctx)
│
├─ findDirtyRepos(ctx) discover all dirty repos
│
└─ tryCommit(repo, ...) for each dirty repo
│
├─ stageAll / unstageExcluded
│
├─ generateStagedCommitGroups subagent decides grouping
│ └─ createAgentSession no tools, diff inline
│ └─ prompt: "Organize changes into logical commits"
│
├─ generateCommitMessageViaSubagent single-commit fallback
│
└─ git commit for each group
The subagent uses createAgentSession from @earendil-works/pi-coding-agent, the same pattern as pi-goal's runGoalCompletionAuditor. No pi-subagents dependency required.
Requirements
- pi coding agent 0.71+
- Node.js 18+
- Git
smol-toml(installed vianpm install)
Development
git clone <this-repo>
cd pi-committer
npm install
npm test # unit tests
npm run test:e2e # end-to-end tests
License
MIT
