planflow-plugin 0.1.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.
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/bin/cli.js +169 -0
- package/bin/postinstall.js +87 -0
- package/commands/pfActivity/SKILL.md +725 -0
- package/commands/pfAssign/SKILL.md +623 -0
- package/commands/pfCloudLink/SKILL.md +192 -0
- package/commands/pfCloudList/SKILL.md +222 -0
- package/commands/pfCloudNew/SKILL.md +187 -0
- package/commands/pfCloudUnlink/SKILL.md +152 -0
- package/commands/pfComment/SKILL.md +227 -0
- package/commands/pfComments/SKILL.md +159 -0
- package/commands/pfConnectionStatus/SKILL.md +433 -0
- package/commands/pfDiscord/SKILL.md +740 -0
- package/commands/pfGithubBranch/SKILL.md +672 -0
- package/commands/pfGithubIssue/SKILL.md +963 -0
- package/commands/pfGithubLink/SKILL.md +859 -0
- package/commands/pfGithubPr/SKILL.md +1335 -0
- package/commands/pfGithubUnlink/SKILL.md +401 -0
- package/commands/pfLive/SKILL.md +185 -0
- package/commands/pfLogin/SKILL.md +249 -0
- package/commands/pfLogout/SKILL.md +155 -0
- package/commands/pfMyTasks/SKILL.md +198 -0
- package/commands/pfNotificationSettings/SKILL.md +619 -0
- package/commands/pfNotifications/SKILL.md +420 -0
- package/commands/pfNotificationsClear/SKILL.md +421 -0
- package/commands/pfReact/SKILL.md +232 -0
- package/commands/pfSlack/SKILL.md +659 -0
- package/commands/pfSyncPull/SKILL.md +210 -0
- package/commands/pfSyncPush/SKILL.md +299 -0
- package/commands/pfSyncStatus/SKILL.md +212 -0
- package/commands/pfTeamInvite/SKILL.md +161 -0
- package/commands/pfTeamList/SKILL.md +253 -0
- package/commands/pfTeamRemove/SKILL.md +115 -0
- package/commands/pfTeamRole/SKILL.md +160 -0
- package/commands/pfTestWebhooks/SKILL.md +722 -0
- package/commands/pfUnassign/SKILL.md +134 -0
- package/commands/pfWhoami/SKILL.md +258 -0
- package/commands/pfWorkload/SKILL.md +219 -0
- package/commands/planExportCsv/SKILL.md +106 -0
- package/commands/planExportGithub/SKILL.md +222 -0
- package/commands/planExportJson/SKILL.md +159 -0
- package/commands/planExportSummary/SKILL.md +158 -0
- package/commands/planNew/SKILL.md +641 -0
- package/commands/planNext/SKILL.md +1200 -0
- package/commands/planSettingsAutoSync/SKILL.md +199 -0
- package/commands/planSettingsLanguage/SKILL.md +201 -0
- package/commands/planSettingsReset/SKILL.md +237 -0
- package/commands/planSettingsShow/SKILL.md +482 -0
- package/commands/planSpec/SKILL.md +929 -0
- package/commands/planUpdate/SKILL.md +2518 -0
- package/commands/team/SKILL.md +740 -0
- package/locales/en.json +1499 -0
- package/locales/ka.json +1499 -0
- package/package.json +48 -0
- package/templates/PROJECT_PLAN.template.md +157 -0
- package/templates/backend-api.template.md +562 -0
- package/templates/frontend-spa.template.md +610 -0
- package/templates/fullstack.template.md +397 -0
- package/templates/ka/backend-api.template.md +562 -0
- package/templates/ka/frontend-spa.template.md +610 -0
- package/templates/ka/fullstack.template.md +397 -0
- package/templates/sections/architecture.md +21 -0
- package/templates/sections/overview.md +15 -0
- package/templates/sections/tasks.md +22 -0
- package/templates/sections/tech-stack.md +19 -0
|
@@ -0,0 +1,1335 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pfGithubPr
|
|
3
|
+
description: Create a Pull Request from a PlanFlow task
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# PlanFlow GitHub Pull Request
|
|
7
|
+
|
|
8
|
+
Create or open a Pull Request from a task ID. The PR title, body, and linked issue are automatically generated from task details.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
/pfGithubPr T2.1 # Create/open PR for task
|
|
14
|
+
/pfGithubPr T2.1 --draft # Create as draft PR
|
|
15
|
+
/pfGithubPr T2.1 --no-open # Create without opening browser
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Step 0: Load Configuration
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
function getMergedConfig() {
|
|
22
|
+
let globalConfig = {}
|
|
23
|
+
let localConfig = {}
|
|
24
|
+
|
|
25
|
+
const globalPath = expandPath("~/.config/claude/plan-plugin-config.json")
|
|
26
|
+
if (fileExists(globalPath)) {
|
|
27
|
+
try { globalConfig = JSON.parse(readFile(globalPath)) } catch (e) {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (fileExists("./.plan-config.json")) {
|
|
31
|
+
try { localConfig = JSON.parse(readFile("./.plan-config.json")) } catch (e) {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
...globalConfig,
|
|
36
|
+
...localConfig,
|
|
37
|
+
cloud: {
|
|
38
|
+
...(globalConfig.cloud || {}),
|
|
39
|
+
...(localConfig.cloud || {})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const config = getMergedConfig()
|
|
45
|
+
const language = config.language || "en"
|
|
46
|
+
const cloudConfig = config.cloud || {}
|
|
47
|
+
const isAuthenticated = !!cloudConfig.apiToken
|
|
48
|
+
const apiUrl = cloudConfig.apiUrl || "https://api.planflow.tools"
|
|
49
|
+
const projectId = cloudConfig.projectId || null
|
|
50
|
+
const githubConfig = localConfig.github || {}
|
|
51
|
+
|
|
52
|
+
const t = JSON.parse(readFile(`locales/${language}.json`))
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Step 0.5: Show Notification Badge
|
|
56
|
+
|
|
57
|
+
Only if authenticated AND linked to a project:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
if [ -n "$TOKEN" ] && [ -n "$PROJECT_ID" ]; then
|
|
61
|
+
RESPONSE=$(curl -s --connect-timeout 3 --max-time 5 \
|
|
62
|
+
-X GET \
|
|
63
|
+
-H "Accept: application/json" \
|
|
64
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
65
|
+
"${API_URL}/projects/${PROJECT_ID}/notifications?limit=1&unread=true" 2>/dev/null)
|
|
66
|
+
|
|
67
|
+
if [ $? -eq 0 ]; then
|
|
68
|
+
UNREAD_COUNT=$(echo "$RESPONSE" | grep -o '"unreadCount":[0-9]*' | grep -o '[0-9]*')
|
|
69
|
+
if [ -n "$UNREAD_COUNT" ] && [ "$UNREAD_COUNT" -gt 0 ]; then
|
|
70
|
+
echo "🔔 $UNREAD_COUNT unread notification(s) — /pfNotifications to view"
|
|
71
|
+
echo ""
|
|
72
|
+
fi
|
|
73
|
+
fi
|
|
74
|
+
fi
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Step 1: Parse Arguments
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
const args = commandArgs.trim()
|
|
81
|
+
const taskIdPattern = /^T\d+\.\d+$/i
|
|
82
|
+
|
|
83
|
+
// Parse task ID and flags
|
|
84
|
+
const parts = args.split(/\s+/)
|
|
85
|
+
const taskId = parts[0]
|
|
86
|
+
const flags = parts.slice(1)
|
|
87
|
+
|
|
88
|
+
const noOpen = flags.includes("--no-open")
|
|
89
|
+
const isDraft = flags.includes("--draft")
|
|
90
|
+
const shouldOpen = !noOpen // Default is to open in browser
|
|
91
|
+
|
|
92
|
+
if (!taskId || !taskIdPattern.test(taskId)) {
|
|
93
|
+
showUsageError()
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Usage error output:**
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
101
|
+
│ ❌ ERROR │
|
|
102
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
103
|
+
│ │
|
|
104
|
+
│ {t.github.pr.invalidTaskId} │
|
|
105
|
+
│ │
|
|
106
|
+
│ Invalid or missing task ID. │
|
|
107
|
+
│ │
|
|
108
|
+
│ Usage: /pfGithubPr <task-id> │
|
|
109
|
+
│ │
|
|
110
|
+
│ Examples: │
|
|
111
|
+
│ • /pfGithubPr T2.1 │
|
|
112
|
+
│ • /pfGithubPr T2.1 --draft │
|
|
113
|
+
│ • /pfGithubPr T2.1 --no-open │
|
|
114
|
+
│ │
|
|
115
|
+
│ Task ID should be like: T1.1, T2.3, T10.5 │
|
|
116
|
+
│ │
|
|
117
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Step 2: Validate Prerequisites
|
|
121
|
+
|
|
122
|
+
### 2a: Check Authentication
|
|
123
|
+
|
|
124
|
+
If not authenticated:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
128
|
+
│ ❌ ERROR │
|
|
129
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
130
|
+
│ │
|
|
131
|
+
│ {t.commands.sync.notAuthenticated} │
|
|
132
|
+
│ │
|
|
133
|
+
│ You must be logged in to create Pull Requests. │
|
|
134
|
+
│ │
|
|
135
|
+
│ 💡 Next Steps: │
|
|
136
|
+
│ • /pfLogin Sign in to PlanFlow │
|
|
137
|
+
│ │
|
|
138
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 2b: Check Project Link
|
|
142
|
+
|
|
143
|
+
If not linked to a cloud project:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
147
|
+
│ ❌ ERROR │
|
|
148
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
149
|
+
│ │
|
|
150
|
+
│ {t.commands.sync.notLinked} │
|
|
151
|
+
│ │
|
|
152
|
+
│ You must link to a cloud project first. │
|
|
153
|
+
│ │
|
|
154
|
+
│ 💡 Next Steps: │
|
|
155
|
+
│ • /pfCloudLink Link to existing project │
|
|
156
|
+
│ • /pfCloudNew Create new cloud project │
|
|
157
|
+
│ │
|
|
158
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 2c: Check GitHub Integration
|
|
162
|
+
|
|
163
|
+
If no GitHub repository is linked:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
167
|
+
│ ❌ ERROR │
|
|
168
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
169
|
+
│ │
|
|
170
|
+
│ {t.commands.github.notLinked} │
|
|
171
|
+
│ │
|
|
172
|
+
│ No GitHub repository is linked to this project. │
|
|
173
|
+
│ │
|
|
174
|
+
│ 💡 To link a repository: │
|
|
175
|
+
│ • /pfGithubLink owner/repo │
|
|
176
|
+
│ │
|
|
177
|
+
│ Example: │
|
|
178
|
+
│ • /pfGithubLink microsoft/vscode │
|
|
179
|
+
│ │
|
|
180
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 2d: Check Git Repository
|
|
184
|
+
|
|
185
|
+
Verify we're in a git repository:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
|
189
|
+
echo "❌ Not a git repository"
|
|
190
|
+
echo ""
|
|
191
|
+
echo "Please run this command from within a git repository."
|
|
192
|
+
exit 1
|
|
193
|
+
fi
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 2e: Check Current Branch
|
|
197
|
+
|
|
198
|
+
Verify we're not on main/master:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
202
|
+
|
|
203
|
+
if [ "$CURRENT_BRANCH" = "main" ] || [ "$CURRENT_BRANCH" = "master" ]; then
|
|
204
|
+
echo "⚠️ You're on the $CURRENT_BRANCH branch."
|
|
205
|
+
echo ""
|
|
206
|
+
echo "PRs are usually created from feature branches."
|
|
207
|
+
echo ""
|
|
208
|
+
echo "💡 Create a feature branch first:"
|
|
209
|
+
echo " • /pfGithubBranch $TASK_ID"
|
|
210
|
+
echo ""
|
|
211
|
+
# Continue anyway - user might want to create PR from main for specific cases
|
|
212
|
+
fi
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 2f: Check for Uncommitted Changes
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
if ! git diff-index --quiet HEAD -- 2>/dev/null; then
|
|
219
|
+
echo "⚠️ Warning: You have uncommitted changes"
|
|
220
|
+
echo " Consider committing before creating a PR."
|
|
221
|
+
echo ""
|
|
222
|
+
fi
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### 2g: Check Remote Tracking
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# Check if current branch is pushed to remote
|
|
229
|
+
REMOTE_BRANCH=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null)
|
|
230
|
+
|
|
231
|
+
if [ -z "$REMOTE_BRANCH" ]; then
|
|
232
|
+
echo "⚠️ Branch not pushed to remote yet."
|
|
233
|
+
echo ""
|
|
234
|
+
echo "Pushing branch to origin..."
|
|
235
|
+
git push -u origin "$CURRENT_BRANCH"
|
|
236
|
+
echo ""
|
|
237
|
+
fi
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Step 3: Fetch Task Details
|
|
241
|
+
|
|
242
|
+
### 3a: Try Cloud API First
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
|
246
|
+
--connect-timeout 5 \
|
|
247
|
+
--max-time 10 \
|
|
248
|
+
-X GET \
|
|
249
|
+
-H "Accept: application/json" \
|
|
250
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
251
|
+
"${API_URL}/projects/${PROJECT_ID}/tasks/${TASK_ID}")
|
|
252
|
+
|
|
253
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
254
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
255
|
+
|
|
256
|
+
if [ "$HTTP_CODE" -eq 200 ]; then
|
|
257
|
+
TASK_TITLE=$(echo "$BODY" | jq -r '.data.task.name // .data.name // empty')
|
|
258
|
+
TASK_DESCRIPTION=$(echo "$BODY" | jq -r '.data.task.description // .data.description // empty')
|
|
259
|
+
TASK_STATUS=$(echo "$BODY" | jq -r '.data.task.status // .data.status // empty')
|
|
260
|
+
TASK_COMPLEXITY=$(echo "$BODY" | jq -r '.data.task.complexity // .data.complexity // empty')
|
|
261
|
+
fi
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 3b: Fallback to Local PROJECT_PLAN.md
|
|
265
|
+
|
|
266
|
+
If cloud API fails or task not found, parse from local file:
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
# Extract task details from PROJECT_PLAN.md
|
|
270
|
+
# Try table format first: | T1.1 | Task Title | Complexity | Status | Dependencies |
|
|
271
|
+
TASK_LINE=$(grep -E "^\|\s*${TASK_ID}\s*\|" PROJECT_PLAN.md | head -1)
|
|
272
|
+
|
|
273
|
+
if [ -n "$TASK_LINE" ]; then
|
|
274
|
+
TASK_TITLE=$(echo "$TASK_LINE" | cut -d'|' -f3 | sed 's/^\s*//' | sed 's/\s*$//')
|
|
275
|
+
TASK_COMPLEXITY=$(echo "$TASK_LINE" | cut -d'|' -f4 | sed 's/^\s*//' | sed 's/\s*$//')
|
|
276
|
+
TASK_STATUS=$(echo "$TASK_LINE" | cut -d'|' -f5 | sed 's/^\s*//' | sed 's/\s*$//')
|
|
277
|
+
else
|
|
278
|
+
# Try markdown heading format: #### **T1.1**: Task Title
|
|
279
|
+
TASK_LINE=$(grep -E "^\s*####\s*\*\*${TASK_ID}\*\*:" PROJECT_PLAN.md | head -1)
|
|
280
|
+
if [ -n "$TASK_LINE" ]; then
|
|
281
|
+
TASK_TITLE=$(echo "$TASK_LINE" | sed 's/.*\*\*:\s*//' | sed 's/\s*$//')
|
|
282
|
+
fi
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
if [ -z "$TASK_TITLE" ]; then
|
|
286
|
+
echo "❌ Task not found: $TASK_ID"
|
|
287
|
+
exit 1
|
|
288
|
+
fi
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Step 4: Check for Existing PR
|
|
292
|
+
|
|
293
|
+
### 4a: Query Existing PRs
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
# Check if a PR already exists for this task
|
|
297
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
|
298
|
+
--connect-timeout 5 \
|
|
299
|
+
--max-time 10 \
|
|
300
|
+
-X GET \
|
|
301
|
+
-H "Accept: application/json" \
|
|
302
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
303
|
+
"${API_URL}/projects/${PROJECT_ID}/tasks/${TASK_ID}/github-pr")
|
|
304
|
+
|
|
305
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
306
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
307
|
+
|
|
308
|
+
if [ "$HTTP_CODE" -eq 200 ]; then
|
|
309
|
+
EXISTING_PR_NUMBER=$(echo "$BODY" | jq -r '.data.prNumber // empty')
|
|
310
|
+
EXISTING_PR_URL=$(echo "$BODY" | jq -r '.data.prUrl // empty')
|
|
311
|
+
EXISTING_PR_STATE=$(echo "$BODY" | jq -r '.data.state // empty')
|
|
312
|
+
|
|
313
|
+
if [ -n "$EXISTING_PR_NUMBER" ]; then
|
|
314
|
+
# PR already exists
|
|
315
|
+
showExistingPR
|
|
316
|
+
fi
|
|
317
|
+
fi
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### 4b: Show Existing PR Info
|
|
321
|
+
|
|
322
|
+
If a PR already exists:
|
|
323
|
+
|
|
324
|
+
```
|
|
325
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
326
|
+
│ 🐙 Existing Pull Request Found │
|
|
327
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
328
|
+
│ │
|
|
329
|
+
│ {t.github.pr.alreadyExists} │
|
|
330
|
+
│ │
|
|
331
|
+
│ ── Pull Request ──────────────────────────────────────────────────────── │
|
|
332
|
+
│ │
|
|
333
|
+
│ 📝 Task: T2.1 - Implement login API │
|
|
334
|
+
│ 🔀 PR: #45 │
|
|
335
|
+
│ 🔗 URL: https://github.com/owner/repo/pull/45 │
|
|
336
|
+
│ 📊 State: open (awaiting review) │
|
|
337
|
+
│ │
|
|
338
|
+
│ ╭────────────────────────────────────────────────────────────────────────╮ │
|
|
339
|
+
│ │ ✓ PR linked to task T2.1 │ │
|
|
340
|
+
│ ╰────────────────────────────────────────────────────────────────────────╯ │
|
|
341
|
+
│ │
|
|
342
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
343
|
+
│ │
|
|
344
|
+
│ 💡 Options: │
|
|
345
|
+
│ • View the existing PR in browser │
|
|
346
|
+
│ • Close the existing PR to create a new one │
|
|
347
|
+
│ │
|
|
348
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Step 5: Fetch GitHub Integration Details
|
|
352
|
+
|
|
353
|
+
### 5a: Get Repository Info
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
|
357
|
+
--connect-timeout 5 \
|
|
358
|
+
--max-time 10 \
|
|
359
|
+
-X GET \
|
|
360
|
+
-H "Accept: application/json" \
|
|
361
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
362
|
+
"${API_URL}/projects/${PROJECT_ID}/integrations/github")
|
|
363
|
+
|
|
364
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
365
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
366
|
+
|
|
367
|
+
if [ "$HTTP_CODE" -eq 200 ]; then
|
|
368
|
+
GITHUB_OWNER=$(echo "$BODY" | jq -r '.data.owner // empty')
|
|
369
|
+
GITHUB_REPO=$(echo "$BODY" | jq -r '.data.repo // empty')
|
|
370
|
+
DEFAULT_BRANCH=$(echo "$BODY" | jq -r '.data.defaultBranch // "main"')
|
|
371
|
+
fi
|
|
372
|
+
|
|
373
|
+
if [ -z "$GITHUB_OWNER" ] || [ -z "$GITHUB_REPO" ]; then
|
|
374
|
+
echo "❌ No GitHub repository linked."
|
|
375
|
+
echo ""
|
|
376
|
+
echo "💡 Link a repository first:"
|
|
377
|
+
echo " • /pfGithubLink owner/repo"
|
|
378
|
+
exit 1
|
|
379
|
+
fi
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### 5b: Check for Linked Issue
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
# Check if there's a linked GitHub issue for this task
|
|
386
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
|
387
|
+
--connect-timeout 5 \
|
|
388
|
+
--max-time 10 \
|
|
389
|
+
-X GET \
|
|
390
|
+
-H "Accept: application/json" \
|
|
391
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
392
|
+
"${API_URL}/projects/${PROJECT_ID}/tasks/${TASK_ID}/github-issue")
|
|
393
|
+
|
|
394
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
395
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
396
|
+
|
|
397
|
+
LINKED_ISSUE_NUMBER=""
|
|
398
|
+
if [ "$HTTP_CODE" -eq 200 ]; then
|
|
399
|
+
LINKED_ISSUE_NUMBER=$(echo "$BODY" | jq -r '.data.issueNumber // empty')
|
|
400
|
+
fi
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Step 6: Create Pull Request
|
|
404
|
+
|
|
405
|
+
### 6a: Build PR Title and Body
|
|
406
|
+
|
|
407
|
+
```javascript
|
|
408
|
+
function buildPRTitle(taskId, taskTitle) {
|
|
409
|
+
return `[${taskId}] ${taskTitle}`
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function buildPRBody(task, linkedIssue) {
|
|
413
|
+
let body = `## Summary
|
|
414
|
+
|
|
415
|
+
This PR implements task **${task.id}**: ${task.title}
|
|
416
|
+
|
|
417
|
+
## Task Details
|
|
418
|
+
|
|
419
|
+
| Property | Value |
|
|
420
|
+
|----------|-------|
|
|
421
|
+
| Task ID | ${task.id} |
|
|
422
|
+
| Complexity | ${task.complexity || 'Not specified'} |
|
|
423
|
+
| Status | ${task.status || 'IN_PROGRESS'} |
|
|
424
|
+
|
|
425
|
+
## Description
|
|
426
|
+
|
|
427
|
+
${task.description || 'No description provided.'}
|
|
428
|
+
|
|
429
|
+
## Test Plan
|
|
430
|
+
|
|
431
|
+
- [ ] TODO: Add test plan
|
|
432
|
+
|
|
433
|
+
## Checklist
|
|
434
|
+
|
|
435
|
+
- [ ] Code follows project style guidelines
|
|
436
|
+
- [ ] Tests have been added/updated
|
|
437
|
+
- [ ] Documentation has been updated (if needed)
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
`
|
|
442
|
+
|
|
443
|
+
// Add closes directive
|
|
444
|
+
if (linkedIssue) {
|
|
445
|
+
body += `Closes #${linkedIssue}\n`
|
|
446
|
+
}
|
|
447
|
+
body += `Closes ${task.id}\n\n`
|
|
448
|
+
body += `*This PR was created from [PlanFlow](https://planflow.tools) task ${task.id}.*`
|
|
449
|
+
|
|
450
|
+
return body
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### 6b: Create PR via API (Preferred)
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
# Build PR title and body
|
|
458
|
+
PR_TITLE="[$TASK_ID] $TASK_TITLE"
|
|
459
|
+
|
|
460
|
+
# Build PR body with proper escaping
|
|
461
|
+
PR_BODY="## Summary
|
|
462
|
+
|
|
463
|
+
This PR implements task **$TASK_ID**: $TASK_TITLE
|
|
464
|
+
|
|
465
|
+
## Task Details
|
|
466
|
+
|
|
467
|
+
| Property | Value |
|
|
468
|
+
|----------|-------|
|
|
469
|
+
| Task ID | $TASK_ID |
|
|
470
|
+
| Complexity | ${TASK_COMPLEXITY:-Not specified} |
|
|
471
|
+
| Status | ${TASK_STATUS:-IN_PROGRESS} |
|
|
472
|
+
|
|
473
|
+
## Description
|
|
474
|
+
|
|
475
|
+
${TASK_DESCRIPTION:-No description provided.}
|
|
476
|
+
|
|
477
|
+
## Test Plan
|
|
478
|
+
|
|
479
|
+
- [ ] TODO: Add test plan
|
|
480
|
+
|
|
481
|
+
## Checklist
|
|
482
|
+
|
|
483
|
+
- [ ] Code follows project style guidelines
|
|
484
|
+
- [ ] Tests have been added/updated
|
|
485
|
+
- [ ] Documentation has been updated (if needed)
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
"
|
|
490
|
+
|
|
491
|
+
# Add closes directive
|
|
492
|
+
if [ -n "$LINKED_ISSUE_NUMBER" ]; then
|
|
493
|
+
PR_BODY="${PR_BODY}Closes #${LINKED_ISSUE_NUMBER}
|
|
494
|
+
"
|
|
495
|
+
fi
|
|
496
|
+
PR_BODY="${PR_BODY}Closes $TASK_ID
|
|
497
|
+
|
|
498
|
+
*This PR was created from [PlanFlow](https://planflow.tools) task $TASK_ID.*"
|
|
499
|
+
|
|
500
|
+
# Create PR via API
|
|
501
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
|
502
|
+
--connect-timeout 5 \
|
|
503
|
+
--max-time 15 \
|
|
504
|
+
-X POST \
|
|
505
|
+
-H "Content-Type: application/json" \
|
|
506
|
+
-H "Accept: application/json" \
|
|
507
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
508
|
+
-d "$(jq -n \
|
|
509
|
+
--arg title "$PR_TITLE" \
|
|
510
|
+
--arg body "$PR_BODY" \
|
|
511
|
+
--arg head "$CURRENT_BRANCH" \
|
|
512
|
+
--arg base "$DEFAULT_BRANCH" \
|
|
513
|
+
--arg taskId "$TASK_ID" \
|
|
514
|
+
--argjson draft "$IS_DRAFT_JSON" \
|
|
515
|
+
'{
|
|
516
|
+
title: $title,
|
|
517
|
+
body: $body,
|
|
518
|
+
head: $head,
|
|
519
|
+
base: $base,
|
|
520
|
+
taskId: $taskId,
|
|
521
|
+
draft: $draft
|
|
522
|
+
}')" \
|
|
523
|
+
"${API_URL}/projects/${PROJECT_ID}/integrations/github/pulls")
|
|
524
|
+
|
|
525
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
526
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### 6c: Fallback to GitHub URL (if API not supported)
|
|
530
|
+
|
|
531
|
+
If the API doesn't support direct PR creation, generate a GitHub URL with pre-filled content:
|
|
532
|
+
|
|
533
|
+
```bash
|
|
534
|
+
# URL encode the PR body
|
|
535
|
+
urlencode() {
|
|
536
|
+
python3 -c "import urllib.parse; print(urllib.parse.quote('''$1''', safe=''))"
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
PR_BODY_ENCODED=$(urlencode "$PR_BODY")
|
|
540
|
+
PR_TITLE_ENCODED=$(urlencode "$PR_TITLE")
|
|
541
|
+
|
|
542
|
+
# Generate GitHub compare/PR URL
|
|
543
|
+
COMPARE_URL="https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/compare/${DEFAULT_BRANCH}...${CURRENT_BRANCH}?expand=1&title=${PR_TITLE_ENCODED}&body=${PR_BODY_ENCODED}"
|
|
544
|
+
|
|
545
|
+
# If draft mode
|
|
546
|
+
if [ "$IS_DRAFT" = "true" ]; then
|
|
547
|
+
COMPARE_URL="${COMPARE_URL}&draft=1"
|
|
548
|
+
fi
|
|
549
|
+
|
|
550
|
+
echo "Opening GitHub to create PR..."
|
|
551
|
+
echo ""
|
|
552
|
+
echo "🔗 $COMPARE_URL"
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### 6d: Handle API Response
|
|
556
|
+
|
|
557
|
+
**Success (201):**
|
|
558
|
+
|
|
559
|
+
```
|
|
560
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
561
|
+
│ ✅ SUCCESS │
|
|
562
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
563
|
+
│ │
|
|
564
|
+
│ {t.github.pr.created} │
|
|
565
|
+
│ │
|
|
566
|
+
│ ── Pull Request Created ───────────────────────────────────────────────── │
|
|
567
|
+
│ │
|
|
568
|
+
│ 📝 Task: T2.1 - Implement login API │
|
|
569
|
+
│ 🔀 PR: #45 │
|
|
570
|
+
│ 🌿 Branch: feature/T2.1-implement-login-api → main │
|
|
571
|
+
│ 🔗 URL: https://github.com/owner/repo/pull/45 │
|
|
572
|
+
│ 📊 State: open │
|
|
573
|
+
│ │
|
|
574
|
+
│ ╭────────────────────────────────────────────────────────────────────────╮ │
|
|
575
|
+
│ │ ✓ PR linked to task T2.1 │ │
|
|
576
|
+
│ │ ✓ Will auto-close issue #42 when merged │ │
|
|
577
|
+
│ ╰────────────────────────────────────────────────────────────────────────╯ │
|
|
578
|
+
│ │
|
|
579
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
580
|
+
│ │
|
|
581
|
+
│ 💡 What's Next? │
|
|
582
|
+
│ │
|
|
583
|
+
│ • Request reviews from team members │
|
|
584
|
+
│ • Address any CI/CD feedback │
|
|
585
|
+
│ • When approved, merge the PR │
|
|
586
|
+
│ │
|
|
587
|
+
│ 💡 Tip: When the PR is merged, task T2.1 will be │
|
|
588
|
+
│ automatically marked as complete! │
|
|
589
|
+
│ │
|
|
590
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
591
|
+
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
**Draft PR Created:**
|
|
595
|
+
|
|
596
|
+
```
|
|
597
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
598
|
+
│ ✅ SUCCESS │
|
|
599
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
600
|
+
│ │
|
|
601
|
+
│ {t.github.pr.draftCreated} │
|
|
602
|
+
│ │
|
|
603
|
+
│ ── Draft Pull Request Created ─────────────────────────────────────────── │
|
|
604
|
+
│ │
|
|
605
|
+
│ 📝 Task: T2.1 - Implement login API │
|
|
606
|
+
│ 🔀 PR: #45 (draft) │
|
|
607
|
+
│ 🌿 Branch: feature/T2.1-implement-login-api → main │
|
|
608
|
+
│ 🔗 URL: https://github.com/owner/repo/pull/45 │
|
|
609
|
+
│ 📊 State: draft │
|
|
610
|
+
│ │
|
|
611
|
+
│ 💡 Mark as "Ready for Review" when you're done: │
|
|
612
|
+
│ • Open the PR in GitHub and click "Ready for review" │
|
|
613
|
+
│ │
|
|
614
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
**Branch Not Pushed Error:**
|
|
618
|
+
|
|
619
|
+
```
|
|
620
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
621
|
+
│ ❌ ERROR │
|
|
622
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
623
|
+
│ │
|
|
624
|
+
│ {t.github.pr.branchNotPushed} │
|
|
625
|
+
│ │
|
|
626
|
+
│ Branch not found on remote: feature/T2.1-implement-login-api │
|
|
627
|
+
│ │
|
|
628
|
+
│ 💡 Push your branch first: │
|
|
629
|
+
│ • git push -u origin feature/T2.1-implement-login-api │
|
|
630
|
+
│ │
|
|
631
|
+
│ Then try again: │
|
|
632
|
+
│ • /pfGithubPr T2.1 │
|
|
633
|
+
│ │
|
|
634
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
**No Commits Difference:**
|
|
638
|
+
|
|
639
|
+
```
|
|
640
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
641
|
+
│ ⚠️ WARNING │
|
|
642
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
643
|
+
│ │
|
|
644
|
+
│ {t.github.pr.noCommits} │
|
|
645
|
+
│ │
|
|
646
|
+
│ No commits difference between your branch and main. │
|
|
647
|
+
│ │
|
|
648
|
+
│ Your branch appears to be up-to-date with main. │
|
|
649
|
+
│ Make some commits before creating a PR. │
|
|
650
|
+
│ │
|
|
651
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
## Step 7: Open PR in Browser
|
|
655
|
+
|
|
656
|
+
If `--no-open` flag is not set:
|
|
657
|
+
|
|
658
|
+
```bash
|
|
659
|
+
# Open in default browser
|
|
660
|
+
if [ "$SHOULD_OPEN" = "true" ] && [ -n "$PR_URL" ]; then
|
|
661
|
+
# macOS
|
|
662
|
+
if command -v open &> /dev/null; then
|
|
663
|
+
open "$PR_URL"
|
|
664
|
+
# Linux
|
|
665
|
+
elif command -v xdg-open &> /dev/null; then
|
|
666
|
+
xdg-open "$PR_URL"
|
|
667
|
+
# Windows (WSL)
|
|
668
|
+
elif command -v wslview &> /dev/null; then
|
|
669
|
+
wslview "$PR_URL"
|
|
670
|
+
fi
|
|
671
|
+
fi
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
## Error Handling
|
|
675
|
+
|
|
676
|
+
### Network Error
|
|
677
|
+
|
|
678
|
+
```
|
|
679
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
680
|
+
│ ❌ ERROR │
|
|
681
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
682
|
+
│ │
|
|
683
|
+
│ Network error. Could not connect to PlanFlow API. │
|
|
684
|
+
│ │
|
|
685
|
+
│ Please check your internet connection and try again. │
|
|
686
|
+
│ │
|
|
687
|
+
│ 💡 To retry: │
|
|
688
|
+
│ • /pfGithubPr T2.1 │
|
|
689
|
+
│ │
|
|
690
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
### Token Expired (401)
|
|
694
|
+
|
|
695
|
+
```
|
|
696
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
697
|
+
│ ❌ ERROR │
|
|
698
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
699
|
+
│ │
|
|
700
|
+
│ Authentication failed. Your session may have expired. │
|
|
701
|
+
│ │
|
|
702
|
+
│ 💡 To re-authenticate: │
|
|
703
|
+
│ • /pfLogin │
|
|
704
|
+
│ │
|
|
705
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### Permission Denied (403)
|
|
709
|
+
|
|
710
|
+
```
|
|
711
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
712
|
+
│ ❌ ERROR │
|
|
713
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
714
|
+
│ │
|
|
715
|
+
│ Permission denied. You don't have access to create Pull Requests. │
|
|
716
|
+
│ │
|
|
717
|
+
│ To create GitHub PRs, you need: │
|
|
718
|
+
│ • Editor role or higher in the PlanFlow project │
|
|
719
|
+
│ • Write access to the linked GitHub repository │
|
|
720
|
+
│ │
|
|
721
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
## Translation Keys
|
|
725
|
+
|
|
726
|
+
Add to `locales/en.json` and `locales/ka.json`:
|
|
727
|
+
|
|
728
|
+
```json
|
|
729
|
+
{
|
|
730
|
+
"github": {
|
|
731
|
+
"pr": {
|
|
732
|
+
"title": "Create Pull Request",
|
|
733
|
+
"created": "Pull Request created successfully!",
|
|
734
|
+
"draftCreated": "Draft Pull Request created!",
|
|
735
|
+
"alreadyExists": "A Pull Request already exists for this task.",
|
|
736
|
+
"taskNotFound": "Task not found.",
|
|
737
|
+
"invalidTaskId": "Invalid task ID format.",
|
|
738
|
+
"branchNotPushed": "Branch not pushed to remote.",
|
|
739
|
+
"noCommits": "No commits difference between branches.",
|
|
740
|
+
"githubError": "GitHub API error.",
|
|
741
|
+
"permissionDenied": "Permission denied.",
|
|
742
|
+
"notLinked": "No GitHub repository linked.",
|
|
743
|
+
"notGitRepo": "Not a git repository.",
|
|
744
|
+
"onMainBranch": "You're on the main branch.",
|
|
745
|
+
"uncommittedChanges": "You have uncommitted changes.",
|
|
746
|
+
"usage": "Usage: /pfGithubPr <task-id>",
|
|
747
|
+
"example": "Example: /pfGithubPr T2.1",
|
|
748
|
+
"task": "Task:",
|
|
749
|
+
"pr": "PR:",
|
|
750
|
+
"branch": "Branch:",
|
|
751
|
+
"url": "URL:",
|
|
752
|
+
"state": "State:",
|
|
753
|
+
"linked": "PR linked to task",
|
|
754
|
+
"willAutoClose": "Will auto-close issue when merged",
|
|
755
|
+
"autoCompleteTip": "When the PR is merged, the task will be automatically marked as complete!",
|
|
756
|
+
"whatsNext": "What's Next?",
|
|
757
|
+
"requestReviews": "Request reviews from team members",
|
|
758
|
+
"addressCI": "Address any CI/CD feedback",
|
|
759
|
+
"mergePR": "When approved, merge the PR",
|
|
760
|
+
"markReady": "Mark as \"Ready for Review\" when done"
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
**Georgian translations:**
|
|
767
|
+
|
|
768
|
+
```json
|
|
769
|
+
{
|
|
770
|
+
"github": {
|
|
771
|
+
"pr": {
|
|
772
|
+
"title": "Pull Request-ის შექმნა",
|
|
773
|
+
"created": "Pull Request წარმატებით შეიქმნა!",
|
|
774
|
+
"draftCreated": "Draft Pull Request შეიქმნა!",
|
|
775
|
+
"alreadyExists": "ამ ამოცანისთვის Pull Request უკვე არსებობს.",
|
|
776
|
+
"taskNotFound": "ამოცანა ვერ მოიძებნა.",
|
|
777
|
+
"invalidTaskId": "ამოცანის ID-ის არასწორი ფორმატი.",
|
|
778
|
+
"branchNotPushed": "ბრანჩი არ არის push-ული remote-ზე.",
|
|
779
|
+
"noCommits": "ბრანჩებს შორის კომიტების სხვაობა არ არის.",
|
|
780
|
+
"githubError": "GitHub API-ის შეცდომა.",
|
|
781
|
+
"permissionDenied": "წვდომა აკრძალულია.",
|
|
782
|
+
"notLinked": "GitHub რეპოზიტორია არ არის დაკავშირებული.",
|
|
783
|
+
"notGitRepo": "ეს არ არის git რეპოზიტორია.",
|
|
784
|
+
"onMainBranch": "თქვენ main ბრანჩზე ხართ.",
|
|
785
|
+
"uncommittedChanges": "გაქვთ შეუნახავი ცვლილებები.",
|
|
786
|
+
"usage": "გამოყენება: /pfGithubPr <task-id>",
|
|
787
|
+
"example": "მაგალითი: /pfGithubPr T2.1",
|
|
788
|
+
"task": "ამოცანა:",
|
|
789
|
+
"pr": "PR:",
|
|
790
|
+
"branch": "ბრანჩი:",
|
|
791
|
+
"url": "URL:",
|
|
792
|
+
"state": "სტატუსი:",
|
|
793
|
+
"linked": "PR დაკავშირებულია ამოცანასთან",
|
|
794
|
+
"willAutoClose": "Issue ავტომატურად დაიხურება merge-ისას",
|
|
795
|
+
"autoCompleteTip": "PR-ის merge-ისას ამოცანა ავტომატურად დასრულდება!",
|
|
796
|
+
"whatsNext": "შემდეგი ნაბიჯები?",
|
|
797
|
+
"requestReviews": "მოითხოვეთ review გუნდის წევრებისგან",
|
|
798
|
+
"addressCI": "გაითვალისწინეთ CI/CD feedback",
|
|
799
|
+
"mergePR": "დამტკიცებისას გააერთიანეთ PR",
|
|
800
|
+
"markReady": "მონიშნეთ \"Ready for Review\" დასრულებისას"
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
## Full Bash Implementation
|
|
807
|
+
|
|
808
|
+
```bash
|
|
809
|
+
#!/bin/bash
|
|
810
|
+
|
|
811
|
+
# Step 0: Load config
|
|
812
|
+
GLOBAL_CONFIG_PATH="$HOME/.config/claude/plan-plugin-config.json"
|
|
813
|
+
LOCAL_CONFIG_PATH="./.plan-config.json"
|
|
814
|
+
|
|
815
|
+
# Read configs and merge
|
|
816
|
+
if [ -f "$GLOBAL_CONFIG_PATH" ]; then
|
|
817
|
+
API_TOKEN=$(jq -r '.cloud.apiToken // empty' "$GLOBAL_CONFIG_PATH")
|
|
818
|
+
API_URL=$(jq -r '.cloud.apiUrl // "https://api.planflow.tools"' "$GLOBAL_CONFIG_PATH")
|
|
819
|
+
fi
|
|
820
|
+
|
|
821
|
+
if [ -f "$LOCAL_CONFIG_PATH" ]; then
|
|
822
|
+
PROJECT_ID=$(jq -r '.cloud.projectId // empty' "$LOCAL_CONFIG_PATH")
|
|
823
|
+
# Local can override global
|
|
824
|
+
LOCAL_TOKEN=$(jq -r '.cloud.apiToken // empty' "$LOCAL_CONFIG_PATH")
|
|
825
|
+
[ -n "$LOCAL_TOKEN" ] && API_TOKEN="$LOCAL_TOKEN"
|
|
826
|
+
fi
|
|
827
|
+
|
|
828
|
+
# Parse arguments
|
|
829
|
+
TASK_ID="$1"
|
|
830
|
+
shift
|
|
831
|
+
NO_OPEN=false
|
|
832
|
+
IS_DRAFT=false
|
|
833
|
+
|
|
834
|
+
while [[ $# -gt 0 ]]; do
|
|
835
|
+
case "$1" in
|
|
836
|
+
--no-open)
|
|
837
|
+
NO_OPEN=true
|
|
838
|
+
shift
|
|
839
|
+
;;
|
|
840
|
+
--draft)
|
|
841
|
+
IS_DRAFT=true
|
|
842
|
+
shift
|
|
843
|
+
;;
|
|
844
|
+
--open)
|
|
845
|
+
NO_OPEN=false
|
|
846
|
+
shift
|
|
847
|
+
;;
|
|
848
|
+
*)
|
|
849
|
+
shift
|
|
850
|
+
;;
|
|
851
|
+
esac
|
|
852
|
+
done
|
|
853
|
+
|
|
854
|
+
# Convert draft flag to JSON boolean
|
|
855
|
+
if [ "$IS_DRAFT" = "true" ]; then
|
|
856
|
+
IS_DRAFT_JSON="true"
|
|
857
|
+
else
|
|
858
|
+
IS_DRAFT_JSON="false"
|
|
859
|
+
fi
|
|
860
|
+
|
|
861
|
+
# Validate task ID format
|
|
862
|
+
if [ -z "$TASK_ID" ] || ! echo "$TASK_ID" | grep -qiE '^T[0-9]+\.[0-9]+$'; then
|
|
863
|
+
echo "╭──────────────────────────────────────────────────────────────────────────────╮"
|
|
864
|
+
echo "│ ❌ ERROR │"
|
|
865
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
866
|
+
echo "│ │"
|
|
867
|
+
echo "│ Invalid or missing task ID: $TASK_ID"
|
|
868
|
+
echo "│ │"
|
|
869
|
+
echo "│ Usage: /pfGithubPr <task-id> │"
|
|
870
|
+
echo "│ │"
|
|
871
|
+
echo "│ Examples: │"
|
|
872
|
+
echo "│ • /pfGithubPr T2.1 │"
|
|
873
|
+
echo "│ • /pfGithubPr T2.1 --draft │"
|
|
874
|
+
echo "│ • /pfGithubPr T2.1 --no-open │"
|
|
875
|
+
echo "│ │"
|
|
876
|
+
echo "│ Task ID should be like: T1.1, T2.3, T10.5 │"
|
|
877
|
+
echo "│ │"
|
|
878
|
+
echo "╰──────────────────────────────────────────────────────────────────────────────╯"
|
|
879
|
+
exit 1
|
|
880
|
+
fi
|
|
881
|
+
|
|
882
|
+
# Normalize task ID to uppercase
|
|
883
|
+
TASK_ID=$(echo "$TASK_ID" | tr '[:lower:]' '[:upper:]')
|
|
884
|
+
|
|
885
|
+
# Validate prerequisites
|
|
886
|
+
if [ -z "$API_TOKEN" ]; then
|
|
887
|
+
echo "❌ Not authenticated. Run /pfLogin first."
|
|
888
|
+
exit 1
|
|
889
|
+
fi
|
|
890
|
+
|
|
891
|
+
if [ -z "$PROJECT_ID" ]; then
|
|
892
|
+
echo "❌ No project linked. Run /pfCloudLink first."
|
|
893
|
+
exit 1
|
|
894
|
+
fi
|
|
895
|
+
|
|
896
|
+
# Check if in git repo
|
|
897
|
+
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
|
898
|
+
echo "❌ Not a git repository"
|
|
899
|
+
echo ""
|
|
900
|
+
echo "Please run this command from within a git repository."
|
|
901
|
+
exit 1
|
|
902
|
+
fi
|
|
903
|
+
|
|
904
|
+
# Get current branch
|
|
905
|
+
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
906
|
+
|
|
907
|
+
# Warn if on main/master
|
|
908
|
+
if [ "$CURRENT_BRANCH" = "main" ] || [ "$CURRENT_BRANCH" = "master" ]; then
|
|
909
|
+
echo "⚠️ You're on the $CURRENT_BRANCH branch."
|
|
910
|
+
echo ""
|
|
911
|
+
echo "PRs are usually created from feature branches."
|
|
912
|
+
echo ""
|
|
913
|
+
echo "💡 Create a feature branch first:"
|
|
914
|
+
echo " • /pfGithubBranch $TASK_ID"
|
|
915
|
+
echo ""
|
|
916
|
+
read -p "Continue anyway? (y/N): " -n 1 -r
|
|
917
|
+
echo
|
|
918
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
919
|
+
exit 0
|
|
920
|
+
fi
|
|
921
|
+
fi
|
|
922
|
+
|
|
923
|
+
# Check for uncommitted changes
|
|
924
|
+
if ! git diff-index --quiet HEAD -- 2>/dev/null; then
|
|
925
|
+
echo "⚠️ Warning: You have uncommitted changes"
|
|
926
|
+
echo " Consider committing before creating a PR."
|
|
927
|
+
echo ""
|
|
928
|
+
fi
|
|
929
|
+
|
|
930
|
+
# Check if branch is pushed to remote
|
|
931
|
+
REMOTE_BRANCH=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null)
|
|
932
|
+
if [ -z "$REMOTE_BRANCH" ]; then
|
|
933
|
+
echo "📤 Branch not pushed to remote. Pushing now..."
|
|
934
|
+
if ! git push -u origin "$CURRENT_BRANCH" 2>/dev/null; then
|
|
935
|
+
echo ""
|
|
936
|
+
echo "╭──────────────────────────────────────────────────────────────────────────────╮"
|
|
937
|
+
echo "│ ❌ ERROR │"
|
|
938
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
939
|
+
echo "│ │"
|
|
940
|
+
echo "│ Failed to push branch to remote. │"
|
|
941
|
+
echo "│ │"
|
|
942
|
+
echo "│ 💡 Push manually first: │"
|
|
943
|
+
echo "│ • git push -u origin $CURRENT_BRANCH"
|
|
944
|
+
echo "│ │"
|
|
945
|
+
echo "│ Then try again: │"
|
|
946
|
+
echo "│ • /pfGithubPr $TASK_ID │"
|
|
947
|
+
echo "│ │"
|
|
948
|
+
echo "╰──────────────────────────────────────────────────────────────────────────────╯"
|
|
949
|
+
exit 1
|
|
950
|
+
fi
|
|
951
|
+
echo "✅ Branch pushed to origin/$CURRENT_BRANCH"
|
|
952
|
+
echo ""
|
|
953
|
+
fi
|
|
954
|
+
|
|
955
|
+
echo "🔍 Fetching task and repository details..."
|
|
956
|
+
|
|
957
|
+
# Fetch task details from cloud
|
|
958
|
+
TASK_TITLE=""
|
|
959
|
+
TASK_DESCRIPTION=""
|
|
960
|
+
TASK_STATUS=""
|
|
961
|
+
TASK_COMPLEXITY=""
|
|
962
|
+
|
|
963
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
|
964
|
+
--connect-timeout 5 \
|
|
965
|
+
--max-time 10 \
|
|
966
|
+
-X GET \
|
|
967
|
+
-H "Accept: application/json" \
|
|
968
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
969
|
+
"${API_URL}/projects/${PROJECT_ID}/tasks/${TASK_ID}" 2>/dev/null)
|
|
970
|
+
|
|
971
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
972
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
973
|
+
|
|
974
|
+
if [ "$HTTP_CODE" -eq 200 ]; then
|
|
975
|
+
TASK_TITLE=$(echo "$BODY" | jq -r '.data.task.name // .data.name // empty' 2>/dev/null)
|
|
976
|
+
TASK_DESCRIPTION=$(echo "$BODY" | jq -r '.data.task.description // .data.description // empty' 2>/dev/null)
|
|
977
|
+
TASK_STATUS=$(echo "$BODY" | jq -r '.data.task.status // .data.status // empty' 2>/dev/null)
|
|
978
|
+
TASK_COMPLEXITY=$(echo "$BODY" | jq -r '.data.task.complexity // .data.complexity // empty' 2>/dev/null)
|
|
979
|
+
fi
|
|
980
|
+
|
|
981
|
+
# Fallback to local PROJECT_PLAN.md
|
|
982
|
+
if [ -z "$TASK_TITLE" ] && [ -f "PROJECT_PLAN.md" ]; then
|
|
983
|
+
echo "⚠️ Cloud task not found, using local PROJECT_PLAN.md..."
|
|
984
|
+
|
|
985
|
+
# Try table format: | T1.1 | Task Title | Complexity | Status | Dependencies |
|
|
986
|
+
TASK_LINE=$(grep -E "^\|\s*${TASK_ID}\s*\|" PROJECT_PLAN.md | head -1)
|
|
987
|
+
|
|
988
|
+
if [ -n "$TASK_LINE" ]; then
|
|
989
|
+
TASK_TITLE=$(echo "$TASK_LINE" | cut -d'|' -f3 | sed 's/^\s*//' | sed 's/\s*$//')
|
|
990
|
+
TASK_COMPLEXITY=$(echo "$TASK_LINE" | cut -d'|' -f4 | sed 's/^\s*//' | sed 's/\s*$//')
|
|
991
|
+
TASK_STATUS=$(echo "$TASK_LINE" | cut -d'|' -f5 | sed 's/^\s*//' | sed 's/\s*$//')
|
|
992
|
+
else
|
|
993
|
+
# Try markdown heading format: #### **T1.1**: Task Title
|
|
994
|
+
TASK_LINE=$(grep -E "^\s*####\s*\*\*${TASK_ID}\*\*:" PROJECT_PLAN.md | head -1)
|
|
995
|
+
if [ -n "$TASK_LINE" ]; then
|
|
996
|
+
TASK_TITLE=$(echo "$TASK_LINE" | sed 's/.*\*\*:\s*//' | sed 's/\s*$//')
|
|
997
|
+
fi
|
|
998
|
+
fi
|
|
999
|
+
fi
|
|
1000
|
+
|
|
1001
|
+
if [ -z "$TASK_TITLE" ]; then
|
|
1002
|
+
echo "╭──────────────────────────────────────────────────────────────────────────────╮"
|
|
1003
|
+
echo "│ ❌ ERROR │"
|
|
1004
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
1005
|
+
echo "│ │"
|
|
1006
|
+
echo "│ Task not found: $TASK_ID"
|
|
1007
|
+
echo "│ │"
|
|
1008
|
+
echo "│ Make sure the task exists in PROJECT_PLAN.md or is synced to cloud. │"
|
|
1009
|
+
echo "│ │"
|
|
1010
|
+
echo "│ 💡 Try: │"
|
|
1011
|
+
echo "│ • /pfSyncPush Sync your local tasks to cloud │"
|
|
1012
|
+
echo "│ • Check PROJECT_PLAN.md for valid task IDs │"
|
|
1013
|
+
echo "│ │"
|
|
1014
|
+
echo "╰──────────────────────────────────────────────────────────────────────────────╯"
|
|
1015
|
+
exit 1
|
|
1016
|
+
fi
|
|
1017
|
+
|
|
1018
|
+
# Fetch GitHub integration info
|
|
1019
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
|
1020
|
+
--connect-timeout 5 \
|
|
1021
|
+
--max-time 10 \
|
|
1022
|
+
-X GET \
|
|
1023
|
+
-H "Accept: application/json" \
|
|
1024
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
1025
|
+
"${API_URL}/projects/${PROJECT_ID}/integrations/github" 2>/dev/null)
|
|
1026
|
+
|
|
1027
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
1028
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
1029
|
+
|
|
1030
|
+
GITHUB_OWNER=""
|
|
1031
|
+
GITHUB_REPO=""
|
|
1032
|
+
DEFAULT_BRANCH="main"
|
|
1033
|
+
|
|
1034
|
+
if [ "$HTTP_CODE" -eq 200 ]; then
|
|
1035
|
+
GITHUB_OWNER=$(echo "$BODY" | jq -r '.data.owner // empty' 2>/dev/null)
|
|
1036
|
+
GITHUB_REPO=$(echo "$BODY" | jq -r '.data.repo // empty' 2>/dev/null)
|
|
1037
|
+
DEFAULT_BRANCH=$(echo "$BODY" | jq -r '.data.defaultBranch // "main"' 2>/dev/null)
|
|
1038
|
+
fi
|
|
1039
|
+
|
|
1040
|
+
if [ -z "$GITHUB_OWNER" ] || [ -z "$GITHUB_REPO" ]; then
|
|
1041
|
+
echo "╭──────────────────────────────────────────────────────────────────────────────╮"
|
|
1042
|
+
echo "│ ❌ ERROR │"
|
|
1043
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
1044
|
+
echo "│ │"
|
|
1045
|
+
echo "│ No GitHub repository is linked to this project. │"
|
|
1046
|
+
echo "│ │"
|
|
1047
|
+
echo "│ 💡 To link a repository: │"
|
|
1048
|
+
echo "│ • /pfGithubLink owner/repo │"
|
|
1049
|
+
echo "│ │"
|
|
1050
|
+
echo "╰──────────────────────────────────────────────────────────────────────────────╯"
|
|
1051
|
+
exit 1
|
|
1052
|
+
fi
|
|
1053
|
+
|
|
1054
|
+
# Check for linked GitHub issue
|
|
1055
|
+
LINKED_ISSUE_NUMBER=""
|
|
1056
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
|
1057
|
+
--connect-timeout 5 \
|
|
1058
|
+
--max-time 10 \
|
|
1059
|
+
-X GET \
|
|
1060
|
+
-H "Accept: application/json" \
|
|
1061
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
1062
|
+
"${API_URL}/projects/${PROJECT_ID}/tasks/${TASK_ID}/github-issue" 2>/dev/null)
|
|
1063
|
+
|
|
1064
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
1065
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
1066
|
+
|
|
1067
|
+
if [ "$HTTP_CODE" -eq 200 ]; then
|
|
1068
|
+
LINKED_ISSUE_NUMBER=$(echo "$BODY" | jq -r '.data.issueNumber // empty' 2>/dev/null)
|
|
1069
|
+
fi
|
|
1070
|
+
|
|
1071
|
+
echo "🔀 Creating Pull Request for: $TASK_ID - $TASK_TITLE"
|
|
1072
|
+
|
|
1073
|
+
# Build PR title
|
|
1074
|
+
PR_TITLE="[$TASK_ID] $TASK_TITLE"
|
|
1075
|
+
|
|
1076
|
+
# Handle null/empty description
|
|
1077
|
+
if [ -z "$TASK_DESCRIPTION" ] || [ "$TASK_DESCRIPTION" = "null" ]; then
|
|
1078
|
+
TASK_DESCRIPTION="No description provided."
|
|
1079
|
+
fi
|
|
1080
|
+
|
|
1081
|
+
# Build closes directive
|
|
1082
|
+
CLOSES_DIRECTIVE=""
|
|
1083
|
+
if [ -n "$LINKED_ISSUE_NUMBER" ]; then
|
|
1084
|
+
CLOSES_DIRECTIVE="Closes #${LINKED_ISSUE_NUMBER}"$'\n'
|
|
1085
|
+
fi
|
|
1086
|
+
CLOSES_DIRECTIVE="${CLOSES_DIRECTIVE}Closes $TASK_ID"
|
|
1087
|
+
|
|
1088
|
+
# Create PR via API
|
|
1089
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
|
1090
|
+
--connect-timeout 5 \
|
|
1091
|
+
--max-time 15 \
|
|
1092
|
+
-X POST \
|
|
1093
|
+
-H "Content-Type: application/json" \
|
|
1094
|
+
-H "Accept: application/json" \
|
|
1095
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
1096
|
+
-d "$(jq -n \
|
|
1097
|
+
--arg title "$PR_TITLE" \
|
|
1098
|
+
--arg head "$CURRENT_BRANCH" \
|
|
1099
|
+
--arg base "$DEFAULT_BRANCH" \
|
|
1100
|
+
--arg taskId "$TASK_ID" \
|
|
1101
|
+
--arg complexity "${TASK_COMPLEXITY:-Not specified}" \
|
|
1102
|
+
--arg status "${TASK_STATUS:-IN_PROGRESS}" \
|
|
1103
|
+
--arg description "$TASK_DESCRIPTION" \
|
|
1104
|
+
--arg closes "$CLOSES_DIRECTIVE" \
|
|
1105
|
+
--argjson draft "$IS_DRAFT_JSON" \
|
|
1106
|
+
'{
|
|
1107
|
+
title: $title,
|
|
1108
|
+
head: $head,
|
|
1109
|
+
base: $base,
|
|
1110
|
+
taskId: $taskId,
|
|
1111
|
+
draft: $draft,
|
|
1112
|
+
body: "## Summary\n\nThis PR implements task **\($taskId)**: \($title | split("] ") | .[1] // $title)\n\n## Task Details\n\n| Property | Value |\n|----------|-------|\n| Task ID | \($taskId) |\n| Complexity | \($complexity) |\n| Status | \($status) |\n\n## Description\n\n\($description)\n\n## Test Plan\n\n- [ ] TODO: Add test plan\n\n## Checklist\n\n- [ ] Code follows project style guidelines\n- [ ] Tests have been added/updated\n- [ ] Documentation has been updated (if needed)\n\n---\n\n\($closes)\n\n*This PR was created from [PlanFlow](https://planflow.tools) task \($taskId).*"
|
|
1113
|
+
}')" \
|
|
1114
|
+
"${API_URL}/projects/${PROJECT_ID}/integrations/github/pulls")
|
|
1115
|
+
|
|
1116
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
1117
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
1118
|
+
|
|
1119
|
+
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
|
1120
|
+
PR_NUMBER=$(echo "$BODY" | jq -r '.data.prNumber // .data.number // empty')
|
|
1121
|
+
PR_URL=$(echo "$BODY" | jq -r '.data.prUrl // .data.url // .data.html_url // empty')
|
|
1122
|
+
PR_STATE=$(echo "$BODY" | jq -r '.data.state // "open"')
|
|
1123
|
+
|
|
1124
|
+
if [ "$IS_DRAFT" = "true" ]; then
|
|
1125
|
+
PR_STATE="draft"
|
|
1126
|
+
fi
|
|
1127
|
+
|
|
1128
|
+
echo ""
|
|
1129
|
+
echo "╭──────────────────────────────────────────────────────────────────────────────╮"
|
|
1130
|
+
echo "│ ✅ SUCCESS │"
|
|
1131
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
1132
|
+
echo "│ │"
|
|
1133
|
+
if [ "$IS_DRAFT" = "true" ]; then
|
|
1134
|
+
echo "│ Draft Pull Request created! │"
|
|
1135
|
+
else
|
|
1136
|
+
echo "│ Pull Request created successfully! │"
|
|
1137
|
+
fi
|
|
1138
|
+
echo "│ │"
|
|
1139
|
+
echo "│ ── Pull Request Created ───────────────────────────────────────────────── │"
|
|
1140
|
+
echo "│ │"
|
|
1141
|
+
echo "│ 📝 Task: $TASK_ID - $TASK_TITLE"
|
|
1142
|
+
echo "│ 🔀 PR: #$PR_NUMBER"
|
|
1143
|
+
echo "│ 🌿 Branch: $CURRENT_BRANCH → $DEFAULT_BRANCH"
|
|
1144
|
+
echo "│ 🔗 URL: $PR_URL"
|
|
1145
|
+
echo "│ 📊 State: $PR_STATE"
|
|
1146
|
+
echo "│ │"
|
|
1147
|
+
echo "│ ╭────────────────────────────────────────────────────────────────────────╮ │"
|
|
1148
|
+
echo "│ │ ✓ PR linked to task $TASK_ID │ │"
|
|
1149
|
+
if [ -n "$LINKED_ISSUE_NUMBER" ]; then
|
|
1150
|
+
echo "│ │ ✓ Will auto-close issue #$LINKED_ISSUE_NUMBER when merged │ │"
|
|
1151
|
+
fi
|
|
1152
|
+
echo "│ ╰────────────────────────────────────────────────────────────────────────╯ │"
|
|
1153
|
+
echo "│ │"
|
|
1154
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
1155
|
+
echo "│ │"
|
|
1156
|
+
echo "│ 💡 What's Next? │"
|
|
1157
|
+
echo "│ │"
|
|
1158
|
+
if [ "$IS_DRAFT" = "true" ]; then
|
|
1159
|
+
echo "│ • Mark as \"Ready for Review\" when done │"
|
|
1160
|
+
fi
|
|
1161
|
+
echo "│ • Request reviews from team members │"
|
|
1162
|
+
echo "│ • Address any CI/CD feedback │"
|
|
1163
|
+
echo "│ • When approved, merge the PR │"
|
|
1164
|
+
echo "│ │"
|
|
1165
|
+
echo "│ 💡 Tip: When the PR is merged, task $TASK_ID will be │"
|
|
1166
|
+
echo "│ automatically marked as complete! │"
|
|
1167
|
+
echo "│ │"
|
|
1168
|
+
echo "╰──────────────────────────────────────────────────────────────────────────────╯"
|
|
1169
|
+
|
|
1170
|
+
# Open in browser if requested
|
|
1171
|
+
if [ "$NO_OPEN" = "false" ] && [ -n "$PR_URL" ]; then
|
|
1172
|
+
if command -v open &> /dev/null; then
|
|
1173
|
+
open "$PR_URL" 2>/dev/null
|
|
1174
|
+
elif command -v xdg-open &> /dev/null; then
|
|
1175
|
+
xdg-open "$PR_URL" 2>/dev/null
|
|
1176
|
+
elif command -v wslview &> /dev/null; then
|
|
1177
|
+
wslview "$PR_URL" 2>/dev/null
|
|
1178
|
+
fi
|
|
1179
|
+
fi
|
|
1180
|
+
|
|
1181
|
+
exit 0
|
|
1182
|
+
elif [ "$HTTP_CODE" -eq 401 ]; then
|
|
1183
|
+
echo "❌ Authentication failed. Run /pfLogin to refresh."
|
|
1184
|
+
exit 1
|
|
1185
|
+
elif [ "$HTTP_CODE" -eq 403 ]; then
|
|
1186
|
+
echo "❌ Permission denied. You need Editor role and GitHub write access."
|
|
1187
|
+
exit 1
|
|
1188
|
+
elif [ "$HTTP_CODE" -eq 404 ]; then
|
|
1189
|
+
echo "❌ Task, project, or branch not found."
|
|
1190
|
+
exit 1
|
|
1191
|
+
elif [ "$HTTP_CODE" -eq 409 ]; then
|
|
1192
|
+
# PR already exists
|
|
1193
|
+
EXISTING_URL=$(echo "$BODY" | jq -r '.data.existingPrUrl // .data.prUrl // empty')
|
|
1194
|
+
EXISTING_NUMBER=$(echo "$BODY" | jq -r '.data.existingPrNumber // .data.prNumber // empty')
|
|
1195
|
+
EXISTING_STATE=$(echo "$BODY" | jq -r '.data.state // "open"')
|
|
1196
|
+
|
|
1197
|
+
echo ""
|
|
1198
|
+
echo "╭──────────────────────────────────────────────────────────────────────────────╮"
|
|
1199
|
+
echo "│ 🐙 Existing Pull Request Found │"
|
|
1200
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
1201
|
+
echo "│ │"
|
|
1202
|
+
echo "│ A Pull Request already exists for this task. │"
|
|
1203
|
+
echo "│ │"
|
|
1204
|
+
echo "│ ── Pull Request ──────────────────────────────────────────────────────── │"
|
|
1205
|
+
echo "│ │"
|
|
1206
|
+
echo "│ 📝 Task: $TASK_ID - $TASK_TITLE"
|
|
1207
|
+
echo "│ 🔀 PR: #$EXISTING_NUMBER"
|
|
1208
|
+
echo "│ 🔗 URL: $EXISTING_URL"
|
|
1209
|
+
echo "│ 📊 State: $EXISTING_STATE"
|
|
1210
|
+
echo "│ │"
|
|
1211
|
+
echo "│ ╭────────────────────────────────────────────────────────────────────────╮ │"
|
|
1212
|
+
echo "│ │ ✓ PR linked to task $TASK_ID │ │"
|
|
1213
|
+
echo "│ ╰────────────────────────────────────────────────────────────────────────╯ │"
|
|
1214
|
+
echo "│ │"
|
|
1215
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
1216
|
+
echo "│ │"
|
|
1217
|
+
echo "│ 💡 Options: │"
|
|
1218
|
+
echo "│ • View the existing PR in browser │"
|
|
1219
|
+
echo "│ • Close the existing PR to create a new one │"
|
|
1220
|
+
echo "│ │"
|
|
1221
|
+
echo "╰──────────────────────────────────────────────────────────────────────────────╯"
|
|
1222
|
+
|
|
1223
|
+
# Open existing PR
|
|
1224
|
+
if [ "$NO_OPEN" = "false" ] && [ -n "$EXISTING_URL" ]; then
|
|
1225
|
+
if command -v open &> /dev/null; then
|
|
1226
|
+
open "$EXISTING_URL" 2>/dev/null
|
|
1227
|
+
elif command -v xdg-open &> /dev/null; then
|
|
1228
|
+
xdg-open "$EXISTING_URL" 2>/dev/null
|
|
1229
|
+
fi
|
|
1230
|
+
fi
|
|
1231
|
+
exit 0
|
|
1232
|
+
elif [ "$HTTP_CODE" -eq 422 ]; then
|
|
1233
|
+
# Validation error - possibly no commits or branch issue
|
|
1234
|
+
ERROR_MSG=$(echo "$BODY" | jq -r '.error.message // .message // "Validation failed"')
|
|
1235
|
+
echo ""
|
|
1236
|
+
echo "╭──────────────────────────────────────────────────────────────────────────────╮"
|
|
1237
|
+
echo "│ ❌ ERROR │"
|
|
1238
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
1239
|
+
echo "│ │"
|
|
1240
|
+
echo "│ Cannot create Pull Request: $ERROR_MSG"
|
|
1241
|
+
echo "│ │"
|
|
1242
|
+
echo "│ Possible reasons: │"
|
|
1243
|
+
echo "│ • No commits between branches │"
|
|
1244
|
+
echo "│ • Branch doesn't exist on remote │"
|
|
1245
|
+
echo "│ • A PR already exists for this branch │"
|
|
1246
|
+
echo "│ │"
|
|
1247
|
+
echo "│ 💡 Try: │"
|
|
1248
|
+
echo "│ • Make sure you have commits to include │"
|
|
1249
|
+
echo "│ • git push origin $CURRENT_BRANCH"
|
|
1250
|
+
echo "│ • Check existing PRs on GitHub │"
|
|
1251
|
+
echo "│ │"
|
|
1252
|
+
echo "╰──────────────────────────────────────────────────────────────────────────────╯"
|
|
1253
|
+
exit 1
|
|
1254
|
+
else
|
|
1255
|
+
echo "╭──────────────────────────────────────────────────────────────────────────────╮"
|
|
1256
|
+
echo "│ ❌ ERROR │"
|
|
1257
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
1258
|
+
echo "│ │"
|
|
1259
|
+
echo "│ Failed to create Pull Request (HTTP $HTTP_CODE)"
|
|
1260
|
+
echo "│ │"
|
|
1261
|
+
echo "│ Possible reasons: │"
|
|
1262
|
+
echo "│ • GitHub API rate limit exceeded │"
|
|
1263
|
+
echo "│ • GitHub integration token expired │"
|
|
1264
|
+
echo "│ • Repository permissions issue │"
|
|
1265
|
+
echo "│ │"
|
|
1266
|
+
echo "│ 💡 Try again later, or check: │"
|
|
1267
|
+
echo "│ • /pfGithubLink Verify GitHub integration status │"
|
|
1268
|
+
echo "│ │"
|
|
1269
|
+
echo "╰──────────────────────────────────────────────────────────────────────────────╯"
|
|
1270
|
+
exit 1
|
|
1271
|
+
fi
|
|
1272
|
+
```
|
|
1273
|
+
|
|
1274
|
+
## Testing
|
|
1275
|
+
|
|
1276
|
+
```bash
|
|
1277
|
+
# Test 1: Create PR for valid task
|
|
1278
|
+
/pfGithubPr T2.1
|
|
1279
|
+
# Expected: Creates PR and opens in browser
|
|
1280
|
+
|
|
1281
|
+
# Test 2: Create draft PR
|
|
1282
|
+
/pfGithubPr T2.1 --draft
|
|
1283
|
+
# Expected: Creates draft PR
|
|
1284
|
+
|
|
1285
|
+
# Test 3: Create without opening browser
|
|
1286
|
+
/pfGithubPr T2.1 --no-open
|
|
1287
|
+
# Expected: Creates PR, shows URL but doesn't open
|
|
1288
|
+
|
|
1289
|
+
# Test 4: PR already exists
|
|
1290
|
+
/pfGithubPr T2.1
|
|
1291
|
+
# Expected: Shows existing PR info
|
|
1292
|
+
|
|
1293
|
+
# Test 5: Invalid task ID
|
|
1294
|
+
/pfGithubPr invalid
|
|
1295
|
+
# Expected: Error with format hint
|
|
1296
|
+
|
|
1297
|
+
# Test 6: Task not found
|
|
1298
|
+
/pfGithubPr T99.99
|
|
1299
|
+
# Expected: Error with suggestions
|
|
1300
|
+
|
|
1301
|
+
# Test 7: Not on feature branch
|
|
1302
|
+
git checkout main
|
|
1303
|
+
/pfGithubPr T2.1
|
|
1304
|
+
# Expected: Warning about being on main
|
|
1305
|
+
|
|
1306
|
+
# Test 8: Branch not pushed
|
|
1307
|
+
git checkout -b new-branch
|
|
1308
|
+
/pfGithubPr T2.1
|
|
1309
|
+
# Expected: Pushes branch first, then creates PR
|
|
1310
|
+
|
|
1311
|
+
# Test 9: Not authenticated
|
|
1312
|
+
# (Clear token first)
|
|
1313
|
+
/pfGithubPr T2.1
|
|
1314
|
+
# Expected: Error "Not authenticated"
|
|
1315
|
+
|
|
1316
|
+
# Test 10: GitHub not linked
|
|
1317
|
+
# (Unlink GitHub first)
|
|
1318
|
+
/pfGithubPr T2.1
|
|
1319
|
+
# Expected: Error "No GitHub repository linked"
|
|
1320
|
+
```
|
|
1321
|
+
|
|
1322
|
+
## Success Criteria
|
|
1323
|
+
|
|
1324
|
+
- [ ] Creates GitHub PR from task ID with proper title/body
|
|
1325
|
+
- [ ] Opens created PR in browser by default
|
|
1326
|
+
- [ ] --draft flag creates draft PR
|
|
1327
|
+
- [ ] --no-open flag prevents browser opening
|
|
1328
|
+
- [ ] Handles existing PRs gracefully (shows link)
|
|
1329
|
+
- [ ] Links to related GitHub issue if one exists
|
|
1330
|
+
- [ ] Auto-pushes branch if not on remote
|
|
1331
|
+
- [ ] Warns if on main/master branch
|
|
1332
|
+
- [ ] Falls back to local PROJECT_PLAN.md when cloud unavailable
|
|
1333
|
+
- [ ] Shows helpful next steps after creation
|
|
1334
|
+
- [ ] Validates prerequisites (auth, project, GitHub link, git repo)
|
|
1335
|
+
- [ ] Works in both English and Georgian
|