@davidwells/cogneato 0.4.2 → 0.4.4

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 (61) hide show
  1. package/.github/workflows/claude.yml +1436 -0
  2. package/dist/cognito/cognito-fetch.d.ts +5 -0
  3. package/dist/cognito/cognito-fetch.d.ts.map +1 -1
  4. package/dist/cognito/cognito-fetch.js.map +1 -1
  5. package/dist/cognito/operations/initiate-user-srp-auth.d.ts +3 -1
  6. package/dist/cognito/operations/initiate-user-srp-auth.d.ts.map +1 -1
  7. package/dist/cognito/operations/initiate-user-srp-auth.js +2 -1
  8. package/dist/cognito/operations/initiate-user-srp-auth.js.map +1 -1
  9. package/dist/cognito/operations/respond-device-srp-auth.d.ts +3 -1
  10. package/dist/cognito/operations/respond-device-srp-auth.d.ts.map +1 -1
  11. package/dist/cognito/operations/respond-device-srp-auth.js +2 -1
  12. package/dist/cognito/operations/respond-device-srp-auth.js.map +1 -1
  13. package/dist/cognito/operations/respond-new-password-required.d.ts +3 -1
  14. package/dist/cognito/operations/respond-new-password-required.d.ts.map +1 -1
  15. package/dist/cognito/operations/respond-new-password-required.js +2 -1
  16. package/dist/cognito/operations/respond-new-password-required.js.map +1 -1
  17. package/dist/cognito/operations/respond-password-verifier.d.ts +3 -1
  18. package/dist/cognito/operations/respond-password-verifier.d.ts.map +1 -1
  19. package/dist/cognito/operations/respond-password-verifier.js +2 -1
  20. package/dist/cognito/operations/respond-password-verifier.js.map +1 -1
  21. package/dist/cognito/operations/respond-sms-mfa.d.ts +3 -1
  22. package/dist/cognito/operations/respond-sms-mfa.d.ts.map +1 -1
  23. package/dist/cognito/operations/respond-sms-mfa.js +2 -1
  24. package/dist/cognito/operations/respond-sms-mfa.js.map +1 -1
  25. package/dist/cognito/operations/respond-software-token-mfa.d.ts +3 -1
  26. package/dist/cognito/operations/respond-software-token-mfa.d.ts.map +1 -1
  27. package/dist/cognito/operations/respond-software-token-mfa.js +2 -1
  28. package/dist/cognito/operations/respond-software-token-mfa.js.map +1 -1
  29. package/dist/cognito/types/guards/guard-confirm-device-response.d.ts.map +1 -1
  30. package/dist/cognito/types/guards/guard-confirm-device-response.js +1 -3
  31. package/dist/cognito/types/guards/guard-confirm-device-response.js.map +1 -1
  32. package/dist/login/generator.d.ts +3 -1
  33. package/dist/login/generator.d.ts.map +1 -1
  34. package/dist/login/generator.js +7 -1
  35. package/dist/login/generator.js.map +1 -1
  36. package/dist/login/verify-device.d.ts +3 -1
  37. package/dist/login/verify-device.d.ts.map +1 -1
  38. package/dist/login/verify-device.js +3 -1
  39. package/dist/login/verify-device.js.map +1 -1
  40. package/dist/login/verify-srp.d.ts +3 -1
  41. package/dist/login/verify-srp.d.ts.map +1 -1
  42. package/dist/login/verify-srp.js +2 -1
  43. package/dist/login/verify-srp.js.map +1 -1
  44. package/dist/srp/low-level/_helpers.d.ts.map +1 -1
  45. package/dist/srp/low-level/_helpers.js.map +1 -1
  46. package/dist/srp/low-level/math.d.ts.map +1 -1
  47. package/dist/srp/low-level/math.js.map +1 -1
  48. package/package.json +10 -5
  49. package/src/cognito/cognito-fetch.ts +4 -1
  50. package/src/cognito/operations/initiate-user-srp-auth.ts +4 -1
  51. package/src/cognito/operations/respond-device-srp-auth.ts +4 -1
  52. package/src/cognito/operations/respond-new-password-required.ts +4 -1
  53. package/src/cognito/operations/respond-password-verifier.ts +4 -1
  54. package/src/cognito/operations/respond-sms-mfa.ts +4 -1
  55. package/src/cognito/operations/respond-software-token-mfa.ts +4 -1
  56. package/src/cognito/types/guards/guard-confirm-device-response.ts +1 -5
  57. package/src/login/generator.ts +9 -0
  58. package/src/login/verify-device.ts +5 -0
  59. package/src/login/verify-srp.ts +4 -0
  60. package/src/srp/low-level/_helpers.ts +2 -4
  61. package/src/srp/low-level/math.ts +2 -4
@@ -0,0 +1,1436 @@
1
+ name: Claude Code
2
+
3
+ on:
4
+ # Manual/programmatic trigger
5
+ workflow_dispatch:
6
+ inputs:
7
+ trigger_text:
8
+ description: 'Text to trigger Claude with (optional)'
9
+ required: true
10
+ type: string
11
+ default: '@claude'
12
+ actor:
13
+ description: 'Actor to trigger Claude'
14
+ required: true
15
+ type: string
16
+ default: 'david'
17
+ # PR related events
18
+ pull_request_target:
19
+ types: [opened, synchronize, reopened]
20
+ pull_request_review_comment:
21
+ types: [created]
22
+ pull_request_review:
23
+ types: [submitted]
24
+ # Issue related events (added)
25
+ issues:
26
+ types: [opened, assigned]
27
+ issue_comment:
28
+ types: [created]
29
+
30
+ # Concurrency control (one run per Issue/PR)
31
+ concurrency:
32
+ group: claude-${{ github.repository }}-${{ github.event.number || github.run_id }}
33
+ cancel-in-progress: false
34
+
35
+ jobs:
36
+ setup:
37
+ # Security-focused conditional execution (full support for Issues, PRs, and manual dispatch)
38
+ if: |
39
+ (
40
+ github.event_name == 'workflow_dispatch' &&
41
+ github.event.inputs.trigger_text != '' &&
42
+ github.event.inputs.actor != ''
43
+ ) ||
44
+ (
45
+ github.event_name == 'pull_request_target' &&
46
+ (
47
+ github.event.pull_request.head.repo.full_name == github.repository ||
48
+ contains(fromJSON('["COLLABORATOR", "MEMBER", "OWNER"]'), github.event.pull_request.author_association)
49
+ ) &&
50
+ contains(github.event.pull_request.body, '@claude')
51
+ ) ||
52
+ (
53
+ github.event_name == 'issue_comment' &&
54
+ (
55
+ github.event.sender.login == github.repository_owner ||
56
+ github.event.sender.login == 'github-actions[bot]' ||
57
+ contains(fromJSON('["COLLABORATOR", "MEMBER", "OWNER"]'), github.event.comment.author_association)
58
+ ) &&
59
+ contains(github.event.comment.body, '@claude')
60
+ ) ||
61
+ (
62
+ github.event_name == 'issues' &&
63
+ (
64
+ github.event.sender.login == github.repository_owner ||
65
+ contains(fromJSON('["COLLABORATOR", "MEMBER", "OWNER"]'), github.event.issue.author_association)
66
+ ) &&
67
+ (
68
+ contains(github.event.issue.body, '@claude') ||
69
+ contains(github.event.issue.title, '@claude')
70
+ )
71
+ ) ||
72
+ (
73
+ github.event_name == 'pull_request_review_comment' &&
74
+ (
75
+ github.event.sender.login == github.repository_owner ||
76
+ contains(fromJSON('["COLLABORATOR", "MEMBER", "OWNER"]'), github.event.comment.author_association)
77
+ ) &&
78
+ contains(github.event.comment.body, '@claude')
79
+ ) ||
80
+ (
81
+ github.event_name == 'pull_request_review' &&
82
+ (
83
+ github.event.sender.login == github.repository_owner ||
84
+ contains(fromJSON('["COLLABORATOR", "MEMBER", "OWNER"]'), github.event.review.author_association)
85
+ ) &&
86
+ contains(github.event.review.body, '@claude')
87
+ )
88
+
89
+ runs-on: ubuntu-latest
90
+ timeout-minutes: 2
91
+ permissions:
92
+ # 📁 Content management (highest permissions)
93
+ contents: write
94
+ pull-requests: write
95
+ issues: write
96
+ discussions: write
97
+ # 🔧 Development & CI/CD management
98
+ actions: write
99
+ checks: write
100
+ statuses: write
101
+ pages: write
102
+ deployments: write
103
+ # 📦 Package & security management
104
+ packages: write
105
+ security-events: write
106
+ # 🎯 Project management
107
+ repository-projects: write
108
+ # 🆔 Authentication & token management
109
+ id-token: write
110
+ # Outputs
111
+ outputs:
112
+ should-continue: ${{ steps.should-continue.outputs.should-continue }}
113
+ issue-number: ${{ steps.context-info.outputs.issue-number }}
114
+ pr-number: ${{ steps.context-info.outputs.pr-number }}
115
+ head-ref: ${{ steps.context-info.outputs.head-ref }}
116
+ base-ref: ${{ steps.context-info.outputs.base-ref }}
117
+ head-sha: ${{ steps.context-info.outputs.head-sha }}
118
+ is-pr: ${{ steps.context-info.outputs.is-pr }}
119
+ trigger-text: ${{ steps.context-info.outputs.trigger-text }}
120
+ has-linked-pr: ${{ steps.context-info.outputs.has-linked-pr }}
121
+ status-comment-id: ${{ steps.find_comment.outputs.comment-id || steps.create_comment.outputs.comment-id }}
122
+ ######################
123
+ # Setup steps
124
+ ######################
125
+ steps:
126
+ # Verify user permissions from dispatch
127
+ - name: Check dispatch permissions
128
+ id: check_membership
129
+ if: github.event_name == 'workflow_dispatch'
130
+ uses: actions/github-script@v7
131
+ with:
132
+ script: |
133
+ const allowedUsersInput = '${{ secrets.ALLOWED_USERS }}'
134
+
135
+ // Skip if no allowed users configured
136
+ if (!allowedUsersInput || allowedUsersInput.trim() === '') {
137
+ console.log('No allowed users configured in secrets, allowing all')
138
+ core.setOutput('is_member', true)
139
+ return
140
+ }
141
+
142
+ // Parse the allowed users (comma-separated)
143
+ const allowedUsers = allowedUsersInput.split(',').map(u => u.trim()).filter(u => u)
144
+
145
+ if (allowedUsers.length === 0) {
146
+ console.log('Empty allowed users list, allowing all')
147
+ core.setOutput('is_member', true)
148
+ return
149
+ }
150
+
151
+ let actor
152
+ if (context.eventName === 'issue_comment') {
153
+ actor = context.payload.comment.user.login
154
+ } else if (context.eventName === 'pull_request_review_comment') {
155
+ actor = context.payload.comment.user.login
156
+ } else if (context.eventName === 'pull_request_review') {
157
+ actor = context.payload.review.user.login
158
+ } else if (context.eventName === 'issues') {
159
+ actor = context.payload.issue.user.login
160
+ }
161
+
162
+ console.log(`Checking permissions for user: ${actor}`)
163
+ console.log(`Allowed users: ${allowedUsers.join(', ')}`)
164
+
165
+ if (allowedUsers.includes(actor)) {
166
+ console.log(`User ${actor} is in the allowed list`)
167
+ core.setOutput('is_member', true)
168
+ return
169
+ }
170
+
171
+ // Get organization name from context
172
+ const orgName = context.repo.owner
173
+ console.log(`Organization: ${orgName}`)
174
+
175
+ // Fallback: Check if user has repository permissions
176
+ try {
177
+ const collaboration = await github.rest.repos.getCollaboratorPermissionLevel({
178
+ owner: context.repo.owner,
179
+ repo: context.repo.repo,
180
+ username: actor
181
+ })
182
+
183
+ const permission = collaboration.data.permission
184
+ console.log(`User ${actor} has permission level: ${permission}`)
185
+
186
+ // Allow if user has push access or higher (write, maintain, admin)
187
+ const allowed = ['write', 'maintain', 'admin'].includes(permission)
188
+
189
+ if (allowed) {
190
+ core.setOutput('is_member', true)
191
+ } else {
192
+ core.setFailed(`User ${actor} does not have permission to run this workflow`)
193
+ return
194
+ }
195
+ } catch (error) {
196
+ console.log(`Error checking permissions: ${error.message}`)
197
+
198
+ // Final fallback: Check if user is a public member of the organization
199
+ try {
200
+ const membership = await github.rest.orgs.getMembershipForUser({
201
+ org: orgName,
202
+ username: actor
203
+ })
204
+
205
+ const allowed = membership.data.state === 'active'
206
+
207
+ if (allowed) {
208
+ core.setOutput('is_member', true)
209
+ } else {
210
+ core.setFailed(`User ${actor} does not have permission to run this workflow`)
211
+ return
212
+ }
213
+ } catch (membershipError) {
214
+ console.log(`Error checking organization membership: ${membershipError.message}`)
215
+ core.setFailed(`User ${actor} does not have permission to run this workflow`)
216
+ return
217
+ }
218
+ }
219
+ # Collect information from the event
220
+ - name: Get Context Information
221
+ id: context-info
222
+ uses: actions/github-script@v7
223
+ with:
224
+ script: |
225
+ let issueNumber, prNumber, headRef, baseRef, headSha
226
+ let triggerText = ''
227
+ let hasLinkedPR = false
228
+ let isPR = false
229
+
230
+ if (context.eventName === 'workflow_dispatch') {
231
+ console.log('Workflow dispatch event')
232
+ console.log(context.payload)
233
+ triggerText = context.payload.inputs.trigger_text
234
+ // Automatically add @claude to the trigger text if it's not already there
235
+ if (!triggerText.includes('@claude')) {
236
+ triggerText = '@claude ' + triggerText
237
+ }
238
+ console.log(`Workflow dispatch #${issueNumber}: ${triggerText}`)
239
+
240
+ } else if (context.eventName === 'pull_request_target') {
241
+ // When a PR is created or updated
242
+ isPR = true
243
+ issueNumber = context.payload.pull_request.number
244
+ prNumber = context.payload.pull_request.number
245
+ headRef = context.payload.pull_request.head.ref
246
+ baseRef = context.payload.pull_request.base.ref
247
+ headSha = context.payload.pull_request.head.sha
248
+ triggerText = context.payload.pull_request.body
249
+
250
+ console.log(`PR #${prNumber}: ${baseRef} <- ${headRef} (${headSha})`)
251
+
252
+ } else if (context.eventName === 'issues') {
253
+ // When an Issue is created or assigned
254
+ isPR = false
255
+ issueNumber = context.payload.issue.number
256
+ triggerText = `${context.payload.issue.title} ${context.payload.issue.body}`
257
+
258
+ console.log(`Issue #${issueNumber} created`)
259
+
260
+ } else if (context.eventName === 'issue_comment') {
261
+ // Issue/PR comment
262
+ issueNumber = context.payload.issue.number
263
+ triggerText = context.payload.comment.body
264
+
265
+ if (context.payload.issue.pull_request) {
266
+ // Comment on a PR
267
+ isPR = true
268
+ try {
269
+ const pr = await github.rest.pulls.get({
270
+ owner: context.repo.owner,
271
+ repo: context.repo.repo,
272
+ pull_number: issueNumber
273
+ })
274
+ prNumber = issueNumber
275
+ headRef = pr.data.head.ref
276
+ baseRef = pr.data.base.ref
277
+ headSha = pr.data.head.sha
278
+
279
+ console.log(`PR Comment #${prNumber}: ${baseRef} <- ${headRef}`)
280
+ } catch (error) {
281
+ console.error('Error fetching PR info:', error)
282
+ // In case of error, treat as a regular Issue
283
+ isPR = false
284
+ }
285
+ } else {
286
+ // Regular Issue comment - check for existing linked PRs
287
+ isPR = false
288
+
289
+ try {
290
+ // Get timeline events to find linked pull requests
291
+ const { data: timeline } = await github.rest.issues.listEventsForTimeline({
292
+ owner: context.repo.owner,
293
+ repo: context.repo.repo,
294
+ issue_number: issueNumber,
295
+ per_page: 100,
296
+ headers: {
297
+ accept: 'application/vnd.github.mockingbird-preview+json'
298
+ }
299
+ })
300
+
301
+ console.log(`Timeline: ${JSON.stringify(timeline, null, 2)}`)
302
+
303
+ const linkedPRs = timeline
304
+ // filter out event.event is not cross-referenced
305
+ .filter(event => event.event === 'cross-referenced')
306
+ // filter out event.source?.issue?.pull_request is null
307
+ .filter(event => event.source?.issue?.pull_request?.url)
308
+ // return url and pr name, and the issue number and the body and the actor
309
+ .map(event => ({
310
+ issueNumber: event.source?.issue?.number,
311
+ actor: event.actor?.login,
312
+ url: event.source?.issue?.pull_request?.url,
313
+ title: event.source?.issue?.title,
314
+ body: event.source?.issue?.body,
315
+ }))
316
+
317
+ hasLinkedPR = linkedPRs.length > 0
318
+ console.log(`Linked PRs:`, linkedPRs)
319
+ console.log(`Issue Comment #${issueNumber}, already has linked PR: ${hasLinkedPR}`)
320
+
321
+ } catch (error) {
322
+ console.error('Error checking for linked PRs:', error)
323
+ }
324
+ }
325
+
326
+ } else if (context.eventName === 'pull_request_review_comment' || context.eventName === 'pull_request_review') {
327
+ // PR review related
328
+ isPR = true
329
+ issueNumber = context.payload.pull_request.number
330
+ prNumber = context.payload.pull_request.number
331
+ headRef = context.payload.pull_request.head.ref
332
+ baseRef = context.payload.pull_request.base.ref
333
+ headSha = context.payload.pull_request.head.sha
334
+
335
+ if (context.eventName === 'pull_request_review_comment') {
336
+ triggerText = context.payload.comment.body
337
+ } else {
338
+ triggerText = context.payload.review.body
339
+ }
340
+
341
+ console.log(`PR Review #${prNumber}: ${baseRef} <- ${headRef}`)
342
+ }
343
+
344
+ // Set outputs
345
+ core.setOutput('issue-number', issueNumber)
346
+ core.setOutput('pr-number', prNumber || '')
347
+ core.setOutput('head-ref', headRef || '')
348
+ core.setOutput('base-ref', baseRef || '')
349
+ core.setOutput('head-sha', headSha || '')
350
+ core.setOutput('is-pr', isPR)
351
+ core.setOutput('trigger-text', triggerText)
352
+ core.setOutput('has-linked-pr', hasLinkedPR)
353
+
354
+ console.log(`Final Context:`)
355
+ console.log(`Event: ${context.eventName}`)
356
+ console.log(`Issue #${issueNumber}`)
357
+ console.log(`isPR: ${isPR}`)
358
+ console.log(`Trigger Text: ${triggerText}`)
359
+ console.log(`Already has linked PR: ${hasLinkedPR}`)
360
+
361
+ - name: Validate Environment
362
+ run: |
363
+ echo "🔍 Runtime Environment Information"
364
+ echo "=================================="
365
+ echo "Event: ${{ github.event_name }}"
366
+ echo "Actor: ${{ github.actor }}"
367
+ echo "Repository: ${{ github.repository }}"
368
+ echo "Issue Number: ${{ steps.context-info.outputs.issue-number }}"
369
+ echo "Is PR: ${{ steps.context-info.outputs.is-pr }}"
370
+ echo "PR Number: ${{ steps.context-info.outputs.pr-number }}"
371
+ echo "Head Ref: ${{ steps.context-info.outputs.head-ref }}"
372
+ echo "Base Ref: ${{ steps.context-info.outputs.base-ref }}"
373
+ echo "Head SHA: ${{ steps.context-info.outputs.head-sha }}"
374
+ echo "Has Linked PR: ${{ steps.context-info.outputs.has-linked-pr }}"
375
+ echo "=================================="
376
+
377
+ echo "✅ Environment validation complete"
378
+
379
+ - name: Exit early if Issue already has linked PR
380
+ id: should-continue
381
+ run: |
382
+ IS_PR="${{ steps.context-info.outputs.is-pr }}"
383
+ HAS_LINKED_PR="${{ steps.context-info.outputs.has-linked-pr }}"
384
+
385
+ if [[ "$IS_PR" == "false" && "$HAS_LINKED_PR" == "true" ]]; then
386
+ echo "Issue already has linked PR. Will skip remaining steps."
387
+ echo "should-continue=false" >> $GITHUB_OUTPUT
388
+ else
389
+ echo "No linked PRs found or this is a PR. Continuing."
390
+ echo "should-continue=true" >> $GITHUB_OUTPUT
391
+ fi
392
+
393
+ - name: Debug issue number
394
+ if: github.event_name != 'workflow_dispatch'
395
+ run: echo "The issue number is ${{ steps.context-info.outputs.issue-number }}"
396
+
397
+ # Only add comment if it doesn't exist
398
+ - name: Find existing status comment
399
+ if: steps.should-continue.outputs.should-continue == 'true' && github.event_name != 'workflow_dispatch'
400
+ uses: peter-evans/find-comment@v3
401
+ id: find_comment # We'll check the output of this step
402
+ with:
403
+ issue-number: ${{ steps.context-info.outputs.issue-number }}
404
+ comment-author: 'github-actions[bot]'
405
+ body-includes: '<!-- claude-run-status -->'
406
+
407
+ - name: Create initial "in-progress" comment if it doesn't exist
408
+ # This step ONLY runs if the 'find-comment' step found nothing
409
+ if: steps.should-continue.outputs.should-continue == 'true' && steps.find_comment.outputs.comment-id == '' && github.event_name != 'workflow_dispatch'
410
+ uses: peter-evans/create-or-update-comment@v4
411
+ id: create_comment
412
+ with:
413
+ issue-number: ${{ steps.context-info.outputs.issue-number }}
414
+ body: |
415
+ Claude Code is running... ⏳
416
+ <!-- claude-run-status -->
417
+ #########################################################
418
+ # Claude Code
419
+ #########################################################
420
+ claude:
421
+ needs: setup
422
+ # Security-focused conditional execution (full support for Issues and PRs)
423
+ if: needs.setup.outputs.should-continue == 'true'
424
+ runs-on: ubuntu-latest
425
+ timeout-minutes: 20
426
+ permissions:
427
+ # 📁 Content management (highest permissions)
428
+ contents: write
429
+ pull-requests: write
430
+ issues: write
431
+ discussions: write
432
+
433
+ # 🔧 Development & CI/CD management
434
+ actions: write
435
+ checks: write
436
+ statuses: write
437
+ pages: write
438
+ deployments: write
439
+
440
+ # 📦 Package & security management
441
+ packages: write
442
+ security-events: write
443
+
444
+ # 🎯 Project management
445
+ repository-projects: write
446
+
447
+ # 🆔 Authentication & token management
448
+ id-token: write
449
+
450
+ steps:
451
+ - name: Checkout Repository
452
+ uses: actions/checkout@v4
453
+ with:
454
+ # Checkout the feature branch for PRs, or the default branch for Issues
455
+ ref: ${{ needs.setup.outputs.head-sha || github.ref }}
456
+ fetch-depth: ${{ needs.setup.outputs.is-pr == 'true' && 0 || 1 }}
457
+ token: ${{ secrets.GITHUB_TOKEN }}
458
+
459
+ - name: Validate Environment
460
+ id: validate-env
461
+ run: |
462
+ echo "🔍 Runtime Environment Information"
463
+ echo "=================================="
464
+ echo "Event: ${{ github.event_name }}"
465
+ echo "Actor: ${{ github.actor }}"
466
+ echo "Repository: ${{ github.repository }}"
467
+ echo "Issue Number: ${{ needs.setup.outputs.issue-number }}"
468
+ echo "Is PR: ${{ needs.setup.outputs.is-pr }}"
469
+ echo "PR Number: ${{ needs.setup.outputs.pr-number }}"
470
+ echo "Head Ref: ${{ needs.setup.outputs.head-ref }}"
471
+ echo "Base Ref: ${{ needs.setup.outputs.base-ref }}"
472
+ echo "Head SHA: ${{ needs.setup.outputs.head-sha }}"
473
+ echo "Has Linked PR: ${{ needs.setup.outputs.has-linked-pr }}"
474
+
475
+ # Capture trigger text once using heredoc (safely handles multiline/special chars)
476
+ TRIGGER_TEXT=$(cat << 'TRIGGER_EOF'
477
+ ${{ needs.setup.outputs.trigger-text }}
478
+ TRIGGER_EOF
479
+ )
480
+ echo "Trigger Text: $TRIGGER_TEXT"
481
+ echo "=================================="
482
+
483
+ # Check for model keywords (case-insensitive)
484
+ if echo "$TRIGGER_TEXT" | grep -qi '\bopus\b'; then
485
+ echo "🧠 Opus model requested"
486
+ echo "model=opus" >> $GITHUB_OUTPUT
487
+ elif echo "$TRIGGER_TEXT" | grep -qi '\bhaiku\b'; then
488
+ echo "⚡ Haiku model requested"
489
+ echo "model=haiku" >> $GITHUB_OUTPUT
490
+ elif echo "$TRIGGER_TEXT" | grep -qi '\bsonnet\b'; then
491
+ echo "🎵 Using default Sonnet model"
492
+ echo "model=sonnet" >> $GITHUB_OUTPUT
493
+ else
494
+ echo "🧠 Using default Opus model"
495
+ echo "model=opus" >> $GITHUB_OUTPUT
496
+ fi
497
+
498
+ - name: Fetch Base Branch (PR only)
499
+ if: needs.setup.outputs.is-pr == 'true' && needs.setup.outputs.base-ref
500
+ run: |
501
+ echo "📥 Fetching base branch: ${{ needs.setup.outputs.base-ref }}"
502
+ git fetch origin ${{ needs.setup.outputs.base-ref }}:${{ needs.setup.outputs.base-ref }}
503
+
504
+ echo "📋 Changed files:"
505
+ git diff --name-only origin/${{ needs.setup.outputs.base-ref }}..HEAD || echo "Failed to get diff"
506
+
507
+ echo "📊 Change statistics:"
508
+ git diff --stat origin/${{ needs.setup.outputs.base-ref }}..HEAD || echo "Failed to get stats"
509
+
510
+ - name: Get Project Information
511
+ id: project-info
512
+ run: |
513
+ echo "📁 Collecting project information"
514
+
515
+ # Determine project type
516
+ project_type="unknown"
517
+ framework=""
518
+
519
+ if [ -f "package.json" ]; then
520
+ project_type="node"
521
+ echo "📦 Node.js project detected"
522
+
523
+ # Detect framework
524
+ if grep -q "next" package.json; then
525
+ framework="Next.js"
526
+ elif grep -q "react" package.json; then
527
+ framework="React"
528
+ elif grep -q "vue" package.json; then
529
+ framework="Vue.js"
530
+ elif grep -q "angular" package.json; then
531
+ framework="Angular"
532
+ elif grep -q "express" package.json; then
533
+ framework="Express"
534
+ fi
535
+ elif [ -f "requirements.txt" ] || [ -f "pyproject.toml" ]; then
536
+ project_type="python"
537
+ framework="Python"
538
+ echo "🐍 Python project detected"
539
+ elif [ -f "Cargo.toml" ]; then
540
+ project_type="rust"
541
+ framework="Rust"
542
+ echo "🦀 Rust project detected"
543
+ elif [ -f "go.mod" ]; then
544
+ project_type="go"
545
+ framework="Go"
546
+ echo "🐹 Go project detected"
547
+ elif [ -f "pom.xml" ] || [ -f "build.gradle" ]; then
548
+ project_type="java"
549
+ framework="Java"
550
+ echo "☕ Java project detected"
551
+ fi
552
+
553
+ echo "project-type=$project_type" >> $GITHUB_OUTPUT
554
+ echo "framework=$framework" >> $GITHUB_OUTPUT
555
+
556
+ # Estimate number of files
557
+ total_files=$(find . -type f \( -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" -o -name "*.py" -o -name "*.rs" -o -name "*.go" -o -name "*.java" \) | wc -l)
558
+ echo "total-files=$total_files" >> $GITHUB_OUTPUT
559
+
560
+ echo "📊 Project summary: $framework ($total_files files)"
561
+
562
+ # timeout_minutes: "60"
563
+
564
+ - name: Run Claude Code
565
+ id: claude
566
+ uses: anthropics/claude-code-action@main
567
+ timeout-minutes: 20
568
+ continue-on-error: true
569
+ with:
570
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
571
+ github_token: ${{ secrets.GH_PAT || secrets.GITHUB_TOKEN || github.token }}
572
+ # GITHUB ACTIONS (Maximum Freedom):
573
+ settings: |
574
+ {
575
+ "permissions": {
576
+ "allowedTools": [
577
+ "Edit", "View", "Replace", "Write", "Create",
578
+ "BatchTool", "GlobTool", "GrepTool", "NotebookEditCell",
579
+ "Bash(git:*)", "Bash(npm:*)", "Bash(npx:*)", "Bash(yarn:*)",
580
+ "Bash(python:*)", "Bash(bun:*)", "Bash(pnpm:*)",
581
+ "Bash(docker:*)", "Bash(make:*)", "Bash(cargo:*)", "Bash(go:*)",
582
+ "Bash(ls:*)", "Bash(cat:*)", "Bash(echo:*)", "Bash(curl:*)",
583
+ "mcp__*"
584
+ ],
585
+ "deny": [
586
+ "Bash(sudo:*)",
587
+ "Bash(rm -rf /)"
588
+ ]
589
+ }
590
+ }
591
+ claude_args: |
592
+ --model ${{ steps.validate-env.outputs.model }} --dangerously-skip-permissions
593
+ # If the workflow is triggered by a workflow_dispatch event, use the agent mode, otherwise use the tag mode
594
+ mode: ${{ github.event_name == 'workflow_dispatch' && 'agent' || 'tag' }}
595
+ # direct prompt from workflow_dispatch event
596
+ prompt: ${{ github.event_name == 'workflow_dispatch' && needs.setup.outputs.trigger-text || '' }}
597
+ # This is an optional setting that allows Claude to read CI results on PRs
598
+ additional_permissions: |
599
+ actions: read
600
+ env:
601
+ # Pass context information to Claude Code
602
+ GITHUB_CONTEXT_TYPE: ${{ needs.setup.outputs.is-pr == 'true' && 'PR' || 'ISSUE' }}
603
+ ISSUE_NUMBER: ${{ needs.setup.outputs.issue-number }}
604
+ PR_NUMBER: ${{ needs.setup.outputs.pr-number }}
605
+ BASE_BRANCH: ${{ needs.setup.outputs.base-ref }}
606
+ HEAD_BRANCH: ${{ needs.setup.outputs.head-ref }}
607
+ HEAD_SHA: ${{ needs.setup.outputs.head-sha }}
608
+ GITHUB_EVENT_NAME: ${{ github.event_name }}
609
+ TRIGGER_TEXT: ${{ needs.setup.outputs.trigger-text }}
610
+ PROJECT_TYPE: ${{ steps.project-info.outputs.project-type }}
611
+ PROJECT_FRAMEWORK: ${{ steps.project-info.outputs.framework }}
612
+ TOTAL_FILES: ${{ steps.project-info.outputs.total-files }}
613
+ GITHUB_ACTOR: ${{ github.actor }}
614
+ REPOSITORY_NAME: ${{ github.repository }}
615
+
616
+ # 🔑 Enhanced permission information
617
+ CLAUDE_PERMISSIONS_LEVEL: "ENHANCED"
618
+ REPO_ADMIN_MODE: "true"
619
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
620
+
621
+ # 📊 Repository information
622
+ REPOSITORY_OWNER: ${{ github.repository_owner }}
623
+ REPOSITORY_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
624
+ REPOSITORY_PRIVATE: ${{ github.event.repository.private }}
625
+ REPOSITORY_FORK: ${{ github.event.repository.fork }}
626
+
627
+ # 🎯 Execution context
628
+ WORKFLOW_RUN_ID: ${{ github.run_id }}
629
+ WORKFLOW_RUN_NUMBER: ${{ github.run_number }}
630
+ COMMIT_SHA: ${{ github.sha }}
631
+ REF_NAME: ${{ github.ref_name }}
632
+
633
+ # 🔧 Available feature flags
634
+ CAN_CREATE_RELEASES: "true"
635
+ CAN_MANAGE_LABELS: "true"
636
+ CAN_MANAGE_MILESTONES: "true"
637
+ CAN_MANAGE_PROJECTS: "true"
638
+ CAN_MANAGE_WIKI: "true"
639
+ CAN_MANAGE_PAGES: "true"
640
+ CAN_MANAGE_DEPLOYMENTS: "true"
641
+ CAN_MANAGE_SECURITY: "true"
642
+ CAN_MANAGE_PACKAGES: "true"
643
+ CAN_MANAGE_ACTIONS: "true"
644
+
645
+ - name: Run Advanced Repository Management
646
+ id: advanced-management
647
+ if: steps.claude.outcome == 'success' && needs.setup.outputs.issue-number
648
+ uses: actions/github-script@v7
649
+ with:
650
+ github-token: ${{ secrets.GITHUB_TOKEN }}
651
+ script: |
652
+ const issueNumber = ${{ needs.setup.outputs.issue-number }};
653
+ const isPR = '${{ needs.setup.outputs.is-pr }}' === 'true';
654
+ const triggerText = (${{ toJSON(needs.setup.outputs.trigger-text) }} || '').toLowerCase();
655
+ const framework = '${{ steps.project-info.outputs.framework }}';
656
+ const hashSymbol = String.fromCharCode(35);
657
+
658
+ console.log('🚀 Starting advanced repository management...');
659
+
660
+ const managementResults = {
661
+ labels: [],
662
+ milestones: [],
663
+ projects: [],
664
+ releases: [],
665
+ security: [],
666
+ wiki: [],
667
+ pages: [],
668
+ actions: []
669
+ };
670
+
671
+ try {
672
+ // 1. 🏷️ Intelligent Label Management
673
+ console.log('📋 Running automatic label management...');
674
+
675
+ // Automatically create necessary labels
676
+ const requiredLabels = [
677
+ { name: 'claude-code', color: '7B68EE', description: 'Items created or modified by Claude Code' },
678
+ { name: 'auto-generated', color: '00D084', description: 'Automatically generated content' },
679
+ { name: 'security-fix', color: 'FF4444', description: 'Security-related fixes' },
680
+ { name: 'performance', color: 'FFA500', description: 'Performance improvements' },
681
+ { name: 'technical-debt', color: '8B4513', description: 'Resolving technical debt' },
682
+ { name: 'documentation', color: '0366D6', description: 'Documentation related' },
683
+ { name: 'ci-cd', color: '28A745', description: 'CI/CD improvements' }
684
+ ];
685
+
686
+ for (const label of requiredLabels) {
687
+ try {
688
+ await github.rest.issues.createLabel({
689
+ owner: context.repo.owner,
690
+ repo: context.repo.repo,
691
+ name: label.name,
692
+ color: label.color,
693
+ description: label.description
694
+ });
695
+ managementResults.labels.push(`✅ Created: ${label.name}`);
696
+ } catch (error) {
697
+ if (error.status === 422) {
698
+ managementResults.labels.push(`📋 Exists: ${label.name}`);
699
+ } else {
700
+ managementResults.labels.push(`❌ Error: ${label.name} - ${error.message}`);
701
+ }
702
+ }
703
+ }
704
+
705
+ // Automatically apply relevant labels
706
+ const autoLabels = ['claude-code', 'auto-generated'];
707
+ if (triggerText.includes('security')) {
708
+ autoLabels.push('security-fix');
709
+ }
710
+ if (triggerText.includes('performance')) {
711
+ autoLabels.push('performance');
712
+ }
713
+ if (triggerText.includes('technical debt')) {
714
+ autoLabels.push('technical-debt');
715
+ }
716
+ if (triggerText.includes('document')) {
717
+ autoLabels.push('documentation');
718
+ }
719
+ if (triggerText.includes('ci') || triggerText.includes('cd') || triggerText.includes('deploy')) {
720
+ autoLabels.push('ci-cd');
721
+ }
722
+
723
+ await github.rest.issues.addLabels({
724
+ owner: context.repo.owner,
725
+ repo: context.repo.repo,
726
+ issue_number: issueNumber,
727
+ labels: autoLabels
728
+ });
729
+
730
+ managementResults.labels.push(`🏷️ Applied: ${autoLabels.join(', ')}`);
731
+
732
+ // 2. 🎯 Automatic Milestone Management
733
+ console.log('🎯 Running milestone management...');
734
+
735
+ try {
736
+ // Create a milestone for the current year and month
737
+ const now = new Date();
738
+ const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
739
+ const currentMilestone = `${now.getFullYear()}-${monthNames[now.getMonth()]}`;
740
+
741
+ try {
742
+ const milestone = await github.rest.issues.createMilestone({
743
+ owner: context.repo.owner,
744
+ repo: context.repo.repo,
745
+ title: currentMilestone,
746
+ description: `Tasks and improvements for ${currentMilestone}`,
747
+ due_on: new Date(now.getFullYear(), now.getMonth() + 1, 0).toISOString()
748
+ });
749
+ managementResults.milestones.push(`✅ Created: ${currentMilestone}`);
750
+ } catch (error) {
751
+ if (error.status === 422) {
752
+ managementResults.milestones.push(`📅 Exists: ${currentMilestone}`);
753
+ } else {
754
+ managementResults.milestones.push(`❌ Error: ${error.message}`);
755
+ }
756
+ }
757
+ } catch (error) {
758
+ managementResults.milestones.push(`❌ Milestone management error: ${error.message}`);
759
+ }
760
+
761
+ // 3. 📊 Project Board Management
762
+ console.log('📊 Running project management...');
763
+
764
+ try {
765
+ // Get projects for the repository
766
+ const projects = await github.rest.projects.listForRepo({
767
+ owner: context.repo.owner,
768
+ repo: context.repo.repo
769
+ });
770
+
771
+ if (projects.data.length > 0) {
772
+ const project = projects.data[0];
773
+ managementResults.projects.push(`📊 Project detected: ${project.name}`);
774
+
775
+ // Add a card to the To Do column
776
+ const columns = await github.rest.projects.listColumns({
777
+ project_id: project.id
778
+ });
779
+
780
+ const todoColumn = columns.data.find(col =>
781
+ col.name.toLowerCase().includes('todo') ||
782
+ col.name.toLowerCase().includes('backlog')
783
+ );
784
+
785
+ if (todoColumn) {
786
+ await github.rest.projects.createCard({
787
+ column_id: todoColumn.id,
788
+ content_id: context.payload.issue.id, // Use issue ID for content_id
789
+ content_type: 'Issue'
790
+ });
791
+ managementResults.projects.push(`📋 Card added: ${project.name}`);
792
+ }
793
+ } else {
794
+ managementResults.projects.push(`ℹ️ Project board not found`);
795
+ }
796
+ } catch (error) {
797
+ managementResults.projects.push(`❌ Project management error: ${error.message}`);
798
+ }
799
+
800
+ // 4. 🔒 Security Alert Handling
801
+ console.log('🔒 Running security check...');
802
+
803
+ try {
804
+ if (triggerText.includes('security') || triggerText.includes('vulnerability')) {
805
+ // Check for security alerts
806
+ try {
807
+ const vulnerabilities = await github.rest.secretScanning.listAlertsForRepo({
808
+ owner: context.repo.owner,
809
+ repo: context.repo.repo,
810
+ state: 'open'
811
+ });
812
+
813
+ managementResults.security.push(`🔍 Open security alerts: ${vulnerabilities.data.length}`);
814
+
815
+ if (vulnerabilities.data.length > 0) {
816
+ managementResults.security.push(`⚠️ Security alert confirmation required`);
817
+ }
818
+ } catch (error) {
819
+ managementResults.security.push(`ℹ️ Security alert check: Access restricted or feature disabled`);
820
+ }
821
+ } else {
822
+ managementResults.security.push(`ℹ️ Security check: Skipped`);
823
+ }
824
+ } catch (error) {
825
+ managementResults.security.push(`❌ Security check error: ${error.message}`);
826
+ }
827
+
828
+ // 5. 📚 Automatic Wiki Update
829
+ console.log('📚 Running Wiki management...');
830
+
831
+ try {
832
+ if (triggerText.includes('wiki') || triggerText.includes('document')) {
833
+ // Check if Wiki page exists
834
+ try {
835
+ const repoInfo = await github.rest.repos.get({
836
+ owner: context.repo.owner,
837
+ repo: context.repo.repo
838
+ });
839
+
840
+ if (repoInfo.data.has_wiki) {
841
+ managementResults.wiki.push(`📚 Wiki enabled: Updatable`);
842
+ // Actual Wiki update is performed by Claude Code
843
+ } else {
844
+ managementResults.wiki.push(`📚 Wiki disabled: Needs to be enabled in settings`);
845
+ }
846
+ } catch (error) {
847
+ managementResults.wiki.push(`❌ Wiki check error: ${error.message}`);
848
+ }
849
+ } else {
850
+ managementResults.wiki.push(`ℹ️ Wiki update: Skipped`);
851
+ }
852
+ } catch (error) {
853
+ managementResults.wiki.push(`❌ Wiki management error: ${error.message}`);
854
+ }
855
+
856
+ // 6. 🌐 GitHub Pages Management
857
+ console.log('🌐 Running GitHub Pages management...');
858
+
859
+ try {
860
+ if (triggerText.includes('pages') || triggerText.includes('deploy') || triggerText.includes('site')) {
861
+ try {
862
+ const pages = await github.rest.repos.getPages({
863
+ owner: context.repo.owner,
864
+ repo: context.repo.repo
865
+ });
866
+
867
+ managementResults.pages.push(`🌐 Pages enabled: ${pages.data.html_url}`);
868
+
869
+ // Trigger a Pages build
870
+ await github.rest.repos.requestPagesBuild({
871
+ owner: context.repo.owner,
872
+ repo: context.repo.repo
873
+ });
874
+
875
+ managementResults.pages.push(`🔄 Triggered Pages rebuild`);
876
+ } catch (error) {
877
+ if (error.status === 404) {
878
+ managementResults.pages.push(`🌐 Pages disabled: Needs to be enabled in settings`);
879
+ } else {
880
+ managementResults.pages.push(`❌ Pages management error: ${error.message}`);
881
+ }
882
+ }
883
+ } else {
884
+ managementResults.pages.push(`ℹ️ Pages management: Skipped`);
885
+ }
886
+ } catch (error) {
887
+ managementResults.pages.push(`❌ Pages management error: ${error.message}`);
888
+ }
889
+
890
+ // 7. ⚙️ Actions Workflow Management
891
+ console.log('⚙️ Running Actions management...');
892
+
893
+ try {
894
+ if (triggerText.includes('workflow') || triggerText.includes('action') || triggerText.includes('ci') || triggerText.includes('cd')) {
895
+ const workflows = await github.rest.actions.listRepoWorkflows({
896
+ owner: context.repo.owner,
897
+ repo: context.repo.repo
898
+ });
899
+
900
+ managementResults.actions.push(`⚙️ Number of workflows: ${workflows.data.total_count}`);
901
+
902
+ // Check for disabled workflows
903
+ const disabledWorkflows = workflows.data.workflows.filter(w => w.state === 'disabled_manually');
904
+ if (disabledWorkflows.length > 0) {
905
+ managementResults.actions.push(`⚠️ Disabled workflows: ${disabledWorkflows.length}`);
906
+ }
907
+ } else {
908
+ managementResults.actions.push(`ℹ️ Actions management: Skipped`);
909
+ }
910
+ } catch (error) {
911
+ managementResults.actions.push(`❌ Actions management error: ${error.message}`);
912
+ }
913
+
914
+ console.log('✅ Advanced repository management complete');
915
+
916
+ // Save results to output
917
+ core.setOutput('management-results', JSON.stringify(managementResults));
918
+ core.setOutput('management-success', 'true');
919
+
920
+ } catch (error) {
921
+ console.error('❌ Advanced repository management error:', error);
922
+ core.setOutput('management-error', error.message);
923
+ core.setOutput('management-success', 'false');
924
+ }
925
+
926
+ - name: Check for Changes and Prepare for PR
927
+ id: check-changes
928
+ if: steps.claude.outcome == 'success' && needs.setup.outputs.is-pr == 'false' && steps.claude.outputs.branch_name
929
+ run: |
930
+ set -e # Exit immediately if a command exits with a non-zero status.
931
+
932
+ BRANCH_NAME="${{ steps.claude.outputs.branch_name }}"
933
+ DEFAULT_BRANCH="origin/${{ github.event.repository.default_branch }}"
934
+
935
+ echo "--- 1. Checking if remote branch '${BRANCH_NAME}' exists ---"
936
+ # Use `git ls-remote` to check for the branch's existence. It exits with 0 if it exists, 2 if not.
937
+ if ! git ls-remote --exit-code --heads origin "${BRANCH_NAME}" >/dev/null 2>&1; then
938
+ echo "✅ Remote branch '${BRANCH_NAME}' not found. This indicates no code changes were committed."
939
+ echo "has-changes=false" >> $GITHUB_OUTPUT
940
+ echo "branch-exists=false" >> $GITHUB_OUTPUT
941
+ # Exit successfully as this is an expected outcome.
942
+ exit 0
943
+ fi
944
+
945
+ echo "✅ Remote branch found. Proceeding with original fetch and reset logic."
946
+ echo "branch-exists=true" >> $GITHUB_OUTPUT
947
+
948
+ echo "--- 2. DEBUG: Initial Git State ---"
949
+ echo "Current branch: $(git rev-parse --abbrev-ref HEAD)"
950
+ echo "Current commit: $(git log -1 --pretty=%h)"
951
+ echo "Workspace status:"
952
+ git status -s
953
+ echo "-----------------------------------"
954
+
955
+ echo "🚀 3. Fetching the specific branch pushed by Claude: '${BRANCH_NAME}'..."
956
+ git fetch origin "+refs/heads/${BRANCH_NAME}:refs/remotes/origin/${BRANCH_NAME}"
957
+
958
+ echo "--- 4. DEBUG: After Fetch ---"
959
+ echo "Remote commit for '${BRANCH_NAME}' is: $(git log origin/${BRANCH_NAME} -1 --pretty=%h)"
960
+ echo "-----------------------------"
961
+
962
+ echo "🔄 5. Forcibly resetting local branch to match the fetched remote state..."
963
+ git checkout "${BRANCH_NAME}"
964
+ git reset --hard "origin/${BRANCH_NAME}"
965
+
966
+ echo "--- 6. DEBUG: After Resetting Local Branch ---"
967
+ echo "Current branch is now: $(git rev-parse --abbrev-ref HEAD)"
968
+ echo "Current commit is now: $(git log -1 --pretty=%h)"
969
+ echo "Workspace status is now:"
970
+ git status -s
971
+ echo "---------------------------------------------"
972
+
973
+ BRANCH_RANGE="${DEFAULT_BRANCH}...${BRANCH_NAME}"
974
+
975
+ echo "🔍 7. Checking for changes in range: ${BRANCH_RANGE}..."
976
+
977
+ # Use the exit code of 'git diff --quiet' to check for changes.
978
+ if git diff --quiet $BRANCH_RANGE; then
979
+ echo "✅ No changes detected between branches. Setting has-changes=false."
980
+ echo "has-changes=false" >> $GITHUB_OUTPUT
981
+ else
982
+ echo "📝 Changes detected. Setting has-changes=true."
983
+ echo "has-changes=true" >> $GITHUB_OUTPUT
984
+
985
+ echo "---"
986
+ echo "📄 Changed files (compared to default branch):"
987
+ git diff --name-only $BRANCH_RANGE
988
+
989
+ echo "---"
990
+ echo "📊 Change statistics:"
991
+ git diff --stat $BRANCH_RANGE
992
+ fi
993
+ #########################################################
994
+ # IF we have changes, create or update a pull request
995
+ #########################################################
996
+ - name: Create or Update Pull Request
997
+ id: auto-pr
998
+ # The 'if' condition is now correctly chained.
999
+ if: |
1000
+ steps.claude.outcome == 'success'
1001
+ && needs.setup.outputs.is-pr == 'false'
1002
+ && steps.claude.outputs.branch_name
1003
+ && steps.check-changes.outputs.has-changes == 'true'
1004
+ uses: actions/github-script@v7
1005
+ with:
1006
+ github-token: ${{ secrets.GITHUB_TOKEN }}
1007
+ script: |
1008
+ const issueNumber = ${{ needs.setup.outputs.issue-number }}
1009
+ const branchName = '${{ steps.claude.outputs.branch_name }}'
1010
+ const defaultBranch = '${{ github.event.repository.default_branch }}'
1011
+ const owner = context.repo.owner
1012
+ const repo = context.repo.repo
1013
+
1014
+ try {
1015
+ // 1. Check for an existing PR for this branch
1016
+ console.log(`Checking for existing PRs for branch: ${branchName}`)
1017
+
1018
+ let existingPr = null
1019
+ try {
1020
+ const { data: pulls } = await github.rest.pulls.list({
1021
+ owner,
1022
+ repo,
1023
+ head: `${owner}:${branchName}`,
1024
+ state: 'open',
1025
+ per_page: 1
1026
+ })
1027
+ existingPr = pulls.length > 0 ? pulls[0] : null
1028
+ } catch (error) {
1029
+ existingPr = null
1030
+ }
1031
+
1032
+ let pr
1033
+
1034
+ if (existingPr) {
1035
+ // 2. If PR exists, use it
1036
+ pr = existingPr
1037
+ console.log(`✅ Found existing PR: #${pr.number}. No new PR will be created.`)
1038
+
1039
+ // Optional: Post a comment to the existing PR to notify of the update
1040
+ const updateComment = `🔄 **Claude Code has updated this branch** with new changes.\n\n[View the latest workflow run](https://github.com/${owner}/${repo}/actions/runs/${{ github.run_id }})`
1041
+ await github.rest.issues.createComment({
1042
+ owner,
1043
+ repo,
1044
+ issue_number: pr.number,
1045
+ body: updateComment,
1046
+ })
1047
+
1048
+ } else {
1049
+ // 3. If no PR exists, create one
1050
+ console.log(`No existing PR found. Attempting to create a new PR for branch: ${branchName}`)
1051
+
1052
+ const { data: issue } = await github.rest.issues.get({
1053
+ owner,
1054
+ repo,
1055
+ issue_number: issueNumber
1056
+ })
1057
+
1058
+ // Get the last issue comment by claude on issueNumber
1059
+ const { data: comments } = await github.rest.issues.listComments({
1060
+ owner,
1061
+ repo,
1062
+ issue_number: issueNumber
1063
+ })
1064
+ const claudeComment = comments.find(c => c.user.login === 'claude[bot]' && c.body.includes('Claude'))
1065
+ const claudeCommentBody = claudeComment ? claudeComment.body.replace(/\[Create PR ➔\]\([^)]+\)/, '') : ''
1066
+
1067
+ const prTitle = `🤖 Claude Code: ${issue.title}`
1068
+ const prBody = `## 🤖 Automated fix by Claude Code
1069
+
1070
+ **Related Issue:** #${issueNumber}
1071
+ **Executed by:** @${{ github.actor }}
1072
+
1073
+ ---
1074
+
1075
+ ${claudeCommentBody}
1076
+
1077
+ **If additional fixes are needed:** Mention \`@ claude\` in a comment on this PR.
1078
+
1079
+ *resolves #${issueNumber}*
1080
+
1081
+ Please review @copilot
1082
+
1083
+ <!-- CLAUDE_ISSUE_RESPONSE start -->
1084
+ <!-- CLAUDE_ISSUE_RESPONSE end -->
1085
+
1086
+ ---
1087
+ *Automated PR by [Claude Code Action - Run #${{ github.run_id }}](https://github.com/${owner}/${repo}/actions/runs/${{ github.run_id }})*
1088
+ `
1089
+
1090
+ const { data: newPr } = await github.rest.pulls.create({
1091
+ owner,
1092
+ repo,
1093
+ title: prTitle,
1094
+ head: branchName,
1095
+ base: defaultBranch,
1096
+ body: prBody,
1097
+ draft: false
1098
+ })
1099
+
1100
+ pr = newPr
1101
+ console.log(`🎉 PR created successfully: #${pr.number}`)
1102
+
1103
+ // Add first comment to the PR for sticky comment
1104
+ const stickyComment = `
1105
+ **ℹ️ Action Run:** https://github.com/${owner}/${repo}/actions/runs/${{ github.run_id }}
1106
+
1107
+ <!-- claude-run-status -->
1108
+ `
1109
+
1110
+ /* Add sticky comment to the PR for other data */
1111
+ await github.rest.issues.createComment({
1112
+ issue_number: pr.number,
1113
+ owner,
1114
+ repo,
1115
+ body: stickyComment,
1116
+ author: 'github-actions[bot]'
1117
+ })
1118
+ }
1119
+
1120
+ // 4. Set outputs with the PR info (whether new or existing)
1121
+ core.setOutput('pr-number', pr.number)
1122
+ core.setOutput('pr-url', pr.html_url)
1123
+ core.setOutput('branch-name', branchName)
1124
+
1125
+ // Ping our webhook - PR creation successful
1126
+ try {
1127
+ const webhookUrl = '${{ secrets.CLAUDE_CODE_NOTIFICATIONS_URL }}';
1128
+ const webhookToken = '${{ secrets.CLAUDE_CODE_NOTIFICATIONS_KEY }}';
1129
+
1130
+ const jobId = `claude-${context.repo.owner}-${context.repo.repo}-${issueNumber}-${Date.now()}`;
1131
+ const actionUrl = `https://github.com/${owner}/${repo}/issues/${issueNumber}`;
1132
+
1133
+ const webhookData = {
1134
+ jobId: jobId,
1135
+ status: 'completed',
1136
+ repository: `${context.repo.owner}/${context.repo.repo}`,
1137
+ url: actionUrl,
1138
+ branch: branchName || context.payload.repository?.default_branch || 'main',
1139
+ commit: '${{ github.sha }}',
1140
+ results: {
1141
+ prCreated: true,
1142
+ prNumber: pr.number,
1143
+ prUrl: pr.html_url,
1144
+ issueNumber: issueNumber,
1145
+ actor: '${{ github.actor }}',
1146
+ event: '${{ github.event_name }}',
1147
+ framework: '${{ steps.project-info.outputs.framework }}',
1148
+ totalFiles: '${{ steps.project-info.outputs.total-files }}',
1149
+ hasChanges: '${{ steps.check-changes.outputs.has-changes }}',
1150
+ managementSuccess: '${{ steps.advanced-management.outputs.management-success }}'
1151
+ }
1152
+ };
1153
+
1154
+ console.log('🔔 Sending webhook notification (PR created)...');
1155
+ console.log('Webhook URL:', webhookUrl);
1156
+ console.log('Job ID:', jobId);
1157
+
1158
+ const response = await fetch(`${webhookUrl}/job-complete`, {
1159
+ method: 'POST',
1160
+ headers: {
1161
+ 'Content-Type': 'application/json',
1162
+ 'Authorization': `Bearer ${webhookToken}`
1163
+ },
1164
+ body: JSON.stringify(webhookData)
1165
+ });
1166
+
1167
+ if (response.ok) {
1168
+ console.log('✅ Webhook notification sent successfully');
1169
+ } else {
1170
+ console.warn(`⚠️ Webhook notification failed: ${response.status} ${response.statusText}`);
1171
+ }
1172
+ } catch (error) {
1173
+ console.warn('⚠️ Webhook notification error:', error.message);
1174
+ }
1175
+
1176
+ } catch (error) {
1177
+ console.error('❌ PR creation/update error:', error)
1178
+ core.setOutput('error', error.message)
1179
+
1180
+ const failureComment = `❌ **Automatic PR creation failed**\n\n**Error:** \`${error.message}\`\n\n**Solution**: A branch named \`${branchName}\` was created with the changes. Please create a PR from it manually or rerun the job.\n**Details**: [Actions run log](${{ github.server_url }}/${owner}/${repo}/actions/runs/${{ github.run_id }})`
1181
+ await github.rest.issues.createComment({
1182
+ issue_number: issueNumber,
1183
+ owner,
1184
+ repo,
1185
+ body: failureComment
1186
+ })
1187
+
1188
+ // Ping our webhook - PR creation failed
1189
+ try {
1190
+ const webhookUrl = '${{ secrets.CLAUDE_CODE_NOTIFICATIONS_URL }}';
1191
+ const webhookToken = '${{ secrets.CLAUDE_CODE_NOTIFICATIONS_KEY }}';
1192
+
1193
+ const jobId = `claude-${context.repo.owner}-${context.repo.repo}-${issueNumber}-${Date.now()}`;
1194
+ const actionUrl = `https://github.com/${owner}/${repo}/issues/${issueNumber}`;
1195
+
1196
+ const webhookData = {
1197
+ jobId: jobId,
1198
+ status: 'failed',
1199
+ repository: `${context.repo.owner}/${context.repo.repo}`,
1200
+ url: actionUrl,
1201
+ branch: branchName || context.payload.repository?.default_branch || 'main',
1202
+ commit: '${{ github.sha }}',
1203
+ results: {
1204
+ prCreated: false,
1205
+ prNumber: null,
1206
+ prUrl: null,
1207
+ issueNumber: issueNumber,
1208
+ actor: '${{ github.actor }}',
1209
+ event: '${{ github.event_name }}',
1210
+ framework: '${{ steps.project-info.outputs.framework }}',
1211
+ totalFiles: '${{ steps.project-info.outputs.total-files }}',
1212
+ hasChanges: '${{ steps.check-changes.outputs.has-changes }}',
1213
+ managementSuccess: '${{ steps.advanced-management.outputs.management-success }}',
1214
+ error: error.message
1215
+ }
1216
+ };
1217
+
1218
+ console.log('🔔 Sending webhook notification (PR creation failed)...');
1219
+ console.log('Webhook URL:', webhookUrl);
1220
+ console.log('Job ID:', jobId);
1221
+
1222
+ const response = await fetch(`${webhookUrl}/job-complete`, {
1223
+ method: 'POST',
1224
+ headers: {
1225
+ 'Content-Type': 'application/json',
1226
+ 'Authorization': `Bearer ${webhookToken}`
1227
+ },
1228
+ body: JSON.stringify(webhookData)
1229
+ });
1230
+
1231
+ if (response.ok) {
1232
+ console.log('✅ Webhook notification sent successfully');
1233
+ } else {
1234
+ console.warn(`⚠️ Webhook notification failed: ${response.status} ${response.statusText}`);
1235
+ }
1236
+ } catch (webhookError) {
1237
+ console.warn('⚠️ Webhook notification error:', webhookError.message);
1238
+ }
1239
+ }
1240
+
1241
+ - name: Notify on Success
1242
+ if: steps.claude.outcome == 'success' && needs.setup.outputs.issue-number
1243
+ id: generate_success_comment
1244
+ uses: actions/github-script@v7
1245
+ with:
1246
+ script: |
1247
+ const isPR = '${{ needs.setup.outputs.is-pr }}' === 'true';
1248
+ const contextType = isPR ? 'Pull Request' : 'Issue';
1249
+ const eventName = '${{ github.event_name }}';
1250
+ const framework = '${{ steps.project-info.outputs.framework }}' || 'Unknown';
1251
+ const totalFiles = '${{ steps.project-info.outputs.total-files }}' || '0';
1252
+ const hasChanges = '${{ steps.check-changes.outputs.has-changes }}' === 'true';
1253
+ const autoPrNumber = '${{ steps.auto-pr.outputs.pr-number }}';
1254
+ const autoPrUrl = '${{ steps.auto-pr.outputs.pr-url }}';
1255
+ const branchName = '${{ steps.auto-pr.outputs.branch-name }}';
1256
+ const managementSuccess = '${{ steps.advanced-management.outputs.management-success }}' === 'true';
1257
+ const managementResults = '${{ steps.advanced-management.outputs.management-results }}';
1258
+ const owner = context.repo.owner
1259
+ const repo = context.repo.repo
1260
+
1261
+ const eventIcons = {
1262
+ 'pull_request_target': '🔀',
1263
+ 'issue_comment': '💬',
1264
+ 'issues': '📋',
1265
+ 'pull_request_review_comment': '📝',
1266
+ 'pull_request_review': '👀'
1267
+ };
1268
+ const icon = eventIcons[eventName] || '🤖';
1269
+ const hashSymbol = '#';
1270
+
1271
+ let message = `${icon} **Claude Code execution complete** ✅\n\n`;
1272
+
1273
+ // Result of automatic PR creation
1274
+ let prMessage = '';
1275
+ if (!isPR && hasChanges && autoPrNumber) {
1276
+ message += `🎉 **Automatic PR created successfully!**\n`;
1277
+ message += `- PR: [${hashSymbol}${autoPrNumber}](${autoPrUrl})\n`;
1278
+ message += `- Branch: \`${branchName}\`\n`;
1279
+ message += `- Next step: Review PR → Merge\n\n`;
1280
+ prMessage = `https://github.com/${owner}/${repo}/pull/${autoPrNumber}/files`
1281
+ } else if (!isPR && !hasChanges) {
1282
+ message += `ℹ️ **Analysis only** (no code changes)\n\n`;
1283
+ }
1284
+
1285
+ // Execution info (compact version)
1286
+ message += `**📊 Execution Info:** ${contextType} ${hashSymbol}${${{ needs.setup.outputs.issue-number }}} | ${framework} (${totalFiles} files) | @${{ github.actor }}\n`;
1287
+ if (isPR) {
1288
+ message += `**🌿 Branch:** \`${{ needs.setup.outputs.head-ref }}\` → \`${{ needs.setup.outputs.base-ref }}\`\n`;
1289
+ }
1290
+ message += `**ℹ️ Action Run:** https://github.com/${owner}/${repo}/actions/runs/${{ github.run_id }}\n`
1291
+ if (prMessage) {
1292
+ message += `**📝 PR Changes:** [https://github.com/${owner}/${repo}/pull/${autoPrNumber}/files](${prMessage})\n`
1293
+ }
1294
+
1295
+ // Repository management results (summary)
1296
+ if (managementSuccess && managementResults) {
1297
+ try {
1298
+ const results = JSON.parse(managementResults);
1299
+ const sections = ['labels', 'milestones', 'projects', 'security', 'wiki', 'pages', 'actions'];
1300
+ const hasResults = sections.some(s => results[s] && results[s].length > 0);
1301
+
1302
+ if (hasResults) {
1303
+ // Check if there are actually any results to show
1304
+ let hasDisplayableResults = false;
1305
+ const displayableResults = [];
1306
+
1307
+ sections.forEach(section => {
1308
+ if (results[section] && results[section].length > 0) {
1309
+ const summary = results[section].filter(r => r.includes('✅') || r.includes('⚠️')).slice(0, 2);
1310
+ if (summary.length > 0) {
1311
+ hasDisplayableResults = true;
1312
+ displayableResults.push(...summary.map(r => `- ${r}`));
1313
+ }
1314
+ }
1315
+ });
1316
+
1317
+ if (hasDisplayableResults) {
1318
+ message += `\n**🚀 Automated management executed:**\n`;
1319
+ message += displayableResults.join('\n') + '\n';
1320
+ }
1321
+ }
1322
+ } catch (error) {
1323
+ // Do not display in case of error
1324
+ }
1325
+ }
1326
+
1327
+ // Future action links here
1328
+ // message += `\n---\n`;
1329
+
1330
+ message += `\n<!-- claude-run-status -->`;
1331
+
1332
+ // Set as output steps.generate_success_comment.outputs.result
1333
+ // return message;
1334
+ core.setOutput('comment-body', message)
1335
+
1336
+ - name: Notify on Failure
1337
+ if: steps.claude.outcome == 'failure' && needs.setup.outputs.issue-number
1338
+ id: generate_error_comment
1339
+ uses: actions/github-script@v7
1340
+ with:
1341
+ script: |
1342
+ const isPR = '${{ needs.setup.outputs.is-pr }}' === 'true';
1343
+ const contextType = isPR ? 'Pull Request' : 'Issue';
1344
+ const managementError = '${{ steps.advanced-management.outputs.management-error }}';
1345
+ const hashSymbol = '#';
1346
+
1347
+ let message = `❌ **Claude Code execution failed**\n\n`;
1348
+ message += `An error occurred while processing ${contextType} ${hashSymbol}${{ needs.setup.outputs.issue-number }}.\n\n`;
1349
+
1350
+ // Error info (compact version)
1351
+ message += `**📊 Error Info:** ${contextType} | \`${{ github.event_name }}\` | @${{ github.actor }}\n`;
1352
+ message += `**🔗 Detailed Log:** [Actions run log](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})\n\n`;
1353
+
1354
+ if (managementError) {
1355
+ message += `⚠️ **Repository Management Error:** \`${managementError}\`\n\n`;
1356
+ }
1357
+
1358
+ // Main causes and solutions (concise list)
1359
+ message += `**🤔 Possible Causes and Solutions:**\n\n`;
1360
+
1361
+ message += `**1. Temporary Issue** (most common)\n`;
1362
+ message += `- Temporary limitations of the Claude API\n`;
1363
+ message += `- → **Solution**: Rerun with \`claude\` after 5 minutes\n\n`;
1364
+
1365
+ message += `**2. Timeout** (15-minute limit)\n`;
1366
+ message += `- The task may be too complex\n`;
1367
+ message += `- → **Solution**: Break down the task with more specific instructions\n\n`;
1368
+
1369
+ message += `**3. Configuration Issue**\n`;
1370
+ message += `- Expired or misconfigured tokens\n`;
1371
+ message += `- → **Solution**: Check in Settings → Secrets → Actions\n`;
1372
+ message += ` - \`CLAUDE_ACCESS_TOKEN\`\n`;
1373
+ message += ` - \`CLAUDE_REFRESH_TOKEN\`\n`;
1374
+ message += ` - \`CLAUDE_EXPIRES_AT\`\n\n`;
1375
+
1376
+ // Concise rerun guide
1377
+ message += `**💡 Rerun Tips:**\n`;
1378
+ message += `- Be specific: \`claude review src/components/Button.tsx\`\n`;
1379
+ message += `- Step by step: Start with one file\n`;
1380
+ message += `- Be clear: State the expected outcome\n\n`;
1381
+
1382
+ message += `---\n`;
1383
+ message += `🔄 **Rerun**: Please try again with \`claude [specific instructions]\`\n`;
1384
+ message += `📞 **Support**: If the problem persists, please contact an administrator`;
1385
+
1386
+ message += `\n<!-- claude-run-status -->`;
1387
+
1388
+ // Set as output steps.generate_error_comment.outputs.result
1389
+ // return message;
1390
+ core.setOutput('comment-body', message)
1391
+
1392
+ # Update the sticky comment with the success or error message
1393
+ - name: Post or Update Sticky Comment
1394
+ if: |
1395
+ always()
1396
+ && (steps.generate_success_comment.outputs.comment-body || steps.generate_error_comment.outputs.comment-body)
1397
+ && needs.setup.outputs.status-comment-id
1398
+ uses: peter-evans/create-or-update-comment@v4
1399
+ with:
1400
+ issue-number: ${{ needs.setup.outputs.issue-number }}
1401
+ # Pass the ID found in the previous step to ensure we UPDATE
1402
+ comment-id: ${{ needs.setup.outputs.status-comment-id }}
1403
+ # The body comes from your script
1404
+ body: |
1405
+ ${{ steps.generate_success_comment.outputs.comment-body || steps.generate_error_comment.outputs.comment-body }}
1406
+ # Use 'replace' to overwrite the old "in-progress" message
1407
+ edit-mode: replace
1408
+
1409
+ - name: Output Execution Log
1410
+ if: always()
1411
+ run: |
1412
+ echo "📊 ===== Execution Summary ====="
1413
+ echo "Status: ${{ steps.claude.outcome }}"
1414
+ echo "Context Type: ${{ needs.setup.outputs.is-pr == 'true' && 'PR' || 'Issue' }}"
1415
+ echo "Issue/PR: '#${{ needs.setup.outputs.issue-number }}'"
1416
+ echo "Branch: ${{ needs.setup.outputs.head-ref || 'default' }}"
1417
+ echo "Actor: ${{ github.actor }}"
1418
+ echo "Event: ${{ github.event_name }}"
1419
+ echo "Project: ${{ steps.project-info.outputs.framework || 'Unknown' }}"
1420
+ echo "Files: ${{ steps.project-info.outputs.total-files || '0' }}"
1421
+ echo "Duration: ${{ steps.claude.outputs.duration || 'N/A' }}"
1422
+ echo ""
1423
+ echo "🔧 === Auto PR Creation Result ==="
1424
+ echo "Has Changes: ${{ steps.check-changes.outputs.has-changes || 'N/A' }}"
1425
+ echo "Auto PR Number: ${{ steps.auto-pr.outputs.pr-number || 'N/A' }}"
1426
+ echo "Auto PR URL: ${{ steps.auto-pr.outputs.pr-url || 'N/A' }}"
1427
+ echo "Branch Name: ${{ steps.auto-pr.outputs.branch-name || 'N/A' }}"
1428
+ echo "Auto PR Error: ${{ steps.auto-pr.outputs.error || 'None' }}"
1429
+ echo ""
1430
+ echo "🚀 === Advanced Repository Management Result ==="
1431
+ echo "Management Success: ${{ steps.advanced-management.outputs.management-success || 'N/A' }}"
1432
+ echo "Management Error: ${{ steps.advanced-management.outputs.management-error || 'None' }}"
1433
+ echo "Management Results Available: ${{ steps.advanced-management.outputs.management-results && 'Yes' || 'No' }}"
1434
+ echo ""
1435
+ echo "Timestamp: $(date -u)"
1436
+ echo "=============================="