version: 0.0.1
name: commit
about: AI-generated commit message
args:
- name: dry_run
short: 'd'
input: bool
default: 'false'
help: 'Print commit message without committing'
api:
output: dialog
input: query|project_path
actions:
- tag: tag_status
run: cmd
expect: string
action: cd {query|project_path} && git status --porcelain
- tag: tag_stat
run: cmd
expect: string
action: cd {query|project_path} && git diff HEAD --stat 2>/dev/null || true
- tag: tag_recent_commits
run: cmd
expect: string
action: cd {query|project_path} && git log --oneline -5 --no-decorate 2>/dev/null || echo "No commits yet"
- tag: tag_changed_files
run: cmd
expect: list
check: .+
action: cd {query|project_path} && git -c core.quotepath=false status --porcelain -uall --no-renames | sed 's/^...//'
- tag: tag_file_diff
run: cmd
expect: list
action: |
cd {query|project_path}
f="{tag_changed_files}"
if git ls-files --error-unmatch "$f" >/dev/null 2>&1; then
# Tracked: diff is safe even for binaries ("Binary files differ")
git diff HEAD -- "$f" 2>/dev/null | head -c 6000
elif [ -d "$f" ]; then
echo "=== NEW DIRECTORY: $f ==="
elif [ ! -s "$f" ]; then
# Empty file: grep binary detection false-positives on these
echo "=== NEW EMPTY FILE: $f ==="
elif grep -Iq . "$f" 2>/dev/null; then
# Untracked text file
echo "=== NEW FILE: $f ==="
head -c 3000 "$f"
else
# Untracked binary: existence + size only
echo "=== NEW BINARY FILE: $f ($(du -h "$f" | cut -f1)) ==="
fi
- tag: tag_file_summary
run: small
expect: list
action: |
[Task]
Describe the PURPOSE of this change in one phrase (max 15 words).
- Focus on WHAT and WHY, not mechanics.
- Deleted file: what was removed and, if obvious, what replaced it.
- New file: what it is and its role.
- Binary/empty file stub: just state what was added.
- Ignore trivial changes (formatting, typos).
Format: <path>: <phrase>
[Example]
book/src/assets/css/index/body.css: deleted, merged into shared main.css
[Diff]
{tag_file_diff}
- tag: tag_commit_message
run: medium
expect: string
action: |
[Task]
Write ONE conventional commit message (will appear in a git-cliff changelog).
Rules:
- Format strictly: <type>: <message> (NO scope/parentheses)
- Types: feat, fix, docs, refactor, test, chore, style, perf
- Max 100 chars, imperative mood, lowercase after type
- Type = the most significant change
- Synthesize ACROSS summaries: merge the 1-2 most significant changes
into one sentence with "and" or a comma
- Drop trivial changes: config tweaks, version bumps, formatting
- Do NOT just copy one file summary — combine what matters
- NEVER use single quotes, double quotes or backticks (shell-safe output)
- Recent commits show the project style — match their quality
[Examples — style reference from unrelated projects, do NOT reuse their wording]
[Bad] fix: fix bug
[Good] fix: handle empty response in payment webhook
[Bad] feat: add stuff
[Good] feat: add retry with backoff to s3 uploads
[Bad] docs: update docs
[Good] docs: document rate limiting in api reference
[Synthesis example — how to merge summaries]
Input summaries:
- api/webhooks.rs → retry failed deliveries with exponential backoff
- api/routes.rs → return 400 instead of 500 on empty payload
- config.rs → bump default timeout constant
Output:
fix: retry failed webhooks and return 400 on empty payloads
[Recent commits]
{tag_recent_commits}
[Changed files with status]
{tag_status}
[Diff stat]
{tag_stat}
[Per-file summaries]
{tag_file_summary|uniq|join}
- tag: tag_commit_exec
run: cmd
expect: string
confirm: true
when: '{dry_run|contains:false}'
action: cd {query|project_path} && git add . && git commit -m '{tag_commit_message|lower}'
- tag: tag_commit
run: value
expect: string
action:
- when: '{dry_run|contains:true}'
then: '{tag_commit_message|lower|clipboard}'
- when: '{dry_run|contains:false}'
then: '{tag_commit_exec}'