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,963 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pfGithubIssue
|
|
3
|
+
description: Create a GitHub Issue from a PlanFlow task
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# PlanFlow GitHub Issue
|
|
7
|
+
|
|
8
|
+
Create a GitHub Issue from a task ID. The issue title, body, and labels are automatically generated from task details.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
/pfGithubIssue T2.1 # Create GitHub issue from task
|
|
14
|
+
/pfGithubIssue T2.1 --open # Create and open in browser (default)
|
|
15
|
+
/pfGithubIssue 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 shouldOpen = !noOpen // Default is to open in browser
|
|
90
|
+
|
|
91
|
+
if (!taskId || !taskIdPattern.test(taskId)) {
|
|
92
|
+
showUsageError()
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Usage error output:**
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
100
|
+
│ ❌ ERROR │
|
|
101
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
102
|
+
│ │
|
|
103
|
+
│ {t.github.issue.invalidTaskId} │
|
|
104
|
+
│ │
|
|
105
|
+
│ Invalid or missing task ID. │
|
|
106
|
+
│ │
|
|
107
|
+
│ Usage: /pfGithubIssue <task-id> │
|
|
108
|
+
│ │
|
|
109
|
+
│ Examples: │
|
|
110
|
+
│ • /pfGithubIssue T2.1 │
|
|
111
|
+
│ • /pfGithubIssue T2.1 --no-open │
|
|
112
|
+
│ │
|
|
113
|
+
│ Task ID should be like: T1.1, T2.3, T10.5 │
|
|
114
|
+
│ │
|
|
115
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Step 2: Validate Prerequisites
|
|
119
|
+
|
|
120
|
+
### 2a: Check Authentication
|
|
121
|
+
|
|
122
|
+
If not authenticated:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
126
|
+
│ ❌ ERROR │
|
|
127
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
128
|
+
│ │
|
|
129
|
+
│ {t.commands.sync.notAuthenticated} │
|
|
130
|
+
│ │
|
|
131
|
+
│ You must be logged in to create GitHub issues. │
|
|
132
|
+
│ │
|
|
133
|
+
│ 💡 Next Steps: │
|
|
134
|
+
│ • /pfLogin Sign in to PlanFlow │
|
|
135
|
+
│ │
|
|
136
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 2b: Check Project Link
|
|
140
|
+
|
|
141
|
+
If not linked to a cloud project:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
145
|
+
│ ❌ ERROR │
|
|
146
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
147
|
+
│ │
|
|
148
|
+
│ {t.commands.sync.notLinked} │
|
|
149
|
+
│ │
|
|
150
|
+
│ You must link to a cloud project first. │
|
|
151
|
+
│ │
|
|
152
|
+
│ 💡 Next Steps: │
|
|
153
|
+
│ • /pfCloudLink Link to existing project │
|
|
154
|
+
│ • /pfCloudNew Create new cloud project │
|
|
155
|
+
│ │
|
|
156
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 2c: Check GitHub Integration
|
|
160
|
+
|
|
161
|
+
If no GitHub repository is linked:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
165
|
+
│ ❌ ERROR │
|
|
166
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
167
|
+
│ │
|
|
168
|
+
│ {t.commands.github.notLinked} │
|
|
169
|
+
│ │
|
|
170
|
+
│ No GitHub repository is linked to this project. │
|
|
171
|
+
│ │
|
|
172
|
+
│ 💡 To link a repository: │
|
|
173
|
+
│ • /pfGithubLink owner/repo │
|
|
174
|
+
│ │
|
|
175
|
+
│ Example: │
|
|
176
|
+
│ • /pfGithubLink microsoft/vscode │
|
|
177
|
+
│ │
|
|
178
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Step 3: Fetch Task Details
|
|
182
|
+
|
|
183
|
+
### 3a: Try Cloud API First
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
|
187
|
+
--connect-timeout 5 \
|
|
188
|
+
--max-time 10 \
|
|
189
|
+
-X GET \
|
|
190
|
+
-H "Accept: application/json" \
|
|
191
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
192
|
+
"${API_URL}/projects/${PROJECT_ID}/tasks/${TASK_ID}")
|
|
193
|
+
|
|
194
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
195
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
196
|
+
|
|
197
|
+
if [ "$HTTP_CODE" -eq 200 ]; then
|
|
198
|
+
TASK_TITLE=$(echo "$BODY" | jq -r '.data.task.name // .data.name // empty')
|
|
199
|
+
TASK_DESCRIPTION=$(echo "$BODY" | jq -r '.data.task.description // .data.description // empty')
|
|
200
|
+
TASK_STATUS=$(echo "$BODY" | jq -r '.data.task.status // .data.status // empty')
|
|
201
|
+
TASK_COMPLEXITY=$(echo "$BODY" | jq -r '.data.task.complexity // .data.complexity // empty')
|
|
202
|
+
fi
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 3b: Fallback to Local PROJECT_PLAN.md
|
|
206
|
+
|
|
207
|
+
If cloud API fails or task not found, parse from local file:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# Extract task details from PROJECT_PLAN.md
|
|
211
|
+
# Try table format first: | T1.1 | Task Title | Complexity | Status | Dependencies |
|
|
212
|
+
TASK_LINE=$(grep -E "^\|\s*${TASK_ID}\s*\|" PROJECT_PLAN.md | head -1)
|
|
213
|
+
|
|
214
|
+
if [ -n "$TASK_LINE" ]; then
|
|
215
|
+
TASK_TITLE=$(echo "$TASK_LINE" | cut -d'|' -f3 | sed 's/^\s*//' | sed 's/\s*$//')
|
|
216
|
+
TASK_COMPLEXITY=$(echo "$TASK_LINE" | cut -d'|' -f4 | sed 's/^\s*//' | sed 's/\s*$//')
|
|
217
|
+
TASK_STATUS=$(echo "$TASK_LINE" | cut -d'|' -f5 | sed 's/^\s*//' | sed 's/\s*$//')
|
|
218
|
+
else
|
|
219
|
+
# Try markdown heading format: #### **T1.1**: Task Title
|
|
220
|
+
TASK_LINE=$(grep -E "^\s*####\s*\*\*${TASK_ID}\*\*:" PROJECT_PLAN.md | head -1)
|
|
221
|
+
if [ -n "$TASK_LINE" ]; then
|
|
222
|
+
TASK_TITLE=$(echo "$TASK_LINE" | sed 's/.*\*\*:\s*//' | sed 's/\s*$//')
|
|
223
|
+
fi
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
if [ -z "$TASK_TITLE" ]; then
|
|
227
|
+
echo "❌ Task not found: $TASK_ID"
|
|
228
|
+
exit 1
|
|
229
|
+
fi
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Step 4: Check if Issue Already Exists
|
|
233
|
+
|
|
234
|
+
### 4a: Query Existing Issues
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
# Check if an issue already exists for this task
|
|
238
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
|
239
|
+
--connect-timeout 5 \
|
|
240
|
+
--max-time 10 \
|
|
241
|
+
-X GET \
|
|
242
|
+
-H "Accept: application/json" \
|
|
243
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
244
|
+
"${API_URL}/projects/${PROJECT_ID}/tasks/${TASK_ID}/github-issue")
|
|
245
|
+
|
|
246
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
247
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
248
|
+
|
|
249
|
+
if [ "$HTTP_CODE" -eq 200 ]; then
|
|
250
|
+
EXISTING_ISSUE_NUMBER=$(echo "$BODY" | jq -r '.data.issueNumber // empty')
|
|
251
|
+
EXISTING_ISSUE_URL=$(echo "$BODY" | jq -r '.data.issueUrl // empty')
|
|
252
|
+
|
|
253
|
+
if [ -n "$EXISTING_ISSUE_NUMBER" ]; then
|
|
254
|
+
# Issue already exists
|
|
255
|
+
showExistingIssue
|
|
256
|
+
fi
|
|
257
|
+
fi
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### 4b: Show Existing Issue Warning
|
|
261
|
+
|
|
262
|
+
If an issue already exists:
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
266
|
+
│ ⚠️ Issue Already Exists │
|
|
267
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
268
|
+
│ │
|
|
269
|
+
│ {t.github.issue.alreadyExists} │
|
|
270
|
+
│ │
|
|
271
|
+
│ ── Existing Issue ──────────────────────────────────────────────────────── │
|
|
272
|
+
│ │
|
|
273
|
+
│ 📝 Task: T2.1 - Implement login API │
|
|
274
|
+
│ 🐙 Issue: #42 │
|
|
275
|
+
│ 🔗 URL: https://github.com/owner/repo/issues/42 │
|
|
276
|
+
│ 📊 State: open │
|
|
277
|
+
│ │
|
|
278
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
279
|
+
│ │
|
|
280
|
+
│ 💡 Options: │
|
|
281
|
+
│ • View the existing issue in browser │
|
|
282
|
+
│ • Close the existing issue first to create a new one │
|
|
283
|
+
│ │
|
|
284
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Step 5: Create GitHub Issue
|
|
288
|
+
|
|
289
|
+
### 5a: Build Issue Body
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
function buildIssueBody(task) {
|
|
293
|
+
const body = `## Task Details
|
|
294
|
+
|
|
295
|
+
**Task ID:** ${task.id}
|
|
296
|
+
**Complexity:** ${task.complexity || 'Not specified'}
|
|
297
|
+
**Status:** ${task.status || 'TODO'}
|
|
298
|
+
|
|
299
|
+
## Description
|
|
300
|
+
|
|
301
|
+
${task.description || 'No description provided.'}
|
|
302
|
+
|
|
303
|
+
## Dependencies
|
|
304
|
+
|
|
305
|
+
${task.dependencies && task.dependencies.length > 0
|
|
306
|
+
? task.dependencies.map(d => `- ${d}`).join('\n')
|
|
307
|
+
: 'None'}
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
*This issue was created from [PlanFlow](https://planflow.tools) task ${task.id}.*
|
|
312
|
+
*Closes ${task.id} when merged.*
|
|
313
|
+
`
|
|
314
|
+
return body
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### 5b: Determine Labels
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
function getLabels(task) {
|
|
322
|
+
const labels = []
|
|
323
|
+
|
|
324
|
+
// Add complexity label
|
|
325
|
+
if (task.complexity) {
|
|
326
|
+
const complexity = task.complexity.toLowerCase()
|
|
327
|
+
if (complexity === 'low') labels.push('easy')
|
|
328
|
+
else if (complexity === 'medium') labels.push('medium')
|
|
329
|
+
else if (complexity === 'high') labels.push('hard')
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Add status label
|
|
333
|
+
if (task.status === 'TODO') labels.push('todo')
|
|
334
|
+
else if (task.status === 'IN_PROGRESS') labels.push('in-progress')
|
|
335
|
+
else if (task.status === 'BLOCKED') labels.push('blocked')
|
|
336
|
+
|
|
337
|
+
return labels
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### 5c: Create Issue via API
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
# Build request body
|
|
345
|
+
ISSUE_TITLE="[$TASK_ID] $TASK_TITLE"
|
|
346
|
+
ISSUE_BODY=$(cat <<EOF
|
|
347
|
+
## Task Details
|
|
348
|
+
|
|
349
|
+
**Task ID:** $TASK_ID
|
|
350
|
+
**Complexity:** $TASK_COMPLEXITY
|
|
351
|
+
**Status:** $TASK_STATUS
|
|
352
|
+
|
|
353
|
+
## Description
|
|
354
|
+
|
|
355
|
+
${TASK_DESCRIPTION:-No description provided.}
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
*This issue was created from [PlanFlow](https://planflow.tools) task $TASK_ID.*
|
|
360
|
+
*Closes $TASK_ID when merged.*
|
|
361
|
+
EOF
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# Create issue
|
|
365
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
|
366
|
+
--connect-timeout 5 \
|
|
367
|
+
--max-time 15 \
|
|
368
|
+
-X POST \
|
|
369
|
+
-H "Content-Type: application/json" \
|
|
370
|
+
-H "Accept: application/json" \
|
|
371
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
372
|
+
-d "$(jq -n \
|
|
373
|
+
--arg title "$ISSUE_TITLE" \
|
|
374
|
+
--arg body "$ISSUE_BODY" \
|
|
375
|
+
--arg taskId "$TASK_ID" \
|
|
376
|
+
'{title: $title, body: $body, taskId: $taskId}')" \
|
|
377
|
+
"${API_URL}/projects/${PROJECT_ID}/integrations/github/issues")
|
|
378
|
+
|
|
379
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
380
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### 5d: Handle Response
|
|
384
|
+
|
|
385
|
+
**Success (201):**
|
|
386
|
+
|
|
387
|
+
```
|
|
388
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
389
|
+
│ ✅ SUCCESS │
|
|
390
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
391
|
+
│ │
|
|
392
|
+
│ {t.github.issue.created} │
|
|
393
|
+
│ │
|
|
394
|
+
│ ── Issue Created ────────────────────────────────────────────────────── │
|
|
395
|
+
│ │
|
|
396
|
+
│ 📝 Task: T2.1 - Implement login API │
|
|
397
|
+
│ 🐙 Issue: #42 │
|
|
398
|
+
│ 🔗 URL: https://github.com/owner/repo/issues/42 │
|
|
399
|
+
│ 📊 State: open │
|
|
400
|
+
│ │
|
|
401
|
+
│ ╭────────────────────────────────────────────────────────────────────────╮ │
|
|
402
|
+
│ │ ✓ Issue linked to task T2.1 │ │
|
|
403
|
+
│ ╰────────────────────────────────────────────────────────────────────────╯ │
|
|
404
|
+
│ │
|
|
405
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
406
|
+
│ │
|
|
407
|
+
│ 💡 What's Next? │
|
|
408
|
+
│ │
|
|
409
|
+
│ Create a branch to work on this issue: │
|
|
410
|
+
│ • /pfGithubBranch T2.1 │
|
|
411
|
+
│ │
|
|
412
|
+
│ When done, create a pull request: │
|
|
413
|
+
│ • /pfGithubPr T2.1 │
|
|
414
|
+
│ │
|
|
415
|
+
│ 💡 Tip: Include "Closes #42" in your PR description │
|
|
416
|
+
│ to auto-close the issue when merged! │
|
|
417
|
+
│ │
|
|
418
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
419
|
+
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**GitHub Not Linked (400):**
|
|
423
|
+
|
|
424
|
+
```
|
|
425
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
426
|
+
│ ❌ ERROR │
|
|
427
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
428
|
+
│ │
|
|
429
|
+
│ {t.commands.github.notLinked} │
|
|
430
|
+
│ │
|
|
431
|
+
│ No GitHub repository is linked to this project. │
|
|
432
|
+
│ │
|
|
433
|
+
│ 💡 To link a repository: │
|
|
434
|
+
│ • /pfGithubLink owner/repo │
|
|
435
|
+
│ │
|
|
436
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**Task Not Found (404):**
|
|
440
|
+
|
|
441
|
+
```
|
|
442
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
443
|
+
│ ❌ ERROR │
|
|
444
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
445
|
+
│ │
|
|
446
|
+
│ {t.github.issue.taskNotFound} │
|
|
447
|
+
│ │
|
|
448
|
+
│ Task not found: T99.1 │
|
|
449
|
+
│ │
|
|
450
|
+
│ Make sure the task exists in your PROJECT_PLAN.md or cloud project. │
|
|
451
|
+
│ │
|
|
452
|
+
│ 💡 Try: │
|
|
453
|
+
│ • /pfSyncPush Sync your local tasks to cloud │
|
|
454
|
+
│ • Check PROJECT_PLAN.md for valid task IDs │
|
|
455
|
+
│ │
|
|
456
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
**GitHub API Error (502/503):**
|
|
460
|
+
|
|
461
|
+
```
|
|
462
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
463
|
+
│ ❌ ERROR │
|
|
464
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
465
|
+
│ │
|
|
466
|
+
│ {t.github.issue.githubError} │
|
|
467
|
+
│ │
|
|
468
|
+
│ GitHub API error. Could not create the issue. │
|
|
469
|
+
│ │
|
|
470
|
+
│ Possible reasons: │
|
|
471
|
+
│ • GitHub API rate limit exceeded │
|
|
472
|
+
│ • GitHub integration token expired │
|
|
473
|
+
│ • Repository permissions issue │
|
|
474
|
+
│ │
|
|
475
|
+
│ 💡 Try again in a few moments, or check: │
|
|
476
|
+
│ • /pfGithubLink Verify GitHub integration status │
|
|
477
|
+
│ │
|
|
478
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Step 6: Open Issue in Browser (Optional)
|
|
482
|
+
|
|
483
|
+
If `--no-open` flag is not set and issue was created successfully:
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
# Open in default browser
|
|
487
|
+
if [ "$SHOULD_OPEN" = "true" ] && [ -n "$ISSUE_URL" ]; then
|
|
488
|
+
# macOS
|
|
489
|
+
if command -v open &> /dev/null; then
|
|
490
|
+
open "$ISSUE_URL"
|
|
491
|
+
# Linux
|
|
492
|
+
elif command -v xdg-open &> /dev/null; then
|
|
493
|
+
xdg-open "$ISSUE_URL"
|
|
494
|
+
# Windows (WSL)
|
|
495
|
+
elif command -v wslview &> /dev/null; then
|
|
496
|
+
wslview "$ISSUE_URL"
|
|
497
|
+
fi
|
|
498
|
+
fi
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
## Error Handling
|
|
502
|
+
|
|
503
|
+
### Network Error
|
|
504
|
+
|
|
505
|
+
```
|
|
506
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
507
|
+
│ ❌ ERROR │
|
|
508
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
509
|
+
│ │
|
|
510
|
+
│ Network error. Could not connect to PlanFlow API. │
|
|
511
|
+
│ │
|
|
512
|
+
│ Please check your internet connection and try again. │
|
|
513
|
+
│ │
|
|
514
|
+
│ 💡 To retry: │
|
|
515
|
+
│ • /pfGithubIssue T2.1 │
|
|
516
|
+
│ │
|
|
517
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### Token Expired (401)
|
|
521
|
+
|
|
522
|
+
```
|
|
523
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
524
|
+
│ ❌ ERROR │
|
|
525
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
526
|
+
│ │
|
|
527
|
+
│ Authentication failed. Your session may have expired. │
|
|
528
|
+
│ │
|
|
529
|
+
│ 💡 To re-authenticate: │
|
|
530
|
+
│ • /pfLogin │
|
|
531
|
+
│ │
|
|
532
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### Permission Denied (403)
|
|
536
|
+
|
|
537
|
+
```
|
|
538
|
+
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
539
|
+
│ ❌ ERROR │
|
|
540
|
+
├──────────────────────────────────────────────────────────────────────────────┤
|
|
541
|
+
│ │
|
|
542
|
+
│ Permission denied. You don't have access to create issues. │
|
|
543
|
+
│ │
|
|
544
|
+
│ To create GitHub issues, you need: │
|
|
545
|
+
│ • Editor role or higher in the PlanFlow project │
|
|
546
|
+
│ • Write access to the linked GitHub repository │
|
|
547
|
+
│ │
|
|
548
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
## Translation Keys
|
|
552
|
+
|
|
553
|
+
Add to `locales/en.json` and `locales/ka.json`:
|
|
554
|
+
|
|
555
|
+
```json
|
|
556
|
+
{
|
|
557
|
+
"github": {
|
|
558
|
+
"issue": {
|
|
559
|
+
"title": "Create GitHub Issue",
|
|
560
|
+
"created": "GitHub issue created successfully!",
|
|
561
|
+
"alreadyExists": "An issue already exists for this task.",
|
|
562
|
+
"taskNotFound": "Task not found.",
|
|
563
|
+
"invalidTaskId": "Invalid task ID format.",
|
|
564
|
+
"githubError": "GitHub API error.",
|
|
565
|
+
"permissionDenied": "Permission denied.",
|
|
566
|
+
"notLinked": "No GitHub repository linked.",
|
|
567
|
+
"usage": "Usage: /pfGithubIssue <task-id>",
|
|
568
|
+
"example": "Example: /pfGithubIssue T2.1",
|
|
569
|
+
"task": "Task:",
|
|
570
|
+
"issue": "Issue:",
|
|
571
|
+
"url": "URL:",
|
|
572
|
+
"state": "State:",
|
|
573
|
+
"linked": "Issue linked to task",
|
|
574
|
+
"whatsNext": "What's Next?",
|
|
575
|
+
"createBranch": "Create a branch to work on this issue:",
|
|
576
|
+
"createPr": "When done, create a pull request:",
|
|
577
|
+
"autoCloseTip": "Include \"Closes #{issue}\" in your PR description to auto-close the issue!"
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
**Georgian translations:**
|
|
584
|
+
|
|
585
|
+
```json
|
|
586
|
+
{
|
|
587
|
+
"github": {
|
|
588
|
+
"issue": {
|
|
589
|
+
"title": "GitHub Issue-ის შექმნა",
|
|
590
|
+
"created": "GitHub issue წარმატებით შეიქმნა!",
|
|
591
|
+
"alreadyExists": "ამ ამოცანისთვის issue უკვე არსებობს.",
|
|
592
|
+
"taskNotFound": "ამოცანა ვერ მოიძებნა.",
|
|
593
|
+
"invalidTaskId": "ამოცანის ID-ის არასწორი ფორმატი.",
|
|
594
|
+
"githubError": "GitHub API-ის შეცდომა.",
|
|
595
|
+
"permissionDenied": "წვდომა აკრძალულია.",
|
|
596
|
+
"notLinked": "GitHub რეპოზიტორია არ არის დაკავშირებული.",
|
|
597
|
+
"usage": "გამოყენება: /pfGithubIssue <task-id>",
|
|
598
|
+
"example": "მაგალითი: /pfGithubIssue T2.1",
|
|
599
|
+
"task": "ამოცანა:",
|
|
600
|
+
"issue": "Issue:",
|
|
601
|
+
"url": "URL:",
|
|
602
|
+
"state": "სტატუსი:",
|
|
603
|
+
"linked": "Issue დაკავშირებულია ამოცანასთან",
|
|
604
|
+
"whatsNext": "შემდეგი ნაბიჯები?",
|
|
605
|
+
"createBranch": "შექმენი ბრანჩი ამ issue-ზე სამუშაოდ:",
|
|
606
|
+
"createPr": "დასრულებისას შექმენი pull request:",
|
|
607
|
+
"autoCloseTip": "ჩასვი \"Closes #{issue}\" PR-ის აღწერაში issue-ის ავტომატურად დასახურად!"
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
## Full Bash Implementation
|
|
614
|
+
|
|
615
|
+
```bash
|
|
616
|
+
#!/bin/bash
|
|
617
|
+
|
|
618
|
+
# Step 0: Load config
|
|
619
|
+
GLOBAL_CONFIG_PATH="$HOME/.config/claude/plan-plugin-config.json"
|
|
620
|
+
LOCAL_CONFIG_PATH="./.plan-config.json"
|
|
621
|
+
|
|
622
|
+
# Read configs and merge
|
|
623
|
+
if [ -f "$GLOBAL_CONFIG_PATH" ]; then
|
|
624
|
+
API_TOKEN=$(jq -r '.cloud.apiToken // empty' "$GLOBAL_CONFIG_PATH")
|
|
625
|
+
API_URL=$(jq -r '.cloud.apiUrl // "https://api.planflow.tools"' "$GLOBAL_CONFIG_PATH")
|
|
626
|
+
fi
|
|
627
|
+
|
|
628
|
+
if [ -f "$LOCAL_CONFIG_PATH" ]; then
|
|
629
|
+
PROJECT_ID=$(jq -r '.cloud.projectId // empty' "$LOCAL_CONFIG_PATH")
|
|
630
|
+
# Local can override global
|
|
631
|
+
LOCAL_TOKEN=$(jq -r '.cloud.apiToken // empty' "$LOCAL_CONFIG_PATH")
|
|
632
|
+
[ -n "$LOCAL_TOKEN" ] && API_TOKEN="$LOCAL_TOKEN"
|
|
633
|
+
fi
|
|
634
|
+
|
|
635
|
+
# Parse arguments
|
|
636
|
+
TASK_ID="$1"
|
|
637
|
+
shift
|
|
638
|
+
NO_OPEN=false
|
|
639
|
+
|
|
640
|
+
while [[ $# -gt 0 ]]; do
|
|
641
|
+
case "$1" in
|
|
642
|
+
--no-open)
|
|
643
|
+
NO_OPEN=true
|
|
644
|
+
shift
|
|
645
|
+
;;
|
|
646
|
+
--open)
|
|
647
|
+
NO_OPEN=false
|
|
648
|
+
shift
|
|
649
|
+
;;
|
|
650
|
+
*)
|
|
651
|
+
shift
|
|
652
|
+
;;
|
|
653
|
+
esac
|
|
654
|
+
done
|
|
655
|
+
|
|
656
|
+
# Validate task ID format
|
|
657
|
+
if [ -z "$TASK_ID" ] || ! echo "$TASK_ID" | grep -qiE '^T[0-9]+\.[0-9]+$'; then
|
|
658
|
+
echo "╭──────────────────────────────────────────────────────────────────────────────╮"
|
|
659
|
+
echo "│ ❌ ERROR │"
|
|
660
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
661
|
+
echo "│ │"
|
|
662
|
+
echo "│ Invalid or missing task ID: $TASK_ID │"
|
|
663
|
+
echo "│ │"
|
|
664
|
+
echo "│ Usage: /pfGithubIssue <task-id> │"
|
|
665
|
+
echo "│ │"
|
|
666
|
+
echo "│ Examples: │"
|
|
667
|
+
echo "│ • /pfGithubIssue T2.1 │"
|
|
668
|
+
echo "│ • /pfGithubIssue T2.1 --no-open │"
|
|
669
|
+
echo "│ │"
|
|
670
|
+
echo "│ Task ID should be like: T1.1, T2.3, T10.5 │"
|
|
671
|
+
echo "│ │"
|
|
672
|
+
echo "╰──────────────────────────────────────────────────────────────────────────────╯"
|
|
673
|
+
exit 1
|
|
674
|
+
fi
|
|
675
|
+
|
|
676
|
+
# Normalize task ID to uppercase
|
|
677
|
+
TASK_ID=$(echo "$TASK_ID" | tr '[:lower:]' '[:upper:]')
|
|
678
|
+
|
|
679
|
+
# Validate prerequisites
|
|
680
|
+
if [ -z "$API_TOKEN" ]; then
|
|
681
|
+
echo "❌ Not authenticated. Run /pfLogin first."
|
|
682
|
+
exit 1
|
|
683
|
+
fi
|
|
684
|
+
|
|
685
|
+
if [ -z "$PROJECT_ID" ]; then
|
|
686
|
+
echo "❌ No project linked. Run /pfCloudLink first."
|
|
687
|
+
exit 1
|
|
688
|
+
fi
|
|
689
|
+
|
|
690
|
+
# Fetch task details from cloud
|
|
691
|
+
TASK_TITLE=""
|
|
692
|
+
TASK_DESCRIPTION=""
|
|
693
|
+
TASK_STATUS=""
|
|
694
|
+
TASK_COMPLEXITY=""
|
|
695
|
+
|
|
696
|
+
echo "🔍 Fetching task details..."
|
|
697
|
+
|
|
698
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
|
699
|
+
--connect-timeout 5 \
|
|
700
|
+
--max-time 10 \
|
|
701
|
+
-X GET \
|
|
702
|
+
-H "Accept: application/json" \
|
|
703
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
704
|
+
"${API_URL}/projects/${PROJECT_ID}/tasks/${TASK_ID}" 2>/dev/null)
|
|
705
|
+
|
|
706
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
707
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
708
|
+
|
|
709
|
+
if [ "$HTTP_CODE" -eq 200 ]; then
|
|
710
|
+
TASK_TITLE=$(echo "$BODY" | jq -r '.data.task.name // .data.name // empty' 2>/dev/null)
|
|
711
|
+
TASK_DESCRIPTION=$(echo "$BODY" | jq -r '.data.task.description // .data.description // empty' 2>/dev/null)
|
|
712
|
+
TASK_STATUS=$(echo "$BODY" | jq -r '.data.task.status // .data.status // empty' 2>/dev/null)
|
|
713
|
+
TASK_COMPLEXITY=$(echo "$BODY" | jq -r '.data.task.complexity // .data.complexity // empty' 2>/dev/null)
|
|
714
|
+
fi
|
|
715
|
+
|
|
716
|
+
# Fallback to local PROJECT_PLAN.md
|
|
717
|
+
if [ -z "$TASK_TITLE" ] && [ -f "PROJECT_PLAN.md" ]; then
|
|
718
|
+
echo "⚠️ Cloud task not found, using local PROJECT_PLAN.md..."
|
|
719
|
+
|
|
720
|
+
# Try table format: | T1.1 | Task Title | Complexity | Status | Dependencies |
|
|
721
|
+
TASK_LINE=$(grep -E "^\|\s*${TASK_ID}\s*\|" PROJECT_PLAN.md | head -1)
|
|
722
|
+
|
|
723
|
+
if [ -n "$TASK_LINE" ]; then
|
|
724
|
+
TASK_TITLE=$(echo "$TASK_LINE" | cut -d'|' -f3 | sed 's/^\s*//' | sed 's/\s*$//')
|
|
725
|
+
TASK_COMPLEXITY=$(echo "$TASK_LINE" | cut -d'|' -f4 | sed 's/^\s*//' | sed 's/\s*$//')
|
|
726
|
+
TASK_STATUS=$(echo "$TASK_LINE" | cut -d'|' -f5 | sed 's/^\s*//' | sed 's/\s*$//')
|
|
727
|
+
else
|
|
728
|
+
# Try markdown heading format: #### **T1.1**: Task Title
|
|
729
|
+
TASK_LINE=$(grep -E "^\s*####\s*\*\*${TASK_ID}\*\*:" PROJECT_PLAN.md | head -1)
|
|
730
|
+
if [ -n "$TASK_LINE" ]; then
|
|
731
|
+
TASK_TITLE=$(echo "$TASK_LINE" | sed 's/.*\*\*:\s*//' | sed 's/\s*$//')
|
|
732
|
+
fi
|
|
733
|
+
fi
|
|
734
|
+
fi
|
|
735
|
+
|
|
736
|
+
if [ -z "$TASK_TITLE" ]; then
|
|
737
|
+
echo "╭──────────────────────────────────────────────────────────────────────────────╮"
|
|
738
|
+
echo "│ ❌ ERROR │"
|
|
739
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
740
|
+
echo "│ │"
|
|
741
|
+
echo "│ Task not found: $TASK_ID │"
|
|
742
|
+
echo "│ │"
|
|
743
|
+
echo "│ Make sure the task exists in PROJECT_PLAN.md or is synced to cloud. │"
|
|
744
|
+
echo "│ │"
|
|
745
|
+
echo "│ 💡 Try: │"
|
|
746
|
+
echo "│ • /pfSyncPush Sync your local tasks to cloud │"
|
|
747
|
+
echo "│ • Check PROJECT_PLAN.md for valid task IDs │"
|
|
748
|
+
echo "│ │"
|
|
749
|
+
echo "╰──────────────────────────────────────────────────────────────────────────────╯"
|
|
750
|
+
exit 1
|
|
751
|
+
fi
|
|
752
|
+
|
|
753
|
+
echo "📝 Creating GitHub issue for: $TASK_ID - $TASK_TITLE"
|
|
754
|
+
|
|
755
|
+
# Build issue title and body
|
|
756
|
+
ISSUE_TITLE="[$TASK_ID] $TASK_TITLE"
|
|
757
|
+
|
|
758
|
+
# Handle null/empty description
|
|
759
|
+
if [ -z "$TASK_DESCRIPTION" ] || [ "$TASK_DESCRIPTION" = "null" ]; then
|
|
760
|
+
TASK_DESCRIPTION="No description provided."
|
|
761
|
+
fi
|
|
762
|
+
|
|
763
|
+
# Create issue via API
|
|
764
|
+
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
|
765
|
+
--connect-timeout 5 \
|
|
766
|
+
--max-time 15 \
|
|
767
|
+
-X POST \
|
|
768
|
+
-H "Content-Type: application/json" \
|
|
769
|
+
-H "Accept: application/json" \
|
|
770
|
+
-H "Authorization: Bearer $API_TOKEN" \
|
|
771
|
+
-d "$(jq -n \
|
|
772
|
+
--arg title "$ISSUE_TITLE" \
|
|
773
|
+
--arg taskId "$TASK_ID" \
|
|
774
|
+
--arg complexity "${TASK_COMPLEXITY:-Not specified}" \
|
|
775
|
+
--arg status "${TASK_STATUS:-TODO}" \
|
|
776
|
+
--arg description "$TASK_DESCRIPTION" \
|
|
777
|
+
'{
|
|
778
|
+
title: $title,
|
|
779
|
+
taskId: $taskId,
|
|
780
|
+
body: "## Task Details\n\n**Task ID:** \($taskId)\n**Complexity:** \($complexity)\n**Status:** \($status)\n\n## Description\n\n\($description)\n\n---\n\n*This issue was created from [PlanFlow](https://planflow.tools) task \($taskId).*\n*Closes \($taskId) when merged.*"
|
|
781
|
+
}')" \
|
|
782
|
+
"${API_URL}/projects/${PROJECT_ID}/integrations/github/issues")
|
|
783
|
+
|
|
784
|
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
785
|
+
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
786
|
+
|
|
787
|
+
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
|
788
|
+
ISSUE_NUMBER=$(echo "$BODY" | jq -r '.data.issueNumber // .data.number // empty')
|
|
789
|
+
ISSUE_URL=$(echo "$BODY" | jq -r '.data.issueUrl // .data.url // .data.html_url // empty')
|
|
790
|
+
ISSUE_STATE=$(echo "$BODY" | jq -r '.data.state // "open"')
|
|
791
|
+
|
|
792
|
+
echo ""
|
|
793
|
+
echo "╭──────────────────────────────────────────────────────────────────────────────╮"
|
|
794
|
+
echo "│ ✅ SUCCESS │"
|
|
795
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
796
|
+
echo "│ │"
|
|
797
|
+
echo "│ GitHub issue created successfully! │"
|
|
798
|
+
echo "│ │"
|
|
799
|
+
echo "│ ── Issue Created ──────────────────────────────────────────────────────── │"
|
|
800
|
+
echo "│ │"
|
|
801
|
+
echo "│ 📝 Task: $TASK_ID - $TASK_TITLE"
|
|
802
|
+
echo "│ 🐙 Issue: #$ISSUE_NUMBER"
|
|
803
|
+
echo "│ 🔗 URL: $ISSUE_URL"
|
|
804
|
+
echo "│ 📊 State: $ISSUE_STATE"
|
|
805
|
+
echo "│ │"
|
|
806
|
+
echo "│ ╭────────────────────────────────────────────────────────────────────────╮ │"
|
|
807
|
+
echo "│ │ ✓ Issue linked to task $TASK_ID │ │"
|
|
808
|
+
echo "│ ╰────────────────────────────────────────────────────────────────────────╯ │"
|
|
809
|
+
echo "│ │"
|
|
810
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
811
|
+
echo "│ │"
|
|
812
|
+
echo "│ 💡 What's Next? │"
|
|
813
|
+
echo "│ │"
|
|
814
|
+
echo "│ Create a branch to work on this issue: │"
|
|
815
|
+
echo "│ • /pfGithubBranch $TASK_ID │"
|
|
816
|
+
echo "│ │"
|
|
817
|
+
echo "│ When done, create a pull request: │"
|
|
818
|
+
echo "│ • /pfGithubPr $TASK_ID │"
|
|
819
|
+
echo "│ │"
|
|
820
|
+
echo "│ 💡 Tip: Include \"Closes #$ISSUE_NUMBER\" in your PR description │"
|
|
821
|
+
echo "│ to auto-close the issue when merged! │"
|
|
822
|
+
echo "│ │"
|
|
823
|
+
echo "╰──────────────────────────────────────────────────────────────────────────────╯"
|
|
824
|
+
|
|
825
|
+
# Open in browser if requested
|
|
826
|
+
if [ "$NO_OPEN" = "false" ] && [ -n "$ISSUE_URL" ]; then
|
|
827
|
+
if command -v open &> /dev/null; then
|
|
828
|
+
open "$ISSUE_URL" 2>/dev/null
|
|
829
|
+
elif command -v xdg-open &> /dev/null; then
|
|
830
|
+
xdg-open "$ISSUE_URL" 2>/dev/null
|
|
831
|
+
elif command -v wslview &> /dev/null; then
|
|
832
|
+
wslview "$ISSUE_URL" 2>/dev/null
|
|
833
|
+
fi
|
|
834
|
+
fi
|
|
835
|
+
|
|
836
|
+
exit 0
|
|
837
|
+
elif [ "$HTTP_CODE" -eq 400 ]; then
|
|
838
|
+
ERROR_MSG=$(echo "$BODY" | jq -r '.error.message // .message // "Bad request"')
|
|
839
|
+
echo ""
|
|
840
|
+
echo "╭──────────────────────────────────────────────────────────────────────────────╮"
|
|
841
|
+
echo "│ ❌ ERROR │"
|
|
842
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
843
|
+
echo "│ │"
|
|
844
|
+
echo "│ $ERROR_MSG"
|
|
845
|
+
echo "│ │"
|
|
846
|
+
echo "│ 💡 Make sure GitHub is linked: │"
|
|
847
|
+
echo "│ • /pfGithubLink owner/repo │"
|
|
848
|
+
echo "│ │"
|
|
849
|
+
echo "╰──────────────────────────────────────────────────────────────────────────────╯"
|
|
850
|
+
exit 1
|
|
851
|
+
elif [ "$HTTP_CODE" -eq 401 ]; then
|
|
852
|
+
echo "❌ Authentication failed. Run /pfLogin to refresh."
|
|
853
|
+
exit 1
|
|
854
|
+
elif [ "$HTTP_CODE" -eq 403 ]; then
|
|
855
|
+
echo "❌ Permission denied. You need Editor role and GitHub write access."
|
|
856
|
+
exit 1
|
|
857
|
+
elif [ "$HTTP_CODE" -eq 404 ]; then
|
|
858
|
+
echo "❌ Task or project not found."
|
|
859
|
+
exit 1
|
|
860
|
+
elif [ "$HTTP_CODE" -eq 409 ]; then
|
|
861
|
+
# Issue already exists
|
|
862
|
+
EXISTING_URL=$(echo "$BODY" | jq -r '.data.existingIssueUrl // .data.issueUrl // empty')
|
|
863
|
+
EXISTING_NUMBER=$(echo "$BODY" | jq -r '.data.existingIssueNumber // .data.issueNumber // empty')
|
|
864
|
+
|
|
865
|
+
echo ""
|
|
866
|
+
echo "╭──────────────────────────────────────────────────────────────────────────────╮"
|
|
867
|
+
echo "│ ⚠️ Issue Already Exists │"
|
|
868
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
869
|
+
echo "│ │"
|
|
870
|
+
echo "│ An issue already exists for this task. │"
|
|
871
|
+
echo "│ │"
|
|
872
|
+
echo "│ ── Existing Issue ──────────────────────────────────────────────────────── │"
|
|
873
|
+
echo "│ │"
|
|
874
|
+
echo "│ 📝 Task: $TASK_ID - $TASK_TITLE"
|
|
875
|
+
echo "│ 🐙 Issue: #$EXISTING_NUMBER"
|
|
876
|
+
echo "│ 🔗 URL: $EXISTING_URL"
|
|
877
|
+
echo "│ │"
|
|
878
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
879
|
+
echo "│ │"
|
|
880
|
+
echo "│ 💡 Options: │"
|
|
881
|
+
echo "│ • View the existing issue in browser │"
|
|
882
|
+
echo "│ • Close the existing issue first to create a new one │"
|
|
883
|
+
echo "│ │"
|
|
884
|
+
echo "╰──────────────────────────────────────────────────────────────────────────────╯"
|
|
885
|
+
|
|
886
|
+
# Open existing issue
|
|
887
|
+
if [ "$NO_OPEN" = "false" ] && [ -n "$EXISTING_URL" ]; then
|
|
888
|
+
if command -v open &> /dev/null; then
|
|
889
|
+
open "$EXISTING_URL" 2>/dev/null
|
|
890
|
+
elif command -v xdg-open &> /dev/null; then
|
|
891
|
+
xdg-open "$EXISTING_URL" 2>/dev/null
|
|
892
|
+
fi
|
|
893
|
+
fi
|
|
894
|
+
exit 0
|
|
895
|
+
else
|
|
896
|
+
echo "╭──────────────────────────────────────────────────────────────────────────────╮"
|
|
897
|
+
echo "│ ❌ ERROR │"
|
|
898
|
+
echo "├──────────────────────────────────────────────────────────────────────────────┤"
|
|
899
|
+
echo "│ │"
|
|
900
|
+
echo "│ Failed to create GitHub issue (HTTP $HTTP_CODE) │"
|
|
901
|
+
echo "│ │"
|
|
902
|
+
echo "│ Possible reasons: │"
|
|
903
|
+
echo "│ • GitHub API rate limit exceeded │"
|
|
904
|
+
echo "│ • GitHub integration token expired │"
|
|
905
|
+
echo "│ • Repository permissions issue │"
|
|
906
|
+
echo "│ │"
|
|
907
|
+
echo "│ 💡 Try again later, or check: │"
|
|
908
|
+
echo "│ • /pfGithubLink Verify GitHub integration status │"
|
|
909
|
+
echo "│ │"
|
|
910
|
+
echo "╰──────────────────────────────────────────────────────────────────────────────╯"
|
|
911
|
+
exit 1
|
|
912
|
+
fi
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
## Testing
|
|
916
|
+
|
|
917
|
+
```bash
|
|
918
|
+
# Test 1: Create issue for valid task
|
|
919
|
+
/pfGithubIssue T2.1
|
|
920
|
+
# Expected: Creates issue and opens in browser
|
|
921
|
+
|
|
922
|
+
# Test 2: Create without opening browser
|
|
923
|
+
/pfGithubIssue T2.1 --no-open
|
|
924
|
+
# Expected: Creates issue, shows URL but doesn't open
|
|
925
|
+
|
|
926
|
+
# Test 3: Issue already exists
|
|
927
|
+
/pfGithubIssue T2.1
|
|
928
|
+
# Expected: Shows existing issue info
|
|
929
|
+
|
|
930
|
+
# Test 4: Invalid task ID
|
|
931
|
+
/pfGithubIssue invalid
|
|
932
|
+
# Expected: Error with format hint
|
|
933
|
+
|
|
934
|
+
# Test 5: Task not found
|
|
935
|
+
/pfGithubIssue T99.99
|
|
936
|
+
# Expected: Error with suggestions
|
|
937
|
+
|
|
938
|
+
# Test 6: Not authenticated
|
|
939
|
+
# (Clear token first)
|
|
940
|
+
/pfGithubIssue T2.1
|
|
941
|
+
# Expected: Error "Not authenticated"
|
|
942
|
+
|
|
943
|
+
# Test 7: No project linked
|
|
944
|
+
# (Clear projectId first)
|
|
945
|
+
/pfGithubIssue T2.1
|
|
946
|
+
# Expected: Error "No project linked"
|
|
947
|
+
|
|
948
|
+
# Test 8: GitHub not linked
|
|
949
|
+
# (Unlink GitHub first)
|
|
950
|
+
/pfGithubIssue T2.1
|
|
951
|
+
# Expected: Error "No GitHub repository linked"
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
## Success Criteria
|
|
955
|
+
|
|
956
|
+
- [ ] Creates GitHub issue from task ID with proper title/body
|
|
957
|
+
- [ ] Opens created issue in browser by default
|
|
958
|
+
- [ ] --no-open flag prevents browser opening
|
|
959
|
+
- [ ] Handles existing issues gracefully (shows link)
|
|
960
|
+
- [ ] Falls back to local PROJECT_PLAN.md when cloud unavailable
|
|
961
|
+
- [ ] Shows helpful next steps after creation
|
|
962
|
+
- [ ] Validates prerequisites (auth, project, GitHub link)
|
|
963
|
+
- [ ] Works in both English and Georgian
|