forge-workflow 0.0.1
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.
- package/.claude/commands/dev.md +314 -0
- package/.claude/commands/plan.md +389 -0
- package/.claude/commands/premerge.md +179 -0
- package/.claude/commands/research.md +42 -0
- package/.claude/commands/review.md +442 -0
- package/.claude/commands/rollback.md +721 -0
- package/.claude/commands/ship.md +134 -0
- package/.claude/commands/sonarcloud.md +152 -0
- package/.claude/commands/status.md +77 -0
- package/.claude/commands/validate.md +237 -0
- package/.claude/commands/verify.md +221 -0
- package/.claude/rules/greptile-review-process.md +285 -0
- package/.claude/rules/workflow.md +105 -0
- package/.claude/scripts/greptile-resolve.sh +526 -0
- package/.claude/scripts/load-env.sh +32 -0
- package/.forge/hooks/check-tdd.js +240 -0
- package/.github/PLUGIN_TEMPLATE.json +32 -0
- package/.mcp.json.example +12 -0
- package/AGENTS.md +169 -0
- package/CLAUDE.md +99 -0
- package/LICENSE +21 -0
- package/README.md +414 -0
- package/bin/forge-cmd.js +313 -0
- package/bin/forge-validate.js +303 -0
- package/bin/forge.js +4228 -0
- package/docs/AGENT_INSTALL_PROMPT.md +342 -0
- package/docs/ENHANCED_ONBOARDING.md +602 -0
- package/docs/EXAMPLES.md +482 -0
- package/docs/GREPTILE_SETUP.md +400 -0
- package/docs/MANUAL_REVIEW_GUIDE.md +106 -0
- package/docs/ROADMAP.md +359 -0
- package/docs/SETUP.md +632 -0
- package/docs/TOOLCHAIN.md +849 -0
- package/docs/VALIDATION.md +363 -0
- package/docs/WORKFLOW.md +400 -0
- package/docs/planning/PROGRESS.md +396 -0
- package/docs/plans/.gitkeep +0 -0
- package/docs/plans/2026-02-27-forge-test-suite-v2-decisions.md +21 -0
- package/docs/plans/2026-02-27-forge-test-suite-v2-design.md +362 -0
- package/docs/plans/2026-02-27-forge-test-suite-v2-tasks.md +343 -0
- package/docs/plans/2026-03-02-superpowers-gaps-decisions.md +26 -0
- package/docs/plans/2026-03-02-superpowers-gaps-design.md +239 -0
- package/docs/plans/2026-03-02-superpowers-gaps-tasks.md +260 -0
- package/docs/plans/2026-03-04-agent-command-parity-design.md +163 -0
- package/docs/plans/2026-03-04-verify-worktree-cleanup-decisions.md +7 -0
- package/docs/plans/2026-03-04-verify-worktree-cleanup-design.md +165 -0
- package/docs/plans/2026-03-05-forge-uto-decisions.md +6 -0
- package/docs/plans/2026-03-05-forge-uto-design.md +116 -0
- package/docs/plans/2026-03-05-forge-uto-tasks.md +244 -0
- package/docs/plans/2026-03-10-command-creator-and-eval-decisions.md +52 -0
- package/docs/plans/2026-03-10-command-creator-and-eval-design.md +350 -0
- package/docs/plans/2026-03-10-command-creator-and-eval-tasks.md +426 -0
- package/docs/plans/2026-03-10-stale-workflow-refs-decisions.md +8 -0
- package/docs/plans/2026-03-10-stale-workflow-refs-design.md +80 -0
- package/docs/plans/2026-03-10-stale-workflow-refs-tasks.md +90 -0
- package/docs/plans/2026-03-14-beads-plan-context-decisions.md +9 -0
- package/docs/plans/2026-03-14-beads-plan-context-design.md +171 -0
- package/docs/plans/2026-03-14-beads-plan-context-tasks.md +160 -0
- package/docs/plans/2026-03-14-skill-eval-loop-decisions.md +33 -0
- package/docs/plans/2026-03-14-skill-eval-loop-design.md +118 -0
- package/docs/plans/2026-03-14-skill-eval-loop-results.md +78 -0
- package/docs/plans/2026-03-14-skill-eval-loop-tasks.md +160 -0
- package/docs/plans/2026-03-15-agent-command-parity-v2-decisions.md +11 -0
- package/docs/plans/2026-03-15-agent-command-parity-v2-design.md +145 -0
- package/docs/plans/2026-03-15-agent-command-parity-v2-tasks.md +211 -0
- package/docs/research/TEMPLATE.md +292 -0
- package/docs/research/advanced-testing.md +297 -0
- package/docs/research/agent-permissions.md +167 -0
- package/docs/research/dependency-chain.md +328 -0
- package/docs/research/forge-workflow-v2.md +550 -0
- package/docs/research/plugin-architecture.md +772 -0
- package/docs/research/pr4-cli-automation.md +326 -0
- package/docs/research/premerge-verify-restructure.md +205 -0
- package/docs/research/skills-restructure.md +508 -0
- package/docs/research/sonarcloud-perfection-plan.md +166 -0
- package/docs/research/sonarcloud-quality-gate.md +184 -0
- package/docs/research/superpowers-integration.md +403 -0
- package/docs/research/superpowers.md +319 -0
- package/docs/research/test-environment.md +519 -0
- package/install.sh +1062 -0
- package/lefthook.yml +39 -0
- package/lib/agents/README.md +198 -0
- package/lib/agents/claude.plugin.json +28 -0
- package/lib/agents/cline.plugin.json +22 -0
- package/lib/agents/codex.plugin.json +19 -0
- package/lib/agents/copilot.plugin.json +24 -0
- package/lib/agents/cursor.plugin.json +25 -0
- package/lib/agents/kilocode.plugin.json +22 -0
- package/lib/agents/opencode.plugin.json +20 -0
- package/lib/agents/roo.plugin.json +23 -0
- package/lib/agents-config.js +2112 -0
- package/lib/commands/dev.js +513 -0
- package/lib/commands/plan.js +696 -0
- package/lib/commands/recommend.js +119 -0
- package/lib/commands/ship.js +377 -0
- package/lib/commands/status.js +378 -0
- package/lib/commands/validate.js +602 -0
- package/lib/context-merge.js +359 -0
- package/lib/plugin-catalog.js +360 -0
- package/lib/plugin-manager.js +166 -0
- package/lib/plugin-recommender.js +141 -0
- package/lib/project-discovery.js +491 -0
- package/lib/setup.js +118 -0
- package/lib/workflow-profiles.js +203 -0
- package/package.json +115 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Greptile Review Thread Resolution Tool
|
|
3
|
+
# Uses GitHub GraphQL API to systematically resolve review threads
|
|
4
|
+
# Uses GitHub REST API to reply to review comments
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
# Colors for output (defined before use)
|
|
9
|
+
RED='\033[0;31m'
|
|
10
|
+
GREEN='\033[0;32m'
|
|
11
|
+
YELLOW='\033[1;33m'
|
|
12
|
+
BLUE='\033[0;34m'
|
|
13
|
+
NC='\033[0m' # No Color
|
|
14
|
+
|
|
15
|
+
# Auto-detect repository from git config
|
|
16
|
+
detect_repo() {
|
|
17
|
+
local repo_info
|
|
18
|
+
repo_info=$(gh repo view --json owner,name 2>/dev/null) || {
|
|
19
|
+
echo -e "${RED}Error: Not in a git repository or gh CLI not authenticated${NC}"
|
|
20
|
+
echo "Run: gh auth login"
|
|
21
|
+
exit 1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
OWNER=$(echo "$repo_info" | jq -r '.owner.login')
|
|
25
|
+
REPO=$(echo "$repo_info" | jq -r '.name')
|
|
26
|
+
|
|
27
|
+
if [ -z "$OWNER" ] || [ -z "$REPO" ]; then
|
|
28
|
+
echo -e "${RED}Error: Could not detect repository${NC}"
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Initialize repo detection
|
|
34
|
+
detect_repo
|
|
35
|
+
|
|
36
|
+
# Check for jq dependency
|
|
37
|
+
# Note: exit 1 is intentional - script cannot function without jq for JSON parsing
|
|
38
|
+
check_jq() {
|
|
39
|
+
if ! command -v jq &> /dev/null; then
|
|
40
|
+
echo -e "${RED}Error: jq is required but not installed${NC}"
|
|
41
|
+
echo ""
|
|
42
|
+
echo "This script requires jq for JSON parsing and cannot continue without it."
|
|
43
|
+
echo ""
|
|
44
|
+
echo "Please install jq:"
|
|
45
|
+
echo " • Ubuntu/Debian: sudo apt-get install jq"
|
|
46
|
+
echo " • macOS: brew install jq"
|
|
47
|
+
echo " • Windows: choco install jq"
|
|
48
|
+
echo " • Or download from: https://jqlang.github.io/jq/download/"
|
|
49
|
+
echo ""
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
usage() {
|
|
55
|
+
cat <<EOF
|
|
56
|
+
Usage: $(basename "$0") <command> <pr-number> [options]
|
|
57
|
+
|
|
58
|
+
Commands:
|
|
59
|
+
list <pr-number> List all review threads
|
|
60
|
+
list <pr-number> --unresolved List only unresolved threads
|
|
61
|
+
list-all <pr-number> List inline threads + direct PR comments from Greptile
|
|
62
|
+
reply <pr-number> <comment-id> <message> Reply to a review comment
|
|
63
|
+
resolve <pr-number> <thread-id> Resolve a specific thread
|
|
64
|
+
reply-and-resolve <pr-number> <comment-id> <thread-id> <message> Reply and resolve in one step
|
|
65
|
+
resolve-all <pr-number> Resolve ALL unresolved Greptile threads
|
|
66
|
+
stats <pr-number> Show resolution statistics
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
$(basename "$0") list 24
|
|
70
|
+
$(basename "$0") list 24 --unresolved
|
|
71
|
+
$(basename "$0") list-all 24
|
|
72
|
+
$(basename "$0") reply 24 2787717459 "Fixed in commit abc123"
|
|
73
|
+
$(basename "$0") resolve 24 PRRT_kwDORErEU85tuh6I
|
|
74
|
+
$(basename "$0") reply-and-resolve 24 2787717459 PRRT_kwDORErEU85tuh6I "Fixed in commit abc123"
|
|
75
|
+
$(basename "$0") resolve-all 24
|
|
76
|
+
$(basename "$0") stats 24
|
|
77
|
+
|
|
78
|
+
Comment IDs (databaseId) and Thread IDs are shown in the list command output.
|
|
79
|
+
EOF
|
|
80
|
+
exit 1
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Fetch all review threads for a PR (with pagination)
|
|
84
|
+
fetch_threads() {
|
|
85
|
+
local pr_number="$1"
|
|
86
|
+
local all_threads="[]"
|
|
87
|
+
local has_next_page=true
|
|
88
|
+
local end_cursor="null"
|
|
89
|
+
|
|
90
|
+
while [ "$has_next_page" = "true" ]; do
|
|
91
|
+
local response
|
|
92
|
+
if [ "$end_cursor" != "null" ]; then
|
|
93
|
+
response=$(gh api graphql -f owner="$OWNER" -f repo="$REPO" -F prNumber="$pr_number" -f after="$end_cursor" -f query='
|
|
94
|
+
query($owner: String!, $repo: String!, $prNumber: Int!, $after: String) {
|
|
95
|
+
repository(owner: $owner, name: $repo) {
|
|
96
|
+
pullRequest(number: $prNumber) {
|
|
97
|
+
reviewThreads(first: 100, after: $after) {
|
|
98
|
+
pageInfo {
|
|
99
|
+
hasNextPage
|
|
100
|
+
endCursor
|
|
101
|
+
}
|
|
102
|
+
nodes {
|
|
103
|
+
id
|
|
104
|
+
isResolved
|
|
105
|
+
comments(first: 1) {
|
|
106
|
+
nodes {
|
|
107
|
+
databaseId
|
|
108
|
+
author { login }
|
|
109
|
+
body
|
|
110
|
+
path
|
|
111
|
+
line
|
|
112
|
+
createdAt
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
')
|
|
121
|
+
else
|
|
122
|
+
response=$(gh api graphql -f owner="$OWNER" -f repo="$REPO" -F prNumber="$pr_number" -f query='
|
|
123
|
+
query($owner: String!, $repo: String!, $prNumber: Int!) {
|
|
124
|
+
repository(owner: $owner, name: $repo) {
|
|
125
|
+
pullRequest(number: $prNumber) {
|
|
126
|
+
reviewThreads(first: 100) {
|
|
127
|
+
pageInfo {
|
|
128
|
+
hasNextPage
|
|
129
|
+
endCursor
|
|
130
|
+
}
|
|
131
|
+
nodes {
|
|
132
|
+
id
|
|
133
|
+
isResolved
|
|
134
|
+
comments(first: 1) {
|
|
135
|
+
nodes {
|
|
136
|
+
databaseId
|
|
137
|
+
author { login }
|
|
138
|
+
body
|
|
139
|
+
path
|
|
140
|
+
line
|
|
141
|
+
createdAt
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
')
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
# Extract pagination info
|
|
153
|
+
has_next_page=$(echo "$response" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage')
|
|
154
|
+
end_cursor=$(echo "$response" | jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor')
|
|
155
|
+
|
|
156
|
+
# Merge threads
|
|
157
|
+
local page_threads
|
|
158
|
+
page_threads=$(echo "$response" | jq -r '.data.repository.pullRequest.reviewThreads.nodes')
|
|
159
|
+
all_threads=$(echo "$all_threads" | jq --argjson new "$page_threads" '. + $new')
|
|
160
|
+
done
|
|
161
|
+
|
|
162
|
+
# Return in original format
|
|
163
|
+
echo "{\"data\":{\"repository\":{\"pullRequest\":{\"reviewThreads\":{\"nodes\":$all_threads}}}}}"
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# Reply to a review comment (REST API)
|
|
167
|
+
reply_to_comment() {
|
|
168
|
+
local pr_number="$1"
|
|
169
|
+
local comment_id="$2"
|
|
170
|
+
local message="$3"
|
|
171
|
+
|
|
172
|
+
echo -e "${BLUE}Replying to comment: $comment_id${NC}"
|
|
173
|
+
|
|
174
|
+
gh api "repos/$OWNER/$REPO/pulls/$pr_number/comments/$comment_id/replies" \
|
|
175
|
+
-f body="$message" || {
|
|
176
|
+
echo -e "${RED}❌ Failed to reply to comment $comment_id${NC}"
|
|
177
|
+
return 1
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
echo -e "${GREEN}✅ Reply posted successfully${NC}"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# Resolve a specific thread (GraphQL)
|
|
184
|
+
resolve_thread() {
|
|
185
|
+
local thread_id="$1"
|
|
186
|
+
|
|
187
|
+
echo -e "${BLUE}Resolving thread: $thread_id${NC}"
|
|
188
|
+
|
|
189
|
+
gh api graphql -f threadId="$thread_id" -f query='
|
|
190
|
+
mutation($threadId: ID!) {
|
|
191
|
+
resolveReviewThread(input: { threadId: $threadId }) {
|
|
192
|
+
thread {
|
|
193
|
+
id
|
|
194
|
+
isResolved
|
|
195
|
+
resolvedBy { login }
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
' || {
|
|
200
|
+
echo -e "${RED}❌ Failed to resolve thread $thread_id${NC}"
|
|
201
|
+
return 1
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
echo -e "${GREEN}✅ Thread resolved successfully${NC}"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# List threads command
|
|
208
|
+
cmd_list() {
|
|
209
|
+
local pr_number="$1"
|
|
210
|
+
local unresolved_only="${2:-}"
|
|
211
|
+
|
|
212
|
+
echo -e "${BLUE}Fetching review threads for PR #$pr_number...${NC}\n"
|
|
213
|
+
|
|
214
|
+
local response
|
|
215
|
+
response=$(fetch_threads "$pr_number")
|
|
216
|
+
|
|
217
|
+
# Parse with jq
|
|
218
|
+
local threads
|
|
219
|
+
threads=$(echo "$response" | jq -r '.data.repository.pullRequest.reviewThreads.nodes')
|
|
220
|
+
|
|
221
|
+
local total_count
|
|
222
|
+
total_count=$(echo "$threads" | jq 'length')
|
|
223
|
+
|
|
224
|
+
local resolved_count=0
|
|
225
|
+
local unresolved_count=0
|
|
226
|
+
local greptile_count=0
|
|
227
|
+
|
|
228
|
+
while IFS=$'\t' read -r thread_id is_resolved author body_b64 path line comment_id; do
|
|
229
|
+
# Values already extracted by jq (body is base64-encoded to handle newlines)
|
|
230
|
+
local body
|
|
231
|
+
body=$(echo "$body_b64" | base64 -d 2>/dev/null || echo "")
|
|
232
|
+
|
|
233
|
+
# Count
|
|
234
|
+
if [ "$is_resolved" = "true" ]; then
|
|
235
|
+
resolved_count=$((resolved_count + 1))
|
|
236
|
+
else
|
|
237
|
+
unresolved_count=$((unresolved_count + 1))
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
if [[ "$author" == "greptile-apps"* ]]; then
|
|
241
|
+
greptile_count=$((greptile_count + 1))
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
# Filter if needed
|
|
245
|
+
if [ "$unresolved_only" = "--unresolved" ] && [ "$is_resolved" = "true" ]; then
|
|
246
|
+
continue
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
# Display
|
|
250
|
+
if [ "$is_resolved" = "true" ]; then
|
|
251
|
+
echo -e "${GREEN}✓ RESOLVED${NC} | $path:$line"
|
|
252
|
+
else
|
|
253
|
+
echo -e "${RED}✗ UNRESOLVED${NC} | $path:$line"
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
echo -e " ${BLUE}Thread ID:${NC} $thread_id"
|
|
257
|
+
echo -e " ${BLUE}Comment ID:${NC} $comment_id"
|
|
258
|
+
echo -e " ${BLUE}Author:${NC} $author"
|
|
259
|
+
|
|
260
|
+
# Extract title from body (first line)
|
|
261
|
+
local title
|
|
262
|
+
title=$(echo "$body" | head -n 1 | sed 's/\*\*//g')
|
|
263
|
+
echo -e " ${BLUE}Issue:${NC} $title"
|
|
264
|
+
echo ""
|
|
265
|
+
done < <(echo "$threads" | jq -r '.[] | [.id, .isResolved, (.comments.nodes[0].author.login // "unknown"), ((.comments.nodes[0].body // "") | @base64), (.comments.nodes[0].path // "unknown"), (.comments.nodes[0].line // "?"), (.comments.nodes[0].databaseId // "?")] | join("\t")')
|
|
266
|
+
|
|
267
|
+
echo -e "\n${YELLOW}═══════════════════════════════════════${NC}"
|
|
268
|
+
echo -e "${BLUE}Statistics for PR #$pr_number:${NC}"
|
|
269
|
+
echo -e " Total threads: $total_count"
|
|
270
|
+
echo -e " ${GREEN}Resolved: $resolved_count${NC}"
|
|
271
|
+
echo -e " ${RED}Unresolved: $unresolved_count${NC}"
|
|
272
|
+
echo -e " Greptile threads: $greptile_count"
|
|
273
|
+
echo -e "${YELLOW}═══════════════════════════════════════${NC}"
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
# Reply to comment command
|
|
277
|
+
cmd_reply() {
|
|
278
|
+
local pr_number="$1"
|
|
279
|
+
local comment_id="$2"
|
|
280
|
+
local message="${3:-}"
|
|
281
|
+
|
|
282
|
+
if [ -z "$comment_id" ]; then
|
|
283
|
+
echo -e "${RED}Error: Comment ID required${NC}"
|
|
284
|
+
usage
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
if [ -z "$message" ]; then
|
|
288
|
+
echo -e "${RED}Error: Message required${NC}"
|
|
289
|
+
usage
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
echo -e "${BLUE}Replying to comment for PR #$pr_number...${NC}\n"
|
|
293
|
+
|
|
294
|
+
reply_to_comment "$pr_number" "$comment_id" "$message"
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
# Resolve specific thread
|
|
298
|
+
cmd_resolve() {
|
|
299
|
+
local pr_number="$1"
|
|
300
|
+
local thread_id="$2"
|
|
301
|
+
|
|
302
|
+
if [ -z "$thread_id" ]; then
|
|
303
|
+
echo -e "${RED}Error: Thread ID required${NC}"
|
|
304
|
+
usage
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
echo -e "${BLUE}Resolving thread for PR #$pr_number...${NC}\n"
|
|
308
|
+
|
|
309
|
+
resolve_thread "$thread_id"
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
# Reply and resolve in one step
|
|
313
|
+
cmd_reply_and_resolve() {
|
|
314
|
+
local pr_number="$1"
|
|
315
|
+
local comment_id="$2"
|
|
316
|
+
local thread_id="$3"
|
|
317
|
+
local message="${4:-}"
|
|
318
|
+
|
|
319
|
+
if [ -z "$comment_id" ] || [ -z "$thread_id" ]; then
|
|
320
|
+
echo -e "${RED}Error: Both comment ID and thread ID required${NC}"
|
|
321
|
+
usage
|
|
322
|
+
fi
|
|
323
|
+
|
|
324
|
+
if [ -z "$message" ]; then
|
|
325
|
+
echo -e "${RED}Error: Message required${NC}"
|
|
326
|
+
usage
|
|
327
|
+
fi
|
|
328
|
+
|
|
329
|
+
echo -e "${BLUE}Replying and resolving for PR #$pr_number...${NC}\n"
|
|
330
|
+
|
|
331
|
+
# Step 1: Reply
|
|
332
|
+
reply_to_comment "$pr_number" "$comment_id" "$message" || return 1
|
|
333
|
+
|
|
334
|
+
# Step 2: Resolve
|
|
335
|
+
resolve_thread "$thread_id" || return 1
|
|
336
|
+
|
|
337
|
+
echo -e "\n${GREEN}✓ Successfully replied and resolved!${NC}"
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
# Resolve all unresolved Greptile threads
|
|
341
|
+
cmd_resolve_all() {
|
|
342
|
+
local pr_number="$1"
|
|
343
|
+
|
|
344
|
+
echo -e "${YELLOW}⚠️ WARNING: This will resolve ALL unresolved Greptile threads for PR #$pr_number${NC}"
|
|
345
|
+
echo -e "${YELLOW}Make sure you have fixed all issues AND replied to each thread before running this!${NC}\n"
|
|
346
|
+
|
|
347
|
+
read -p "Continue? (y/n) " -n 1 -r
|
|
348
|
+
echo
|
|
349
|
+
|
|
350
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
351
|
+
echo "Cancelled."
|
|
352
|
+
exit 0
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
echo -e "\n${BLUE}Fetching unresolved Greptile threads...${NC}\n"
|
|
356
|
+
|
|
357
|
+
local response
|
|
358
|
+
response=$(fetch_threads "$pr_number")
|
|
359
|
+
|
|
360
|
+
local threads
|
|
361
|
+
threads=$(echo "$response" | jq -r '.data.repository.pullRequest.reviewThreads.nodes')
|
|
362
|
+
|
|
363
|
+
local resolved_count=0
|
|
364
|
+
local failed_count=0
|
|
365
|
+
|
|
366
|
+
while IFS= read -r thread; do
|
|
367
|
+
local thread_id
|
|
368
|
+
thread_id=$(echo "$thread" | jq -r '.id')
|
|
369
|
+
|
|
370
|
+
local is_resolved
|
|
371
|
+
is_resolved=$(echo "$thread" | jq -r '.isResolved')
|
|
372
|
+
|
|
373
|
+
local author
|
|
374
|
+
author=$(echo "$thread" | jq -r '.comments.nodes[0].author.login // "unknown"')
|
|
375
|
+
|
|
376
|
+
# Skip if already resolved
|
|
377
|
+
if [ "$is_resolved" = "true" ]; then
|
|
378
|
+
continue
|
|
379
|
+
fi
|
|
380
|
+
|
|
381
|
+
# Skip if not Greptile
|
|
382
|
+
if [[ "$author" != "greptile-apps"* ]]; then
|
|
383
|
+
continue
|
|
384
|
+
fi
|
|
385
|
+
|
|
386
|
+
# Resolve
|
|
387
|
+
echo -e "${BLUE}Resolving thread $thread_id...${NC}"
|
|
388
|
+
if resolve_thread "$thread_id" > /dev/null 2>&1; then
|
|
389
|
+
resolved_count=$((resolved_count + 1))
|
|
390
|
+
echo -e "${GREEN}✓ Resolved${NC}"
|
|
391
|
+
else
|
|
392
|
+
failed_count=$((failed_count + 1))
|
|
393
|
+
echo -e "${RED}✗ Failed${NC}"
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
# Small delay to avoid rate limiting
|
|
397
|
+
sleep 0.5
|
|
398
|
+
done < <(echo "$threads" | jq -r '.[] | @json')
|
|
399
|
+
|
|
400
|
+
echo -e "\n${YELLOW}═══════════════════════════════════════${NC}"
|
|
401
|
+
echo -e "${GREEN}✓ Resolved: $resolved_count threads${NC}"
|
|
402
|
+
if [ "$failed_count" -gt 0 ]; then
|
|
403
|
+
echo -e "${RED}✗ Failed: $failed_count threads${NC}"
|
|
404
|
+
fi
|
|
405
|
+
echo -e "${YELLOW}═══════════════════════════════════════${NC}"
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
# List all Greptile feedback: inline review threads + direct PR issue comments
|
|
409
|
+
cmd_list_all() {
|
|
410
|
+
local pr_number="$1"
|
|
411
|
+
|
|
412
|
+
echo -e "${BLUE}═══ Greptile Inline Review Threads ═══${NC}\n"
|
|
413
|
+
cmd_list "$pr_number"
|
|
414
|
+
|
|
415
|
+
echo -e "\n${BLUE}═══ Greptile Direct PR Comments ═══${NC}\n"
|
|
416
|
+
|
|
417
|
+
local comments
|
|
418
|
+
comments=$(gh api "repos/$OWNER/$REPO/issues/$pr_number/comments" \
|
|
419
|
+
--jq '[.[] | select(.user.login | startswith("greptile-apps"))]')
|
|
420
|
+
|
|
421
|
+
local count
|
|
422
|
+
count=$(echo "$comments" | jq 'length')
|
|
423
|
+
|
|
424
|
+
if [ "$count" -eq 0 ]; then
|
|
425
|
+
echo -e "${GREEN}No direct PR comments from Greptile.${NC}"
|
|
426
|
+
else
|
|
427
|
+
echo -e "Found ${YELLOW}$count${NC} direct comment(s) from Greptile:\n"
|
|
428
|
+
while IFS=$'\t' read -r comment_id created_at body_preview; do
|
|
429
|
+
echo -e " ${BLUE}Comment ID:${NC} $comment_id"
|
|
430
|
+
echo -e " ${BLUE}Posted:${NC} $created_at"
|
|
431
|
+
echo -e " ${BLUE}Preview:${NC} $body_preview"
|
|
432
|
+
echo -e " ${BLUE}Reply with:${NC} gh pr comment $pr_number --body \"...\""
|
|
433
|
+
echo ""
|
|
434
|
+
done < <(echo "$comments" | jq -r '.[] | [.id, .created_at, (.body | split("\n")[0:3] | join(" | ") | .[0:120])] | join("\t")')
|
|
435
|
+
fi
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
# Stats command
|
|
439
|
+
cmd_stats() {
|
|
440
|
+
local pr_number="$1"
|
|
441
|
+
|
|
442
|
+
echo -e "${BLUE}Fetching statistics for PR #$pr_number...${NC}\n"
|
|
443
|
+
|
|
444
|
+
local response
|
|
445
|
+
response=$(fetch_threads "$pr_number")
|
|
446
|
+
|
|
447
|
+
local threads
|
|
448
|
+
threads=$(echo "$response" | jq -r '.data.repository.pullRequest.reviewThreads.nodes')
|
|
449
|
+
|
|
450
|
+
local total_count
|
|
451
|
+
total_count=$(echo "$threads" | jq 'length')
|
|
452
|
+
|
|
453
|
+
local resolved_count
|
|
454
|
+
resolved_count=$(echo "$threads" | jq '[.[] | select(.isResolved == true)] | length')
|
|
455
|
+
|
|
456
|
+
local unresolved_count
|
|
457
|
+
unresolved_count=$(echo "$threads" | jq '[.[] | select(.isResolved == false)] | length')
|
|
458
|
+
|
|
459
|
+
local greptile_count
|
|
460
|
+
greptile_count=$(echo "$threads" | jq '[.[] | select(.comments.nodes[0].author.login | startswith("greptile-apps"))] | length')
|
|
461
|
+
|
|
462
|
+
local greptile_unresolved
|
|
463
|
+
greptile_unresolved=$(echo "$threads" | jq '[.[] | select(.isResolved == false and (.comments.nodes[0].author.login | startswith("greptile-apps")))] | length')
|
|
464
|
+
|
|
465
|
+
echo -e "${YELLOW}═══════════════════════════════════════${NC}"
|
|
466
|
+
echo -e "${BLUE}PR #$pr_number Review Thread Statistics:${NC}"
|
|
467
|
+
echo -e "${YELLOW}═══════════════════════════════════════${NC}"
|
|
468
|
+
echo -e "Total threads: $total_count"
|
|
469
|
+
echo -e "${GREEN}Resolved: $resolved_count${NC}"
|
|
470
|
+
echo -e "${RED}Unresolved: $unresolved_count${NC}"
|
|
471
|
+
echo -e ""
|
|
472
|
+
echo -e "Greptile threads: $greptile_count"
|
|
473
|
+
echo -e "${RED}Greptile unresolved: $greptile_unresolved${NC}"
|
|
474
|
+
echo -e "${YELLOW}═══════════════════════════════════════${NC}"
|
|
475
|
+
|
|
476
|
+
if [ "$greptile_unresolved" -eq 0 ]; then
|
|
477
|
+
echo -e "\n${GREEN}✓ All Greptile threads resolved!${NC}"
|
|
478
|
+
else
|
|
479
|
+
echo -e "\n${YELLOW}⚠️ $greptile_unresolved Greptile thread(s) still unresolved${NC}"
|
|
480
|
+
echo -e "Run: $(basename "$0") list $pr_number --unresolved"
|
|
481
|
+
fi
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
# Main command dispatcher
|
|
485
|
+
main() {
|
|
486
|
+
# Check for jq dependency
|
|
487
|
+
check_jq
|
|
488
|
+
|
|
489
|
+
if [ $# -lt 2 ]; then
|
|
490
|
+
usage
|
|
491
|
+
fi
|
|
492
|
+
|
|
493
|
+
local command="$1"
|
|
494
|
+
local pr_number="$2"
|
|
495
|
+
shift 2
|
|
496
|
+
|
|
497
|
+
case "$command" in
|
|
498
|
+
list)
|
|
499
|
+
cmd_list "$pr_number" "$@"
|
|
500
|
+
;;
|
|
501
|
+
list-all)
|
|
502
|
+
cmd_list_all "$pr_number"
|
|
503
|
+
;;
|
|
504
|
+
reply)
|
|
505
|
+
cmd_reply "$pr_number" "$@"
|
|
506
|
+
;;
|
|
507
|
+
resolve)
|
|
508
|
+
cmd_resolve "$pr_number" "$@"
|
|
509
|
+
;;
|
|
510
|
+
reply-and-resolve)
|
|
511
|
+
cmd_reply_and_resolve "$pr_number" "$@"
|
|
512
|
+
;;
|
|
513
|
+
resolve-all)
|
|
514
|
+
cmd_resolve_all "$pr_number"
|
|
515
|
+
;;
|
|
516
|
+
stats)
|
|
517
|
+
cmd_stats "$pr_number"
|
|
518
|
+
;;
|
|
519
|
+
*)
|
|
520
|
+
echo -e "${RED}Unknown command: $command${NC}\n"
|
|
521
|
+
usage
|
|
522
|
+
;;
|
|
523
|
+
esac
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
main "$@"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Load environment variables from .env.local files
|
|
3
|
+
# Works on Windows Git Bash and Unix systems
|
|
4
|
+
|
|
5
|
+
# Function to load env file
|
|
6
|
+
load_env_file() {
|
|
7
|
+
local env_file="$1"
|
|
8
|
+
if [ -f "$env_file" ]; then
|
|
9
|
+
# Export each line that matches KEY=VALUE pattern (skip comments and empty lines)
|
|
10
|
+
while IFS='=' read -r key value; do
|
|
11
|
+
# Skip comments and empty lines
|
|
12
|
+
[[ "$key" =~ ^#.*$ ]] && continue
|
|
13
|
+
[[ -z "$key" ]] && continue
|
|
14
|
+
# Remove quotes from value
|
|
15
|
+
value="${value%\"}"
|
|
16
|
+
value="${value#\"}"
|
|
17
|
+
value="${value%\'}"
|
|
18
|
+
value="${value#\'}"
|
|
19
|
+
# Export the variable
|
|
20
|
+
export "$key=$value"
|
|
21
|
+
done < "$env_file"
|
|
22
|
+
fi
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Load from project root .env.local
|
|
26
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
27
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
28
|
+
|
|
29
|
+
load_env_file "$PROJECT_ROOT/.env.local"
|
|
30
|
+
load_env_file "$PROJECT_ROOT/next-app/.env.local"
|
|
31
|
+
|
|
32
|
+
echo "Environment loaded. PARALLEL_API_KEY length: ${#PARALLEL_API_KEY}"
|