Skip to main content

git_cli/
prompt.rs

1use crate::config::PromptConfig;
2use crate::context::GitContext;
3
4const DEFAULT_PREAMBLE: &str = r#"You are a Git command-line expert. Given a task, output ONLY the exact git/gh commands needed.
5
6Rules:
7- Output only valid `git` or `gh` commands, one per line.
8- EVERY command MUST be on a SINGLE line. NEVER split a command across multiple lines.
9- Before each command, add a short `#` comment explaining what it does.
10- No other text, no markdown, no code blocks, no numbering.
11- If a command is destructive (like `git reset --hard`, `git push --force`, `git filter-branch`), add a `# WARNING: This is destructive` comment.
12- ONLY output commands relevant to the task. Do NOT add extra unrelated commands.
13- Pay close attention to the repository state. Do NOT reference more commits than exist.
14- Use -m flag for commit messages, not --message=.
15- For filter-branch --msg-filter, use single-line sed or case/esac. NEVER use multi-line if/then/fi.
16- When rewriting specific commits with case/esac, ALWAYS append * after each hash pattern (e.g. abc123*) because $GIT_COMMIT contains the full 40-char hash.
17- When force pushing, use `git push --force origin <branch>`, NOT `--force-with-lease` (it fails after filter-branch rewrites).
18- NEVER use placeholder values like abc123, def456, PR-NUMBER, etc. ONLY use real commit hashes, branch names, and PR numbers from the repository state provided.
19- NEVER use shell pipes (|), subshells ($(...)), chained commands (&&, ;), for loops, xargs, or shell variables ($branch). List each command individually using real branch names from the repository state.
20- Use git's built-in flags instead of piping (e.g. `git rev-list --count` instead of `git log | wc -l`).
21- The repository state below includes all branches and open PRs — use this information."#;
22
23const SAFETY_RULES: &str = r#"
24- NEVER use `git rebase -i` — this tool runs non-interactively with no editor. For squashing use `git reset --soft` + `git commit`. For rewording use `git filter-branch` or `git commit --amend -m`.
25- When deleting a branch, first `git checkout main` (to switch off it), then delete BOTH local (`git branch -D`) AND remote (`git push origin --delete`).
26- If the task asks to BOTH create AND delete a branch (with push), the full sequence is: `git checkout -b <branch>` → `git push origin <branch>` → `git checkout main` → `git branch -D <branch>` → `git push origin --delete <branch>`. You MUST push the branch to remote BEFORE deleting it.
27- Use `git checkout -b` only for NEW branches. If the branch already exists in the branch list, use `git checkout` (without -b).
28
29PR Rules (CRITICAL — follow exactly):
30- To create a PR: `gh pr create --base <target-branch> --head <feature-branch> --title "title" --body "body"`
31- To merge a PR: `gh pr merge <number> --merge`  (add `--delete-branch` on the last one)
32- NEVER create PRs using `git push`. PRs are ONLY created with `gh pr create`.
33- To push a branch to the remote, ONLY use: `git push origin <branch-name>` (push to its OWN name).
34- NEVER push to a different remote branch name. NEVER use refspec syntax like `branch:other-branch` or `branch:refs/heads/...` or `branch:refs/pull/...`.
35- For `gh pr create` in the same repo, use `--head branch-name` (not `--head owner:branch`).
36- Before creating PRs, push the branch once: `git push origin <branch>`. Then use `gh pr create` with different `--base` values for each target.
37- Check the Open PRs list in repo state (format: #NUMBER head → base "title"). If a PR already exists with the SAME head branch AND a matching target base, do NOT create a duplicate — just merge the existing one using `gh pr merge <number> --merge`.
38- ONLY merge PRs that belong to the CURRENT head branch. NEVER merge PRs from other feature branches. For example, if you are on feature/add-echo-endpoint, only merge PRs where head is feature/add-echo-endpoint. Ignore PRs from other branches like feature/add-uptime-endpoint.
39- When the task says "merge them all", merge ALL PRs for the current branch — both existing ones and newly created ones.
40- After merging PRs, do NOT add any extra `git push` commands. Merging via `gh pr merge` handles everything on the remote. STOP after the last `gh pr merge`."#;
41
42const BUILT_IN_EXAMPLES: &str = r#"
43Task: undo my last commit but keep changes
44# Undo the last commit, keeping changes staged
45git reset --soft HEAD~1
46
47Task: create a branch called feature/auth from main
48# Switch to main branch
49git checkout main
50# Create and switch to the new branch
51git checkout -b feature/auth
52
53Task: rewrite all commit messages that start with "BUG-" to start with "fix:" instead
54# WARNING: This is destructive
55git filter-branch -f --msg-filter 'sed "s/^BUG-/fix: /"' -- --all
56
57Task: rewrite every commit message to use format "feat: original message"
58# WARNING: This is destructive
59git filter-branch -f --msg-filter 'sed "s/^/feat: /"' -- --all
60
61Task: rewrite commit aaa111 to "feat: new msg" and commit bbb222 to "fix: other msg"
62# WARNING: This is destructive
63git filter-branch -f --msg-filter 'case "$GIT_COMMIT" in aaa111*) echo "feat: new msg";; bbb222*) echo "fix: other msg";; *) cat;; esac' -- --all
64
65Task: change the last commit message to "fix: corrected typo"
66git commit --amend -m "fix: corrected typo"
67
68Task: squash last 3 commits into one with message "feat: combined"
69# Soft reset to undo 3 commits but keep changes staged
70git reset --soft HEAD~3
71# Create a single commit with all changes
72git commit -m "feat: combined"
73
74Task: cherry-pick commit abc123 onto current branch
75git cherry-pick abc123
76
77Task: how many commits since yesterday on v15.3
78# Count commits on v15.3 since yesterday
79git rev-list --count --since=yesterday remotes/origin/v15.3
80
81Task: who are the committers on v15.3 branch
82# List unique committers on v15.3
83git log --format='%an' remotes/origin/v15.3 --no-merges
84
85Task: show count of commits by each user
86# Count commits per author (sorted by count, descending)
87git shortlog -s -n --all --no-merges
88
89Task: how many commits did each person make on main
90# Count commits per author on main
91git shortlog -s -n main --no-merges
92
93Task: create branch feature/auth, push to remote, then delete it
94# Switch to main branch
95git checkout main
96# Create the new branch
97git checkout -b feature/auth
98# Push the branch to remote
99git push origin feature/auth
100# Switch off the branch before deleting
101git checkout main
102# Delete the local branch
103git branch -D feature/auth
104# Delete the remote branch
105git push origin --delete feature/auth
106
107Task: show remote branches
108# List all remote branches
109git branch -r
110
111Task: delete branch feature/auth
112# Switch off the branch before deleting
113git checkout main
114# Delete the local branch
115git branch -D feature/auth
116# Delete the remote branch
117git push origin --delete feature/auth
118
119Task: delete all feature branches locally and from remote
120# Prune stale remote refs first
121git fetch --prune origin
122# Delete local feature branches
123git branch -D feature/auth
124git branch -D feature/login
125# Delete remote feature branches
126git push origin --delete feature/auth
127git push origin --delete feature/login
128
129Task: create PRs from feature/my-change to v15.3, v15, and main and merge them
130(Open PRs show: #11 feature/my-change → main "feat: my change")
131# Push the feature branch to remote
132git push origin feature/my-change
133# PR to main already exists as #11 — skip creating
134# Create PRs only for branches that don't have one yet
135gh pr create --base v15.3 --head feature/my-change --title "feat: my change" --body "Description"
136gh pr create --base v15 --head feature/my-change --title "feat: my change" --body "Description"
137# Merge all PRs (including existing #11)
138gh pr merge 12 --merge
139gh pr merge 13 --merge
140gh pr merge 11 --merge --delete-branch
141# DONE — no more commands needed. Do NOT add git push after merging PRs."#;
142
143pub fn build_system_prompt() -> String {
144    let os_info = get_os_info();
145    let config = PromptConfig::load();
146
147    let preamble = config.preamble.as_deref().unwrap_or(DEFAULT_PREAMBLE);
148
149    let mut custom_examples = String::new();
150    for ex in &config.examples {
151        custom_examples.push_str(&format!("\nTask: {}\n{}\n", ex.task, ex.commands));
152    }
153
154    format!(
155        "{preamble}\n{SAFETY_RULES}\n\nOS: {os_info}\n\nExamples:\n{BUILT_IN_EXAMPLES}{custom_examples}"
156    )
157}
158
159pub fn build_user_prompt(task: &str, context: &GitContext) -> String {
160    let ctx_summary = context.summary();
161    format!("Repository state:\n{ctx_summary}\n\nTask: {task}")
162}
163
164fn get_os_info() -> String {
165    let os = std::env::consts::OS;
166    let arch = std::env::consts::ARCH;
167    format!("{os} ({arch})")
168}