@chaoxu1997/gentle-committer
v0.3.2
Published
Commit automation for the pi coding agent with review/bypass commit modes and work-unit commit discipline (independent project, originally derived from pi-committer)
Maintainers
Readme
gentle-committer
gentle-committer is an independent commit-automation extension for the pi coding agent, originally derived from pi-committer v0.12.8 and adapted for the gentle-pi ecosystem. All credit for the original commit automation goes to the pi-committer contributors; this project is MIT under the same terms.
Commit automation for the pi coding agent with a commit_mode switch: commit directly (bypass) or route each commit through gentle-ai's review gate (review) — plus optional work-unit commit discipline injected into the commit-message prompts.
| Mode | What happens | When to use |
| --- | --- | --- |
| bypass (default) | stage → generate message → git commit (byte-identical to pi-committer) | everyday work; developing gentle-committer itself |
| review | stage + generate message → write a message file → hand off a git commit -F directive so gentle-ai's gate reviews the commit before it lands | features/changes you want reviewed before they land |
Override per-commit without touching config: commit_changes({mode: "review"}) for a big feature, commit_changes({mode: "bypass"}) for a quick fix.
pi install npm:@chaoxu1997/gentle-committerWhat's different from pi-committer
| Area | pi-committer | gentle-committer |
| --- | --- | --- |
| Commit path | always execSync direct | bypass (direct) or review (gentle-ai gate) |
| Per-commit mode | — | commit_changes({mode}) override |
| Commit-message prompt | fixed | + optional work-unit rules (prompt_rules_path) |
| gentle-ai coupling | — | none — review fires via the existing bash gate |
| Everything else | — | unchanged (staged commits, subagent, multi-repo, async, widget) |
Quick start
Install (above) — pi auto-loads it on startup.
Enable + pick a mode in
.pi-committer.toml:[committer] enabled = true commit_mode = "bypass" # or "review"Commit: ask the agent to commit, run
/commit, or (withtrigger_mode = "on_goal") let it auto-commit when a pi-goal completes.
Commit modes
bypass (default) — direct commit
Stage → subagent generates a conventional message → git commit. Byte-identical to upstream pi-committer. No gate.
review — commit through gentle-ai's gate
Stage + generate the message, but do not commit. Instead:
commit_changes({mode:"review"})
→ stages files, generates message
→ writes .git/gentle_committer_pending_msg
→ returns a directive (pending: true, NO commit):
"Run: git commit -F .git/gentle_committer_pending_msg
gentle-ai's gate will review the commit before it lands.
Do NOT modify the staging area (git add/reset/restore)."
→ the model runs `git commit -F <file>`
→ gentle-ai's bash gate intercepts `git commit` → review → receipt → gate
→ the commit lands ONLY if the review passesWhy indirect? An extension cannot call the gentle_review tool in-process (its execute closure is private; ExtensionAPI exposes no invokeTool). So gentle-committer prepares the commit and lets the model's git commit trip gentle-ai's existing gate. Zero coupling — gentle-ai can upgrade without affecting gentle-committer.
Review-mode guardrails:
- [ ] Async worker is disabled (review is synchronous; one message file).
- [ ] Grouped/staged commits collapse to a single commit (one message, one directive).
- [ ] Automatic triggers (
turn_end/tool_result/goal_event) are suppressed — callcommit_changesexplicitly so the directive reaches the model. - [ ] If
commit_mode = "review"but gentle-ai isn't loaded, it warns and falls back tobypass.
Per-call mode override
Don't want to flip a global config? Pass mode to commit_changes:
| You say | Model calls | Result |
| --- | --- | --- |
| "just commit this" | commit_changes({mode:"bypass"}) | direct commit |
| "review then commit" | commit_changes({mode:"review"}) | directive → gate |
| (nothing) | commit_changes() | uses commit_mode config (default bypass) |
SDD vs review-mode — SDD reviews the design/spec (planning); review-mode gates the commit diff (publish). They compose: SDD plan + implement →
commit_changes({mode:"review"}).
Configuration
Create .pi-committer.toml (or .pi-committer.json) in your project root. The extension walks up directories to find it.
[committer]
enabled = true
commit_mode = "bypass" # bypass | review (gentle-committer)
prompt_rules_path = "commit-rules/work-unit-commits.md" # (gentle-committer, optional)
trigger_mode = "on_goal" # on_goal | agent_sensible | after_tool | manual
staged_commits = true
min_changes = 1
exclude_patterns = ["*.log", "node_modules/"]
# subagent_model = "openai/gpt-4o-mini"
# subagent_grouping_min_files = 15 # ≥N files → subagent splits into groups
# subagent_message_min_files = 3 # <N files → deterministic message (no LLM)
# async_threshold = 10 # ≥N files → background subprocess; 0 = always sync
# custom_types = ["api", "wip"]
# allowed_scopes = ["api", "cli", "core"]
# defer_to_goal_audit = false
# detailed_body = true| Key | Default | Purpose |
| --- | --- | --- |
| enabled | false | master switch |
| commit_mode | "bypass" | direct commit vs gentle-ai gate |
| prompt_rules_path | unset | markdown appended to the commit-message prompts |
| trigger_mode | "on_goal" | when to auto-commit |
| staged_commits | true | subagent splits the diff into logical commits |
| async_threshold | 10 | ≥ N files → fork into a background subprocess |
| subagent_model | agent model | model used to generate messages |
| exclude_patterns | [] | glob patterns to skip |
Trigger modes
| Mode | Behaviour |
| --- | --- |
| on_goal (default) | commits when a pi-goal transitions to complete |
| agent_sensible | commits after every agent turn |
| after_tool | commits after each tool call |
| manual | never auto-commits; /commit or commit_changes only |
Work-unit commit rules (prompt_rules_path)
Point prompt_rules_path at a markdown file (this repo ships commit-rules/work-unit-commits.md). Its content is appended to the grouping and single-message prompts — commit-by-work-unit, tests-with-code, scope-equals-work-unit, one-purpose-per-commit. When unset or unreadable, prompts are byte-identical to upstream pi-committer (bypass mode is unaffected).
Features
commit_modeswitch — direct commit (bypass) or gentle-ai-gated commit (review).- Per-call
modeoverride oncommit_changes— pick the path per commit. - Commits automatically when a pi-goal completes (
on_goaltrigger, opt-in). - Splits diffs into separate commits via a subagent (feature + tests in one commit, docs in another).
- Commit messages generated by a subagent using your model — pick it with
/commit-modelorsubagent_model. - Detects and commits in any git repositories the agent touched.
commit_changestool that agents can call to checkpoint work.- Exclusion patterns for files matching globs (
*.log,build/, etc.). - Slash commands:
/commit,/commit-config,/commit-model.
Usage
Enable auto-commit, then drive it by goal, command, or tool:
# .pi-committer.toml
[committer]
enabled = trueAuto-commit on goal completion
With trigger_mode = "on_goal" (default), the extension hooks into pi-goal's lifecycle and commits when a goal transitions to complete:
/goals "Add user authentication" ← start a goal
> agent implements auth logic ← work happens
> pi-goal marks the goal complete ← gentle-committer auto-commitsManual commit
/commitOr ask the agent to "save my progress" — it calls commit_changes (pass {mode:"review"} if you want it gated).
Choose the commit-message model
/commit-modelOpens an interactive selector. Defaults to your current agent model.
Reload config
/commit-configStaged 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 documentationThe subagent decides the grouping from the diff content, not from file extensions. (In review mode this is forced to a single commit — one message file, one directive.)
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 are detected on top of the primary working directory.
Deterministic commit message fallback
When the subagent is unavailable or returns nothing, gentle-committer generates a deterministic message using string analysis — no LLM call:
- Smart scope — longest common ancestor directory across changed files; omitted if files span unrelated directories.
- Specific description — meaningful keywords from file names (strips extensions, skips
__init__/conftest, convertssnake_caseto words). Never "update N modules". - Contamination guard —
isValidDiffContent/isValidDiffStat/isValidCommitMessagereject non-diff output so unrelated shell output can't leak into a message.
Every fallback decision logs a DIAG: diagnostic to help identify root causes.
Architecture
Extension events (turn_end, tool_result, goal_event) + commit_changes tool
│
▼
commitAllRepos(dir, ctx)
│
├─ findDirtyRepos(ctx) discover all dirty repos
│
└─ tryCommit(repo, ...) for each dirty repo
│
├─ stageAll / unstageExcluded / filterGitignored
│
├─ resolveEffectiveCommitMode(pi, override?) bypass | review (+R9 fallback)
│
├─ buildGroupingPrompt / buildSingleMessagePrompt (prompt-builders.ts)
│ └─ + loadCommitRules(prompt_rules_path) append work-unit rules
│
├─ generateStagedCommitGroups / generateCommitMessageViaSubagent
│ └─ createAgentSession no tools, diff inline
│
└─ performCommit(mode, …) (commit-emission.ts)
├─ bypass → execSync("git commit -F -") (byte-identical)
└─ review → write .git/gentle_committer_pending_msg + handoff directiveThe commit-emission shim (commit-emission.ts) and prompt builders (prompt-builders.ts) are shared by both the main process and the async worker, with no circular imports (commit-emission uses only node builtins; prompt-builders takes a type-only CommitterConfig). The subagent uses createAgentSession from @earendil-works/pi-coding-agent — no pi-subagents dependency required.
Requirements
- pi coding agent 0.71+
- Node.js 20+
- Git
Install from source
git clone https://github.com/ChaoXu1997/gentle-committer.git
cd gentle-committer && npm install
# try it in a session without installing
pi -e ./index.tsDevelopment
cd gentle-committer
npm install
npm test # unit + worker-edge tests
npm run test:all # full suite
npm run test:e2e # end-to-end (PI_COMMITTER_E2E=1)Tests run via
tsx(this node build's--experimental-strip-typesis unreliable); the worker fork resolvestsx/jitiloaders automatically.
License
MIT
