diff --git a/plugins/git-pilot/CLAUDE.md b/plugins/git-pilot/CLAUDE.md index e40e9e0..a65bbc8 100644 --- a/plugins/git-pilot/CLAUDE.md +++ b/plugins/git-pilot/CLAUDE.md @@ -1,26 +1,55 @@ -# git-pilot — Git Workflow Guidelines - -## Branch Workflow -- Always work on a feature branch, never directly on the default branch -- Name branches using the configured pattern -- Check .claude/git-pilot.json (local) or ~/.claude/git-pilot.json (global) for current config -- Before making changes, ensure you are on the correct branch - -## Commit Guidelines -- Follow the configured commit message format -- Do NOT include Co-Authored-By, Generated with, or any AI attribution lines in commits -- Keep commit subjects under the configured max length -- Use imperative mood in commit descriptions ("add" not "added") -- One logical change per commit - -## When Finishing Work -- Commit all remaining changes before ending the session -- Check if the user wants to push to remote -- Check if the user wants to create a merge/pull request - -## Configuration -- Users can change git-pilot settings by asking in natural language -- Global config: ~/.claude/git-pilot.json -- Local config: .claude/git-pilot.json -- Local settings override global settings -- When changing config, ask whether the change should be global or project-specific +# git-pilot — Git Workflow Autopilot + +git-pilot manages the full git workflow lifecycle. You MUST follow these rules throughout every session. + +## Rule 1: Always act on hook messages + +When you receive a system message prefixed with `[git-pilot]` from any hook (SessionStart, PostToolUse, Stop), you MUST act on it using AskUserQuestion BEFORE continuing with other work. Never ignore these messages. Present clear, concise options relevant to the prompt. + +## Rule 2: Branch discipline + +- Never work directly on the default branch. If you're on the default branch when starting work, follow the `/branch` skill workflow to create a feature branch before making any changes. +- Name branches using the configured pattern (default: `{{type}}/{{description}}`, kebab-case). +- Available types: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `style`, `perf`, `build`, `ci`. +- If the user's request clearly implies a branch type and description, you can infer the branch name and propose it. Otherwise, ask. +- Check `.claude/git-pilot.json` (local) or `~/.claude/git-pilot.json` (global) for project-specific overrides. + +## Rule 3: Commit discipline + +- Follow the configured commit format (default: `{{type}}({{scope}}): {{description}}`). +- Do NOT include `Co-Authored-By`, `Generated with`, or any AI attribution lines in commits. +- Keep commit subjects under the configured max length (default: 72). +- Use imperative mood ("add" not "added"). +- One logical change per commit. When you've completed a coherent unit of work, commit it — don't accumulate unrelated changes. +- **Body policy**: Check `commit.body.required` in the effective config (defaults, global, local merged). If `false`, commits MUST be subject-line only — do NOT include a body. The only exception is breaking changes: when the subject contains `!:` and `commit.breakingChange.requireBody` is `true`, a body starting with the configured `bodyPrefix` (default: `BREAKING CHANGE: `) is required. + +## Rule 4: Push workflow + +After every successful `git commit`, check for unpushed commits. If there are any and a remote exists: +- Use AskUserQuestion to prompt the user with two options: **"Push to \/\"** and **"Skip"**. +- If they choose to push, run: `git push -u `. +- Do NOT silently skip this step or just mention it in passing. The user expects an interactive prompt. + +## Rule 5: Session end + +When finishing a session, follow the `/finish` skill workflow: +1. Commit any remaining uncommitted changes. +2. If unpushed commits exist, prompt to push (same as Rule 4). +3. If merge request creation is enabled, offer to create one using the appropriate platform CLI (`gh` for GitHub, `glab` for GitLab) or skip silently if the CLI isn't available. + +## Rule 6: Configuration + +- Users can change git-pilot settings by asking in natural language (via `/configure` skill). +- Global config: `~/.claude/git-pilot.json` +- Local config: `.claude/git-pilot.json` +- Local settings override global settings which override plugin defaults. +- When changing config, ask whether the change should be global or project-specific. + +## Skill reference + +| Skill | When to use | +|-------|-------------| +| `/branch` | Proactively when on the default branch before making changes | +| `/finish` | When the user says they're done, or at session end | +| `/summary` | When the user asks for a recap of branch work | +| `/configure` | When the user wants to change git-pilot settings | diff --git a/plugins/git-pilot/hooks/hooks.json b/plugins/git-pilot/hooks/hooks.json index 84612cd..e98a5f4 100644 --- a/plugins/git-pilot/hooks/hooks.json +++ b/plugins/git-pilot/hooks/hooks.json @@ -35,6 +35,16 @@ "timeout": 10 } ] + }, + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/scripts/post-bash.sh", + "timeout": 10 + } + ] } ], "Stop": [ diff --git a/plugins/git-pilot/scripts/post-bash.sh b/plugins/git-pilot/scripts/post-bash.sh new file mode 100755 index 0000000..93271d9 --- /dev/null +++ b/plugins/git-pilot/scripts/post-bash.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# shellcheck source=./config.sh +source "$SCRIPT_DIR/config.sh" +# shellcheck source=./git-utils.sh +source "$SCRIPT_DIR/git-utils.sh" + +input=$(cat) + +cwd=$(echo "$input" | jq -r '.cwd // empty') +command=$(echo "$input" | jq -r '.tool_input.command // empty') + +# Only care about git commit commands +if [[ -z "$command" ]] || ! echo "$command" | grep -qE 'git\s+commit'; then + echo '{"continue": true}' + exit 0 +fi + +if [[ -z "$cwd" ]]; then + echo '{"continue": true}' + exit 0 +fi + +cd "$cwd" + +if ! is_git_repo; then + echo '{"continue": true}' + exit 0 +fi + +config=$(load_config "$cwd") +push_on_finish=$(get_config "$config" '.remote.pushOnFinish' 'ask') +auto_push=$(get_config "$config" '.remote.autoPush' 'false') + +if [[ "$auto_push" == "true" ]]; then + push_on_finish="always" +fi + +# Only prompt when mode is "ask" — "always" and "never" are handled elsewhere +if [[ "$push_on_finish" != "ask" ]]; then + echo '{"continue": true}' + exit 0 +fi + +if ! has_remote; then + echo '{"continue": true}' + exit 0 +fi + +current_branch=$(get_current_branch) +remote_name=$(get_config "$config" '.remote.defaultName' 'origin') + +# Check for unpushed commits +unpushed=$(git log '@{u}..HEAD' --oneline 2>/dev/null || true) +if ! git rev-parse --abbrev-ref '@{u}' >/dev/null 2>&1; then + default_branch=$(get_default_branch "$config") + unpushed=$(git log "${default_branch}..${current_branch}" --oneline 2>/dev/null || true) +fi + +if [[ -z "$unpushed" ]]; then + echo '{"continue": true}' + exit 0 +fi + +unpushed_count=$(echo "$unpushed" | wc -l | tr -d ' ') + +# Emit push prompt — CLAUDE.md instructs Claude to act on this with AskUserQuestion +message="[git-pilot] ${unpushed_count} unpushed commit(s) on '${current_branch}'. Push command: git push -u ${remote_name} ${current_branch}" + +jq -n --arg msg "$message" '{"continue": true, "systemMessage": $msg}' diff --git a/plugins/git-pilot/scripts/pre-commit.sh b/plugins/git-pilot/scripts/pre-commit.sh index 10eb79c..895a135 100755 --- a/plugins/git-pilot/scripts/pre-commit.sh +++ b/plugins/git-pilot/scripts/pre-commit.sh @@ -577,12 +577,27 @@ if [[ "$SCOPE_REQUIRED" == "true" ]]; then fi fi -# --- Breaking change handling --- +# --- Body policy --- +BODY_REQUIRED=$(echo "$CONFIG" | jq -r '.commit.body.required // false') IS_BREAKING=false if [[ "$SUBJECT" =~ !: ]]; then IS_BREAKING=true fi +# Detect if a body is present +HAS_BODY=false +if [[ "$COMMIT_MSG" == *$'\n'* ]]; then + BODY_TEXT=$(echo "$COMMIT_MSG" | tail -n +2 | sed '/^$/d') + if [[ -n "$BODY_TEXT" ]]; then + HAS_BODY=true + fi +fi + +# Reject body when body.required is false and commit is not a breaking change +if [[ "$BODY_REQUIRED" == "false" ]] && [[ "$HAS_BODY" == "true" ]] && [[ "$IS_BREAKING" == "false" ]]; then + output_block "[git-pilot] Commit body is not allowed (commit.body.required is false). Use subject-line only unless it's a breaking change." +fi + if [[ "$IS_BREAKING" == true ]]; then REQUIRE_BODY=$(echo "$CONFIG" | jq -r '.commit.breakingChange.requireBody // true') BODY_PREFIX=$(echo "$CONFIG" | jq -r '.commit.breakingChange.bodyPrefix // "BREAKING CHANGE: "') diff --git a/plugins/git-pilot/scripts/session-start.sh b/plugins/git-pilot/scripts/session-start.sh index ff724fb..c718146 100755 --- a/plugins/git-pilot/scripts/session-start.sh +++ b/plugins/git-pilot/scripts/session-start.sh @@ -65,9 +65,9 @@ if is_git_repo; then branch_types=$(echo "$CONFIG" | jq -r '.branch.types // ["feat","fix","refactor","docs","test","chore","style","perf","build","ci"] | join(", ")') if has_uncommitted_changes; then - messages+=("[git-pilot] You are on '${default_branch}' with uncommitted changes. Ask the user if they want to: (1) stash changes and create a new branch, (2) commit changes first, or (3) continue on the current branch.") + messages+=("[git-pilot] On '${default_branch}' with uncommitted changes. Prompt the user: stash and create branch, commit first, or continue on '${default_branch}'.") else - messages+=("[git-pilot] You are on the default branch '${default_branch}'. Before making changes, create a new branch. Use the naming pattern: ${branch_pattern}. Available types: ${branch_types}. Ask the user what they're working on to determine the branch name, or infer it from their request.") + messages+=("[git-pilot] On default branch '${default_branch}'. Prompt the user to create a branch before making changes. Pattern: ${branch_pattern}, types: ${branch_types}.") fi fi fi @@ -79,7 +79,7 @@ if is_git_repo; then skip_remote=$(get_config "$CONFIG" '.remote.skipRemotePrompt' 'false') if [[ "$prompt_remote" == "true" ]] && [[ "$skip_remote" == "false" ]]; then - messages+=("[git-pilot] No git remote is configured. Ask the user if they'd like to add one (e.g., git@github.com:user/repo.git or https://github.com/user/repo.git). If they want to skip, set remoteSkipped in the session so we don't ask again.") + messages+=("[git-pilot] No git remote configured. Prompt the user to add one or skip.") fi fi fi diff --git a/plugins/git-pilot/scripts/session-stop.sh b/plugins/git-pilot/scripts/session-stop.sh index 6773222..3eab52a 100755 --- a/plugins/git-pilot/scripts/session-stop.sh +++ b/plugins/git-pilot/scripts/session-stop.sh @@ -36,50 +36,77 @@ current_branch=$(get_current_branch) default_branch=$(get_default_branch "$config") remote_name=$(get_config "$config" '.remote.defaultName' 'origin') -messages=() - # --------------------------------------------------------------------------- -# 1. Work summary +# Detect whether HEAD moved during this session # --------------------------------------------------------------------------- -commit_log=$(git log "${default_branch}..${current_branch}" --oneline --no-decorate 2>/dev/null || true) +head_at_start="" +if [[ -n "$session_id" ]]; then + state_file=$(get_state_file "$session_id") + state=$(read_state "$state_file") + head_at_start=$(echo "$state" | jq -r '.headAtStart // empty') +fi -if [[ -z "$commit_log" ]]; then - messages+=("[git-pilot] No commits on this branch relative to '${default_branch}'. Nothing to summarize.") -else - commit_count=$(echo "$commit_log" | wc -l | tr -d ' ') - diffstat=$(git diff --stat "${default_branch}...HEAD" 2>/dev/null || true) - files_changed_count=0 - if [[ -n "$diffstat" ]]; then - # Last line of diffstat is the summary line; count lines above it - files_changed_count=$(echo "$diffstat" | head -n -1 | wc -l | tr -d ' ') +current_head=$(git rev-parse HEAD 2>/dev/null || true) + +# If HEAD hasn't moved since session start, there's nothing to report +session_had_changes=false +if [[ -n "$head_at_start" ]] && [[ "$head_at_start" != "$current_head" ]]; then + session_had_changes=true +elif [[ -z "$head_at_start" ]]; then + # No state file (e.g. session_id was empty) — fall back to showing summary + # only if there are branch commits at all + commit_log=$(git log "${default_branch}..${current_branch}" --oneline --no-decorate 2>/dev/null || true) + if [[ -n "$commit_log" ]]; then + session_had_changes=true fi +fi + +messages=() - # Format commit list as markdown bullet points - commit_list="" - while IFS= read -r line; do - commit_list+="- ${line}"$'\n' - done <<< "$commit_log" - commit_list="${commit_list%$'\n'}" - - summary="[git-pilot] Work Summary: ${current_branch}" - summary+=$'\n\n'"Commits (${commit_count}):" - summary+=$'\n'"${commit_list}" - summary+=$'\n\n'"Files Changed (${files_changed_count}):" - if [[ -n "$diffstat" ]]; then - summary+=$'\n'"${diffstat}" +# --------------------------------------------------------------------------- +# 1. Work summary (only if changes were made during this session) +# --------------------------------------------------------------------------- +if [[ "$session_had_changes" == "true" ]]; then + # Show only session commits when possible, fall back to full branch diff + if [[ -n "$head_at_start" ]]; then + commit_log=$(git log "${head_at_start}..HEAD" --oneline --no-decorate 2>/dev/null || true) + diffstat=$(git diff --stat "${head_at_start}...HEAD" 2>/dev/null || true) + else + commit_log=$(git log "${default_branch}..${current_branch}" --oneline --no-decorate 2>/dev/null || true) + diffstat=$(git diff --stat "${default_branch}...HEAD" 2>/dev/null || true) fi - messages+=("$summary") + if [[ -n "$commit_log" ]]; then + commit_count=$(echo "$commit_log" | wc -l | tr -d ' ') + files_changed_count=0 + if [[ -n "$diffstat" ]]; then + files_changed_count=$(echo "$diffstat" | head -n -1 | wc -l | tr -d ' ') + fi + + commit_list="" + while IFS= read -r line; do + commit_list+="- ${line}"$'\n' + done <<< "$commit_log" + commit_list="${commit_list%$'\n'}" + + summary="[git-pilot] Session Summary: ${current_branch}" + summary+=$'\n\n'"Commits (${commit_count}):" + summary+=$'\n'"${commit_list}" + summary+=$'\n\n'"Files Changed (${files_changed_count}):" + if [[ -n "$diffstat" ]]; then + summary+=$'\n'"${diffstat}" + fi + + messages+=("$summary") + fi fi # --------------------------------------------------------------------------- -# 2. Push workflow +# 2. Push workflow (only "always" mode — "ask" is handled by /finish skill) # --------------------------------------------------------------------------- -if has_remote; then - # Determine unpushed commits +if [[ "$session_had_changes" == "true" ]] && has_remote; then unpushed=$(git log '@{u}..HEAD' --oneline 2>/dev/null || true) if ! git rev-parse --abbrev-ref '@{u}' >/dev/null 2>&1; then - # No upstream: all local commits relative to default branch are unpushed unpushed=$(git log "${default_branch}..${current_branch}" --oneline 2>/dev/null || true) fi @@ -89,32 +116,28 @@ if has_remote; then push_on_finish=$(get_config "$config" '.remote.pushOnFinish' 'ask') auto_push=$(get_config "$config" '.remote.autoPush' 'false') - # autoPush overrides pushOnFinish to "always" if [[ "$auto_push" == "true" ]]; then push_on_finish="always" fi case "$push_on_finish" in - ask) - messages+=("[git-pilot] You have ${unpushed_count} unpushed commit(s) on '${current_branch}'. Ask the user if they want to push to '${remote_name}'.") - ;; always) messages+=("[git-pilot] Pushing ${unpushed_count} commit(s) to '${remote_name}/${current_branch}'. Run: git push -u ${remote_name} ${current_branch}") ;; - never) - # No action + *) + # "ask" and "never" are silent in the stop hook. + # Use /finish skill for interactive push/MR workflows. ;; esac fi # --------------------------------------------------------------------------- - # 3. MR/PR workflow + # 3. MR/PR workflow (only "always" mode — "ask" is handled by /finish skill) # --------------------------------------------------------------------------- mr_enabled=$(get_config "$config" '.mergeRequest.enabled' 'true') mr_create_on_finish=$(get_config "$config" '.mergeRequest.createOnFinish' 'ask') - if [[ "$mr_enabled" == "true" ]] && [[ "$mr_create_on_finish" != "never" ]]; then - # Detect platform + if [[ "$mr_enabled" == "true" ]] && [[ "$mr_create_on_finish" == "always" ]]; then mr_platform=$(get_config "$config" '.mergeRequest.platform' 'auto') cli_tool="" platform="" @@ -139,110 +162,89 @@ if has_remote; then cli_tool="glab" ;; none) - # Skip ;; esac - if [[ -n "$cli_tool" ]]; then - if ! command -v "$cli_tool" >/dev/null 2>&1; then - messages+=("[git-pilot] '${cli_tool}' CLI is not installed. Install it to create merge requests automatically, or create one manually.") - else - # Build MR/PR title - title_from_branch=$(get_config "$config" '.mergeRequest.titleFromBranch' 'true') - mr_title="" - if [[ "$title_from_branch" == "true" ]]; then - # Parse branch name: extract description part after last / - # Convert separators to spaces, capitalize first letter - branch_pattern=$(get_config "$config" '.branch.pattern' '{{type}}/{{description}}') - # Simple parsing: split on / and reconstruct - branch_type=$(echo "$current_branch" | cut -d'/' -f1) - branch_rest=$(echo "$current_branch" | cut -d'/' -f2-) - - if [[ "$branch_pattern" == *"{{scope}}"* ]]; then - # Pattern has scope: type/scope/description - branch_scope=$(echo "$branch_rest" | cut -d'/' -f1) - branch_desc=$(echo "$branch_rest" | cut -d'/' -f2-) - branch_desc=$(echo "$branch_desc" | tr '-' ' ' | tr '_' ' ') - mr_title="${branch_type}(${branch_scope}): ${branch_desc}" - else - branch_desc=$(echo "$branch_rest" | tr '-' ' ' | tr '_' ' ') - mr_title="${branch_type}: ${branch_desc}" - fi - else - mr_title=$(git log -1 --format=%s 2>/dev/null || echo "$current_branch") - fi - - # Build body - body_template=$(echo "$config" | jq -r '.mergeRequest.bodyTemplate // empty') - mr_body="" - if [[ -n "$body_template" ]]; then - # Replace placeholders in template - commits_text=$(git log "${default_branch}..${current_branch}" --oneline --no-decorate 2>/dev/null || true) - files_text=$(git diff --stat "${default_branch}...HEAD" 2>/dev/null || true) - summary_text="" - while IFS= read -r line; do - msg="${line#* }" - summary_text+="- ${msg}"$'\n' - done <<< "$commits_text" - summary_text="${summary_text%$'\n'}" - - mr_body="$body_template" - mr_body="${mr_body//\{\{summary\}\}/$summary_text}" - mr_body="${mr_body//\{\{commits\}\}/$commits_text}" - mr_body="${mr_body//\{\{files\}\}/$files_text}" + if [[ -n "$cli_tool" ]] && command -v "$cli_tool" >/dev/null 2>&1; then + title_from_branch=$(get_config "$config" '.mergeRequest.titleFromBranch' 'true') + mr_title="" + if [[ "$title_from_branch" == "true" ]]; then + branch_pattern=$(get_config "$config" '.branch.pattern' '{{type}}/{{description}}') + branch_type=$(echo "$current_branch" | cut -d'/' -f1) + branch_rest=$(echo "$current_branch" | cut -d'/' -f2-) + + if [[ "$branch_pattern" == *"{{scope}}"* ]]; then + branch_scope=$(echo "$branch_rest" | cut -d'/' -f1) + branch_desc=$(echo "$branch_rest" | cut -d'/' -f2-) + branch_desc=$(echo "$branch_desc" | tr '-' ' ' | tr '_' ' ') + mr_title="${branch_type}(${branch_scope}): ${branch_desc}" else - # Generate default body - commits_text=$(git log "${default_branch}..${current_branch}" --oneline --no-decorate 2>/dev/null || true) - files_text=$(git diff --stat "${default_branch}...HEAD" 2>/dev/null || true) - summary_text="" - while IFS= read -r line; do - msg="${line#* }" - summary_text+="- ${msg}"$'\n' - done <<< "$commits_text" - summary_text="${summary_text%$'\n'}" - - mr_body="## Summary"$'\n'"${summary_text}"$'\n\n'"## Commits"$'\n'"${commits_text}"$'\n\n'"## Files Changed"$'\n'"${files_text}" + branch_desc=$(echo "$branch_rest" | tr '-' ' ' | tr '_' ' ') + mr_title="${branch_type}: ${branch_desc}" fi + else + mr_title=$(git log -1 --format=%s 2>/dev/null || echo "$current_branch") + fi - # Build flags - mr_flags="" - mr_draft=$(get_config "$config" '.mergeRequest.draft' 'false') - if [[ "$mr_draft" == "true" ]]; then - mr_flags+=" --draft" - fi + body_template=$(echo "$config" | jq -r '.mergeRequest.bodyTemplate // empty') + mr_body="" + if [[ -n "$body_template" ]]; then + commits_text=$(git log "${default_branch}..${current_branch}" --oneline --no-decorate 2>/dev/null || true) + files_text=$(git diff --stat "${default_branch}...HEAD" 2>/dev/null || true) + summary_text="" + while IFS= read -r line; do + msg="${line#* }" + summary_text+="- ${msg}"$'\n' + done <<< "$commits_text" + summary_text="${summary_text%$'\n'}" + + mr_body="$body_template" + mr_body="${mr_body//\{\{summary\}\}/$summary_text}" + mr_body="${mr_body//\{\{commits\}\}/$commits_text}" + mr_body="${mr_body//\{\{files\}\}/$files_text}" + else + commits_text=$(git log "${default_branch}..${current_branch}" --oneline --no-decorate 2>/dev/null || true) + files_text=$(git diff --stat "${default_branch}...HEAD" 2>/dev/null || true) + summary_text="" + while IFS= read -r line; do + msg="${line#* }" + summary_text+="- ${msg}"$'\n' + done <<< "$commits_text" + summary_text="${summary_text%$'\n'}" + + mr_body="## Summary"$'\n'"${summary_text}"$'\n\n'"## Commits"$'\n'"${commits_text}"$'\n\n'"## Files Changed"$'\n'"${files_text}" + fi - mr_labels=$(echo "$config" | jq -r '.mergeRequest.labels // [] | .[]' 2>/dev/null) - while IFS= read -r label; do - if [[ -n "$label" ]]; then - mr_flags+=" --label \"${label}\"" - fi - done <<< "$mr_labels" + mr_flags="" + mr_draft=$(get_config "$config" '.mergeRequest.draft' 'false') + if [[ "$mr_draft" == "true" ]]; then + mr_flags+=" --draft" + fi - mr_assign=$(get_config "$config" '.mergeRequest.assignToSelf' 'true') - if [[ "$mr_assign" == "true" ]]; then - mr_flags+=" --assignee @me" + mr_labels=$(echo "$config" | jq -r '.mergeRequest.labels // [] | .[]' 2>/dev/null) + while IFS= read -r label; do + if [[ -n "$label" ]]; then + mr_flags+=" --label \"${label}\"" fi + done <<< "$mr_labels" - # Build the full command - mr_cmd="" - if [[ "$platform" == "github" ]]; then - mr_cmd="gh pr create --title \"${mr_title}\" --body \"${mr_body}\" --base ${default_branch}${mr_flags}" - elif [[ "$platform" == "gitlab" ]]; then - mr_cmd="glab mr create --title \"${mr_title}\" --description \"${mr_body}\" --target-branch ${default_branch}${mr_flags}" - fi + mr_assign=$(get_config "$config" '.mergeRequest.assignToSelf' 'true') + if [[ "$mr_assign" == "true" ]]; then + mr_flags+=" --assignee @me" + fi - if [[ -n "$mr_cmd" ]]; then - case "$mr_create_on_finish" in - ask) - messages+=("[git-pilot] Would you like to create a merge/pull request? Ask the user. Command: ${mr_cmd}") - ;; - always) - messages+=("[git-pilot] Creating merge/pull request. Run: ${mr_cmd}") - ;; - esac - fi + mr_cmd="" + if [[ "$platform" == "github" ]]; then + mr_cmd="gh pr create --title \"${mr_title}\" --body \"${mr_body}\" --base ${default_branch}${mr_flags}" + elif [[ "$platform" == "gitlab" ]]; then + mr_cmd="glab mr create --title \"${mr_title}\" --description \"${mr_body}\" --target-branch ${default_branch}${mr_flags}" + fi + + if [[ -n "$mr_cmd" ]]; then + messages+=("[git-pilot] Creating merge/pull request. Run: ${mr_cmd}") fi fi + # If CLI tool is missing, stay silent — user can use /finish or create MR manually fi fi @@ -258,10 +260,8 @@ fi # 5. Output final JSON # --------------------------------------------------------------------------- if [[ ${#messages[@]} -eq 0 ]]; then - # No messages to report echo '{"continue": true}' else - # Join messages with double newline separator full_message="" for i in "${!messages[@]}"; do if [[ $i -gt 0 ]]; then diff --git a/plugins/git-pilot/scripts/state.sh b/plugins/git-pilot/scripts/state.sh index fccc24f..899156a 100644 --- a/plugins/git-pilot/scripts/state.sh +++ b/plugins/git-pilot/scripts/state.sh @@ -49,17 +49,25 @@ init_state() { local state_file state_file=$(get_state_file "$session_id") + # Capture HEAD commit at session start for change detection + local head_at_start="" + if command -v git >/dev/null 2>&1 && git rev-parse HEAD >/dev/null 2>&1; then + head_at_start=$(git rev-parse HEAD 2>/dev/null || true) + fi + local content content=$(jq -n \ --arg sid "$session_id" \ --arg start "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ --arg wb "$working_branch" \ --arg pb "$previous_branch" \ + --arg head "$head_at_start" \ '{ sessionId: $sid, startTime: $start, workingBranch: $wb, previousBranch: $pb, + headAtStart: $head, changeCount: 0, lastCommitAt: null, modifiedFiles: [],