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- For simple "create a PR" tasks, use ONLY the current branch and real base branches from repository state. Do NOT copy branch names or PR numbers from the examples."#;
42
43const BUILT_IN_EXAMPLES: &str = r#"
44Task: undo my last commit but keep changes
45# Undo the last commit, keeping changes staged
46git reset --soft HEAD~1
47
48Task: create a branch called feature/auth from main
49# Switch to main branch
50git checkout main
51# Create and switch to the new branch
52git checkout -b feature/auth
53
54Task: rewrite all commit messages that start with "BUG-" to start with "fix:" instead
55# WARNING: This is destructive
56git filter-branch -f --msg-filter 'sed "s/^BUG-/fix: /"' -- --all
57
58Task: rewrite every commit message to use format "feat: original message"
59# WARNING: This is destructive
60git filter-branch -f --msg-filter 'sed "s/^/feat: /"' -- --all
61
62Task: rewrite commit aaa111 to "feat: new msg" and commit bbb222 to "fix: other msg"
63# WARNING: This is destructive
64git filter-branch -f --msg-filter 'case "$GIT_COMMIT" in aaa111*) echo "feat: new msg";; bbb222*) echo "fix: other msg";; *) cat;; esac' -- --all
65
66Task: change the last commit message to "fix: corrected typo"
67git commit --amend -m "fix: corrected typo"
68
69Task: squash last 3 commits into one with message "feat: combined"
70# Soft reset to undo 3 commits but keep changes staged
71git reset --soft HEAD~3
72# Create a single commit with all changes
73git commit -m "feat: combined"
74
75Task: cherry-pick commit abc123 onto current branch
76git cherry-pick abc123
77
78Task: how many commits since yesterday on v15.3
79# Count commits on v15.3 since yesterday
80git rev-list --count --since=yesterday remotes/origin/v15.3
81
82Task: who are the committers on v15.3 branch
83# List unique committers on v15.3
84git log --format='%an' remotes/origin/v15.3 --no-merges
85
86Task: show count of commits by each user
87# Count commits per author (sorted by count, descending)
88git shortlog -s -n --all --no-merges
89
90Task: how many commits did each person make on main
91# Count commits per author on main
92git shortlog -s -n main --no-merges
93
94Task: create branch feature/auth, push to remote, then delete it
95# Switch to main branch
96git checkout main
97# Create the new branch
98git checkout -b feature/auth
99# Push the branch to remote
100git push origin feature/auth
101# Switch off the branch before deleting
102git checkout main
103# Delete the local branch
104git branch -D feature/auth
105# Delete the remote branch
106git push origin --delete feature/auth
107
108Task: show remote branches
109# List all remote branches
110git branch -r
111
112Task: delete branch feature/auth
113# Switch off the branch before deleting
114git checkout main
115# Delete the local branch
116git branch -D feature/auth
117# Delete the remote branch
118git push origin --delete feature/auth
119
120Task: delete all feature branches locally and from remote
121# Prune stale remote refs first
122git fetch --prune origin
123# Delete local feature branches
124git branch -D feature/auth
125git branch -D feature/login
126# Delete remote feature branches
127git push origin --delete feature/auth
128git push origin --delete feature/login
129
130Task: create a PR to main
131(Current branch: feature/login)
132# Push the current branch to remote
133git push origin feature/login
134# Create pull request targeting main
135gh pr create --base main --head feature/login --title "feat: login" --body "Description"
136
137Task: create PRs from feature/my-change to v15.3, v15, and main and merge them
138(Open PRs show: #11 feature/my-change → main "feat: my change")
139# Push the feature branch to remote
140git push origin feature/my-change
141# PR to main already exists as #11 — skip creating
142# Create PRs only for branches that don't have one yet
143gh pr create --base v15.3 --head feature/my-change --title "feat: my change" --body "Description"
144gh pr create --base v15 --head feature/my-change --title "feat: my change" --body "Description"
145# Merge all PRs (including existing #11)
146gh pr merge 12 --merge
147gh pr merge 13 --merge
148gh pr merge 11 --merge --delete-branch
149# DONE — no more commands needed. Do NOT add git push after merging PRs."#;
150
151pub fn build_system_prompt() -> String {
152    let os_info = get_os_info();
153    let config = PromptConfig::load();
154
155    let preamble = config.preamble.as_deref().unwrap_or(DEFAULT_PREAMBLE);
156
157    let mut custom_examples = String::new();
158    for ex in &config.examples {
159        custom_examples.push_str(&format!("\nTask: {}\n{}\n", ex.task, ex.commands));
160    }
161
162    format!(
163        "{preamble}\n{SAFETY_RULES}\n\nOS: {os_info}\n\nExamples:\n{BUILT_IN_EXAMPLES}{custom_examples}"
164    )
165}
166
167pub fn build_user_prompt(task: &str, context: &GitContext) -> String {
168    let ctx_summary = context.summary();
169    format!("Repository state:\n{ctx_summary}\n\nTask: {task}")
170}
171
172fn get_os_info() -> String {
173    let os = std::env::consts::OS;
174    let arch = std::env::consts::ARCH;
175    format!("{os} ({arch})")
176}