-
Notifications
You must be signed in to change notification settings - Fork 15
1104 lines (994 loc) · 49.1 KB
/
Copy pathdocsync-ai.yml
File metadata and controls
1104 lines (994 loc) · 49.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
name: DocSync AI - Documentation Sync
on:
workflow_call:
inputs:
base_branch:
description: "Base branch for documentation PR (e.g., master or main)"
required: false
default: "master"
type: string
pr_labels:
description: "Comma-separated labels for the documentation PR"
required: false
default: "documentation,automated"
type: string
trigger_type:
description: 'Trigger type: "scheduled" for weekly cron, "comment" for PR comment, "claude_md_refresh" for manual CLAUDE.md best-practices reformat'
required: false
default: "scheduled"
type: string
comment_body:
description: 'Comment body (only for comment trigger, must start with "@docbot")'
required: false
default: ""
type: string
comment_pr_number:
description: "PR number where comment was made (only for comment trigger)"
required: false
default: ""
type: string
comment_id:
description: "Comment ID for adding thumbs-up reaction (only for comment trigger)"
required: false
default: ""
type: string
secrets:
DOCSYNC_GITHUB_TOKEN:
description: "GitHub PAT for PR creation (repo-specific, needs repo and pull_requests permissions)"
required: true
DOCSYNC_ANTHROPIC_API_KEY:
description: "Anthropic API key for Claude AI"
required: true
DOCSYNC_SLACK_TOKEN:
description: "Slack OAuth bot token for notifications (xoxb-...)"
required: false
DOCSYNC_SLACK_CHANNEL_ID:
description: "Slack channel ID to post notifications"
required: false
DOCSYNC_SLACK_MENTION_USER_IDS:
description: "Slack user ID(s) to mention in notifications"
required: false
AGENT_METRICS_API_URL:
description: "Base URL for the OneAboveAll metrics dashboard API (e.g. https://dashboard.example.com). Leave unset to skip POSTing events."
required: false
AGENT_METRICS_API_KEY:
description: "API key for authenticating POST requests to the metrics dashboard. Must match AGENT_METRICS_API_KEY on the backend. Required when AGENT_METRICS_API_URL is set."
required: false
concurrency:
group: docsync-ai-${{ github.repository }}
cancel-in-progress: false
jobs:
sync-documentation:
name: Sync Documentation (Weekly)
runs-on: ubuntu-latest
# Only run when trigger_type is 'scheduled'
if: inputs.trigger_type == 'scheduled'
permissions:
contents: write
pull-requests: write
outputs:
doc_pr_created: ${{ steps.docsync.outputs.doc_pr_created }}
doc_pr_number: ${{ steps.docsync.outputs.doc_pr_number }}
doc_pr_url: ${{ steps.docsync.outputs.doc_pr_url }}
env:
BASE_BRANCH: ${{ inputs.base_branch }}
PR_LABELS: ${{ inputs.pr_labels }}
SLACK_TOKEN: ${{ secrets.DOCSYNC_SLACK_TOKEN }}
SLACK_CHANNEL: ${{ secrets.DOCSYNC_SLACK_CHANNEL_ID }}
steps:
- name: Debug - Workflow Trigger Info
id: trigger-info
run: |
WORKFLOW_START_TIME=$(date +%s)
echo "start_time=$WORKFLOW_START_TIME" >> $GITHUB_OUTPUT
echo "## Workflow Trigger Info" >> $GITHUB_STEP_SUMMARY
echo "Event: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
echo "Trigger Type: ${{ inputs.trigger_type }}" >> $GITHUB_STEP_SUMMARY
echo "Base Branch: ${{ inputs.base_branch }}" >> $GITHUB_STEP_SUMMARY
echo "Repository: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY
# Log workflow_triggered event
EVENTS_FILE="/tmp/docsync_events_${GITHUB_RUN_ID}.jsonl"
jq -cn \
--arg run_id "$GITHUB_RUN_ID" \
--arg repo "${{ github.repository }}" \
--arg base_branch "${{ inputs.base_branch }}" \
'{
agent: "DocSync AI",
event_type: "workflow_triggered",
run_id: $run_id,
trigger_type: "scheduled",
timestamp: (now | todate),
payload: {
repository: $repo,
base_branch: $base_branch,
workflow_name: "docsync-ai"
}
}' >> "$EVENTS_FILE" 2>/dev/null || true
- name: Checkout Repository
uses: actions/checkout@v4
with:
token: ${{ secrets.DOCSYNC_GITHUB_TOKEN }}
ref: ${{ inputs.base_branch }}
- name: Run DocSync AI Action
id: docsync
uses: deriv-com/shared-actions/.github/actions/docsync_ai@master
with:
github_token: ${{ secrets.DOCSYNC_GITHUB_TOKEN }}
anthropic_api_key: ${{ secrets.DOCSYNC_ANTHROPIC_API_KEY }}
repository: ${{ github.repository }}
base_branch: ${{ inputs.base_branch }}
pr_labels: ${{ inputs.pr_labels }}
- name: Send Slack Notification - Documentation PR Created
if: steps.docsync.outputs.doc_pr_created == 'true' && env.SLACK_TOKEN != '' && env.SLACK_CHANNEL != ''
env:
DOC_PR_NUMBER: ${{ steps.docsync.outputs.doc_pr_number }}
DOC_PR_URL: ${{ steps.docsync.outputs.doc_pr_url }}
SLACK_MENTION_USER: ${{ secrets.DOCSYNC_SLACK_MENTION_USER_IDS }}
run: |
set -e
echo "### Slack Notification" >> $GITHUB_STEP_SUMMARY
echo "Channel: $SLACK_CHANNEL" >> $GITHUB_STEP_SUMMARY
echo "PR: #$DOC_PR_NUMBER" >> $GITHUB_STEP_SUMMARY
# Sanitize inputs
DOC_PR_URL_CLEAN=$(echo "$DOC_PR_URL" | head -c 500)
# Build mention text if user ID(s) are provided
MENTION_TEXT=""
if [ -n "$SLACK_MENTION_USER" ]; then
# Split comma-separated user IDs and build mentions
IFS=',' read -ra USER_IDS <<< "$SLACK_MENTION_USER"
for user_id in "${USER_IDS[@]}"; do
# Trim whitespace
user_id=$(echo "$user_id" | xargs)
if [ -n "$user_id" ]; then
MENTION_TEXT="${MENTION_TEXT}<@${user_id}> "
fi
done
fi
# Create Slack payload with text fallback (required by Slack API)
SLACK_PAYLOAD=$(jq -cn \
--arg channel "$SLACK_CHANNEL" \
--arg repo "${{ github.repository }}" \
--arg doc_pr_url "$DOC_PR_URL_CLEAN" \
--arg doc_pr_num "$DOC_PR_NUMBER" \
--arg mention "$MENTION_TEXT" \
'{
"channel": $channel,
"text": ($mention + "Weekly Documentation Update Ready - PR #" + $doc_pr_num + " in " + $repo),
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ($mention + ":book: *Weekly Documentation Update Ready*\n*<" + $doc_pr_url + "|PR #" + $doc_pr_num + ">* in `" + $repo + "`")
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Documentation has been automatically updated based on all PRs merged this week."
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": ("DocSync AI (Weekly Sync) | <" + $doc_pr_url + "|Review documentation updates>")
}
]
}
]
}')
# Send Slack notification via API with OAuth token
RESPONSE=$(curl -s -X POST "https://slack.com/api/chat.postMessage" \
-H "Authorization: Bearer $SLACK_TOKEN" \
-H "Content-Type: application/json" \
-d "$SLACK_PAYLOAD")
# Check response
SLACK_OK=$(echo "$RESPONSE" | jq -r '.ok' 2>/dev/null || echo "false")
if [ "$SLACK_OK" = "true" ]; then
echo "Slack notification sent successfully" >> $GITHUB_STEP_SUMMARY
else
SLACK_ERROR=$(echo "$RESPONSE" | jq -r '.error // "unknown"' 2>/dev/null)
echo "Warning: Slack API error: $SLACK_ERROR" >> $GITHUB_STEP_SUMMARY
echo "Response: $RESPONSE" >> $GITHUB_STEP_SUMMARY
fi
# Log slack_notification_sent event
EVENTS_FILE="/tmp/docsync_events_${GITHUB_RUN_ID}.jsonl"
SLACK_STATUS=$([ "$SLACK_OK" = "true" ] && echo "success" || echo "failed")
PR_LINK="https://github.com/${{ github.repository }}/pull/$DOC_PR_NUMBER"
jq -cn \
--arg run_id "$GITHUB_RUN_ID" \
--arg status "$SLACK_STATUS" \
--arg channel "$SLACK_CHANNEL" \
--arg pr_link "$PR_LINK" \
'{
agent: "DocSync AI",
event_type: "slack_notification_sent",
run_id: $run_id,
trigger_type: "scheduled",
timestamp: (now | todate),
payload: {
status: $status,
channel: $channel,
message_type: "documentation_update",
pr_link: $pr_link
}
}' >> "$EVENTS_FILE" 2>/dev/null || true
- name: Summary
if: always()
env:
JOB_STATUS: ${{ job.status }}
DOC_PR_CREATED: ${{ steps.docsync.outputs.doc_pr_created }}
DOC_PR_URL: ${{ steps.docsync.outputs.doc_pr_url }}
WORKFLOW_START_TIME: ${{ steps.trigger-info.outputs.start_time }}
AGENT_METRICS_API_URL: ${{ secrets.AGENT_METRICS_API_URL }}
AGENT_METRICS_API_KEY: ${{ secrets.AGENT_METRICS_API_KEY }}
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY
echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Trigger | Weekly Scheduled |" >> $GITHUB_STEP_SUMMARY
echo "| Documentation PR Created | ${DOC_PR_CREATED:-false} |" >> $GITHUB_STEP_SUMMARY
echo "| Status | $JOB_STATUS |" >> $GITHUB_STEP_SUMMARY
echo "| Base Branch | $BASE_BRANCH |" >> $GITHUB_STEP_SUMMARY
# Compute duration
END_TIME=$(date +%s)
START_TIME="${WORKFLOW_START_TIME:-$END_TIME}"
DURATION_SECONDS=$((END_TIME - START_TIME))
# Log workflow_completed event
EVENTS_FILE="/tmp/docsync_events_${GITHUB_RUN_ID}.jsonl"
STATUS_VAL=$([ "$JOB_STATUS" = "success" ] && echo "success" || echo "failed")
DOC_PR_BOOL=$([ "${DOC_PR_CREATED:-false}" = "true" ] && echo "true" || echo "false")
if [ "$JOB_STATUS" = "success" ]; then
ERROR_JSON="null"
else
ERROR_JSON="\"Job failed with status: $JOB_STATUS\""
fi
# Ensure numeric values are valid for jq --argjson
[[ ! "$DURATION_SECONDS" =~ ^[0-9]+$ ]] && DURATION_SECONDS=0
jq -cn \
--arg run_id "$GITHUB_RUN_ID" \
--arg status "$STATUS_VAL" \
--argjson duration "$DURATION_SECONDS" \
--argjson doc_pr_created "$DOC_PR_BOOL" \
--argjson changes_made "$DOC_PR_BOOL" \
--argjson error "$ERROR_JSON" \
'{
agent: "DocSync AI",
event_type: "workflow_completed",
run_id: $run_id,
trigger_type: "scheduled",
timestamp: (now | todate),
payload: {
status: $status,
duration_seconds: $duration,
doc_pr_created: $doc_pr_created,
changes_made: $changes_made,
error: $error
}
}' >> "$EVENTS_FILE" 2>/dev/null || true
# POST accumulated events to the metrics dashboard
if [[ -n "$AGENT_METRICS_API_URL" ]] && [ -f "$EVENTS_FILE" ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📡 Metrics Dashboard" >> $GITHUB_STEP_SUMMARY
CURL_HEADERS=(-H "Content-Type: application/json")
[[ -n "$AGENT_METRICS_API_KEY" ]] && CURL_HEADERS+=(-H "X-API-Key: $AGENT_METRICS_API_KEY")
POST_SUCCESS=0
POST_FAIL=0
while IFS= read -r EVENT_LINE; do
HTTP_CODE=$(curl -s -w "%{http_code}" -o /tmp/api_response.json \
"${CURL_HEADERS[@]}" \
"${AGENT_METRICS_API_URL}/api/v1/events" \
-X POST \
-d "$EVENT_LINE")
if [[ "$HTTP_CODE" == "200" || "$HTTP_CODE" == "201" ]]; then
POST_SUCCESS=$((POST_SUCCESS + 1))
else
POST_FAIL=$((POST_FAIL + 1))
echo "⚠️ Event POST returned HTTP $HTTP_CODE" >> $GITHUB_STEP_SUMMARY
fi
done < "$EVENTS_FILE"
echo "✅ Events sent to dashboard: $POST_SUCCESS success, $POST_FAIL failed" >> $GITHUB_STEP_SUMMARY
else
if [[ -z "$AGENT_METRICS_API_URL" ]]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "ℹ️ AGENT_METRICS_API_URL not set — events logged to job summary only" >> $GITHUB_STEP_SUMMARY
fi
fi
# Display accumulated event log
if [ -f "$EVENTS_FILE" ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "## 📊 DocSync AI — Event Log" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
EVENT_NUM=0
while IFS= read -r EVENT_LINE; do
EVENT_NUM=$((EVENT_NUM + 1))
EVENT_TYPE=$(echo "$EVENT_LINE" | jq -r '.event_type // "unknown"')
{
echo "<details>"
printf "<summary>📋 Event %d: <code>%s</code></summary>\n" "$EVENT_NUM" "$EVENT_TYPE"
echo ""
echo '```json'
echo "$EVENT_LINE" | jq '.'
echo '```'
echo ""
echo "</details>"
echo ""
} >> "$GITHUB_STEP_SUMMARY"
done < "$EVENTS_FILE"
fi
- name: Prepare Event Log Artifact
if: always()
run: |
EVENTS_FILE="/tmp/docsync_events_${GITHUB_RUN_ID}.jsonl"
ARTIFACT_DIR="/tmp/docsync_artifact_${GITHUB_RUN_ID}"
mkdir -p "$ARTIFACT_DIR"
if [ -s "$EVENTS_FILE" ]; then
jq -s '.' "$EVENTS_FILE" > "$ARTIFACT_DIR/docsync-payload.json"
else
echo '[]' > "$ARTIFACT_DIR/docsync-payload.json"
fi
rm -f "$EVENTS_FILE"
- name: Upload DocSync Event Log
if: always()
uses: actions/upload-artifact@v4
with:
name: docsync-event-log-${{ github.run_id }}
path: /tmp/docsync_artifact_${{ github.run_id }}/docsync-payload.json
retention-days: 90
if-no-files-found: warn
update-from-comment:
name: Update Documentation from Comment
runs-on: ubuntu-latest
# Only run when trigger_type is 'comment'
if: inputs.trigger_type == 'comment'
permissions:
contents: write
pull-requests: write
env:
SLACK_TOKEN: ${{ secrets.DOCSYNC_SLACK_TOKEN }}
SLACK_CHANNEL: ${{ secrets.DOCSYNC_SLACK_CHANNEL_ID }}
steps:
# ── Instant thumbs-up so user knows the bot is processing ──
- name: Acknowledge Comment with 👍
if: inputs.comment_id != ''
uses: actions/github-script@v7
with:
github-token: ${{ secrets.DOCSYNC_GITHUB_TOKEN }}
script: |
const commentId = `${{ inputs.comment_id }}`;
if (!commentId || !/^\d+$/.test(commentId)) {
core.info('No valid comment ID, skipping reaction');
return;
}
const [owner, repo] = `${{ github.repository }}`.split('/');
await github.rest.reactions.createForIssueComment({
owner,
repo,
comment_id: Number(commentId),
content: '+1'
});
core.info(`👍 Reaction added to comment ${commentId}`);
- name: Debug - Comment Trigger Info
id: trigger-info
run: |
WORKFLOW_START_TIME=$(date +%s)
echo "start_time=$WORKFLOW_START_TIME" >> $GITHUB_OUTPUT
echo "## Comment Trigger Info" >> $GITHUB_STEP_SUMMARY
echo "Trigger Type: ${{ inputs.trigger_type }}" >> $GITHUB_STEP_SUMMARY
echo "PR Number: ${{ inputs.comment_pr_number }}" >> $GITHUB_STEP_SUMMARY
echo "Comment ID: ${{ inputs.comment_id }}" >> $GITHUB_STEP_SUMMARY
echo "Repository: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY
# Log workflow_triggered event
EVENTS_FILE="/tmp/docsync_events_${GITHUB_RUN_ID}.jsonl"
jq -cn \
--arg run_id "$GITHUB_RUN_ID" \
--arg repo "${{ github.repository }}" \
--arg base_branch "${{ inputs.base_branch }}" \
'{
agent: "DocSync AI",
event_type: "workflow_triggered",
run_id: $run_id,
trigger_type: "comment",
timestamp: (now | todate),
payload: {
repository: $repo,
base_branch: $base_branch,
workflow_name: "docsync-ai"
}
}' >> "$EVENTS_FILE" 2>/dev/null || true
- name: Run DocSync AI Comment Action
id: docsync-comment
uses: deriv-com/shared-actions/.github/actions/docsync_ai_comment@master
with:
github_token: ${{ secrets.DOCSYNC_GITHUB_TOKEN }}
anthropic_api_key: ${{ secrets.DOCSYNC_ANTHROPIC_API_KEY }}
repository: ${{ github.repository }}
pr_number: ${{ inputs.comment_pr_number }}
comment_body: ${{ inputs.comment_body }}
comment_id: ${{ inputs.comment_id }}
base_branch: ${{ inputs.base_branch }}
- name: Send Slack Notification - Documentation Updated
if: steps.docsync-comment.outputs.updated == 'true' && env.SLACK_TOKEN != '' && env.SLACK_CHANNEL != ''
env:
COMMENT_PR_NUMBER: ${{ inputs.comment_pr_number }}
IS_DOCSYNC_PR: ${{ steps.docsync-comment.outputs.is_docsync_pr }}
SLACK_MENTION_USER: ${{ secrets.DOCSYNC_SLACK_MENTION_USER_IDS }}
run: |
set -e
echo "### Slack Notification" >> $GITHUB_STEP_SUMMARY
echo "Channel: $SLACK_CHANNEL" >> $GITHUB_STEP_SUMMARY
echo "PR: #$COMMENT_PR_NUMBER" >> $GITHUB_STEP_SUMMARY
# Build PR URL
PR_URL="https://github.com/${{ github.repository }}/pull/$COMMENT_PR_NUMBER"
# Choose message based on PR type
if [ "$IS_DOCSYNC_PR" = "true" ]; then
UPDATE_TYPE="User suggestion applied to DocSync PR"
else
UPDATE_TYPE="Documentation updated from PR comment"
fi
# Build mention text if user ID(s) are provided
MENTION_TEXT=""
if [ -n "$SLACK_MENTION_USER" ]; then
# Split comma-separated user IDs and build mentions
IFS=',' read -ra USER_IDS <<< "$SLACK_MENTION_USER"
for user_id in "${USER_IDS[@]}"; do
# Trim whitespace
user_id=$(echo "$user_id" | xargs)
if [ -n "$user_id" ]; then
MENTION_TEXT="${MENTION_TEXT}<@${user_id}> "
fi
done
fi
# Create Slack payload with text fallback (required by Slack API)
SLACK_PAYLOAD=$(jq -cn \
--arg channel "$SLACK_CHANNEL" \
--arg repo "${{ github.repository }}" \
--arg pr_url "$PR_URL" \
--arg pr_num "$COMMENT_PR_NUMBER" \
--arg update_type "$UPDATE_TYPE" \
--arg mention "$MENTION_TEXT" \
'{
"channel": $channel,
"text": ($mention + $update_type + " - PR #" + $pr_num + " in " + $repo),
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ($mention + ":memo: *Documentation Updated via Comment*\n*<" + $pr_url + "|PR #" + $pr_num + ">* in `" + $repo + "`")
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": $update_type
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": ("DocSync AI | <" + $pr_url + "|View PR>")
}
]
}
]
}')
# Send Slack notification via API with OAuth token
RESPONSE=$(curl -s -X POST "https://slack.com/api/chat.postMessage" \
-H "Authorization: Bearer $SLACK_TOKEN" \
-H "Content-Type: application/json" \
-d "$SLACK_PAYLOAD")
# Check response
SLACK_OK=$(echo "$RESPONSE" | jq -r '.ok' 2>/dev/null || echo "false")
if [ "$SLACK_OK" = "true" ]; then
echo "Slack notification sent successfully" >> $GITHUB_STEP_SUMMARY
else
SLACK_ERROR=$(echo "$RESPONSE" | jq -r '.error // "unknown"' 2>/dev/null)
echo "Warning: Slack API error: $SLACK_ERROR" >> $GITHUB_STEP_SUMMARY
echo "Response: $RESPONSE" >> $GITHUB_STEP_SUMMARY
fi
# Log slack_notification_sent event
EVENTS_FILE="/tmp/docsync_events_${GITHUB_RUN_ID}.jsonl"
SLACK_STATUS=$([ "$SLACK_OK" = "true" ] && echo "success" || echo "failed")
PR_LINK="https://github.com/${{ github.repository }}/pull/$COMMENT_PR_NUMBER"
jq -cn \
--arg run_id "$GITHUB_RUN_ID" \
--arg status "$SLACK_STATUS" \
--arg channel "$SLACK_CHANNEL" \
--arg pr_link "$PR_LINK" \
'{
agent: "DocSync AI",
event_type: "slack_notification_sent",
run_id: $run_id,
trigger_type: "comment",
timestamp: (now | todate),
payload: {
status: $status,
channel: $channel,
message_type: "documentation_update",
pr_link: $pr_link
}
}' >> "$EVENTS_FILE" 2>/dev/null || true
- name: Summary
if: always()
env:
JOB_STATUS: ${{ job.status }}
UPDATED: ${{ steps.docsync-comment.outputs.updated }}
VALID: ${{ steps.docsync-comment.outputs.command_valid }}
COMMAND: ${{ steps.docsync-comment.outputs.command }}
IS_DOCSYNC_PR: ${{ steps.docsync-comment.outputs.is_docsync_pr }}
WORKFLOW_START_TIME: ${{ steps.trigger-info.outputs.start_time }}
AGENT_METRICS_API_URL: ${{ secrets.AGENT_METRICS_API_URL }}
AGENT_METRICS_API_KEY: ${{ secrets.AGENT_METRICS_API_KEY }}
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY
echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| PR Number | #${{ inputs.comment_pr_number }} |" >> $GITHUB_STEP_SUMMARY
echo "| Valid Command | ${VALID:-false} |" >> $GITHUB_STEP_SUMMARY
echo "| Command | ${COMMAND:-none} |" >> $GITHUB_STEP_SUMMARY
echo "| Is DocSync PR | ${IS_DOCSYNC_PR:-false} |" >> $GITHUB_STEP_SUMMARY
echo "| Documentation Updated | ${UPDATED:-false} |" >> $GITHUB_STEP_SUMMARY
echo "| Status | $JOB_STATUS |" >> $GITHUB_STEP_SUMMARY
# Compute duration
END_TIME=$(date +%s)
START_TIME="${WORKFLOW_START_TIME:-$END_TIME}"
DURATION_SECONDS=$((END_TIME - START_TIME))
# Log workflow_completed event
EVENTS_FILE="/tmp/docsync_events_${GITHUB_RUN_ID}.jsonl"
STATUS_VAL=$([ "$JOB_STATUS" = "success" ] && echo "success" || echo "failed")
CHANGES_BOOL=$([ "${UPDATED:-false}" = "true" ] && echo "true" || echo "false")
if [ "$JOB_STATUS" = "success" ]; then
ERROR_JSON="null"
else
ERROR_JSON="\"Job failed with status: $JOB_STATUS\""
fi
# Ensure numeric values are valid for jq --argjson
[[ ! "$DURATION_SECONDS" =~ ^[0-9]+$ ]] && DURATION_SECONDS=0
jq -cn \
--arg run_id "$GITHUB_RUN_ID" \
--arg status "$STATUS_VAL" \
--argjson duration "$DURATION_SECONDS" \
--argjson doc_pr_created "false" \
--argjson changes_made "$CHANGES_BOOL" \
--argjson error "$ERROR_JSON" \
'{
agent: "DocSync AI",
event_type: "workflow_completed",
run_id: $run_id,
trigger_type: "comment",
timestamp: (now | todate),
payload: {
status: $status,
duration_seconds: $duration,
doc_pr_created: $doc_pr_created,
changes_made: $changes_made,
error: $error
}
}' >> "$EVENTS_FILE" 2>/dev/null || true
# POST accumulated events to the metrics dashboard
if [[ -n "$AGENT_METRICS_API_URL" ]] && [ -f "$EVENTS_FILE" ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📡 Metrics Dashboard" >> $GITHUB_STEP_SUMMARY
CURL_HEADERS=(-H "Content-Type: application/json")
[[ -n "$AGENT_METRICS_API_KEY" ]] && CURL_HEADERS+=(-H "X-API-Key: $AGENT_METRICS_API_KEY")
POST_SUCCESS=0
POST_FAIL=0
while IFS= read -r EVENT_LINE; do
HTTP_CODE=$(curl -s -w "%{http_code}" -o /tmp/api_response.json \
"${CURL_HEADERS[@]}" \
"${AGENT_METRICS_API_URL}/api/v1/events" \
-X POST \
-d "$EVENT_LINE")
if [[ "$HTTP_CODE" == "200" || "$HTTP_CODE" == "201" ]]; then
POST_SUCCESS=$((POST_SUCCESS + 1))
else
POST_FAIL=$((POST_FAIL + 1))
echo "⚠️ Event POST returned HTTP $HTTP_CODE" >> $GITHUB_STEP_SUMMARY
fi
done < "$EVENTS_FILE"
echo "✅ Events sent to dashboard: $POST_SUCCESS success, $POST_FAIL failed" >> $GITHUB_STEP_SUMMARY
else
if [[ -z "$AGENT_METRICS_API_URL" ]]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "ℹ️ AGENT_METRICS_API_URL not set — events logged to job summary only" >> $GITHUB_STEP_SUMMARY
fi
fi
# Display accumulated event log
if [ -f "$EVENTS_FILE" ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "## 📊 DocSync AI — Event Log" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
EVENT_NUM=0
while IFS= read -r EVENT_LINE; do
EVENT_NUM=$((EVENT_NUM + 1))
EVENT_TYPE=$(echo "$EVENT_LINE" | jq -r '.event_type // "unknown"')
{
echo "<details>"
printf "<summary>📋 Event %d: <code>%s</code></summary>\n" "$EVENT_NUM" "$EVENT_TYPE"
echo ""
echo '```json'
echo "$EVENT_LINE" | jq '.'
echo '```'
echo ""
echo "</details>"
echo ""
} >> "$GITHUB_STEP_SUMMARY"
done < "$EVENTS_FILE"
fi
- name: Prepare Event Log Artifact
if: always()
run: |
EVENTS_FILE="/tmp/docsync_events_${GITHUB_RUN_ID}.jsonl"
ARTIFACT_DIR="/tmp/docsync_artifact_${GITHUB_RUN_ID}"
mkdir -p "$ARTIFACT_DIR"
if [ -s "$EVENTS_FILE" ]; then
jq -s '.' "$EVENTS_FILE" > "$ARTIFACT_DIR/docsync-payload.json"
else
echo '[]' > "$ARTIFACT_DIR/docsync-payload.json"
fi
rm -f "$EVENTS_FILE"
- name: Upload DocSync Event Log
if: always()
uses: actions/upload-artifact@v4
with:
name: docsync-event-log-${{ github.run_id }}
path: /tmp/docsync_artifact_${{ github.run_id }}/docsync-payload.json
retention-days: 90
if-no-files-found: warn
refresh-claude-md:
name: Refresh CLAUDE.md to Best Practices (Manual)
runs-on: ubuntu-latest
# Only run when trigger_type is 'claude_md_refresh'
if: inputs.trigger_type == 'claude_md_refresh'
permissions:
contents: write
pull-requests: write
env:
BASE_BRANCH: ${{ inputs.base_branch }}
SLACK_TOKEN: ${{ secrets.DOCSYNC_SLACK_TOKEN }}
SLACK_CHANNEL: ${{ secrets.DOCSYNC_SLACK_CHANNEL_ID }}
steps:
- name: Debug - Workflow Trigger Info
id: trigger-info
run: |
WORKFLOW_START_TIME=$(date +%s)
echo "start_time=$WORKFLOW_START_TIME" >> $GITHUB_OUTPUT
echo "## CLAUDE.md Refresh — Trigger Info" >> $GITHUB_STEP_SUMMARY
echo "Event: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
echo "Trigger Type: ${{ inputs.trigger_type }}" >> $GITHUB_STEP_SUMMARY
echo "Base Branch: ${{ inputs.base_branch }}" >> $GITHUB_STEP_SUMMARY
echo "Repository: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY
echo "Triggered by: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
- name: Checkout Repository
uses: actions/checkout@v4
with:
token: ${{ secrets.DOCSYNC_GITHUB_TOKEN }}
ref: ${{ inputs.base_branch }}
fetch-depth: 0
- name: Locate and Validate CLAUDE.md
id: locate-claude-md
shell: bash
run: |
echo "## CLAUDE.md Detection" >> $GITHUB_STEP_SUMMARY
CLAUDE_FILE=$(find . -maxdepth 1 -type f -iname "claude.md" -print -quit 2>/dev/null)
if [ -z "$CLAUDE_FILE" ]; then
echo "No CLAUDE.md found in repository root — nothing to refresh." >> $GITHUB_STEP_SUMMARY
echo "found=false" >> $GITHUB_OUTPUT
exit 0
fi
CLAUDE_FILE="${CLAUDE_FILE#./}"
REALPATH=$(realpath "$CLAUDE_FILE" 2>/dev/null || echo "")
WORKDIR=$(realpath . 2>/dev/null || echo "")
if [[ -L "$CLAUDE_FILE" ]] || [[ ! "$REALPATH" == "$WORKDIR"/* ]]; then
echo "Invalid CLAUDE.md path detected — skipping." >> $GITHUB_STEP_SUMMARY
echo "found=false" >> $GITHUB_OUTPUT
exit 0
fi
CLAUDE_SIZE=$(wc -c < "$CLAUDE_FILE" | tr -d ' ')
echo "Found: $CLAUDE_FILE ($CLAUDE_SIZE bytes)" >> $GITHUB_STEP_SUMMARY
echo "found=true" >> $GITHUB_OUTPUT
echo "claude_file=$CLAUDE_FILE" >> $GITHUB_OUTPUT
- name: Reformat CLAUDE.md with Claude API
id: reformat
if: steps.locate-claude-md.outputs.found == 'true'
shell: bash
env:
ANTHROPIC_API_KEY: ${{ secrets.DOCSYNC_ANTHROPIC_API_KEY }}
CLAUDE_FILE: ${{ steps.locate-claude-md.outputs.claude_file }}
REPO_NAME: ${{ github.repository }}
run: |
set -e
echo "## Claude API — CLAUDE.md Reformat" >> $GITHUB_STEP_SUMMARY
CURRENT_CONTENT=$(cat "$CLAUDE_FILE" | tr -d '\000' | iconv -c -t UTF-8//IGNORE)
CONTENT_CHARS=${#CURRENT_CONTENT}
echo "Current size: $CONTENT_CHARS chars" >> $GITHUB_STEP_SUMMARY
TEMP_DIR=$(mktemp -d -t claude-refresh-XXXXXXXXXX)
PROMPT_FILE="$TEMP_DIR/prompt.txt"
{
printf '%s\n' "You are an expert in Claude Code (Anthropic's AI coding CLI). Your job is to reformat an existing CLAUDE.md file so it follows the OFFICIAL Claude Code best practices (https://code.claude.com/docs/en/best-practices) AND preserves every piece of information in it. This is a make-it-more-efficient pass, not a prune-it-down pass."
printf '%s\n' ""
printf '%s\n' "## Repository"
printf '%s\n' "$REPO_NAME"
printf '%s\n' ""
printf '%s\n' "## Context from the official best-practices doc"
printf '%s\n' "CLAUDE.md is loaded at the start of every Claude Code session, so it spends context budget on every conversation. The official guidance says:"
printf '%s\n' "- \"There's no required format for CLAUDE.md files, but keep it short and human-readable.\""
printf '%s\n' "- \"For each line, ask: 'Would removing this cause Claude to make mistakes?' If not, cut it. Bloated CLAUDE.md files cause Claude to ignore your actual instructions!\""
printf '%s\n' "- \"You can tune instructions by adding emphasis (e.g., 'IMPORTANT' or 'YOU MUST') to improve adherence.\""
printf '%s\n' "- CLAUDE.md supports importing other files via \`@path/to/import\` syntax."
printf '%s\n' ""
printf '%s\n' "The official doc ships this exact example format:"
printf '%s\n' '```markdown'
printf '%s\n' "# Code style"
printf '%s\n' "- Use ES modules (import/export) syntax, not CommonJS (require)"
printf '%s\n' "- Destructure imports when possible (eg. import { foo } from 'bar')"
printf '%s\n' ""
printf '%s\n' "# Workflow"
printf '%s\n' "- Be sure to typecheck when you're done making a series of code changes"
printf '%s\n' "- Prefer running single tests, and not the whole test suite, for performance"
printf '%s\n' '```'
printf '%s\n' "Match that shape: \`#\` top-level headings, short one-line bullets, fenced code blocks for commands."
printf '%s\n' ""
printf '%s\n' "The official INCLUDE / EXCLUDE table:"
printf '%s\n' "| INCLUDE | EXCLUDE |"
printf '%s\n' "|---|---|"
printf '%s\n' "| Bash commands Claude can't guess | Anything Claude can figure out by reading code |"
printf '%s\n' "| Code style rules that differ from defaults | Standard language conventions Claude already knows |"
printf '%s\n' "| Testing instructions and preferred test runners | Detailed API documentation (link to docs instead) |"
printf '%s\n' "| Repository etiquette (branch naming, PR conventions) | Information that changes frequently |"
printf '%s\n' "| Architectural decisions specific to the project | Long explanations or tutorials |"
printf '%s\n' "| Developer environment quirks (required env vars) | File-by-file descriptions of the codebase |"
printf '%s\n' "| Common gotchas or non-obvious behaviors | Self-evident practices like \"write clean code\" |"
printf '%s\n' ""
printf '%s\n' "## Hard rule — ZERO CONTENT LOSS (overrides the doc's \"cut it\" advice)"
printf '%s\n' "The official doc says to cut lines that don't belong. This refresh does NOT do that. Instead:"
printf '%s\n' "- Every unique command, file path, env var, URL, flag, convention, rule, gotcha, and instruction in the input MUST appear in the output."
printf '%s\n' "- If something falls in the EXCLUDE column (long explanations, API docs, changelogs, etc.), DO NOT DELETE it. Instead: compress its wording to the tightest factual form, and move it to a later section so it doesn't crowd the high-value commands and rules."
printf '%s\n' "- If you are unsure whether something is real information or just prose, treat it as information and keep it."
printf '%s\n' "- Never replace specifics with summaries: \`pnpm run test:unit -- --watch\` must stay as-is; \"run the unit tests\" is not an acceptable substitute. A list of 5 env vars must stay as 5 env vars, not \"required env vars\"."
printf '%s\n' ""
printf '%s\n' "## How to apply best practices WITHOUT deleting anything"
printf '%s\n' "### Structure (re-group, do not drop)"
printf '%s\n' "- Pick ONE top-level heading level (\`#\` preferred per the official example) and use it consistently throughout."
printf '%s\n' "- Group content under clear, short INCLUDE-oriented headings when the content fits (e.g., \`# Code style\`, \`# Workflow\`, \`# Commands\`, \`# Testing\`, \`# Repo etiquette\`, \`# Architecture\`, \`# Environment\`, \`# Gotchas\`)."
printf '%s\n' "- For content that falls in the EXCLUDE column, create a clearly-labelled tail section (e.g., \`# Reference\` or \`# Additional notes\`) and put it AFTER the high-value sections. Keep the content, just deprioritize its position."
printf '%s\n' "- Merge scattered mentions of the same topic under a single heading."
printf '%s\n' "### Style (apply the example format)"
printf '%s\n' "- One-line bullets under each heading. Sub-bullets only when the hierarchy is genuine."
printf '%s\n' "- All commands in fenced code blocks with the correct language tag (\`\`\`bash, \`\`\`ts, \`\`\`sh, etc.)."
printf '%s\n' "- Preserve any \`@path/to/import\` syntax exactly as written — it is a live CLAUDE.md feature."
printf '%s\n' "- Preserve any existing emphasis (IMPORTANT, YOU MUST) from the source. Do not add new emphasis."
printf '%s\n' "### Efficiency (tighten wording, never cut information)"
printf '%s\n' "- Replace wordy prose with tight factual phrasing. Example: \"It is important to always remember to run the tests before pushing\" → \"Always run tests before pushing.\" Same information, fewer tokens."
printf '%s\n' "- Collapse duplicate or near-duplicate bullets into a single bullet whose text covers every unique specific from the originals."
printf '%s\n' "- Remove rhetorical filler only (\"please note that\", \"it is worth mentioning\", \"basically\", \"in order to\") — filler is not information."
printf '%s\n' "- Strip purely cosmetic noise that carries no information: badges, banners, tables of contents, repeated blank lines, trailing whitespace, stray \`---\` separators, decorative emoji spam."
printf '%s\n' ""
printf '%s\n' "## Your task"
printf '%s\n' "Produce a reformatted CLAUDE.md that:"
printf '%s\n' "1. Contains every unique piece of information from the input (hard rule above)."
printf '%s\n' "2. Is shaped like the official example: \`#\` headings, one-line bullets, fenced code blocks."
printf '%s\n' "3. Puts INCLUDE-category content first (commands, style, workflow, testing, etiquette, architecture, environment, gotchas); EXCLUDE-category content is tightened and moved to a tail \`# Reference\` / \`# Additional notes\` section — never deleted."
printf '%s\n' "4. Is shorter than the input ONLY because wording is tighter and duplicates are merged — never because information was dropped."
printf '%s\n' "5. Uses only information already present in the input. Do not invent commands, paths, env vars, or conventions."
printf '%s\n' "6. Returns the file UNCHANGED if it already follows this structure."
printf '%s\n' "7. Outputs ONLY the final file content. No preamble, no explanation, no commentary. Start directly with the markdown."
printf '%s\n' ""
printf '%s\n' "## Current CLAUDE.md Content"
printf '%s\n' '```markdown'
printf '%s\n' "$CURRENT_CONTENT"
printf '%s\n' '```'
printf '%s\n' ""
printf '%s\n' "Now output the reformatted CLAUDE.md:"
} > "$PROMPT_FILE"
chmod 600 "$PROMPT_FILE"
REQUEST_FILE="$TEMP_DIR/request.json"
jq -cn \
--arg model "claude-sonnet-4-6" \
--argjson max_tokens 16384 \
--rawfile content "$PROMPT_FILE" \
'{
model: $model,
max_tokens: $max_tokens,
messages: [{ role: "user", content: $content }]
}' > "$REQUEST_FILE"
chmod 600 "$REQUEST_FILE"
PROMPT_CHARS=$(wc -c < "$PROMPT_FILE" | tr -d ' ')
CALL_START=$(($(date +%s%N 2>/dev/null || echo "$(date +%s)000000000") / 1000000))
RESPONSE=$(curl -s -w "\n%{http_code}" https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d @"$REQUEST_FILE")
CALL_END=$(($(date +%s%N 2>/dev/null || echo "$(date +%s)000000000") / 1000000))
CALL_LATENCY=$((CALL_END - CALL_START))
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
RESPONSE=$(echo "$RESPONSE" | sed '$d')
rm -f "$REQUEST_FILE" "$PROMPT_FILE"
if ! echo "$RESPONSE" | jq empty 2>/dev/null; then
echo "Invalid JSON response from Claude API (HTTP $HTTP_CODE)" >> $GITHUB_STEP_SUMMARY
echo "has_changes=false" >> $GITHUB_OUTPUT
rm -rf "$TEMP_DIR"
exit 1
fi
if echo "$RESPONSE" | jq -e '.error' > /dev/null 2>&1; then
ERR_TYPE=$(echo "$RESPONSE" | jq -r '.error.type // "unknown"')
echo "Claude API error: $ERR_TYPE (HTTP $HTTP_CODE)" >> $GITHUB_STEP_SUMMARY
echo "has_changes=false" >> $GITHUB_OUTPUT
rm -rf "$TEMP_DIR"
exit 1
fi
NEW_CONTENT=$(echo "$RESPONSE" | jq -r '.content[0].text // ""' | tr -d '\000' | iconv -c -t UTF-8//IGNORE)
IN_TOKENS=$(echo "$RESPONSE" | jq -r '.usage.input_tokens // 0')
OUT_TOKENS=$(echo "$RESPONSE" | jq -r '.usage.output_tokens // 0')
NEW_CHARS=${#NEW_CONTENT}
echo "Claude API call successful" >> $GITHUB_STEP_SUMMARY
echo "Input tokens: $IN_TOKENS | Output tokens: $OUT_TOKENS | Latency: ${CALL_LATENCY}ms" >> $GITHUB_STEP_SUMMARY
echo "New size: $NEW_CHARS chars" >> $GITHUB_STEP_SUMMARY
printf '%s' "$NEW_CONTENT" > "$CLAUDE_FILE"
git add "$CLAUDE_FILE"
if git diff --cached --quiet; then
echo "CLAUDE.md is already following best practices — no changes needed." >> $GITHUB_STEP_SUMMARY
echo "has_changes=false" >> $GITHUB_OUTPUT
else
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "Diff will be available in the refresh PR." >> $GITHUB_STEP_SUMMARY
fi
rm -rf "$TEMP_DIR"
- name: Create CLAUDE.md Refresh PR
id: create-refresh-pr
if: steps.reformat.outputs.has_changes == 'true'
shell: bash
env:
GH_TOKEN: ${{ secrets.DOCSYNC_GITHUB_TOKEN }}
CLAUDE_FILE: ${{ steps.locate-claude-md.outputs.claude_file }}
run: |
set -e
git config --local user.name "github-actions[bot]"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
RANDOM_SUFFIX=$(openssl rand -hex 4)
BRANCH="docs/claude-md-refresh-${TIMESTAMP}-${RANDOM_SUFFIX}"
git checkout -b "$BRANCH"
git add "$CLAUDE_FILE"
git commit -m "DocSync AI: Refresh CLAUDE.md to best practices
Reformatted CLAUDE.md to match Claude Code best practices.
All existing information preserved; structure and conciseness improved.
Triggered manually by: ${{ github.actor }}
Generated by DocSync AI (claude_md_refresh)"
git push origin "$BRANCH"
PR_BODY="## CLAUDE.md Best-Practices Refresh
This PR was triggered manually by **${{ github.actor }}** via the DocSync AI \`claude_md_refresh\` workflow.
### What changed
The existing \`CLAUDE.md\` was reformatted to match Claude Code best practices for AI-assisted development:
- Consistent \`##\` section headings
- Commands in fenced code blocks
- Concise bullet points
- Logical section ordering
### What was preserved
**All information from the original file is retained.** Only the structure, formatting, and conciseness were improved. No content was removed.
### Review Checklist
- [ ] All commands and paths are correct
- [ ] No information was accidentally omitted
- [ ] Section headings accurately describe their content
- [ ] Formatting renders correctly in GitHub markdown preview
---
*Automated by DocSync AI (Manual — claude_md_refresh)*"
PR_URL=$(gh pr create \
--title "DocSync AI: Refresh CLAUDE.md to best practices" \
--body "$PR_BODY" \
--base "$BASE_BRANCH" \
--head "$BRANCH")
PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$')
if [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
IFS=',' read -ra LABEL_ARRAY <<< "${{ inputs.pr_labels }}"
for label in "${LABEL_ARRAY[@]}"; do
label=$(echo "$label" | xargs)