n2b 0.7.1 โ 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +291 -118
- data/bin/branch-audit.sh +397 -0
- data/bin/n2b-test-github +22 -0
- data/lib/n2b/base.rb +207 -37
- data/lib/n2b/cli.rb +53 -400
- data/lib/n2b/github_client.rb +391 -0
- data/lib/n2b/jira_client.rb +236 -37
- data/lib/n2b/llm/claude.rb +1 -1
- data/lib/n2b/llm/gemini.rb +1 -1
- data/lib/n2b/llm/open_ai.rb +1 -1
- data/lib/n2b/merge_cli.rb +1771 -136
- data/lib/n2b/message_utils.rb +59 -0
- data/lib/n2b/templates/diff_system_prompt.txt +40 -20
- data/lib/n2b/templates/github_comment.txt +67 -0
- data/lib/n2b/templates/jira_comment.txt +7 -0
- data/lib/n2b/templates/merge_conflict_prompt.txt +2 -2
- data/lib/n2b/version.rb +1 -1
- metadata +8 -3
data/bin/branch-audit.sh
ADDED
@@ -0,0 +1,397 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# Branch Audit Script - Safely analyze all branches for unmerged code
|
4
|
+
# Author: Augment Agent
|
5
|
+
# Purpose: Find valuable code in branches without touching main
|
6
|
+
|
7
|
+
set -e # Exit on any error
|
8
|
+
|
9
|
+
# Colors for output
|
10
|
+
RED='\033[0;31m'
|
11
|
+
GREEN='\033[0;32m'
|
12
|
+
YELLOW='\033[1;33m'
|
13
|
+
BLUE='\033[0;34m'
|
14
|
+
NC='\033[0m' # No Color
|
15
|
+
|
16
|
+
# Configuration
|
17
|
+
MAIN_BRANCH="main"
|
18
|
+
REPORT_FILE="branch_audit_report_$(date +%Y-%m-%d_%H-%M-%S).md"
|
19
|
+
TEMP_DIR="temp_branch_audit_$$"
|
20
|
+
WORKTREE_DIR="worktree_audit_$$"
|
21
|
+
|
22
|
+
# Arrays to store results
|
23
|
+
declare -a MERGEABLE_BRANCHES=()
|
24
|
+
declare -a CONFLICTED_BRANCHES=()
|
25
|
+
declare -a EMPTY_BRANCHES=()
|
26
|
+
declare -a ERROR_BRANCHES=()
|
27
|
+
|
28
|
+
# Cleanup function
|
29
|
+
cleanup() {
|
30
|
+
echo -e "${YELLOW}๐งน Cleaning up temporary files...${NC}"
|
31
|
+
if [ -d "$TEMP_DIR" ]; then
|
32
|
+
rm -rf "$TEMP_DIR"
|
33
|
+
fi
|
34
|
+
if [ -d "$WORKTREE_DIR" ]; then
|
35
|
+
git worktree remove "$WORKTREE_DIR" 2>/dev/null || true
|
36
|
+
rm -rf "$WORKTREE_DIR" 2>/dev/null || true
|
37
|
+
fi
|
38
|
+
}
|
39
|
+
|
40
|
+
# Set up cleanup trap
|
41
|
+
trap cleanup EXIT
|
42
|
+
|
43
|
+
# Function to log with timestamp
|
44
|
+
log() {
|
45
|
+
echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $1"
|
46
|
+
}
|
47
|
+
|
48
|
+
# Function to check if branch exists
|
49
|
+
branch_exists() {
|
50
|
+
git show-ref --verify --quiet "refs/heads/$1" 2>/dev/null
|
51
|
+
}
|
52
|
+
|
53
|
+
# Function to check if remote branch exists
|
54
|
+
remote_branch_exists() {
|
55
|
+
git show-ref --verify --quiet "refs/remotes/$1" 2>/dev/null
|
56
|
+
}
|
57
|
+
|
58
|
+
# Function to get unique commits in branch vs main
|
59
|
+
get_unique_commits() {
|
60
|
+
local branch="$1"
|
61
|
+
git log --oneline "${MAIN_BRANCH}..${branch}" 2>/dev/null || echo ""
|
62
|
+
}
|
63
|
+
|
64
|
+
# Function to get file changes summary
|
65
|
+
get_file_changes() {
|
66
|
+
local branch="$1"
|
67
|
+
git diff --name-status "${MAIN_BRANCH}...${branch}" 2>/dev/null || echo ""
|
68
|
+
}
|
69
|
+
|
70
|
+
# Function to check if fast-forward merge is possible
|
71
|
+
can_fast_forward() {
|
72
|
+
local branch="$1"
|
73
|
+
local merge_base=$(git merge-base "$MAIN_BRANCH" "$branch" 2>/dev/null || echo "")
|
74
|
+
local main_commit=$(git rev-parse "$MAIN_BRANCH" 2>/dev/null || echo "")
|
75
|
+
|
76
|
+
if [ -z "$merge_base" ] || [ -z "$main_commit" ]; then
|
77
|
+
return 1
|
78
|
+
fi
|
79
|
+
|
80
|
+
# If merge base equals main commit, then it's a fast-forward
|
81
|
+
[ "$merge_base" = "$main_commit" ]
|
82
|
+
}
|
83
|
+
|
84
|
+
# Function to analyze a single branch
|
85
|
+
analyze_branch() {
|
86
|
+
local branch="$1"
|
87
|
+
local branch_type="$2" # "local" or "remote"
|
88
|
+
|
89
|
+
log "Analyzing ${branch_type} branch: $branch"
|
90
|
+
|
91
|
+
# Skip main branch and its variants
|
92
|
+
if [[ "$branch" =~ ^(main|master|origin/main|origin/master)$ ]]; then
|
93
|
+
log "Skipping main branch: $branch"
|
94
|
+
return
|
95
|
+
fi
|
96
|
+
|
97
|
+
# For remote branches, create local tracking branch if needed
|
98
|
+
local local_branch="$branch"
|
99
|
+
if [ "$branch_type" = "remote" ]; then
|
100
|
+
local_branch="${branch#origin/}"
|
101
|
+
if ! branch_exists "$local_branch"; then
|
102
|
+
log "Creating local tracking branch for $branch"
|
103
|
+
git branch "$local_branch" "$branch" 2>/dev/null || {
|
104
|
+
ERROR_BRANCHES+=("$branch (failed to create local branch)")
|
105
|
+
return
|
106
|
+
}
|
107
|
+
fi
|
108
|
+
fi
|
109
|
+
|
110
|
+
# Get unique commits
|
111
|
+
local unique_commits=$(get_unique_commits "$local_branch")
|
112
|
+
local commit_count=0
|
113
|
+
if [ -n "$unique_commits" ] && [ "$unique_commits" != "" ]; then
|
114
|
+
commit_count=$(echo "$unique_commits" | wc -l | tr -d ' ')
|
115
|
+
fi
|
116
|
+
|
117
|
+
if [ "$commit_count" -eq 0 ]; then
|
118
|
+
log "Branch $branch has no unique commits"
|
119
|
+
EMPTY_BRANCHES+=("$branch")
|
120
|
+
return
|
121
|
+
fi
|
122
|
+
|
123
|
+
# Check if fast-forward is possible
|
124
|
+
if can_fast_forward "$local_branch"; then
|
125
|
+
log "โ
Branch $branch can be fast-forward merged ($commit_count commits)"
|
126
|
+
MERGEABLE_BRANCHES+=("$branch|$commit_count|$unique_commits")
|
127
|
+
else
|
128
|
+
log "โ ๏ธ Branch $branch has conflicts or diverged ($commit_count commits)"
|
129
|
+
CONFLICTED_BRANCHES+=("$branch|$commit_count|$unique_commits")
|
130
|
+
fi
|
131
|
+
}
|
132
|
+
|
133
|
+
# Main execution starts here
|
134
|
+
main() {
|
135
|
+
echo -e "${GREEN}๐ Starting Branch Audit Analysis${NC}"
|
136
|
+
echo -e "${BLUE}Repository: $(pwd)${NC}"
|
137
|
+
echo -e "${BLUE}Main branch: $MAIN_BRANCH${NC}"
|
138
|
+
echo -e "${BLUE}Report file: $REPORT_FILE${NC}"
|
139
|
+
echo ""
|
140
|
+
|
141
|
+
# Verify we're in a git repository
|
142
|
+
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
143
|
+
echo -e "${RED}โ Error: Not in a git repository${NC}"
|
144
|
+
exit 1
|
145
|
+
fi
|
146
|
+
|
147
|
+
# Verify main branch exists
|
148
|
+
if ! git show-ref --verify --quiet "refs/heads/$MAIN_BRANCH"; then
|
149
|
+
echo -e "${RED}โ Error: Main branch '$MAIN_BRANCH' not found${NC}"
|
150
|
+
exit 1
|
151
|
+
fi
|
152
|
+
|
153
|
+
# Fetch latest from remote
|
154
|
+
log "Fetching latest changes from remote..."
|
155
|
+
git fetch --all --prune
|
156
|
+
|
157
|
+
# Create temporary directory
|
158
|
+
mkdir -p "$TEMP_DIR"
|
159
|
+
|
160
|
+
# Get current branch to restore later
|
161
|
+
CURRENT_BRANCH=$(git branch --show-current)
|
162
|
+
|
163
|
+
# Ensure we're on main branch for analysis
|
164
|
+
log "Switching to main branch for analysis..."
|
165
|
+
git checkout "$MAIN_BRANCH" >/dev/null 2>&1
|
166
|
+
|
167
|
+
# Get all local branches
|
168
|
+
log "Analyzing local branches..."
|
169
|
+
while IFS= read -r branch; do
|
170
|
+
branch=$(echo "$branch" | sed 's/^[* ] //' | xargs)
|
171
|
+
analyze_branch "$branch" "local"
|
172
|
+
done < <(git branch | grep -v "^\*.*$MAIN_BRANCH$")
|
173
|
+
|
174
|
+
# Get all remote branches
|
175
|
+
log "Analyzing remote branches..."
|
176
|
+
while IFS= read -r branch; do
|
177
|
+
branch=$(echo "$branch" | sed 's|^ remotes/||' | xargs)
|
178
|
+
# Skip if we already have this as a local branch
|
179
|
+
local_name="${branch#origin/}"
|
180
|
+
if ! branch_exists "$local_name"; then
|
181
|
+
analyze_branch "$branch" "remote"
|
182
|
+
fi
|
183
|
+
done < <(git branch -r | grep -v "HEAD\|$MAIN_BRANCH$")
|
184
|
+
|
185
|
+
# Restore original branch
|
186
|
+
if [ -n "$CURRENT_BRANCH" ] && [ "$CURRENT_BRANCH" != "$MAIN_BRANCH" ]; then
|
187
|
+
log "Restoring original branch: $CURRENT_BRANCH"
|
188
|
+
git checkout "$CURRENT_BRANCH" >/dev/null 2>&1
|
189
|
+
fi
|
190
|
+
|
191
|
+
# Generate report
|
192
|
+
generate_report
|
193
|
+
|
194
|
+
echo ""
|
195
|
+
echo -e "${GREEN}โ
Branch audit completed!${NC}"
|
196
|
+
echo -e "${BLUE}๐ Report saved to: $REPORT_FILE${NC}"
|
197
|
+
}
|
198
|
+
|
199
|
+
# Function to generate detailed report
|
200
|
+
generate_report() {
|
201
|
+
log "Generating detailed report..."
|
202
|
+
|
203
|
+
cat > "$REPORT_FILE" << EOF
|
204
|
+
# Branch Audit Report
|
205
|
+
**Generated:** $(date)
|
206
|
+
**Repository:** $(pwd)
|
207
|
+
**Main Branch:** $MAIN_BRANCH
|
208
|
+
|
209
|
+
## ๐ Executive Summary
|
210
|
+
|
211
|
+
- **Mergeable Branches (Fast-Forward):** ${#MERGEABLE_BRANCHES[@]}
|
212
|
+
- **Conflicted Branches (Manual Review):** ${#CONFLICTED_BRANCHES[@]}
|
213
|
+
- **Empty Branches (No Changes):** ${#EMPTY_BRANCHES[@]}
|
214
|
+
- **Error Branches:** ${#ERROR_BRANCHES[@]}
|
215
|
+
- **Total Analyzed:** $((${#MERGEABLE_BRANCHES[@]} + ${#CONFLICTED_BRANCHES[@]} + ${#EMPTY_BRANCHES[@]} + ${#ERROR_BRANCHES[@]}))
|
216
|
+
|
217
|
+
---
|
218
|
+
|
219
|
+
EOF
|
220
|
+
|
221
|
+
# Mergeable branches section
|
222
|
+
if [ ${#MERGEABLE_BRANCHES[@]} -gt 0 ]; then
|
223
|
+
cat >> "$REPORT_FILE" << EOF
|
224
|
+
## โ
Mergeable Branches (Fast-Forward Possible)
|
225
|
+
|
226
|
+
These branches can be safely merged into main without conflicts:
|
227
|
+
|
228
|
+
EOF
|
229
|
+
for branch_info in "${MERGEABLE_BRANCHES[@]}"; do
|
230
|
+
IFS='|' read -r branch commit_count commits <<< "$branch_info"
|
231
|
+
cat >> "$REPORT_FILE" << EOF
|
232
|
+
### ๐ฟ \`$branch\`
|
233
|
+
- **Unique Commits:** $commit_count
|
234
|
+
- **File Changes:** $(get_file_changes "$branch" | wc -l) files
|
235
|
+
- **Recent Commits:**
|
236
|
+
\`\`\`
|
237
|
+
$commits
|
238
|
+
\`\`\`
|
239
|
+
- **Files Modified:**
|
240
|
+
\`\`\`
|
241
|
+
$(get_file_changes "$branch" | head -10)
|
242
|
+
$([ $(get_file_changes "$branch" | wc -l) -gt 10 ] && echo "... and $(($(get_file_changes "$branch" | wc -l) - 10)) more files")
|
243
|
+
\`\`\`
|
244
|
+
|
245
|
+
**Recommendation:** โ
Safe to merge with \`git merge $branch\`
|
246
|
+
|
247
|
+
---
|
248
|
+
|
249
|
+
EOF
|
250
|
+
done
|
251
|
+
fi
|
252
|
+
|
253
|
+
# Conflicted branches section
|
254
|
+
if [ ${#CONFLICTED_BRANCHES[@]} -gt 0 ]; then
|
255
|
+
cat >> "$REPORT_FILE" << EOF
|
256
|
+
## โ ๏ธ Conflicted Branches (Manual Review Required)
|
257
|
+
|
258
|
+
These branches have diverged from main and may have conflicts:
|
259
|
+
|
260
|
+
EOF
|
261
|
+
for branch_info in "${CONFLICTED_BRANCHES[@]}"; do
|
262
|
+
IFS='|' read -r branch commit_count commits <<< "$branch_info"
|
263
|
+
cat >> "$REPORT_FILE" << EOF
|
264
|
+
### ๐ \`$branch\`
|
265
|
+
- **Unique Commits:** $commit_count
|
266
|
+
- **File Changes:** $(get_file_changes "$branch" | wc -l) files
|
267
|
+
- **Recent Commits:**
|
268
|
+
\`\`\`
|
269
|
+
$commits
|
270
|
+
\`\`\`
|
271
|
+
- **Files Modified:**
|
272
|
+
\`\`\`
|
273
|
+
$(get_file_changes "$branch" | head -10)
|
274
|
+
$([ $(get_file_changes "$branch" | wc -l) -gt 10 ] && echo "... and $(($(get_file_changes "$branch" | wc -l) - 10)) more files")
|
275
|
+
\`\`\`
|
276
|
+
|
277
|
+
**Recommendation:** โ ๏ธ Manual review required - check for conflicts before merging
|
278
|
+
|
279
|
+
---
|
280
|
+
|
281
|
+
EOF
|
282
|
+
done
|
283
|
+
fi
|
284
|
+
|
285
|
+
# Empty branches section
|
286
|
+
if [ ${#EMPTY_BRANCHES[@]} -gt 0 ]; then
|
287
|
+
cat >> "$REPORT_FILE" << EOF
|
288
|
+
## ๐๏ธ Empty Branches (Safe to Delete)
|
289
|
+
|
290
|
+
These branches have no unique commits compared to main:
|
291
|
+
|
292
|
+
EOF
|
293
|
+
for branch in "${EMPTY_BRANCHES[@]}"; do
|
294
|
+
cat >> "$REPORT_FILE" << EOF
|
295
|
+
- \`$branch\` - No unique changes
|
296
|
+
EOF
|
297
|
+
done
|
298
|
+
cat >> "$REPORT_FILE" << EOF
|
299
|
+
|
300
|
+
**Cleanup Commands:**
|
301
|
+
\`\`\`bash
|
302
|
+
# Delete local empty branches
|
303
|
+
EOF
|
304
|
+
for branch in "${EMPTY_BRANCHES[@]}"; do
|
305
|
+
if [[ ! "$branch" =~ ^origin/ ]]; then
|
306
|
+
echo "git branch -d $branch" >> "$REPORT_FILE"
|
307
|
+
fi
|
308
|
+
done
|
309
|
+
cat >> "$REPORT_FILE" << EOF
|
310
|
+
|
311
|
+
# Delete remote empty branches (be careful!)
|
312
|
+
EOF
|
313
|
+
for branch in "${EMPTY_BRANCHES[@]}"; do
|
314
|
+
if [[ "$branch" =~ ^origin/ ]]; then
|
315
|
+
remote_branch="${branch#origin/}"
|
316
|
+
echo "git push origin --delete $remote_branch" >> "$REPORT_FILE"
|
317
|
+
fi
|
318
|
+
done
|
319
|
+
cat >> "$REPORT_FILE" << EOF
|
320
|
+
\`\`\`
|
321
|
+
|
322
|
+
---
|
323
|
+
|
324
|
+
EOF
|
325
|
+
fi
|
326
|
+
|
327
|
+
# Error branches section
|
328
|
+
if [ ${#ERROR_BRANCHES[@]} -gt 0 ]; then
|
329
|
+
cat >> "$REPORT_FILE" << EOF
|
330
|
+
## โ Error Branches
|
331
|
+
|
332
|
+
These branches encountered errors during analysis:
|
333
|
+
|
334
|
+
EOF
|
335
|
+
for branch in "${ERROR_BRANCHES[@]}"; do
|
336
|
+
cat >> "$REPORT_FILE" << EOF
|
337
|
+
- \`$branch\`
|
338
|
+
EOF
|
339
|
+
done
|
340
|
+
cat >> "$REPORT_FILE" << EOF
|
341
|
+
|
342
|
+
**Recommendation:** Manual investigation required
|
343
|
+
|
344
|
+
---
|
345
|
+
|
346
|
+
EOF
|
347
|
+
fi
|
348
|
+
|
349
|
+
# Summary and recommendations
|
350
|
+
cat >> "$REPORT_FILE" << EOF
|
351
|
+
## ๐ฏ Recommended Actions
|
352
|
+
|
353
|
+
### Immediate Actions (Safe)
|
354
|
+
1. **Merge fast-forward branches:** ${#MERGEABLE_BRANCHES[@]} branches ready
|
355
|
+
2. **Delete empty branches:** ${#EMPTY_BRANCHES[@]} branches can be removed
|
356
|
+
3. **Review conflicted branches:** ${#CONFLICTED_BRANCHES[@]} branches need manual review
|
357
|
+
|
358
|
+
### Cleanup Script
|
359
|
+
\`\`\`bash
|
360
|
+
# Run this script to clean up empty branches
|
361
|
+
EOF
|
362
|
+
|
363
|
+
# Generate cleanup commands for empty branches
|
364
|
+
for branch in "${EMPTY_BRANCHES[@]}"; do
|
365
|
+
if [[ ! "$branch" =~ ^origin/ ]]; then
|
366
|
+
echo "git branch -d $branch" >> "$REPORT_FILE"
|
367
|
+
fi
|
368
|
+
done
|
369
|
+
|
370
|
+
cat >> "$REPORT_FILE" << EOF
|
371
|
+
\`\`\`
|
372
|
+
|
373
|
+
### Merge Script for Fast-Forward Branches
|
374
|
+
\`\`\`bash
|
375
|
+
# Run these commands to merge ready branches
|
376
|
+
EOF
|
377
|
+
|
378
|
+
# Generate merge commands for mergeable branches
|
379
|
+
for branch_info in "${MERGEABLE_BRANCHES[@]}"; do
|
380
|
+
IFS='|' read -r branch commit_count commits <<< "$branch_info"
|
381
|
+
if [[ ! "$branch" =~ ^origin/ ]]; then
|
382
|
+
echo "git merge $branch # $commit_count commits" >> "$REPORT_FILE"
|
383
|
+
fi
|
384
|
+
done
|
385
|
+
|
386
|
+
cat >> "$REPORT_FILE" << EOF
|
387
|
+
\`\`\`
|
388
|
+
|
389
|
+
---
|
390
|
+
*Report generated by branch-audit.sh on $(date)*
|
391
|
+
EOF
|
392
|
+
|
393
|
+
log "Report generated: $REPORT_FILE"
|
394
|
+
}
|
395
|
+
|
396
|
+
# Run main function
|
397
|
+
main "$@"
|
data/bin/n2b-test-github
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require_relative '../lib/n2b/github_client'
|
5
|
+
|
6
|
+
config_file = File.expand_path('~/.n2b/config.yml')
|
7
|
+
unless File.exist?(config_file)
|
8
|
+
puts "โ Config file not found: #{config_file}"
|
9
|
+
puts "Please run 'n2b --advanced-config' to set up GitHub integration first."
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
config = YAML.load_file(config_file)
|
14
|
+
|
15
|
+
begin
|
16
|
+
client = N2B::GitHubClient.new(config)
|
17
|
+
rescue => e
|
18
|
+
puts "โ #{e.message}"
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
|
22
|
+
client.test_connection
|