@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.47.0",
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",