@cyanautomation/kaseki-agent 1.47.0 → 1.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -139,6 +139,7 @@ kaseki-agent serve --port 8080
|
|
|
139
139
|
- **Error detection**: Identify failures and anomalies
|
|
140
140
|
- **Post-run analysis**: Detailed result summaries
|
|
141
141
|
- **Log streaming**: Real-time log consumption
|
|
142
|
+
- **Automatic review requests**: PRs on personal repositories automatically request the owner as a reviewer
|
|
142
143
|
|
|
143
144
|
See [docs/API.md](docs/API.md) and [docs/CLI.md](docs/CLI.md) for complete API and CLI documentation.
|
|
144
145
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request repo owner as a reviewer on pull requests
|
|
3
|
+
*
|
|
4
|
+
* When a PR is created against a personal repository by the GitHub App,
|
|
5
|
+
* this module automatically requests the repository owner as a reviewer.
|
|
6
|
+
* This makes the PR appear in the owner's "Review requested" filter.
|
|
7
|
+
*
|
|
8
|
+
* Organization-owned repositories are skipped automatically.
|
|
9
|
+
*/
|
|
10
|
+
interface GitHubUser {
|
|
11
|
+
login: string;
|
|
12
|
+
type: 'User' | 'Organization';
|
|
13
|
+
id: number;
|
|
14
|
+
}
|
|
15
|
+
interface GitHubRepository {
|
|
16
|
+
name: string;
|
|
17
|
+
owner: GitHubUser;
|
|
18
|
+
}
|
|
19
|
+
interface GitHubPullRequest {
|
|
20
|
+
number: number;
|
|
21
|
+
base: {
|
|
22
|
+
repo: GitHubRepository;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
interface ReviewRequestResult {
|
|
26
|
+
success: boolean;
|
|
27
|
+
status: number;
|
|
28
|
+
message: string;
|
|
29
|
+
skipped: boolean;
|
|
30
|
+
skippedReason?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Request repository owner as a reviewer
|
|
34
|
+
*
|
|
35
|
+
* @param pr - GitHub PR payload
|
|
36
|
+
* @param token - GitHub installation access token
|
|
37
|
+
* @param makeFetch - Function to make HTTP requests (defaults to fetch)
|
|
38
|
+
* @returns Result of the review request
|
|
39
|
+
*
|
|
40
|
+
* Returns success=true and skipped=true for organization repos (expected case).
|
|
41
|
+
* Returns success=true and skipped=false for successful personal repo review requests.
|
|
42
|
+
* Returns success=false for unexpected errors that couldn't be recovered.
|
|
43
|
+
*/
|
|
44
|
+
export declare function requestOwnerReview(pr: GitHubPullRequest, token: string, makeFetch?: (url: string, options: any) => Promise<Response>): Promise<ReviewRequestResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Creates a mock fetch function for testing
|
|
47
|
+
*/
|
|
48
|
+
export declare function createMockFetch(responses: {
|
|
49
|
+
status: number;
|
|
50
|
+
body?: Record<string, unknown>;
|
|
51
|
+
}[]): (url: string, options: any) => Promise<Response>;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=request-owner-review.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-owner-review.d.ts","sourceRoot":"","sources":["../src/request-owner-review.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,UAAU,UAAU;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,cAAc,CAAC;IAC9B,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,UAAU,iBAAiB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE;QACJ,IAAI,EAAE,gBAAgB,CAAC;KACxB,CAAC;CACH;AAMD,UAAU,mBAAmB;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAqCD;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,iBAAiB,EACrB,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAS,GAClE,OAAO,CAAC,mBAAmB,CAAC,CAiJ9B;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EAAE,GAC9D,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAC,CAYlD"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request repo owner as a reviewer on pull requests
|
|
3
|
+
*
|
|
4
|
+
* When a PR is created against a personal repository by the GitHub App,
|
|
5
|
+
* this module automatically requests the repository owner as a reviewer.
|
|
6
|
+
* This makes the PR appear in the owner's "Review requested" filter.
|
|
7
|
+
*
|
|
8
|
+
* Organization-owned repositories are skipped automatically.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Extract review request details from PR payload
|
|
12
|
+
*/
|
|
13
|
+
function extractReviewDetails(pr) {
|
|
14
|
+
const owner = pr.base.repo.owner;
|
|
15
|
+
return {
|
|
16
|
+
ownerLogin: owner.login,
|
|
17
|
+
ownerType: owner.type,
|
|
18
|
+
prNumber: pr.number,
|
|
19
|
+
repoOwner: owner.login,
|
|
20
|
+
repoName: pr.base.repo.name,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Determine if an HTTP status code is retryable
|
|
25
|
+
*/
|
|
26
|
+
function isRetryableStatus(status) {
|
|
27
|
+
switch (status) {
|
|
28
|
+
case 429: // Rate limited
|
|
29
|
+
case 500:
|
|
30
|
+
case 502:
|
|
31
|
+
case 503:
|
|
32
|
+
case 504: // Server errors
|
|
33
|
+
return true;
|
|
34
|
+
default:
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Request repository owner as a reviewer
|
|
40
|
+
*
|
|
41
|
+
* @param pr - GitHub PR payload
|
|
42
|
+
* @param token - GitHub installation access token
|
|
43
|
+
* @param makeFetch - Function to make HTTP requests (defaults to fetch)
|
|
44
|
+
* @returns Result of the review request
|
|
45
|
+
*
|
|
46
|
+
* Returns success=true and skipped=true for organization repos (expected case).
|
|
47
|
+
* Returns success=true and skipped=false for successful personal repo review requests.
|
|
48
|
+
* Returns success=false for unexpected errors that couldn't be recovered.
|
|
49
|
+
*/
|
|
50
|
+
export async function requestOwnerReview(pr, token, makeFetch = fetch) {
|
|
51
|
+
// Validate inputs
|
|
52
|
+
if (!pr || !pr.base || !pr.base.repo || !pr.base.repo.owner || !token) {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
status: 0,
|
|
56
|
+
message: 'Invalid PR payload or token',
|
|
57
|
+
skipped: false,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const { ownerLogin, ownerType, prNumber, repoOwner, repoName } = extractReviewDetails(pr);
|
|
61
|
+
// Skip for organization repos (expected case, not an error)
|
|
62
|
+
if (ownerType !== 'User') {
|
|
63
|
+
return {
|
|
64
|
+
success: true,
|
|
65
|
+
status: 0,
|
|
66
|
+
message: `Skipped: PR is on ${ownerType.toLowerCase()} repo`,
|
|
67
|
+
skipped: true,
|
|
68
|
+
skippedReason: `owner_type_is_${ownerType.toLowerCase()}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Build request payload
|
|
72
|
+
const payload = {
|
|
73
|
+
reviewers: [ownerLogin],
|
|
74
|
+
};
|
|
75
|
+
const url = `https://api.github.com/repos/${repoOwner}/${repoName}/pulls/${prNumber}/requested_reviewers`;
|
|
76
|
+
// Request with retry logic
|
|
77
|
+
let lastStatus = 0;
|
|
78
|
+
let lastError = null;
|
|
79
|
+
const maxRetries = 2;
|
|
80
|
+
const baseBackoffMs = 2000;
|
|
81
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
82
|
+
try {
|
|
83
|
+
// Backoff before retrying
|
|
84
|
+
if (attempt > 0) {
|
|
85
|
+
const backoffMs = baseBackoffMs * Math.pow(2, attempt - 1);
|
|
86
|
+
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
87
|
+
}
|
|
88
|
+
const response = await makeFetch(url, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: {
|
|
91
|
+
Authorization: `token ${token}`,
|
|
92
|
+
Accept: 'application/vnd.github.v3+json',
|
|
93
|
+
'Content-Type': 'application/json',
|
|
94
|
+
},
|
|
95
|
+
body: JSON.stringify(payload),
|
|
96
|
+
});
|
|
97
|
+
lastStatus = response.status;
|
|
98
|
+
// Handle successful response
|
|
99
|
+
if (response.status === 201) {
|
|
100
|
+
return {
|
|
101
|
+
success: true,
|
|
102
|
+
status: 201,
|
|
103
|
+
message: `Requested review from ${ownerLogin} on PR #${prNumber}`,
|
|
104
|
+
skipped: false,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// Handle specific error cases
|
|
108
|
+
if (response.status === 422) {
|
|
109
|
+
// Already requested or validation error
|
|
110
|
+
return {
|
|
111
|
+
success: true,
|
|
112
|
+
status: 422,
|
|
113
|
+
message: 'Owner already has review request pending or user cannot be requested',
|
|
114
|
+
skipped: false,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (response.status === 403) {
|
|
118
|
+
// Permission error - don't retry, but non-fatal
|
|
119
|
+
return {
|
|
120
|
+
success: true,
|
|
121
|
+
status: 403,
|
|
122
|
+
message: 'GitHub App lacks permission to request reviewers (HTTP 403)',
|
|
123
|
+
skipped: false,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (response.status === 404) {
|
|
127
|
+
// Not found - don't retry, but non-fatal
|
|
128
|
+
return {
|
|
129
|
+
success: true,
|
|
130
|
+
status: 404,
|
|
131
|
+
message: `Could not find user ${ownerLogin} or PR #${prNumber} is not accessible`,
|
|
132
|
+
skipped: false,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// Retryable error
|
|
136
|
+
if (isRetryableStatus(response.status)) {
|
|
137
|
+
if (attempt < maxRetries) {
|
|
138
|
+
continue; // Retry
|
|
139
|
+
}
|
|
140
|
+
// Max retries exhausted
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
status: response.status,
|
|
144
|
+
message: `GitHub API returned ${response.status} after ${maxRetries} retries`,
|
|
145
|
+
skipped: false,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// Unexpected status - don't retry
|
|
149
|
+
return {
|
|
150
|
+
success: true, // Non-fatal for PR creation
|
|
151
|
+
status: response.status,
|
|
152
|
+
message: `Unexpected HTTP status ${response.status} requesting owner review`,
|
|
153
|
+
skipped: false,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
lastError = error;
|
|
158
|
+
// Network error - retryable
|
|
159
|
+
if (attempt < maxRetries) {
|
|
160
|
+
continue; // Retry
|
|
161
|
+
}
|
|
162
|
+
// Max retries exhausted
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
status: 0,
|
|
166
|
+
message: `Network error: ${lastError.message}`,
|
|
167
|
+
skipped: false,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Should not reach here, but handle just in case
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
status: lastStatus || 0,
|
|
175
|
+
message: lastError ? `Error: ${lastError.message}` : 'Unknown error',
|
|
176
|
+
skipped: false,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Creates a mock fetch function for testing
|
|
181
|
+
*/
|
|
182
|
+
export function createMockFetch(responses) {
|
|
183
|
+
let callCount = 0;
|
|
184
|
+
return async (_url, _options) => {
|
|
185
|
+
const responseConfig = responses[Math.min(callCount, responses.length - 1)];
|
|
186
|
+
callCount++;
|
|
187
|
+
return new Response(JSON.stringify(responseConfig.body || {}), {
|
|
188
|
+
status: responseConfig.status,
|
|
189
|
+
headers: { 'content-type': 'application/json' },
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=request-owner-review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-owner-review.js","sourceRoot":"","sources":["../src/request-owner-review.ts"],"names":[],"mappings":"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"}
|
package/kaseki-agent.sh
CHANGED
|
@@ -2652,6 +2652,175 @@ apply_github_pr_labels() {
|
|
|
2652
2652
|
esac
|
|
2653
2653
|
}
|
|
2654
2654
|
|
|
2655
|
+
request_owner_review() {
|
|
2656
|
+
local pr_response token log_file owner_login owner_type pr_number repo owner
|
|
2657
|
+
pr_response="$1"
|
|
2658
|
+
token="$2"
|
|
2659
|
+
log_file="${3:-/results/git-push.log}"
|
|
2660
|
+
|
|
2661
|
+
if [ -z "$pr_response" ] || [ -z "$token" ]; then
|
|
2662
|
+
printf 'Warning: skipping owner review request because PR response or token is missing\n' | tee -a "$log_file" >&2
|
|
2663
|
+
return 1
|
|
2664
|
+
fi
|
|
2665
|
+
|
|
2666
|
+
# Extract repo owner login, owner type, PR number, and repo name from PR response
|
|
2667
|
+
owner_login=$(printf '%s' "$pr_response" | node -e "
|
|
2668
|
+
const data = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
|
2669
|
+
if (data.base && data.base.repo && data.base.repo.owner) {
|
|
2670
|
+
process.stdout.write(data.base.repo.owner.login || '');
|
|
2671
|
+
}
|
|
2672
|
+
" 2>/dev/null || true)
|
|
2673
|
+
|
|
2674
|
+
owner_type=$(printf '%s' "$pr_response" | node -e "
|
|
2675
|
+
const data = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
|
2676
|
+
if (data.base && data.base.repo && data.base.repo.owner) {
|
|
2677
|
+
process.stdout.write(data.base.repo.owner.type || '');
|
|
2678
|
+
}
|
|
2679
|
+
" 2>/dev/null || true)
|
|
2680
|
+
|
|
2681
|
+
pr_number=$(printf '%s' "$pr_response" | node -e "
|
|
2682
|
+
const data = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
|
2683
|
+
process.stdout.write(String(data.number || ''));
|
|
2684
|
+
" 2>/dev/null || true)
|
|
2685
|
+
|
|
2686
|
+
owner=$(printf '%s' "$pr_response" | node -e "
|
|
2687
|
+
const data = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
|
2688
|
+
if (data.base && data.base.repo) {
|
|
2689
|
+
process.stdout.write(data.base.repo.owner.login || '');
|
|
2690
|
+
}
|
|
2691
|
+
" 2>/dev/null || true)
|
|
2692
|
+
|
|
2693
|
+
repo=$(printf '%s' "$pr_response" | node -e "
|
|
2694
|
+
const data = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
|
2695
|
+
if (data.base && data.base.repo) {
|
|
2696
|
+
process.stdout.write(data.base.repo.name || '');
|
|
2697
|
+
}
|
|
2698
|
+
" 2>/dev/null || true)
|
|
2699
|
+
|
|
2700
|
+
# Validate extracted data
|
|
2701
|
+
if [ -z "$owner_login" ] || [ -z "$owner_type" ] || [ -z "$pr_number" ] || [ -z "$owner" ] || [ -z "$repo" ]; then
|
|
2702
|
+
printf 'Warning: failed to extract owner/PR data from PR response; skipping owner review request\n' | tee -a "$log_file" >&2
|
|
2703
|
+
if [ "${KASEKI_DEBUG:-0}" = "1" ]; then
|
|
2704
|
+
printf 'Debug: owner_login=%s owner_type=%s pr_number=%s owner=%s repo=%s\n' "$owner_login" "$owner_type" "$pr_number" "$owner" "$repo" | tee -a "$log_file"
|
|
2705
|
+
fi
|
|
2706
|
+
return 1
|
|
2707
|
+
fi
|
|
2708
|
+
|
|
2709
|
+
# Skip if repo is owned by an organization (only request review on personal repos)
|
|
2710
|
+
if [ "$owner_type" != "User" ]; then
|
|
2711
|
+
printf 'Skipped owner review request: PR is on organization repo (owner_type=%s)\n' "$owner_type" | tee -a "$log_file"
|
|
2712
|
+
return 0
|
|
2713
|
+
fi
|
|
2714
|
+
|
|
2715
|
+
# Build reviewer request payload
|
|
2716
|
+
local reviewer_payload
|
|
2717
|
+
if ! run_node_subprocess reviewer_payload "const payload = { reviewers: ['$owner_login'] }; process.stdout.write(JSON.stringify(payload));" "" "$log_file"; then
|
|
2718
|
+
printf 'Warning: failed to JSON encode reviewer payload; skipping owner review request\n' | tee -a "$log_file" >&2
|
|
2719
|
+
return 1
|
|
2720
|
+
fi
|
|
2721
|
+
|
|
2722
|
+
# Request owner review with retry logic
|
|
2723
|
+
local retry_count=0 max_retries=2 request_success=0 backoff_delay=2
|
|
2724
|
+
local review_request_log="/results/owner-review-request.log"
|
|
2725
|
+
: > "$review_request_log"
|
|
2726
|
+
|
|
2727
|
+
while [ $retry_count -le "$max_retries" ]; do
|
|
2728
|
+
if [ $retry_count -gt 0 ]; then
|
|
2729
|
+
printf 'Retrying owner review request (attempt %d of %d) after %ds delay...\n' $((retry_count + 1)) "$max_retries" "$backoff_delay" | tee -a "$log_file" >&2
|
|
2730
|
+
sleep "$backoff_delay"
|
|
2731
|
+
# Exponential backoff: 2s → 4s
|
|
2732
|
+
backoff_delay=$((backoff_delay * 2))
|
|
2733
|
+
if [ $backoff_delay -gt 4 ]; then backoff_delay=4; fi
|
|
2734
|
+
fi
|
|
2735
|
+
|
|
2736
|
+
local review_status_file temp_response
|
|
2737
|
+
review_status_file="$(mktemp /tmp/kaseki-review-status.XXXXXX)" || {
|
|
2738
|
+
printf 'Warning: failed to create temp file for review request status\n' | tee -a "$log_file" >&2
|
|
2739
|
+
return 1
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
# Make the API request
|
|
2743
|
+
local curl_exit review_http_status review_response
|
|
2744
|
+
curl -s -w '%{http_code}' -X POST \
|
|
2745
|
+
-H "Authorization: token $token" \
|
|
2746
|
+
-H "Accept: application/vnd.github.v3+json" \
|
|
2747
|
+
-H "Content-Type: application/json" \
|
|
2748
|
+
"https://api.github.com/repos/$owner/$repo/pulls/$pr_number/requested_reviewers" \
|
|
2749
|
+
-d "$reviewer_payload" > "$review_status_file" 2>&1
|
|
2750
|
+
curl_exit=$?
|
|
2751
|
+
|
|
2752
|
+
temp_response="$(cat "$review_status_file" 2>/dev/null || true)"
|
|
2753
|
+
review_http_status="${temp_response: -3}"
|
|
2754
|
+
review_response="${temp_response%???}"
|
|
2755
|
+
rm -f "$review_status_file"
|
|
2756
|
+
|
|
2757
|
+
if [ "$curl_exit" -ne 0 ]; then
|
|
2758
|
+
printf 'Curl error requesting owner review (attempt %d): exit code %d\n' $((retry_count + 1)) "$curl_exit" | tee -a "$log_file" >&2
|
|
2759
|
+
retry_count=$((retry_count + 1))
|
|
2760
|
+
continue
|
|
2761
|
+
fi
|
|
2762
|
+
|
|
2763
|
+
case "$review_http_status" in
|
|
2764
|
+
201)
|
|
2765
|
+
# Success: review request created
|
|
2766
|
+
printf '✓ Requested review from %s on PR #%s\n' "$owner_login" "$pr_number" | tee -a "$log_file" "$review_request_log"
|
|
2767
|
+
request_success=1
|
|
2768
|
+
break
|
|
2769
|
+
;;
|
|
2770
|
+
422)
|
|
2771
|
+
# Unprocessable Entity: usually means reviewer already requested or invalid data
|
|
2772
|
+
printf 'ℹ Owner %s already has review request pending or user cannot be requested (HTTP 422)\n' "$owner_login" | tee -a "$log_file" "$review_request_log"
|
|
2773
|
+
request_success=1
|
|
2774
|
+
break
|
|
2775
|
+
;;
|
|
2776
|
+
403)
|
|
2777
|
+
# Forbidden: insufficient permissions
|
|
2778
|
+
printf '✗ GitHub App lacks permission to request reviewers (HTTP 403)\n' | tee -a "$log_file" "$review_request_log" >&2
|
|
2779
|
+
printf ' Hint: Verify GitHub App has "Pull requests: write" permission\n' | tee -a "$log_file" "$review_request_log" >&2
|
|
2780
|
+
if [ "${KASEKI_DEBUG:-0}" = "1" ]; then
|
|
2781
|
+
printf 'Debug: Review API response:\n%s\n' "$review_response" | tee -a "$log_file"
|
|
2782
|
+
fi
|
|
2783
|
+
request_success=1 # Non-fatal; PR still created successfully
|
|
2784
|
+
break
|
|
2785
|
+
;;
|
|
2786
|
+
404)
|
|
2787
|
+
# Not Found: user doesn't exist or repo not accessible
|
|
2788
|
+
printf '✗ Could not find user %s or PR %d is not accessible (HTTP 404)\n' "$owner_login" "$pr_number" | tee -a "$log_file" "$review_request_log" >&2
|
|
2789
|
+
request_success=1 # Non-fatal
|
|
2790
|
+
break
|
|
2791
|
+
;;
|
|
2792
|
+
429)
|
|
2793
|
+
# Rate limited: retryable
|
|
2794
|
+
printf 'Rate limited requesting owner review (attempt %d); retrying...\n' $((retry_count + 1)) | tee -a "$log_file" >&2
|
|
2795
|
+
retry_count=$((retry_count + 1))
|
|
2796
|
+
continue
|
|
2797
|
+
;;
|
|
2798
|
+
500|502|503|504)
|
|
2799
|
+
# Server errors: retryable
|
|
2800
|
+
printf 'GitHub API server error %s requesting owner review (attempt %d); retrying...\n' "$review_http_status" $((retry_count + 1)) | tee -a "$log_file" >&2
|
|
2801
|
+
retry_count=$((retry_count + 1))
|
|
2802
|
+
continue
|
|
2803
|
+
;;
|
|
2804
|
+
*)
|
|
2805
|
+
# Unexpected status
|
|
2806
|
+
printf '✗ Unexpected HTTP status %s requesting owner review\n' "$review_http_status" | tee -a "$log_file" "$review_request_log" >&2
|
|
2807
|
+
if [ "${KASEKI_DEBUG:-0}" = "1" ]; then
|
|
2808
|
+
printf 'Debug: Review API response:\n%s\n' "$review_response" | tee -a "$log_file"
|
|
2809
|
+
fi
|
|
2810
|
+
request_success=1 # Non-fatal
|
|
2811
|
+
break
|
|
2812
|
+
;;
|
|
2813
|
+
esac
|
|
2814
|
+
done
|
|
2815
|
+
|
|
2816
|
+
if [ $request_success -eq 0 ]; then
|
|
2817
|
+
printf '✗ Failed to request owner review after %d retries\n' "$max_retries" | tee -a "$log_file" "$review_request_log" >&2
|
|
2818
|
+
fi
|
|
2819
|
+
|
|
2820
|
+
# Always return 0: do not block PR creation if review request fails
|
|
2821
|
+
return 0
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2655
2824
|
is_github_pr_error_retryable() {
|
|
2656
2825
|
local http_status error_type
|
|
2657
2826
|
http_status="$1"
|
|
@@ -3381,6 +3550,8 @@ run_github_operations() {
|
|
|
3381
3550
|
printf 'Pull request created: %s\n' "$pr_url" | tee -a /results/git-push.log
|
|
3382
3551
|
if [ -n "$pr_number" ]; then
|
|
3383
3552
|
apply_github_pr_labels "$owner" "$repo" "$pr_number" "$token" /results/git-push.log || true
|
|
3553
|
+
# Request repository owner as reviewer for personal repos
|
|
3554
|
+
request_owner_review "$pr_response" "$token" /results/git-push.log || true
|
|
3384
3555
|
else
|
|
3385
3556
|
printf 'Warning: PR API response missing number field; leaving PR unlabeled\n' | tee -a /results/git-push.log >&2
|
|
3386
3557
|
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanautomation/kaseki-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.48.0",
|
|
4
4
|
"description": "Admin/helper/doctor toolbox and local API client for Kaseki diagnostics, setup, and API-backed coding-agent task workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|