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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -0
  3. package/bin/cli.js +169 -0
  4. package/bin/postinstall.js +87 -0
  5. package/commands/pfActivity/SKILL.md +725 -0
  6. package/commands/pfAssign/SKILL.md +623 -0
  7. package/commands/pfCloudLink/SKILL.md +192 -0
  8. package/commands/pfCloudList/SKILL.md +222 -0
  9. package/commands/pfCloudNew/SKILL.md +187 -0
  10. package/commands/pfCloudUnlink/SKILL.md +152 -0
  11. package/commands/pfComment/SKILL.md +227 -0
  12. package/commands/pfComments/SKILL.md +159 -0
  13. package/commands/pfConnectionStatus/SKILL.md +433 -0
  14. package/commands/pfDiscord/SKILL.md +740 -0
  15. package/commands/pfGithubBranch/SKILL.md +672 -0
  16. package/commands/pfGithubIssue/SKILL.md +963 -0
  17. package/commands/pfGithubLink/SKILL.md +859 -0
  18. package/commands/pfGithubPr/SKILL.md +1335 -0
  19. package/commands/pfGithubUnlink/SKILL.md +401 -0
  20. package/commands/pfLive/SKILL.md +185 -0
  21. package/commands/pfLogin/SKILL.md +249 -0
  22. package/commands/pfLogout/SKILL.md +155 -0
  23. package/commands/pfMyTasks/SKILL.md +198 -0
  24. package/commands/pfNotificationSettings/SKILL.md +619 -0
  25. package/commands/pfNotifications/SKILL.md +420 -0
  26. package/commands/pfNotificationsClear/SKILL.md +421 -0
  27. package/commands/pfReact/SKILL.md +232 -0
  28. package/commands/pfSlack/SKILL.md +659 -0
  29. package/commands/pfSyncPull/SKILL.md +210 -0
  30. package/commands/pfSyncPush/SKILL.md +299 -0
  31. package/commands/pfSyncStatus/SKILL.md +212 -0
  32. package/commands/pfTeamInvite/SKILL.md +161 -0
  33. package/commands/pfTeamList/SKILL.md +253 -0
  34. package/commands/pfTeamRemove/SKILL.md +115 -0
  35. package/commands/pfTeamRole/SKILL.md +160 -0
  36. package/commands/pfTestWebhooks/SKILL.md +722 -0
  37. package/commands/pfUnassign/SKILL.md +134 -0
  38. package/commands/pfWhoami/SKILL.md +258 -0
  39. package/commands/pfWorkload/SKILL.md +219 -0
  40. package/commands/planExportCsv/SKILL.md +106 -0
  41. package/commands/planExportGithub/SKILL.md +222 -0
  42. package/commands/planExportJson/SKILL.md +159 -0
  43. package/commands/planExportSummary/SKILL.md +158 -0
  44. package/commands/planNew/SKILL.md +641 -0
  45. package/commands/planNext/SKILL.md +1200 -0
  46. package/commands/planSettingsAutoSync/SKILL.md +199 -0
  47. package/commands/planSettingsLanguage/SKILL.md +201 -0
  48. package/commands/planSettingsReset/SKILL.md +237 -0
  49. package/commands/planSettingsShow/SKILL.md +482 -0
  50. package/commands/planSpec/SKILL.md +929 -0
  51. package/commands/planUpdate/SKILL.md +2518 -0
  52. package/commands/team/SKILL.md +740 -0
  53. package/locales/en.json +1499 -0
  54. package/locales/ka.json +1499 -0
  55. package/package.json +48 -0
  56. package/templates/PROJECT_PLAN.template.md +157 -0
  57. package/templates/backend-api.template.md +562 -0
  58. package/templates/frontend-spa.template.md +610 -0
  59. package/templates/fullstack.template.md +397 -0
  60. package/templates/ka/backend-api.template.md +562 -0
  61. package/templates/ka/frontend-spa.template.md +610 -0
  62. package/templates/ka/fullstack.template.md +397 -0
  63. package/templates/sections/architecture.md +21 -0
  64. package/templates/sections/overview.md +15 -0
  65. package/templates/sections/tasks.md +22 -0
  66. 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