@magnusekdahl/parallix 1.2.1 → 1.3.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/index.js +190 -185
- package/lib/commands/active.js +502 -579
- package/lib/commands/active.ts +665 -0
- package/lib/commands/checkpoint.js +98 -74
- package/lib/commands/checkpoint.ts +79 -0
- package/lib/commands/config.js +61 -31
- package/lib/commands/config.ts +47 -0
- package/lib/commands/coverage-gate.js +271 -246
- package/lib/commands/coverage-gate.ts +362 -0
- package/lib/commands/diff.js +140 -119
- package/lib/commands/diff.ts +125 -0
- package/lib/commands/draft.js +770 -923
- package/lib/commands/draft.ts +1021 -0
- package/lib/commands/handoff.js +588 -608
- package/lib/commands/handoff.ts +694 -0
- package/lib/commands/integrate.js +1331 -1468
- package/lib/commands/integrate.ts +1688 -0
- package/lib/commands/mission-start.js +279 -237
- package/lib/commands/mission-start.ts +263 -0
- package/lib/commands/rebase.js +606 -582
- package/lib/commands/rebase.ts +635 -0
- package/lib/commands/repair-handoff.js +181 -179
- package/lib/commands/repair-handoff.ts +228 -0
- package/lib/commands/resolve-conflict.js +121 -102
- package/lib/commands/resolve-conflict.ts +112 -0
- package/lib/commands/review.js +3 -4
- package/lib/commands/review.ts +14 -0
- package/lib/commands/setup-review.js +4 -5
- package/lib/commands/setup-review.ts +14 -0
- package/lib/commands/setup.js +3 -3
- package/lib/commands/setup.ts +2 -0
- package/lib/commands/stats-backfill.js +394 -359
- package/lib/commands/stats-backfill.ts +418 -0
- package/lib/commands/stats.js +1424 -1665
- package/lib/commands/stats.ts +2186 -0
- package/lib/commands/status.js +219 -200
- package/lib/commands/status.ts +219 -0
- package/lib/commands/verify.js +3 -1
- package/lib/commands/verify.ts +2 -0
- package/lib/core/nels.js +112 -127
- package/lib/core/nels.ts +189 -0
- package/lib/core/subagent-limit.js +15 -16
- package/lib/core/subagent-limit.ts +25 -0
- package/lib/index.js +142 -59
- package/lib/index.ts +134 -0
- package/lib/review/rebase.js +142 -145
- package/lib/review/rebase.ts +188 -0
- package/lib/review/review-adapter.js +99 -87
- package/lib/review/review-adapter.ts +198 -0
- package/lib/review/review-artifacts.js +486 -629
- package/lib/review/review-artifacts.ts +622 -0
- package/lib/review/review-commands.js +1220 -1373
- package/lib/review/review-commands.ts +1579 -0
- package/lib/review/review-events.js +544 -886
- package/lib/review/review-events.ts +954 -0
- package/lib/review/review-loop.js +945 -1020
- package/lib/review/review-loop.ts +1174 -0
- package/lib/review/review-polling.js +86 -115
- package/lib/review/review-polling.ts +194 -0
- package/lib/review/review-prompts.js +154 -189
- package/lib/review/review-prompts.ts +182 -0
- package/lib/review/review-state.js +246 -225
- package/lib/review/review-state.ts +353 -0
- package/lib/review/review.js +63 -84
- package/lib/review/review.ts +138 -0
- package/lib/tools/backlog.js +694 -680
- package/lib/tools/backlog.ts +835 -0
- package/lib/tools/forgejo.js +1270 -1405
- package/lib/tools/forgejo.ts +1793 -0
- package/lib/tools/gatekeeper.js +106 -96
- package/lib/tools/gatekeeper.ts +124 -0
- package/lib/tools/redgreen.js +200 -181
- package/lib/tools/redgreen.ts +221 -0
- package/lib/tools/sessions.js +58 -52
- package/lib/tools/sessions.ts +81 -0
- package/lib/tools/setup-review.js +902 -975
- package/lib/tools/setup-review.ts +1152 -0
- package/package.json +4 -4
- package/px.js +226 -208
|
@@ -1,52 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Review Polling Module
|
|
3
4
|
* Extracted from parallix/lib/review.js for task-1201
|
|
4
5
|
* Handles polling for review outcomes and disposition comments.
|
|
5
6
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.POLL_TIMEOUT = void 0;
|
|
9
|
+
exports.delay = delay;
|
|
10
|
+
exports.resolvePollIntervalMs = resolvePollIntervalMs;
|
|
11
|
+
exports.resolvePollTimeoutMs = resolvePollTimeoutMs;
|
|
12
|
+
exports.formatElapsed = formatElapsed;
|
|
13
|
+
exports.isPollTimeout = isPollTimeout;
|
|
14
|
+
exports.pollForReview = pollForReview;
|
|
15
|
+
exports.pollForDisposition = pollForDisposition;
|
|
16
|
+
const fmt_js_1 = require("../core/fmt.js");
|
|
17
|
+
const review_adapter_js_1 = require("./review-adapter.js");
|
|
10
18
|
const DEFAULT_POLL_INTERVAL_MS = 10_000;
|
|
11
19
|
const DEFAULT_POLL_MAX_WAIT_MS = 600_000;
|
|
12
20
|
const POLL_PROGRESS_EVERY_MS = 30_000;
|
|
13
|
-
|
|
14
|
-
const POLL_TIMEOUT = Object.freeze({ __isPollTimeout: true });
|
|
15
|
-
|
|
21
|
+
exports.POLL_TIMEOUT = Object.freeze({ __isPollTimeout: true });
|
|
16
22
|
/** @param {number} ms */
|
|
17
23
|
function delay(ms) {
|
|
18
|
-
|
|
24
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
19
25
|
}
|
|
20
|
-
|
|
21
26
|
function resolvePollIntervalMs() {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
const raw = process.env.AUTONOMOUS_REVIEW_POLL_INTERVAL_MS;
|
|
28
|
+
const parsed = raw ? parseInt(raw, 10) : NaN;
|
|
29
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
30
|
+
return DEFAULT_POLL_INTERVAL_MS;
|
|
31
|
+
}
|
|
32
|
+
return parsed;
|
|
26
33
|
}
|
|
27
|
-
|
|
28
34
|
/** @param {number} [explicitSeconds] */
|
|
29
35
|
function resolvePollTimeoutMs(explicitSeconds) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
if (typeof explicitSeconds === 'number' && Number.isFinite(explicitSeconds) && explicitSeconds > 0) {
|
|
37
|
+
return explicitSeconds * 1000;
|
|
38
|
+
}
|
|
39
|
+
const raw = process.env.AUTONOMOUS_REVIEW_POLL_TIMEOUT_MS;
|
|
40
|
+
const parsed = raw ? parseInt(raw, 10) : NaN;
|
|
41
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
42
|
+
return DEFAULT_POLL_MAX_WAIT_MS;
|
|
43
|
+
}
|
|
44
|
+
return parsed;
|
|
37
45
|
}
|
|
38
|
-
|
|
39
46
|
/** @param {number} startMs */
|
|
40
47
|
function formatElapsed(startMs) {
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
const secs = Math.round((Date.now() - startMs) / 1000);
|
|
49
|
+
return `${secs}s`;
|
|
43
50
|
}
|
|
44
|
-
|
|
45
51
|
/** @param {*} result */
|
|
46
52
|
function isPollTimeout(result) {
|
|
47
|
-
|
|
53
|
+
return result === exports.POLL_TIMEOUT;
|
|
48
54
|
}
|
|
49
|
-
|
|
50
55
|
/**
|
|
51
56
|
* @param {number} prNumber
|
|
52
57
|
* @param {string} reviewerUser
|
|
@@ -56,48 +61,36 @@ function isPollTimeout(result) {
|
|
|
56
61
|
* @returns {Promise<string|typeof POLL_TIMEOUT|null>}
|
|
57
62
|
*/
|
|
58
63
|
async function pollForReview(prNumber, reviewerUser, sinceIso, token, options = {}) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
timeoutMs = resolvePollTimeoutMs(),
|
|
64
|
-
verbose = false,
|
|
65
|
-
label = 'review',
|
|
66
|
-
retryCount = 0,
|
|
67
|
-
log = fmt.log.plain
|
|
68
|
-
} = options;
|
|
69
|
-
if (!token) {
|
|
70
|
-
log(fmt.status('WARN', 'No Forgejo token — skipping review-outcome poll (manual handoff required).'));
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const start = Date.now();
|
|
75
|
-
const deadline = start + timeoutMs;
|
|
76
|
-
let lastProgressAt = start;
|
|
77
|
-
log(fmt.status('INFO', `Polling Forgejo for ${label} by ${reviewerUser} on PR #${prNumber} (timeout ${Math.round(timeoutMs / 1000)}s)...`));
|
|
78
|
-
|
|
79
|
-
while (Date.now() < deadline) {
|
|
80
|
-
const review = await getLatestReviewForPrFn(prNumber, reviewerUser, sinceIso, token);
|
|
81
|
-
if (review) {
|
|
82
|
-
if (verbose) {log(fmt.status('INFO', `${label}: reviewer state=${review.state} after ${formatElapsed(start)}.`));}
|
|
83
|
-
return review.state;
|
|
64
|
+
const { getLatestReviewForPrFn = review_adapter_js_1.getLatestReviewForPr, sleepFn = delay, intervalMs = resolvePollIntervalMs(), timeoutMs = resolvePollTimeoutMs(), verbose = false, label = 'review', retryCount = 0, log: logger = fmt_js_1.log.plain } = options;
|
|
65
|
+
if (!token) {
|
|
66
|
+
logger((0, fmt_js_1.status)('WARN', 'No Forgejo token — skipping review-outcome poll (manual handoff required).'));
|
|
67
|
+
return null;
|
|
84
68
|
}
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
69
|
+
const start = Date.now();
|
|
70
|
+
const deadline = start + timeoutMs;
|
|
71
|
+
let lastProgressAt = start;
|
|
72
|
+
logger((0, fmt_js_1.status)('INFO', `Polling Forgejo for ${label} by ${reviewerUser} on PR #${prNumber} (timeout ${Math.round(timeoutMs / 1000)}s)...`));
|
|
73
|
+
while (Date.now() < deadline) {
|
|
74
|
+
const review = await getLatestReviewForPrFn(prNumber, reviewerUser, sinceIso, token);
|
|
75
|
+
if (review) {
|
|
76
|
+
const reviewState = review.state;
|
|
77
|
+
if (verbose) {
|
|
78
|
+
logger((0, fmt_js_1.status)('INFO', `${label}: reviewer state=${reviewState} after ${formatElapsed(start)}.`));
|
|
79
|
+
}
|
|
80
|
+
return reviewState;
|
|
81
|
+
}
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
if (verbose || now - lastProgressAt >= POLL_PROGRESS_EVERY_MS) {
|
|
84
|
+
logger((0, fmt_js_1.status)('INFO', `${label}: still waiting for ${reviewerUser}'s review... (${formatElapsed(start)} elapsed)`));
|
|
85
|
+
lastProgressAt = now;
|
|
86
|
+
}
|
|
87
|
+
await sleepFn(intervalMs);
|
|
90
88
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const isRetry = retryCount > 0;
|
|
96
|
-
const logLevel = isRetry ? 'INFO' : 'WARN';
|
|
97
|
-
log(fmt.status(logLevel, `${label}: no review posted by ${reviewerUser} within ${Math.round(timeoutMs / 1000)}s.`));
|
|
98
|
-
return POLL_TIMEOUT;
|
|
89
|
+
const isRetry = retryCount > 0;
|
|
90
|
+
const logLevel = isRetry ? 'INFO' : 'WARN';
|
|
91
|
+
logger((0, fmt_js_1.status)(logLevel, `${label}: no review posted by ${reviewerUser} within ${Math.round(timeoutMs / 1000)}s.`));
|
|
92
|
+
return exports.POLL_TIMEOUT;
|
|
99
93
|
}
|
|
100
|
-
|
|
101
94
|
/**
|
|
102
95
|
* @param {number} prNumber
|
|
103
96
|
* @param {string} implementerUser
|
|
@@ -107,55 +100,33 @@ async function pollForReview(prNumber, reviewerUser, sinceIso, token, options =
|
|
|
107
100
|
* @returns {Promise<string|typeof POLL_TIMEOUT|null>}
|
|
108
101
|
*/
|
|
109
102
|
async function pollForDisposition(prNumber, implementerUser, sinceIso, token, options = {}) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
timeoutMs = resolvePollTimeoutMs(),
|
|
115
|
-
verbose = false,
|
|
116
|
-
label = 'disposition',
|
|
117
|
-
retryCount = 0,
|
|
118
|
-
log = fmt.log.plain
|
|
119
|
-
} = options;
|
|
120
|
-
if (!token) {
|
|
121
|
-
log(fmt.status('WARN', 'No Forgejo token — skipping disposition poll (manual handoff required).'));
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const start = Date.now();
|
|
126
|
-
const deadline = start + timeoutMs;
|
|
127
|
-
let lastProgressAt = start;
|
|
128
|
-
log(fmt.status('INFO', `Polling Forgejo for ${label} by ${implementerUser} on PR #${prNumber} (timeout ${Math.round(timeoutMs / 1000)}s)...`));
|
|
129
|
-
|
|
130
|
-
while (Date.now() < deadline) {
|
|
131
|
-
const disposition = await getLatestDispositionForPrFn(prNumber, implementerUser, sinceIso, token);
|
|
132
|
-
if (disposition) {
|
|
133
|
-
if (verbose) {log(fmt.status('INFO', `${label}: disposition=${disposition} after ${formatElapsed(start)}.`));}
|
|
134
|
-
return disposition;
|
|
103
|
+
const { getLatestDispositionForPrFn = review_adapter_js_1.getLatestDispositionForPr, sleepFn = delay, intervalMs = resolvePollIntervalMs(), timeoutMs = resolvePollTimeoutMs(), verbose = false, label = 'disposition', retryCount = 0, log: logger = fmt_js_1.log.plain } = options;
|
|
104
|
+
if (!token) {
|
|
105
|
+
logger((0, fmt_js_1.status)('WARN', 'No Forgejo token — skipping disposition poll (manual handoff required).'));
|
|
106
|
+
return null;
|
|
135
107
|
}
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
108
|
+
const start = Date.now();
|
|
109
|
+
const deadline = start + timeoutMs;
|
|
110
|
+
let lastProgressAt = start;
|
|
111
|
+
logger((0, fmt_js_1.status)('INFO', `Polling Forgejo for ${label} by ${implementerUser} on PR #${prNumber} (timeout ${Math.round(timeoutMs / 1000)}s)...`));
|
|
112
|
+
while (Date.now() < deadline) {
|
|
113
|
+
const disposition = await getLatestDispositionForPrFn(prNumber, implementerUser, sinceIso, token);
|
|
114
|
+
if (disposition) {
|
|
115
|
+
const dispositionStr = String(disposition);
|
|
116
|
+
if (verbose) {
|
|
117
|
+
logger((0, fmt_js_1.status)('INFO', `${label}: disposition=${dispositionStr} after ${formatElapsed(start)}.`));
|
|
118
|
+
}
|
|
119
|
+
return dispositionStr;
|
|
120
|
+
}
|
|
121
|
+
const now = Date.now();
|
|
122
|
+
if (verbose || now - lastProgressAt >= POLL_PROGRESS_EVERY_MS) {
|
|
123
|
+
logger((0, fmt_js_1.status)('INFO', `${label}: still waiting for ${implementerUser}'s disposition comment... (${formatElapsed(start)} elapsed)`));
|
|
124
|
+
lastProgressAt = now;
|
|
125
|
+
}
|
|
126
|
+
await sleepFn(intervalMs);
|
|
141
127
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const isRetry = retryCount > 0;
|
|
147
|
-
const logLevel = isRetry ? 'INFO' : 'WARN';
|
|
148
|
-
log(fmt.status(logLevel, `${label}: no disposition comment posted by ${implementerUser} within ${Math.round(timeoutMs / 1000)}s.`));
|
|
149
|
-
return POLL_TIMEOUT;
|
|
128
|
+
const isRetry = retryCount > 0;
|
|
129
|
+
const logLevel = isRetry ? 'INFO' : 'WARN';
|
|
130
|
+
logger((0, fmt_js_1.status)(logLevel, `${label}: no disposition comment posted by ${implementerUser} within ${Math.round(timeoutMs / 1000)}s.`));
|
|
131
|
+
return exports.POLL_TIMEOUT;
|
|
150
132
|
}
|
|
151
|
-
|
|
152
|
-
module.exports = {
|
|
153
|
-
POLL_TIMEOUT,
|
|
154
|
-
delay,
|
|
155
|
-
resolvePollIntervalMs,
|
|
156
|
-
resolvePollTimeoutMs,
|
|
157
|
-
formatElapsed,
|
|
158
|
-
isPollTimeout,
|
|
159
|
-
pollForReview,
|
|
160
|
-
pollForDisposition
|
|
161
|
-
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Polling Module
|
|
3
|
+
* Extracted from parallix/lib/review.js for task-1201
|
|
4
|
+
* Handles polling for review outcomes and disposition comments.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { log, status } from '../core/fmt.js';
|
|
8
|
+
import {
|
|
9
|
+
getLatestReviewForPr,
|
|
10
|
+
getLatestDispositionForPr
|
|
11
|
+
} from './review-adapter.js';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_POLL_INTERVAL_MS = 10_000;
|
|
14
|
+
const DEFAULT_POLL_MAX_WAIT_MS = 600_000;
|
|
15
|
+
const POLL_PROGRESS_EVERY_MS = 30_000;
|
|
16
|
+
|
|
17
|
+
export const POLL_TIMEOUT = Object.freeze({ __isPollTimeout: true });
|
|
18
|
+
|
|
19
|
+
/** @param {number} ms */
|
|
20
|
+
export function delay(ms: number): Promise<void> {
|
|
21
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function resolvePollIntervalMs(): number {
|
|
25
|
+
const raw = process.env.AUTONOMOUS_REVIEW_POLL_INTERVAL_MS;
|
|
26
|
+
const parsed = raw ? parseInt(raw, 10) : NaN;
|
|
27
|
+
if (!Number.isFinite(parsed) || parsed <= 0) { return DEFAULT_POLL_INTERVAL_MS; }
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** @param {number} [explicitSeconds] */
|
|
32
|
+
function resolvePollTimeoutMs(explicitSeconds?: number): number {
|
|
33
|
+
if (typeof explicitSeconds === 'number' && Number.isFinite(explicitSeconds) && explicitSeconds > 0) {
|
|
34
|
+
return explicitSeconds * 1000;
|
|
35
|
+
}
|
|
36
|
+
const raw = process.env.AUTONOMOUS_REVIEW_POLL_TIMEOUT_MS;
|
|
37
|
+
const parsed = raw ? parseInt(raw, 10) : NaN;
|
|
38
|
+
if (!Number.isFinite(parsed) || parsed <= 0) { return DEFAULT_POLL_MAX_WAIT_MS; }
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** @param {number} startMs */
|
|
43
|
+
function formatElapsed(startMs: number): string {
|
|
44
|
+
const secs = Math.round((Date.now() - startMs) / 1000);
|
|
45
|
+
return `${secs}s`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @param {*} result */
|
|
49
|
+
function isPollTimeout(result: unknown): boolean {
|
|
50
|
+
return result === POLL_TIMEOUT;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {number} prNumber
|
|
55
|
+
* @param {string} reviewerUser
|
|
56
|
+
* @param {string} sinceIso
|
|
57
|
+
* @param {string} token
|
|
58
|
+
* @param {{getLatestReviewForPrFn?: Function, sleepFn?: Function, intervalMs?: number, timeoutMs?: number, verbose?: boolean, label?: string, retryCount?: number, log?: Function}} [options]
|
|
59
|
+
* @returns {Promise<string|typeof POLL_TIMEOUT|null>}
|
|
60
|
+
*/
|
|
61
|
+
async function pollForReview(
|
|
62
|
+
prNumber: number,
|
|
63
|
+
reviewerUser: string,
|
|
64
|
+
sinceIso: string,
|
|
65
|
+
token: string,
|
|
66
|
+
options: {
|
|
67
|
+
getLatestReviewForPrFn?: (prNumber: number, reviewerUser: string, sinceIso: string, token: string) => Promise<unknown>;
|
|
68
|
+
sleepFn?: (ms: number) => Promise<void>;
|
|
69
|
+
intervalMs?: number;
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
verbose?: boolean;
|
|
72
|
+
label?: string;
|
|
73
|
+
retryCount?: number;
|
|
74
|
+
log?: (msg: string) => void;
|
|
75
|
+
} = {}
|
|
76
|
+
): Promise<string | typeof POLL_TIMEOUT | null> {
|
|
77
|
+
const {
|
|
78
|
+
getLatestReviewForPrFn = getLatestReviewForPr,
|
|
79
|
+
sleepFn = delay,
|
|
80
|
+
intervalMs = resolvePollIntervalMs(),
|
|
81
|
+
timeoutMs = resolvePollTimeoutMs(),
|
|
82
|
+
verbose = false,
|
|
83
|
+
label = 'review',
|
|
84
|
+
retryCount = 0,
|
|
85
|
+
log: logger = log.plain
|
|
86
|
+
} = options;
|
|
87
|
+
if (!token) {
|
|
88
|
+
logger(status('WARN', 'No Forgejo token — skipping review-outcome poll (manual handoff required).'));
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const start = Date.now();
|
|
93
|
+
const deadline = start + timeoutMs;
|
|
94
|
+
let lastProgressAt = start;
|
|
95
|
+
logger(status('INFO', `Polling Forgejo for ${label} by ${reviewerUser} on PR #${prNumber} (timeout ${Math.round(timeoutMs / 1000)}s)...`));
|
|
96
|
+
|
|
97
|
+
while (Date.now() < deadline) {
|
|
98
|
+
const review = await getLatestReviewForPrFn(prNumber, reviewerUser, sinceIso, token);
|
|
99
|
+
if (review) {
|
|
100
|
+
const reviewState = (review as { state: string }).state;
|
|
101
|
+
if (verbose) { logger(status('INFO', `${label}: reviewer state=${reviewState} after ${formatElapsed(start)}.`)); }
|
|
102
|
+
return reviewState;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const now = Date.now();
|
|
106
|
+
if (verbose || now - lastProgressAt >= POLL_PROGRESS_EVERY_MS) {
|
|
107
|
+
logger(status('INFO', `${label}: still waiting for ${reviewerUser}'s review... (${formatElapsed(start)} elapsed)`));
|
|
108
|
+
lastProgressAt = now;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
await sleepFn(intervalMs);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const isRetry = retryCount > 0;
|
|
115
|
+
const logLevel = isRetry ? 'INFO' : 'WARN';
|
|
116
|
+
logger(status(logLevel, `${label}: no review posted by ${reviewerUser} within ${Math.round(timeoutMs / 1000)}s.`));
|
|
117
|
+
return POLL_TIMEOUT;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {number} prNumber
|
|
122
|
+
* @param {string} implementerUser
|
|
123
|
+
* @param {string} sinceIso
|
|
124
|
+
* @param {string} token
|
|
125
|
+
* @param {{getLatestDispositionForPrFn?: Function, sleepFn?: Function, intervalMs?: number, timeoutMs?: number, verbose?: boolean, label?: string, retryCount?: number, log?: Function}} [options]
|
|
126
|
+
* @returns {Promise<string|typeof POLL_TIMEOUT|null>}
|
|
127
|
+
*/
|
|
128
|
+
async function pollForDisposition(
|
|
129
|
+
prNumber: number,
|
|
130
|
+
implementerUser: string,
|
|
131
|
+
sinceIso: string,
|
|
132
|
+
token: string,
|
|
133
|
+
options: {
|
|
134
|
+
getLatestDispositionForPrFn?: (prNumber: number, implementerUser: string, sinceIso: string, token: string) => Promise<unknown>;
|
|
135
|
+
sleepFn?: (ms: number) => Promise<void>;
|
|
136
|
+
intervalMs?: number;
|
|
137
|
+
timeoutMs?: number;
|
|
138
|
+
verbose?: boolean;
|
|
139
|
+
label?: string;
|
|
140
|
+
retryCount?: number;
|
|
141
|
+
log?: (msg: string) => void;
|
|
142
|
+
} = {}
|
|
143
|
+
): Promise<string | typeof POLL_TIMEOUT | null> {
|
|
144
|
+
const {
|
|
145
|
+
getLatestDispositionForPrFn = getLatestDispositionForPr,
|
|
146
|
+
sleepFn = delay,
|
|
147
|
+
intervalMs = resolvePollIntervalMs(),
|
|
148
|
+
timeoutMs = resolvePollTimeoutMs(),
|
|
149
|
+
verbose = false,
|
|
150
|
+
label = 'disposition',
|
|
151
|
+
retryCount = 0,
|
|
152
|
+
log: logger = log.plain
|
|
153
|
+
} = options;
|
|
154
|
+
if (!token) {
|
|
155
|
+
logger(status('WARN', 'No Forgejo token — skipping disposition poll (manual handoff required).'));
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const start = Date.now();
|
|
160
|
+
const deadline = start + timeoutMs;
|
|
161
|
+
let lastProgressAt = start;
|
|
162
|
+
logger(status('INFO', `Polling Forgejo for ${label} by ${implementerUser} on PR #${prNumber} (timeout ${Math.round(timeoutMs / 1000)}s)...`));
|
|
163
|
+
|
|
164
|
+
while (Date.now() < deadline) {
|
|
165
|
+
const disposition = await getLatestDispositionForPrFn(prNumber, implementerUser, sinceIso, token);
|
|
166
|
+
if (disposition) {
|
|
167
|
+
const dispositionStr = String(disposition);
|
|
168
|
+
if (verbose) { logger(status('INFO', `${label}: disposition=${dispositionStr} after ${formatElapsed(start)}.`)); }
|
|
169
|
+
return dispositionStr;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const now = Date.now();
|
|
173
|
+
if (verbose || now - lastProgressAt >= POLL_PROGRESS_EVERY_MS) {
|
|
174
|
+
logger(status('INFO', `${label}: still waiting for ${implementerUser}'s disposition comment... (${formatElapsed(start)} elapsed)`));
|
|
175
|
+
lastProgressAt = now;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
await sleepFn(intervalMs);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const isRetry = retryCount > 0;
|
|
182
|
+
const logLevel = isRetry ? 'INFO' : 'WARN';
|
|
183
|
+
logger(status(logLevel, `${label}: no disposition comment posted by ${implementerUser} within ${Math.round(timeoutMs / 1000)}s.`));
|
|
184
|
+
return POLL_TIMEOUT;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export {
|
|
188
|
+
resolvePollIntervalMs,
|
|
189
|
+
resolvePollTimeoutMs,
|
|
190
|
+
formatElapsed,
|
|
191
|
+
isPollTimeout,
|
|
192
|
+
pollForReview,
|
|
193
|
+
pollForDisposition
|
|
194
|
+
};
|