pi-committer

Conventional commit automation for the pi coding agent — auto-commits on goal completion with intelligent, subagent-generated messages

Packages

Package details

extension

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_goal trigger, 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-model or the subagent_model config key.
  • Detects and commits in any git repositories the agent touched.
  • commit_changes tool 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:

  1. Injects a session message (custom type pi-committer) summarizing the result — ✓ Background commit complete: N commit(s) — <hash> <summary>… or Background 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.
  2. 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 like update 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_style is 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 via npm 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