@magnusekdahl/parallix 1.1.0 → 1.2.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/config/integration-pipelines.json +9 -0
- package/config/workflow.config.schema.json +12 -0
- package/docs/adr/0032-mission-refinement-state-and-usage-budget-signals.md +10 -8
- package/docs/adr/0036-mission-sizing-and-dependency-wave-heuristics.md +14 -12
- package/docs/adr/0041-integration-pipeline-gates.md +1 -1
- package/docs/adr/0047-per-mission-change-size-budget.md +161 -0
- package/docs/adr/index.md +1 -0
- package/docs/use-cases.md +44 -0
- package/lib/agents/agents.js +149 -100
- package/lib/agents/claude-telemetry.js +14 -10
- package/lib/agents/claude.js +32 -11
- package/lib/agents/codex-telemetry.js +32 -19
- package/lib/agents/codex.js +36 -11
- package/lib/agents/limit-hit.js +42 -25
- package/lib/agents/mistral-telemetry.js +1 -1
- package/lib/agents/mistral.js +13 -3
- package/lib/agents/opencode-export.js +11 -5
- package/lib/agents/opencode-telemetry.js +75 -90
- package/lib/agents/opencode.js +86 -29
- package/lib/agents/stage-telemetry.js +3 -7
- package/lib/commands/active.js +139 -94
- package/lib/commands/checkpoint.js +3 -1
- package/lib/commands/config.js +5 -3
- package/lib/commands/coverage-gate.js +17 -10
- package/lib/commands/diff.js +11 -5
- package/lib/commands/draft.js +95 -55
- package/lib/commands/handoff.js +253 -65
- package/lib/commands/integrate.js +288 -188
- package/lib/commands/mission-start.js +39 -34
- package/lib/commands/rebase.js +32 -21
- package/lib/commands/repair-handoff.js +21 -18
- package/lib/commands/resolve-conflict.js +5 -2
- package/lib/commands/review.js +1 -0
- package/lib/commands/setup-review.js +1 -0
- package/lib/commands/stats-backfill.js +50 -41
- package/lib/commands/stats.js +653 -204
- package/lib/commands/status.js +33 -28
- package/lib/core/fmt.js +58 -15
- package/lib/core/git.js +7 -5
- package/lib/core/gitignore.js +6 -4
- package/lib/core/mission-utils.js +161 -88
- package/lib/core/nels.js +199 -0
- package/lib/core/persistent-data-migration.js +55 -23
- package/lib/core/product-config.js +44 -23
- package/lib/core/runtime-matrix.js +15 -2
- package/lib/core/spawn-tee.js +40 -13
- package/lib/core/state-map.js +32 -13
- package/lib/core/storage.js +19 -5
- package/lib/core/subagent-limit.js +28 -0
- package/lib/core/verification.js +29 -12
- package/lib/review/rebase.js +11 -3
- package/lib/review/review-adapter.js +93 -6
- package/lib/review/review-artifacts.js +89 -23
- package/lib/review/review-commands.js +139 -45
- package/lib/review/review-events.js +103 -52
- package/lib/review/review-loop.js +117 -87
- package/lib/review/review-polling.js +25 -5
- package/lib/review/review-prompts.js +36 -5
- package/lib/review/review-state.js +39 -15
- package/lib/tools/backlog.js +162 -71
- package/lib/tools/forgejo.js +290 -129
- package/lib/tools/gatekeeper.js +15 -0
- package/lib/tools/redgreen.js +220 -0
- package/lib/tools/sessions.js +12 -5
- package/lib/tools/setup-review.js +170 -70
- package/package.json +11 -3
- package/prompts/draft.md +6 -0
- package/templates/mission-scaffold.md +1 -1
|
@@ -37,6 +37,7 @@ function isForgejoProvider(rootDir = process.cwd()) {
|
|
|
37
37
|
return isEnabled(rootDir);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/** @param {string} [rootDir] */
|
|
40
41
|
function resolveArtifactDir(rootDir = process.cwd()) {
|
|
41
42
|
const review = loadAdapterConfig(rootDir).review || {};
|
|
42
43
|
const configured = typeof review.tmpDir === 'string' && review.tmpDir.trim()
|
|
@@ -48,65 +49,151 @@ function resolveArtifactDir(rootDir = process.cwd()) {
|
|
|
48
49
|
return path.isAbsolute(configured) ? configured : path.resolve(rootDir, configured);
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
/**
|
|
53
|
+
* @param {string} rootDir
|
|
54
|
+
* @param {Function} liveFn
|
|
55
|
+
* @param {*} noopValue
|
|
56
|
+
* @returns {*}
|
|
57
|
+
*/
|
|
51
58
|
function withForgejo(rootDir, liveFn, noopValue) {
|
|
52
59
|
return isEnabled(rootDir) ? liveFn() : noopValue;
|
|
53
60
|
}
|
|
54
61
|
|
|
62
|
+
/**
|
|
63
|
+
* @param {string} branch
|
|
64
|
+
* @param {string} [rootDir]
|
|
65
|
+
* @param {{[key: string]: any}} [options]
|
|
66
|
+
* @returns {*}
|
|
67
|
+
*/
|
|
55
68
|
function getPrStatus(branch, rootDir = process.cwd(), options = {}) {
|
|
56
69
|
return withForgejo(rootDir, () => forgejo.getPrStatus(branch, rootDir, options), NOOP_PR_STATUS);
|
|
57
70
|
}
|
|
58
71
|
|
|
72
|
+
/**
|
|
73
|
+
* @param {string} user
|
|
74
|
+
* @param {{rootDir?: string}} [options]
|
|
75
|
+
* @returns {string|null}
|
|
76
|
+
*/
|
|
59
77
|
function readToken(user, options = {}) {
|
|
60
78
|
const rootDir = options.rootDir || process.cwd();
|
|
61
79
|
return isEnabled(rootDir) ? forgejo.readToken(user) : null;
|
|
62
80
|
}
|
|
63
81
|
|
|
82
|
+
/**
|
|
83
|
+
* @param {number} prNumber
|
|
84
|
+
* @param {string} reviewerUser
|
|
85
|
+
* @param {string} sinceIso
|
|
86
|
+
* @param {string} token
|
|
87
|
+
* @param {{rootDir?: string, [key: string]: any}} [options]
|
|
88
|
+
* @returns {Promise<*|null>}
|
|
89
|
+
*/
|
|
64
90
|
async function getLatestReviewForPr(prNumber, reviewerUser, sinceIso, token, options = {}) {
|
|
65
|
-
if (!token) return null;
|
|
91
|
+
if (!token) {return null;}
|
|
66
92
|
return withForgejo(options.rootDir || process.cwd(), () => forgejo.getLatestReviewForPr(prNumber, reviewerUser, sinceIso, token, options), null);
|
|
67
93
|
}
|
|
68
94
|
|
|
95
|
+
/**
|
|
96
|
+
* @param {number} prNumber
|
|
97
|
+
* @param {string} implementerUser
|
|
98
|
+
* @param {string} sinceIso
|
|
99
|
+
* @param {string} token
|
|
100
|
+
* @param {{rootDir?: string, [key: string]: any}} [options]
|
|
101
|
+
* @returns {Promise<*|null>}
|
|
102
|
+
*/
|
|
69
103
|
async function getLatestDispositionForPr(prNumber, implementerUser, sinceIso, token, options = {}) {
|
|
70
|
-
if (!token) return null;
|
|
104
|
+
if (!token) {return null;}
|
|
71
105
|
return withForgejo(options.rootDir || process.cwd(), () => forgejo.getLatestDispositionForPr(prNumber, implementerUser, sinceIso, token, options), null);
|
|
72
106
|
}
|
|
73
107
|
|
|
108
|
+
/**
|
|
109
|
+
* @param {string} branch
|
|
110
|
+
* @param {string} token
|
|
111
|
+
* @param {string} body
|
|
112
|
+
* @param {{rootDir?: string, [key: string]: any}} [options]
|
|
113
|
+
* @returns {*}
|
|
114
|
+
*/
|
|
74
115
|
function postComment(branch, token, body, options = {}) {
|
|
75
116
|
return withForgejo(options.rootDir || process.cwd(), () => forgejo.postComment(branch, token, body, options), { ok: true, skipped: true, reason: 'review-provider-disabled' });
|
|
76
117
|
}
|
|
77
118
|
|
|
119
|
+
/**
|
|
120
|
+
* @param {string} branch
|
|
121
|
+
* @param {string} token
|
|
122
|
+
* @param {string} outcome
|
|
123
|
+
* @param {string} summary
|
|
124
|
+
* @param {{rootDir?: string, [key: string]: any}} [options]
|
|
125
|
+
* @returns {*}
|
|
126
|
+
*/
|
|
78
127
|
function postReview(branch, token, outcome, summary, options = {}) {
|
|
79
128
|
return withForgejo(options.rootDir || process.cwd(), () => forgejo.postReview(branch, token, outcome, summary, options), { ok: true, skipped: true, reason: 'review-provider-disabled' });
|
|
80
129
|
}
|
|
81
130
|
|
|
131
|
+
/**
|
|
132
|
+
* @param {string} branch
|
|
133
|
+
* @param {string} user
|
|
134
|
+
* @param {string} token
|
|
135
|
+
* @param {{rootDir?: string, [key: string]: any}} [options]
|
|
136
|
+
* @returns {*}
|
|
137
|
+
*/
|
|
82
138
|
function createPr(branch, user, token, options = {}) {
|
|
83
139
|
return withForgejo(options.rootDir || process.cwd(), () => forgejo.createPr(branch, user, token, options), { ok: true, skipped: true, url: null });
|
|
84
140
|
}
|
|
85
141
|
|
|
86
|
-
|
|
87
|
-
|
|
142
|
+
/**
|
|
143
|
+
* @param {string} [url]
|
|
144
|
+
* @param {{rootDir?: string, [key: string]: any}} [options]
|
|
145
|
+
* @returns {*}
|
|
146
|
+
*/
|
|
147
|
+
function forgejoAvailable(url = process.env.FORGEJO_URL || 'http://localhost:3300', /** @type {{rootDir?: string, [key: string]: any}} */ options = {}) {
|
|
148
|
+
return withForgejo(options.rootDir || process.cwd(), () => forgejo.forgejoAvailable(url, /** @type {{request?: Function, timeout?: number}} */ (options)), false);
|
|
88
149
|
}
|
|
89
150
|
|
|
151
|
+
/**
|
|
152
|
+
* @param {string} [url]
|
|
153
|
+
* @param {{rootDir?: string, [key: string]: any}} [options]
|
|
154
|
+
* @returns {*}
|
|
155
|
+
*/
|
|
90
156
|
function providerAvailable(url = process.env.FORGEJO_URL || 'http://localhost:3300', options = {}) {
|
|
91
157
|
return forgejoAvailable(url, options);
|
|
92
158
|
}
|
|
93
159
|
|
|
160
|
+
/**
|
|
161
|
+
* @param {string} branch
|
|
162
|
+
* @param {string} token
|
|
163
|
+
* @param {{rootDir?: string, [key: string]: any}} [options]
|
|
164
|
+
* @returns {Promise<*>}
|
|
165
|
+
*/
|
|
94
166
|
async function getComments(branch, token, options = {}) {
|
|
95
167
|
return withForgejo(options.rootDir || process.cwd(), () => forgejo.getComments(branch, token, options), []);
|
|
96
168
|
}
|
|
97
169
|
|
|
98
|
-
|
|
99
|
-
|
|
170
|
+
/**
|
|
171
|
+
* @param {string} branch
|
|
172
|
+
* @param {string} token
|
|
173
|
+
* @param {string} user
|
|
174
|
+
* @param {{rootDir?: string, [key: string]: any}} [options]
|
|
175
|
+
* @returns {*}
|
|
176
|
+
*/
|
|
177
|
+
function closePr(branch, token, user, /** @type {{rootDir?: string, [key: string]: any}} */ options = {}) {
|
|
178
|
+
return withForgejo(options.rootDir || process.cwd(), () => (/** @type {any} */ (forgejo).closePr)(branch, token, user, options), { ok: true, skipped: true, reason: 'review-provider-disabled' });
|
|
100
179
|
}
|
|
101
180
|
|
|
181
|
+
/** @param {string} explicitUser */
|
|
102
182
|
function resolveForgejoUser(explicitUser) {
|
|
103
183
|
return explicitUser || process.env.FORGEJO_USER || null;
|
|
104
184
|
}
|
|
105
185
|
|
|
186
|
+
/** @param {string} explicitUser */
|
|
106
187
|
function resolveReviewUser(explicitUser) {
|
|
107
188
|
return resolveForgejoUser(explicitUser);
|
|
108
189
|
}
|
|
109
190
|
|
|
191
|
+
/**
|
|
192
|
+
* @param {string} branch
|
|
193
|
+
* @param {string} token
|
|
194
|
+
* @param {{rootDir?: string, [key: string]: any}} [options]
|
|
195
|
+
* @returns {*|null}
|
|
196
|
+
*/
|
|
110
197
|
function getPrAuthor(branch, token, options = {}) {
|
|
111
198
|
return withForgejo(options.rootDir || process.cwd(), () => forgejo.getPrAuthor(branch, token, options), null);
|
|
112
199
|
}
|
|
@@ -10,16 +10,21 @@ const path = require('path');
|
|
|
10
10
|
const fmt = require('../core/fmt');
|
|
11
11
|
const { missionBranchName, resolveWorktree } = require('../core/mission-utils');
|
|
12
12
|
const { readReviewState, writeReviewState, reviewStateFile, ReviewState, resolveReviewIdentity } = require('./review-state');
|
|
13
|
-
const { readToken, postComment, postReview,
|
|
13
|
+
const { readToken, postComment, postReview, getPrAuthor, isEnabled, resolveArtifactDir: resolveConfiguredArtifactDir } = require('./review-adapter');
|
|
14
14
|
const { createEvent, consumeHumanNotes, VALID_EVENT_TYPES } = require('./review-events');
|
|
15
15
|
|
|
16
16
|
// ============================================================================
|
|
17
17
|
// Metadata Footer
|
|
18
18
|
// ============================================================================
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} slug
|
|
22
|
+
* @param {string} [rootDir]
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
20
25
|
function buildMetadataFooter(slug, rootDir) {
|
|
21
26
|
const state = readReviewState(slug, rootDir);
|
|
22
|
-
if (!state) return '';
|
|
27
|
+
if (!state) {return '';}
|
|
23
28
|
return `\n\n---\n\`[workflow-round:${state.round}, workflow-phase:${state.phase}]\``;
|
|
24
29
|
}
|
|
25
30
|
|
|
@@ -27,6 +32,12 @@ function buildMetadataFooter(slug, rootDir) {
|
|
|
27
32
|
// Artifact Path Utilities
|
|
28
33
|
// ============================================================================
|
|
29
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} slug
|
|
37
|
+
* @param {string} artifactName
|
|
38
|
+
* @param {string} [tmpDir]
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
30
41
|
function reviewArtifactPath(slug, artifactName, tmpDir = os.tmpdir()) {
|
|
31
42
|
return path.join(tmpDir, `${slug}-${artifactName}`);
|
|
32
43
|
}
|
|
@@ -44,6 +55,12 @@ function reviewArtifactPath(slug, artifactName, tmpDir = os.tmpdir()) {
|
|
|
44
55
|
// the loop passes the resolved default dir explicitly. This is defense-in-depth:
|
|
45
56
|
// once the prompts substitute {{artifactDir}}, producer and consumer share one
|
|
46
57
|
// dir by construction and this fallback is rarely exercised.
|
|
58
|
+
/**
|
|
59
|
+
* @param {string} slug
|
|
60
|
+
* @param {string} artifactName
|
|
61
|
+
* @param {{tmpDir: string, explicitTmpDir?: boolean, fallbackToTmp?: boolean, readArtifactFn: Function}} opts
|
|
62
|
+
* @returns {{path: string, value: string|null}}
|
|
63
|
+
*/
|
|
47
64
|
function resolveArtifactRead(slug, artifactName, { tmpDir, explicitTmpDir, fallbackToTmp, readArtifactFn }) {
|
|
48
65
|
const primaryPath = reviewArtifactPath(slug, artifactName, tmpDir);
|
|
49
66
|
const primaryValue = readArtifactFn(primaryPath);
|
|
@@ -77,6 +94,11 @@ function resolveArtifactDir(rootDir = process.cwd()) {
|
|
|
77
94
|
return resolveConfiguredArtifactDir(rootDir);
|
|
78
95
|
}
|
|
79
96
|
|
|
97
|
+
/**
|
|
98
|
+
* @param {string} filePath
|
|
99
|
+
* @param {Function} [readFileSync]
|
|
100
|
+
* @returns {string|null}
|
|
101
|
+
*/
|
|
80
102
|
function readArtifactFile(filePath, readFileSync = fs.readFileSync) {
|
|
81
103
|
try {
|
|
82
104
|
const value = readFileSync(filePath, 'utf8');
|
|
@@ -86,6 +108,11 @@ function readArtifactFile(filePath, readFileSync = fs.readFileSync) {
|
|
|
86
108
|
}
|
|
87
109
|
}
|
|
88
110
|
|
|
111
|
+
/**
|
|
112
|
+
* @param {string} filePath
|
|
113
|
+
* @param {Function} [unlinkSync]
|
|
114
|
+
* @returns {void}
|
|
115
|
+
*/
|
|
89
116
|
function deleteArtifactFile(filePath, unlinkSync = fs.unlinkSync) {
|
|
90
117
|
try {
|
|
91
118
|
unlinkSync(filePath);
|
|
@@ -98,11 +125,18 @@ function deleteArtifactFile(filePath, unlinkSync = fs.unlinkSync) {
|
|
|
98
125
|
// Normalization Utilities
|
|
99
126
|
// ============================================================================
|
|
100
127
|
|
|
128
|
+
/**
|
|
129
|
+
* @param {string} value
|
|
130
|
+
* @returns {string|null}
|
|
131
|
+
*/
|
|
101
132
|
function normalizeReviewVerdict(value) {
|
|
102
133
|
const normalized = String(value || '').trim().toLowerCase();
|
|
103
134
|
return ['approve', 'request-changes', 'comment'].includes(normalized) ? normalized : null;
|
|
104
135
|
}
|
|
105
136
|
|
|
137
|
+
/** @param {string} value
|
|
138
|
+
* @returns {string|null}
|
|
139
|
+
*/
|
|
106
140
|
function normalizeDisposition(value) {
|
|
107
141
|
const normalized = String(value || '').trim().toUpperCase();
|
|
108
142
|
return ['CHANGES_MADE', 'PUSHBACK_ALL', 'PARKED', 'BLOCKED'].includes(normalized) ? normalized : null;
|
|
@@ -112,6 +146,12 @@ function normalizeDisposition(value) {
|
|
|
112
146
|
// Workflow Comment/Review Posting
|
|
113
147
|
// ============================================================================
|
|
114
148
|
|
|
149
|
+
/**
|
|
150
|
+
* @param {string} slug
|
|
151
|
+
* @param {string} message
|
|
152
|
+
* @param {{log?: Function, error?: Function, readTokenFn?: Function, postCommentFn?: Function, buildMetadataFooterFn?: Function, readReviewStateFn?: Function, rootDir?: string, reviewIdentity?: string, forgejoUser?: string}} [options]
|
|
153
|
+
* @returns {{ok: boolean, error?: string}}
|
|
154
|
+
*/
|
|
115
155
|
function postWorkflowComment(slug, message, options = {}) {
|
|
116
156
|
const log = options.log || fmt.log.plain;
|
|
117
157
|
const error = options.error || fmt.log.plainError;
|
|
@@ -147,12 +187,13 @@ function postWorkflowComment(slug, message, options = {}) {
|
|
|
147
187
|
// the real HTTP status and response body (e.g. `422 approve your own pull is not
|
|
148
188
|
// allowed`) instead of masking it behind a generic "API error". Falls back to
|
|
149
189
|
// the result's own error/raw fields, then "API error" when nothing is present.
|
|
190
|
+
/** @param {*} result */
|
|
150
191
|
function describeProviderFailure(result) {
|
|
151
|
-
if (!result || typeof result !== 'object') return 'API error';
|
|
192
|
+
if (!result || typeof result !== 'object') {return 'API error';}
|
|
152
193
|
// Prefer the HTTP status code (statusCode) over curl's process status.
|
|
153
|
-
const httpStatus = (result.statusCode
|
|
194
|
+
const httpStatus = (result.statusCode !== null && Number(result.statusCode) > 0)
|
|
154
195
|
? result.statusCode
|
|
155
|
-
: (result.status
|
|
196
|
+
: (result.status !== null && Number(result.status) >= 100 ? result.status : null);
|
|
156
197
|
let body = null;
|
|
157
198
|
if (result.data && typeof result.data === 'object') {
|
|
158
199
|
body = result.data.message || JSON.stringify(result.data);
|
|
@@ -160,15 +201,21 @@ function describeProviderFailure(result) {
|
|
|
160
201
|
body = result.data;
|
|
161
202
|
}
|
|
162
203
|
const parts = [];
|
|
163
|
-
if (httpStatus
|
|
164
|
-
if (body) parts.push(body);
|
|
165
|
-
if (parts.length) return parts.join(' ');
|
|
204
|
+
if (httpStatus !== null) {parts.push(String(httpStatus));}
|
|
205
|
+
if (body) {parts.push(body);}
|
|
206
|
+
if (parts.length) {return parts.join(' ');}
|
|
166
207
|
return result.error || result.raw || 'API error';
|
|
167
208
|
}
|
|
168
209
|
|
|
169
210
|
// Record a review verdict in local review-state.json / review-events without
|
|
170
211
|
// posting to the review provider. Used by the self-author skip path so the verdict is still
|
|
171
212
|
// captured even though the formal provider approval is intentionally not posted.
|
|
213
|
+
/**
|
|
214
|
+
* @param {string} slug
|
|
215
|
+
* @param {string} outcome
|
|
216
|
+
* @param {{worktree?: string, writeReviewStateFn?: Function, createEventFn?: Function, readReviewStateFn?: Function, log?: Function, error?: Function, reviewer?: string}} [options]
|
|
217
|
+
* @returns {void}
|
|
218
|
+
*/
|
|
172
219
|
function recordLocalReviewVerdict(slug, outcome, options = {}) {
|
|
173
220
|
const worktree = options.worktree || resolveWorktree(slug) || process.cwd();
|
|
174
221
|
const writeReviewStateFn = options.writeReviewStateFn || writeReviewState;
|
|
@@ -199,6 +246,13 @@ function recordLocalReviewVerdict(slug, outcome, options = {}) {
|
|
|
199
246
|
createEventFn(slug, VALID_EVENT_TYPES.REVIEWER_OUTCOME, { verdict: outcome }, { worktree, log, error });
|
|
200
247
|
}
|
|
201
248
|
|
|
249
|
+
/**
|
|
250
|
+
* @param {string} slug
|
|
251
|
+
* @param {string} outcome
|
|
252
|
+
* @param {string} message
|
|
253
|
+
* @param {{log?: Function, error?: Function, readTokenFn?: Function, postReviewFn?: Function, buildMetadataFooterFn?: Function, readReviewStateFn?: Function, worktree?: string, reviewIdentity?: string, forgejoUser?: string, getPrAuthorFn?: Function, writeReviewStateFn?: Function, createEventFn?: Function}} [options]
|
|
254
|
+
* @returns {{ok: boolean, error?: string, skipped?: boolean, reason?: string, prAuthor?: string}}
|
|
255
|
+
*/
|
|
202
256
|
function postWorkflowReview(slug, outcome, message, options = {}) {
|
|
203
257
|
const log = options.log || fmt.log.plain;
|
|
204
258
|
const error = options.error || fmt.log.plainError;
|
|
@@ -262,18 +316,24 @@ function postWorkflowReview(slug, outcome, message, options = {}) {
|
|
|
262
316
|
// Artifact Consumption
|
|
263
317
|
// ============================================================================
|
|
264
318
|
|
|
319
|
+
/**
|
|
320
|
+
* @param {string} slug
|
|
321
|
+
* @param {string} reviewer
|
|
322
|
+
* @param {{log?: Function, error?: Function, readArtifactFn?: Function, deleteArtifactFn?: Function, tmpDir?: string|null, fallbackToTmp?: boolean, worktree?: string, providerEnabled?: boolean|null, forgejoEnabled?: boolean|null, readTokenFn?: Function, getCommentsFn?: Function, postCommentFn?: Function, postReviewFn?: Function, buildMetadataFooterFn?: Function, createEventFn?: Function}} [options]
|
|
323
|
+
* @returns {Promise<{consumed: boolean, ok?: boolean, reviewState?: string|null}>}
|
|
324
|
+
*/
|
|
265
325
|
async function consumeReviewerArtifacts(slug, reviewer, options = {}) {
|
|
266
326
|
const log = options.log || fmt.log.plain;
|
|
267
327
|
const error = options.error || fmt.log.plainError;
|
|
268
328
|
const readArtifactFn = options.readArtifactFn || readArtifactFile;
|
|
269
329
|
const deleteArtifactFn = options.deleteArtifactFn || deleteArtifactFile;
|
|
270
|
-
const explicitTmpDir = options.tmpDir
|
|
330
|
+
const explicitTmpDir = options.tmpDir !== null && options.tmpDir !== undefined;
|
|
271
331
|
const fallbackToTmp = options.fallbackToTmp === true;
|
|
272
332
|
const worktree = options.worktree || resolveWorktree(slug) || process.cwd();
|
|
273
333
|
const tmpDir = options.tmpDir || resolveArtifactDir(worktree);
|
|
274
|
-
const providerEnabled = options.providerEnabled
|
|
334
|
+
const providerEnabled = options.providerEnabled !== null && options.providerEnabled !== undefined
|
|
275
335
|
? options.providerEnabled
|
|
276
|
-
: (options.forgejoEnabled
|
|
336
|
+
: (options.forgejoEnabled !== null && options.forgejoEnabled !== undefined ? options.forgejoEnabled : isEnabled(worktree));
|
|
277
337
|
const reviewStatePath = reviewStateFile(slug, worktree);
|
|
278
338
|
|
|
279
339
|
const findingsResolved = resolveArtifactRead(slug, 'review-findings.md', { tmpDir, explicitTmpDir, fallbackToTmp, readArtifactFn });
|
|
@@ -289,7 +349,7 @@ async function consumeReviewerArtifacts(slug, reviewer, options = {}) {
|
|
|
289
349
|
|
|
290
350
|
// Extract verdict from outcome message if review-verdict.txt is not provided
|
|
291
351
|
// This allows a single review-outcome.md file to contain both the outcome and verdict
|
|
292
|
-
let verdict = normalizeReviewVerdict(verdictRaw);
|
|
352
|
+
let verdict = normalizeReviewVerdict(/** @type {string} */ (verdictRaw || ''));
|
|
293
353
|
if (!verdict && outcomeMessage) {
|
|
294
354
|
// Try to extract verdict from outcome content (e.g., "Verdict: approve" or frontmatter)
|
|
295
355
|
const outcomeVerdictMatch = outcomeMessage.match(/^verdict:\s*(approve|request-changes|comment)/im) ||
|
|
@@ -418,8 +478,8 @@ async function consumeReviewerArtifacts(slug, reviewer, options = {}) {
|
|
|
418
478
|
deleteArtifactFn(outcomePath);
|
|
419
479
|
deleteArtifactFn(verdictPath);
|
|
420
480
|
|
|
421
|
-
if (verdict === 'approve') return { consumed: true, ok: true, reviewState: 'APPROVED' };
|
|
422
|
-
if (verdict === 'request-changes') return { consumed: true, ok: true, reviewState: 'REQUEST_CHANGES' };
|
|
481
|
+
if (verdict === 'approve') {return { consumed: true, ok: true, reviewState: 'APPROVED' };}
|
|
482
|
+
if (verdict === 'request-changes') {return { consumed: true, ok: true, reviewState: 'REQUEST_CHANGES' };}
|
|
423
483
|
// Defensive fallback: a stray `comment` verdict (prompts no longer offer it) cannot be
|
|
424
484
|
// resolved via provider polling when the provider is disabled, so normalize it to the
|
|
425
485
|
// disposition format and pass it through. inferPhaseFromDisposition() maps COMMENT -> fixing.
|
|
@@ -432,18 +492,24 @@ async function consumeReviewerArtifacts(slug, reviewer, options = {}) {
|
|
|
432
492
|
return { consumed: true, ok: true, reviewState: null };
|
|
433
493
|
}
|
|
434
494
|
|
|
495
|
+
/**
|
|
496
|
+
* @param {string} slug
|
|
497
|
+
* @param {string} implementer
|
|
498
|
+
* @param {{log?: Function, error?: Function, readArtifactFn?: Function, deleteArtifactFn?: Function, tmpDir?: string|null, fallbackToTmp?: boolean, worktree?: string, providerEnabled?: boolean|null, forgejoEnabled?: boolean|null, readTokenFn?: Function, getCommentsFn?: Function, postCommentFn?: Function, buildMetadataFooterFn?: Function, createEventFn?: Function}} [options]
|
|
499
|
+
* @returns {Promise<{consumed: boolean, ok?: boolean, disposition?: string|null}>}
|
|
500
|
+
*/
|
|
435
501
|
async function consumeImplementerArtifacts(slug, implementer, options = {}) {
|
|
436
502
|
const log = options.log || fmt.log.plain;
|
|
437
503
|
const error = options.error || fmt.log.plainError;
|
|
438
504
|
const readArtifactFn = options.readArtifactFn || readArtifactFile;
|
|
439
505
|
const deleteArtifactFn = options.deleteArtifactFn || deleteArtifactFile;
|
|
440
|
-
const explicitTmpDir = options.tmpDir
|
|
506
|
+
const explicitTmpDir = options.tmpDir !== null && options.tmpDir !== undefined;
|
|
441
507
|
const fallbackToTmp = options.fallbackToTmp === true;
|
|
442
508
|
const worktree = options.worktree || resolveWorktree(slug) || process.cwd();
|
|
443
509
|
const tmpDir = options.tmpDir || resolveArtifactDir(worktree);
|
|
444
|
-
const providerEnabled = options.providerEnabled
|
|
510
|
+
const providerEnabled = options.providerEnabled !== null && options.providerEnabled !== undefined
|
|
445
511
|
? options.providerEnabled
|
|
446
|
-
: (options.forgejoEnabled
|
|
512
|
+
: (options.forgejoEnabled !== null && options.forgejoEnabled !== undefined ? options.forgejoEnabled : isEnabled(worktree));
|
|
447
513
|
const reviewStatePath = reviewStateFile(slug, worktree);
|
|
448
514
|
|
|
449
515
|
const resolutionResolved = resolveArtifactRead(slug, 'round-resolution.md', { tmpDir, explicitTmpDir, fallbackToTmp, readArtifactFn });
|
|
@@ -453,7 +519,7 @@ async function consumeImplementerArtifacts(slug, implementer, options = {}) {
|
|
|
453
519
|
|
|
454
520
|
const resolution = resolutionResolved.value;
|
|
455
521
|
const dispositionRaw = dispositionResolved.value;
|
|
456
|
-
const disposition = normalizeDisposition(dispositionRaw);
|
|
522
|
+
const disposition = normalizeDisposition(/** @type {string} */ (dispositionRaw || ''));
|
|
457
523
|
const hasAny = resolution !== null || dispositionRaw !== null;
|
|
458
524
|
|
|
459
525
|
if (!hasAny) {
|
|
@@ -492,10 +558,10 @@ async function consumeImplementerArtifacts(slug, implementer, options = {}) {
|
|
|
492
558
|
const parkedMatch = resolution.match(/parked_items:\s*(\[[^\]]*\])/i);
|
|
493
559
|
const blockedMatch = resolution.match(/blocked_reason:\s*"([^"]*)"/i);
|
|
494
560
|
|
|
495
|
-
if (fixedMatch) fixedItems = JSON.parse(fixedMatch[1]);
|
|
496
|
-
if (pushedMatch) pushedBackItems = JSON.parse(pushedMatch[1]);
|
|
497
|
-
if (parkedMatch) parkedItems = JSON.parse(parkedMatch[1]);
|
|
498
|
-
if (blockedMatch) blockedReason = blockedMatch[1];
|
|
561
|
+
if (fixedMatch) {fixedItems = JSON.parse(fixedMatch[1]);}
|
|
562
|
+
if (pushedMatch) {pushedBackItems = JSON.parse(pushedMatch[1]);}
|
|
563
|
+
if (parkedMatch) {parkedItems = JSON.parse(parkedMatch[1]);}
|
|
564
|
+
if (blockedMatch) {blockedReason = blockedMatch[1];}
|
|
499
565
|
} catch (_) {
|
|
500
566
|
// If parsing fails, use empty arrays
|
|
501
567
|
fixedItems = [];
|
|
@@ -507,7 +573,7 @@ async function consumeImplementerArtifacts(slug, implementer, options = {}) {
|
|
|
507
573
|
if (disposition === 'BLOCKED' && !blockedReason) {
|
|
508
574
|
// Check if it's in the resolution content
|
|
509
575
|
const match = resolution.match(/blocked.*?:\s*(.+)/i);
|
|
510
|
-
if (match) blockedReason = match[1].trim();
|
|
576
|
+
if (match) {blockedReason = match[1].trim();}
|
|
511
577
|
}
|
|
512
578
|
|
|
513
579
|
const summaryEventResult = createEventFn(slug, VALID_EVENT_TYPES.IMPLEMENTER_ROUND_SUMMARY, {
|