@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
|
@@ -6,45 +6,50 @@
|
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
9
10
|
const fmt = require('../core/fmt');
|
|
10
|
-
const {
|
|
11
|
-
const { findMissionDir, findMissionArea, resolveWorktree, inferSlug,
|
|
12
|
-
const { resolveTaskFile, getTaskStatus, getAcceptanceCriteria,
|
|
11
|
+
const { run, getCurrentBranch } = require('../core/git');
|
|
12
|
+
const { findMissionDir, findMissionArea, resolveWorktree, inferSlug, findCheckpoints, missionBranchName, missionBaseDir } = require('../core/mission-utils');
|
|
13
|
+
const { resolveTaskFile, getTaskStatus, getAcceptanceCriteria, getTaskAssignee, getTaskImplementer, reportTaskResolution, transitionTask } = require('../tools/backlog');
|
|
13
14
|
const { toVirtual } = require('../core/state-map');
|
|
14
|
-
const { getPrStatus, readToken,
|
|
15
|
+
const { getPrStatus, readToken, postComment, createPr, getComments, closePr, resolveReviewUser, isProviderEnabled } = require('./review-adapter');
|
|
15
16
|
const { buildAutonomousReviewMatrix, formatMatrixSummary } = require('../core/runtime-matrix');
|
|
16
|
-
|
|
17
|
-
const {
|
|
18
|
-
const { createEvent,
|
|
19
|
-
VALID_EVENT_TYPES, ALL_EVENT_TYPES, LEGACY_ARTIFACT_TO_EVENT_TYPE,
|
|
17
|
+
|
|
18
|
+
const { readReviewState, writeReviewState, resolveReviewIdentity } = require('./review-state');
|
|
19
|
+
const { createEvent, importAllLegacyArtifacts, ALL_EVENT_TYPES,
|
|
20
20
|
isValidEventType, shouldMirrorToProvider, readAllEvents } = require('./review-events');
|
|
21
|
-
const {
|
|
21
|
+
const { startAgent } = require('../agents/agents');
|
|
22
22
|
const { performHandoff } = require('../commands/handoff');
|
|
23
23
|
const { formatVerificationCommand, runVerificationGate } = require('../core/verification');
|
|
24
24
|
const { bootstrapReviewSurface } = require('../tools/setup-review');
|
|
25
25
|
const { resolveReviewAdapter } = require('../core/product-config');
|
|
26
26
|
|
|
27
|
-
// Import from review-polling module
|
|
28
|
-
const { POLL_TIMEOUT, delay, resolvePollIntervalMs, resolvePollTimeoutMs, formatElapsed, isPollTimeout, pollForReview, pollForDisposition } = require('./review-polling');
|
|
29
27
|
// Import from review-artifacts module
|
|
30
|
-
const { buildMetadataFooter, reviewArtifactPath,
|
|
28
|
+
const { buildMetadataFooter, reviewArtifactPath, postWorkflowComment, postWorkflowReview, consumeReviewerArtifacts } = require('./review-artifacts');
|
|
31
29
|
|
|
32
30
|
const DEFAULT_MAX_ATTEMPTS = 5;
|
|
33
|
-
const CONTINUE_SKIP_CHECK_TIMEOUT_MS = 10_000;
|
|
34
|
-
const SHARED_FILE_REBASE_CONFLICT_RE = /shared file(?:\(s\))? require agent-assisted resolution|shared-file-conflicts/i;
|
|
35
31
|
|
|
36
32
|
// ============================================================================
|
|
37
33
|
// Internal Helpers
|
|
38
34
|
// ============================================================================
|
|
39
35
|
|
|
36
|
+
/** @param {string[]} args @param {string} flag */
|
|
40
37
|
function flagValue(args, flag) {
|
|
41
38
|
const idx = args.indexOf(flag);
|
|
42
|
-
if (idx === -1) return null;
|
|
39
|
+
if (idx === -1) {return null;}
|
|
43
40
|
const val = args[idx + 1];
|
|
44
|
-
if (!val || val.startsWith('--')) return null;
|
|
41
|
+
if (!val || val.startsWith('--')) {return null;}
|
|
45
42
|
return val;
|
|
46
43
|
}
|
|
47
44
|
|
|
45
|
+
/**
|
|
46
|
+
* @param {string[]} args
|
|
47
|
+
* @param {string} inlineFlag
|
|
48
|
+
* @param {string} fileFlag
|
|
49
|
+
* @param {string} label
|
|
50
|
+
* @param {{readFileSync?: Function, error?: Function, exit?: Function}} [options]
|
|
51
|
+
* @returns {string|null}
|
|
52
|
+
*/
|
|
48
53
|
function readTextFlag(args, inlineFlag, fileFlag, label, options = {}) {
|
|
49
54
|
const readFileSync = options.readFileSync || fs.readFileSync;
|
|
50
55
|
const error = options.error || fmt.log.plainError;
|
|
@@ -54,7 +59,7 @@ function readTextFlag(args, inlineFlag, fileFlag, label, options = {}) {
|
|
|
54
59
|
try {
|
|
55
60
|
return readFileSync(filePath, 'utf8').trimEnd();
|
|
56
61
|
} catch (err) {
|
|
57
|
-
error(`Could not read ${label} from ${fmt.path(filePath)}: ${err.message}`);
|
|
62
|
+
error(`Could not read ${label} from ${fmt.path(filePath)}: ${/** @type {Error} */(err).message}`);
|
|
58
63
|
exit(1);
|
|
59
64
|
return null;
|
|
60
65
|
}
|
|
@@ -63,6 +68,7 @@ function readTextFlag(args, inlineFlag, fileFlag, label, options = {}) {
|
|
|
63
68
|
return flagValue(args, inlineFlag);
|
|
64
69
|
}
|
|
65
70
|
|
|
71
|
+
/** @param {string[]} findings */
|
|
66
72
|
function formatStaticReviewFindings(findings) {
|
|
67
73
|
const lines = [
|
|
68
74
|
'Static review found the following issue(s) before autonomous review:',
|
|
@@ -75,6 +81,7 @@ function formatStaticReviewFindings(findings) {
|
|
|
75
81
|
return lines.join('\n');
|
|
76
82
|
}
|
|
77
83
|
|
|
84
|
+
/** @param {string} slug */
|
|
78
85
|
function formatStaticReviewSuccess(slug) {
|
|
79
86
|
return [
|
|
80
87
|
`Static review for ${slug} found zero issues.`,
|
|
@@ -88,6 +95,11 @@ function formatStaticReviewSuccess(slug) {
|
|
|
88
95
|
].join('\n');
|
|
89
96
|
}
|
|
90
97
|
|
|
98
|
+
/**
|
|
99
|
+
* @param {string} slug
|
|
100
|
+
* @param {{log?: Function, error?: Function, getTaskStatusFn?: Function, resolveTaskFileFn?: Function, transitionTaskFn?: Function, rootDir?: string}} [options]
|
|
101
|
+
* @returns {{repaired: boolean, skipped?: boolean, currentStatus?: string}}
|
|
102
|
+
*/
|
|
91
103
|
function repairStaleActiveTaskAfterReview(slug, options = {}) {
|
|
92
104
|
const log = options.log || fmt.log.plain;
|
|
93
105
|
const error = options.error || fmt.log.plainError;
|
|
@@ -114,6 +126,11 @@ function repairStaleActiveTaskAfterReview(slug, options = {}) {
|
|
|
114
126
|
return { repaired: true, currentStatus };
|
|
115
127
|
}
|
|
116
128
|
|
|
129
|
+
/**
|
|
130
|
+
* @param {string} slug
|
|
131
|
+
* @param {{worktree?: string, taskFile?: string|null, log?: Function, error?: Function}} options
|
|
132
|
+
* @returns {Promise<{ok: boolean, dirty?: boolean, unsafe?: boolean}>}
|
|
133
|
+
*/
|
|
117
134
|
async function commitPersistedReviewOutputs(slug, options = {}) {
|
|
118
135
|
const { commitSafeMissionArtifacts } = require('./review-loop');
|
|
119
136
|
return commitSafeMissionArtifacts(slug, options.worktree || process.cwd(), {
|
|
@@ -123,6 +140,12 @@ async function commitPersistedReviewOutputs(slug, options = {}) {
|
|
|
123
140
|
});
|
|
124
141
|
}
|
|
125
142
|
|
|
143
|
+
/**
|
|
144
|
+
* @param {string} slug
|
|
145
|
+
* @param {string} message
|
|
146
|
+
* @param {{log?: Function, error?: Function, resolveTaskFileFn?: Function, getTaskImplementerFn?: Function, resolveWorktreeFn?: Function, readTokenFn?: Function, postCommentFn?: Function, resolveReviewUserFn?: Function, resolveForgejoUserFn?: Function, readReviewStateFn?: Function, buildMetadataFooterFn?: Function, rootDir?: string}} [options]
|
|
147
|
+
* @returns {{ok: boolean, error?: string}}
|
|
148
|
+
*/
|
|
126
149
|
function postStaticReviewComment(slug, message, options = {}) {
|
|
127
150
|
const log = options.log || fmt.log.plain;
|
|
128
151
|
const error = options.error || fmt.log.plainError;
|
|
@@ -181,9 +204,13 @@ function postStaticReviewComment(slug, message, options = {}) {
|
|
|
181
204
|
* Inspects git diff for changed files, checks the final checkpoint for
|
|
182
205
|
* a Goal Check table, and returns findings.
|
|
183
206
|
*/
|
|
207
|
+
/**
|
|
208
|
+
* @param {string} slug
|
|
209
|
+
* @param {{log?: Function, findMissionDir?: Function, findCheckpoints?: Function, readFileSync?: Function, run?: Function, resolveWorktree?: Function, missionPath?: string, rootDir?: string}} [options]
|
|
210
|
+
* @returns {{ok: boolean, findings: string[]}}
|
|
211
|
+
*/
|
|
184
212
|
function performStaticReview(slug, options = {}) {
|
|
185
213
|
const log = options.log || fmt.log.plain;
|
|
186
|
-
const error = options.error || fmt.log.plainError;
|
|
187
214
|
const findMissionDirFn = options.findMissionDir || findMissionDir;
|
|
188
215
|
const findCheckpointsFn = options.findCheckpoints || findCheckpoints;
|
|
189
216
|
const readFileSyncFn = options.readFileSync || fs.readFileSync;
|
|
@@ -233,12 +260,12 @@ function performStaticReview(slug, options = {}) {
|
|
|
233
260
|
let pastHeader = false;
|
|
234
261
|
for (const line of linesAfterHeader) {
|
|
235
262
|
const trimmed = line.trim();
|
|
236
|
-
if (trimmed === '') continue;
|
|
263
|
+
if (trimmed === '') {continue;}
|
|
237
264
|
if (!pastHeader && headerPattern.test(trimmed)) {
|
|
238
265
|
pastHeader = true;
|
|
239
266
|
continue;
|
|
240
267
|
}
|
|
241
|
-
if (separatorPattern.test(trimmed)) continue;
|
|
268
|
+
if (separatorPattern.test(trimmed)) {continue;}
|
|
242
269
|
if (pastHeader && evidenceLinePattern.test(trimmed)) {
|
|
243
270
|
foundEvidence = true;
|
|
244
271
|
break;
|
|
@@ -252,14 +279,14 @@ function performStaticReview(slug, options = {}) {
|
|
|
252
279
|
}
|
|
253
280
|
}
|
|
254
281
|
} catch (err) {
|
|
255
|
-
findings.push(`Could not read final checkpoint ${path.basename(finalCheckpoint)}: ${err.message}`);
|
|
282
|
+
findings.push(`Could not read final checkpoint ${path.basename(finalCheckpoint)}: ${/** @type {Error} */(err).message}`);
|
|
256
283
|
}
|
|
257
284
|
|
|
258
285
|
// Inspect git diff for changed files
|
|
259
286
|
const baseBranch = require('../core/mission-utils').getPrimaryBranch(rootDir);
|
|
260
287
|
const diffResult = runFn('git', ['diff', `${baseBranch}..HEAD`, '--name-only'], { cwd: rootDir });
|
|
261
288
|
if (diffResult.status === 0 && diffResult.stdout) {
|
|
262
|
-
const changedFiles = diffResult.stdout.trim().split('\n').filter(f => f.trim());
|
|
289
|
+
const changedFiles = diffResult.stdout.trim().split('\n').filter((/** @type {string} */ f) => f.trim());
|
|
263
290
|
const missionDirPrefix = path.relative(rootDir, missionBaseDir(rootDir)).split(path.sep).join('/') + '/';
|
|
264
291
|
log(`Changed files in branch: ${changedFiles.join(', ')}`);
|
|
265
292
|
// Check for unexpected areas — missions may legitimately touch many surfaces.
|
|
@@ -270,11 +297,11 @@ function performStaticReview(slug, options = {}) {
|
|
|
270
297
|
'.agents/', '.github/', '.vscode/', '.graphifyignore',
|
|
271
298
|
];
|
|
272
299
|
const knownExtensions = ['.sh', '.csv', '.json', '.yaml', '.yml', '.toml', '.lock', '.cfg', '.ini', '.env', '.txt', '.properties', '.sql', '.css', '.html', '.xml', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.pdf', '.tar', '.gz', '.zip'];
|
|
273
|
-
|
|
274
|
-
!knownAreas.some(area => f.startsWith(area)) &&
|
|
300
|
+
const unexpectedFiles = changedFiles.filter((/** @type {string} */ f) =>
|
|
301
|
+
!knownAreas.some((/** @type {string} */ area) => f.startsWith(area)) &&
|
|
275
302
|
!f.startsWith(missionDirPrefix) &&
|
|
276
303
|
!f.endsWith('.md') &&
|
|
277
|
-
!knownExtensions.some(ext => f.toLowerCase().endsWith(ext))
|
|
304
|
+
!knownExtensions.some((/** @type {string} */ ext) => f.toLowerCase().endsWith(ext))
|
|
278
305
|
);
|
|
279
306
|
if (unexpectedFiles.length > 0) {
|
|
280
307
|
log(fmt.status('WARN', `Changed files outside known areas (may be intentional): ${unexpectedFiles.join(', ')}`));
|
|
@@ -292,6 +319,12 @@ function performStaticReview(slug, options = {}) {
|
|
|
292
319
|
// Command: verifyReview
|
|
293
320
|
// ============================================================================
|
|
294
321
|
|
|
322
|
+
/**
|
|
323
|
+
* @param {string} slug
|
|
324
|
+
* @param {boolean|string} skipGate
|
|
325
|
+
* @param {{log?: Function, error?: Function, exit?: Function, resolveWorktreeFn?: Function, findMissionDirFn?: Function, getCurrentBranchFn?: Function, resolveTaskFileFn?: Function, getPrStatusFn?: Function, getTaskStatusFn?: Function, toVirtualFn?: Function, findMissionAreaFn?: Function, runFn?: Function, getAcceptanceCriteriaFn?: Function, formatMatrixSummaryFn?: Function, buildAutonomousReviewMatrixFn?: Function, readReviewStateFn?: Function, isReviewProviderEnabledFn?: Function, isForgejoReviewEnabledFn?: Function, cwdFn?: Function, missionPath?: string, skipGate?: boolean}} [options]
|
|
326
|
+
* @returns {void}
|
|
327
|
+
*/
|
|
295
328
|
function verifyReview(slug, skipGate, options = {}) {
|
|
296
329
|
const log = options.log || fmt.log.plain;
|
|
297
330
|
const error = options.error || fmt.log.plainError;
|
|
@@ -419,12 +452,12 @@ function verifyReview(slug, skipGate, options = {}) {
|
|
|
419
452
|
if (acceptanceCriteria.length === 0) {
|
|
420
453
|
log(' - No Acceptance Criteria found on the Backlog task.');
|
|
421
454
|
} else {
|
|
422
|
-
acceptanceCriteria.forEach(line => log(` ${line}`));
|
|
455
|
+
acceptanceCriteria.forEach((/** @type {string} */ line) => log(` ${line}`));
|
|
423
456
|
}
|
|
424
457
|
}
|
|
425
458
|
|
|
426
459
|
log(fmt.status('INFO', 'Autonomous review runtime matrix:'));
|
|
427
|
-
|
|
460
|
+
formatMatrixSummaryFn(buildAutonomousReviewMatrixFn()).forEach((/** @type {string} */ line) => log(line));
|
|
428
461
|
|
|
429
462
|
// Show persisted reviewer state if present
|
|
430
463
|
const persisted = readReviewStateFn(slug);
|
|
@@ -449,13 +482,18 @@ function verifyReview(slug, skipGate, options = {}) {
|
|
|
449
482
|
// Command: submitForReview
|
|
450
483
|
// ============================================================================
|
|
451
484
|
|
|
485
|
+
/**
|
|
486
|
+
* @param {string} slug
|
|
487
|
+
* @param {boolean|string} skipGate
|
|
488
|
+
* @param {{exit?: Function, resolveTaskFileFn?: Function, getTaskImplementerFn?: Function, resolveWorktreeFn?: Function, performHandoffFn?: Function, readReviewStateFn?: Function, isReviewProviderEnabledFn?: Function, isForgejoReviewEnabledFn?: Function, log?: Function}} [options]
|
|
489
|
+
* @returns {Promise<void>}
|
|
490
|
+
*/
|
|
452
491
|
async function submitForReview(slug, skipGate, options = {}) {
|
|
453
492
|
const exit = options.exit || process.exit;
|
|
454
493
|
const resolveTaskFileFn = options.resolveTaskFileFn || resolveTaskFile;
|
|
455
494
|
const getTaskImplementerFn = options.getTaskImplementerFn || getTaskImplementer;
|
|
456
495
|
const resolveWorktreeFn = options.resolveWorktreeFn || resolveWorktree;
|
|
457
496
|
const performHandoffFn = options.performHandoffFn || performHandoff;
|
|
458
|
-
const resolveReviewUserFn = options.resolveReviewUserFn || options.resolveForgejoUserFn || resolveReviewUser;
|
|
459
497
|
const readReviewStateFn = options.readReviewStateFn || readReviewState;
|
|
460
498
|
const isReviewProviderEnabledFn = options.isReviewProviderEnabledFn || options.isForgejoReviewEnabledFn || isProviderEnabled;
|
|
461
499
|
const log = options.log || fmt.log.plain;
|
|
@@ -498,6 +536,11 @@ async function submitForReview(slug, skipGate, options = {}) {
|
|
|
498
536
|
// Command: readComments
|
|
499
537
|
// ============================================================================
|
|
500
538
|
|
|
539
|
+
/**
|
|
540
|
+
* @param {string} slug
|
|
541
|
+
* @param {{log?: Function, error?: Function, exit?: Function, readTokenFn?: Function, readReviewStateFn?: Function, resolveReviewUserFn?: Function, resolveForgejoUserFn?: Function, getCommentsFn?: Function, readAllEventsFn?: Function, isReviewProviderEnabledFn?: Function, isForgejoReviewEnabledFn?: Function, worktree?: string}} [options]
|
|
542
|
+
* @returns {Promise<void>}
|
|
543
|
+
*/
|
|
501
544
|
async function readComments(slug, options = {}) {
|
|
502
545
|
const log = options.log || fmt.log.plain;
|
|
503
546
|
const error = options.error || fmt.log.plainError;
|
|
@@ -522,7 +565,7 @@ async function readComments(slug, options = {}) {
|
|
|
522
565
|
}
|
|
523
566
|
for (const e of events) {
|
|
524
567
|
let label = e.event_type || 'event';
|
|
525
|
-
if (e.round
|
|
568
|
+
if (e.round !== null) {label += ` round ${e.round}`;}
|
|
526
569
|
log(`--- ${label} | ${e.actor || 'unknown'} (${e.timestamp || e.fileCreated || ''}) ---`);
|
|
527
570
|
log(e.content || '(no body)');
|
|
528
571
|
log('');
|
|
@@ -564,7 +607,7 @@ async function readComments(slug, options = {}) {
|
|
|
564
607
|
|
|
565
608
|
for (const c of comments) {
|
|
566
609
|
let label = c.kind;
|
|
567
|
-
if (c.location) label += ` ${c.location}`;
|
|
610
|
+
if (c.location) {label += ` ${c.location}`;}
|
|
568
611
|
log(`--- ${label} | ${c.user} (${c.created}) ---`);
|
|
569
612
|
log(c.body || '(no body)');
|
|
570
613
|
log('');
|
|
@@ -575,6 +618,11 @@ async function readComments(slug, options = {}) {
|
|
|
575
618
|
// Command: pushRound
|
|
576
619
|
// ============================================================================
|
|
577
620
|
|
|
621
|
+
/**
|
|
622
|
+
* @param {string} slug
|
|
623
|
+
* @param {{log?: Function, error?: Function, exit?: Function, resolveWorktreeFn?: Function, resolveTaskFileFn?: Function, getTaskImplementerFn?: Function, transitionTaskFn?: Function, resolveReviewUserFn?: Function, resolveForgejoUserFn?: Function, isReviewProviderEnabledFn?: Function, isForgejoReviewEnabledFn?: Function, readTokenFn?: Function, readReviewStateFn?: Function, createPrFn?: Function, bootstrapReviewSurfaceFn?: Function, resolveReviewAdapterFn?: Function, cwdFn?: Function, force?: boolean}} [options]
|
|
624
|
+
* @returns {Promise<void>}
|
|
625
|
+
*/
|
|
578
626
|
async function pushRound(slug, options = {}) {
|
|
579
627
|
const log = options.log || fmt.log.plain;
|
|
580
628
|
const error = options.error || fmt.log.plainError;
|
|
@@ -677,6 +725,11 @@ async function pushRound(slug, options = {}) {
|
|
|
677
725
|
// Command: showReviewStatus
|
|
678
726
|
// ============================================================================
|
|
679
727
|
|
|
728
|
+
/**
|
|
729
|
+
* @param {string} slug
|
|
730
|
+
* @param {{log?: Function, readReviewStateFn?: Function, resolveWorktreeFn?: Function}} [options]
|
|
731
|
+
* @returns {void}
|
|
732
|
+
*/
|
|
680
733
|
function showReviewStatus(slug, options = {}) {
|
|
681
734
|
const log = options.log || fmt.log.plain;
|
|
682
735
|
const readReviewStateFn = options.readReviewStateFn || readReviewState;
|
|
@@ -712,6 +765,12 @@ function showReviewStatus(slug, options = {}) {
|
|
|
712
765
|
// Command: commentRound
|
|
713
766
|
// ============================================================================
|
|
714
767
|
|
|
768
|
+
/**
|
|
769
|
+
* @param {string} slug
|
|
770
|
+
* @param {string} message
|
|
771
|
+
* @param {{log?: Function, error?: Function, exit?: Function, readReviewStateFn?: Function, writeReviewStateFn?: Function, resolveReviewUserFn?: Function, resolveForgejoUserFn?: Function, rootDir?: string, readTokenFn?: Function, postCommentFn?: Function, buildMetadataFooterFn?: Function}} [options]
|
|
772
|
+
* @returns {void}
|
|
773
|
+
*/
|
|
715
774
|
function commentRound(slug, message, options = {}) {
|
|
716
775
|
const log = options.log || fmt.log.plain;
|
|
717
776
|
const error = options.error || fmt.log.plainError;
|
|
@@ -733,7 +792,7 @@ function commentRound(slug, message, options = {}) {
|
|
|
733
792
|
reviewIdentity = resolveReviewUserFn(reviewIdentity);
|
|
734
793
|
const result = postWorkflowComment(slug, message, {
|
|
735
794
|
rootDir,
|
|
736
|
-
reviewIdentity,
|
|
795
|
+
reviewIdentity: reviewIdentity || undefined,
|
|
737
796
|
readTokenFn: options.readTokenFn,
|
|
738
797
|
postCommentFn: options.postCommentFn,
|
|
739
798
|
buildMetadataFooterFn: options.buildMetadataFooterFn,
|
|
@@ -756,10 +815,14 @@ function commentRound(slug, message, options = {}) {
|
|
|
756
815
|
// Command: consumeArtifacts
|
|
757
816
|
// ============================================================================
|
|
758
817
|
|
|
818
|
+
/**
|
|
819
|
+
* @param {string} slug
|
|
820
|
+
* @param {{log?: Function, error?: Function, resolveWorktreeFn?: Function, transitionTaskFn?: Function, consumeReviewerArtifactsFn?: Function, resolveTaskFileFn?: Function, getTaskAssigneeFn?: Function, getTaskStatusFn?: Function, resolveArtifactDirFn?: Function, readReviewStateFn?: Function, createEventFn?: Function, readArtifactFn?: Function, deleteArtifactFn?: Function}} [options]
|
|
821
|
+
* @returns {Promise<{ok: boolean, consumed: boolean, reviewState?: string|null}>}
|
|
822
|
+
*/
|
|
759
823
|
async function consumeArtifacts(slug, options = {}) {
|
|
760
824
|
const log = options.log || fmt.log.plain;
|
|
761
825
|
const error = options.error || fmt.log.plainError;
|
|
762
|
-
const exit = options.exit || process.exit;
|
|
763
826
|
const resolveWorktreeFn = options.resolveWorktreeFn || resolveWorktree;
|
|
764
827
|
const transitionTaskFn = options.transitionTaskFn || transitionTask;
|
|
765
828
|
const consumeReviewerArtifactsFn = options.consumeReviewerArtifactsFn || consumeReviewerArtifacts;
|
|
@@ -853,6 +916,13 @@ async function consumeArtifacts(slug, options = {}) {
|
|
|
853
916
|
// Command: submitReviewRound
|
|
854
917
|
// ============================================================================
|
|
855
918
|
|
|
919
|
+
/**
|
|
920
|
+
* @param {string} slug
|
|
921
|
+
* @param {string} outcome
|
|
922
|
+
* @param {string} message
|
|
923
|
+
* @param {{log?: Function, error?: Function, exit?: Function, transitionTaskFn?: Function, readReviewStateFn?: Function, writeReviewStateFn?: Function, isReviewProviderEnabledFn?: Function, isForgejoReviewEnabledFn?: Function, resolveReviewUserFn?: Function, resolveForgejoUserFn?: Function, resolveTaskFileFn?: Function, getTaskStatusFn?: Function, worktree?: string, readTokenFn?: Function, postReviewFn?: Function, getPrAuthorFn?: Function, createEventFn?: Function, buildMetadataFooterFn?: Function}} [options]
|
|
924
|
+
* @returns {void}
|
|
925
|
+
*/
|
|
856
926
|
function submitReviewRound(slug, outcome, message, options = {}) {
|
|
857
927
|
const log = options.log || fmt.log.plain;
|
|
858
928
|
const error = options.error || fmt.log.plainError;
|
|
@@ -910,7 +980,7 @@ function submitReviewRound(slug, outcome, message, options = {}) {
|
|
|
910
980
|
'request-changes': 'review',
|
|
911
981
|
'comment': 'review'
|
|
912
982
|
};
|
|
913
|
-
const backlogStatus = backlogStatusMap[outcome];
|
|
983
|
+
const backlogStatus = backlogStatusMap[/** @type {keyof typeof backlogStatusMap} */ (outcome)];
|
|
914
984
|
if (backlogStatus && !transitionTaskFn(slug, backlogStatus, { rootDir: worktree, log })) {
|
|
915
985
|
log(fmt.status('WARN', `Could not transition backlog task ${slug} to ${backlogStatus}.`));
|
|
916
986
|
}
|
|
@@ -932,7 +1002,7 @@ function submitReviewRound(slug, outcome, message, options = {}) {
|
|
|
932
1002
|
reviewIdentity = resolveReviewUserFn(reviewIdentity);
|
|
933
1003
|
const result = postWorkflowReview(slug, outcome, message, {
|
|
934
1004
|
worktree,
|
|
935
|
-
reviewIdentity,
|
|
1005
|
+
reviewIdentity: reviewIdentity || undefined,
|
|
936
1006
|
readTokenFn: options.readTokenFn,
|
|
937
1007
|
postReviewFn: options.postReviewFn,
|
|
938
1008
|
getPrAuthorFn: options.getPrAuthorFn,
|
|
@@ -996,6 +1066,11 @@ function submitReviewRound(slug, outcome, message, options = {}) {
|
|
|
996
1066
|
// Command: closeMissionPr
|
|
997
1067
|
// ============================================================================
|
|
998
1068
|
|
|
1069
|
+
/**
|
|
1070
|
+
* @param {string} slug
|
|
1071
|
+
* @param {{log?: Function, error?: Function, exit?: Function, readTokenFn?: Function, readReviewStateFn?: Function, resolveReviewUserFn?: Function, resolveForgejoUserFn?: Function, closePrFn?: Function, worktree?: string}} [options]
|
|
1072
|
+
* @returns {Promise<void>}
|
|
1073
|
+
*/
|
|
999
1074
|
async function closeMissionPr(slug, options = {}) {
|
|
1000
1075
|
const log = options.log || fmt.log.plain;
|
|
1001
1076
|
const error = options.error || fmt.log.plainError;
|
|
@@ -1037,6 +1112,12 @@ async function closeMissionPr(slug, options = {}) {
|
|
|
1037
1112
|
// Event CLI Handlers
|
|
1038
1113
|
// ============================================================================
|
|
1039
1114
|
|
|
1115
|
+
/**
|
|
1116
|
+
* @param {string} slug
|
|
1117
|
+
* @param {string[]} args
|
|
1118
|
+
* @param {{log?: Function, error?: Function, exit?: Function, resolveWorktreeFn?: Function, readReviewStateFn?: Function}} [options]
|
|
1119
|
+
* @returns {void}
|
|
1120
|
+
*/
|
|
1040
1121
|
function createEventHandler(slug, args, options = {}) {
|
|
1041
1122
|
const log = options.log || fmt.log.plain;
|
|
1042
1123
|
const error = options.error || fmt.log.plainError;
|
|
@@ -1071,14 +1152,14 @@ function createEventHandler(slug, args, options = {}) {
|
|
|
1071
1152
|
content = fs.readFileSync(inputFile, 'utf8');
|
|
1072
1153
|
log(fmt.status('INFO', `Read event content from: ${inputFile}`));
|
|
1073
1154
|
} catch (err) {
|
|
1074
|
-
error(fmt.status('FAIL', `Failed to read input file: ${err.message}`));
|
|
1155
|
+
error(fmt.status('FAIL', `Failed to read input file: ${/** @type {Error} */(err).message}`));
|
|
1075
1156
|
exit(1);
|
|
1076
1157
|
return;
|
|
1077
1158
|
}
|
|
1078
1159
|
}
|
|
1079
1160
|
|
|
1080
1161
|
const round = roundRaw ? parseInt(roundRaw, 10) : undefined;
|
|
1081
|
-
if (roundRaw && isNaN(round)) {
|
|
1162
|
+
if (roundRaw && (isNaN(/** @type {number} */ (round))) ) {
|
|
1082
1163
|
error(fmt.status('FAIL', `--round must be a number, got "${roundRaw}"`));
|
|
1083
1164
|
exit(1);
|
|
1084
1165
|
return;
|
|
@@ -1087,12 +1168,13 @@ function createEventHandler(slug, args, options = {}) {
|
|
|
1087
1168
|
const worktree = resolveWorktreeFn(slug) || process.cwd();
|
|
1088
1169
|
|
|
1089
1170
|
// Build params
|
|
1171
|
+
/** @type {{content: string, round?: number, phase?: string, actor?: string, disposition?: string, verdict?: string, fixedItems?: string[], pushedBackItems?: string[], parkedItems?: string[], blockedReason?: string}} */
|
|
1090
1172
|
const params = { content };
|
|
1091
|
-
if (round !== undefined) params.round = round;
|
|
1092
|
-
if (phase) params.phase = phase;
|
|
1093
|
-
if (actor) params.actor = actor;
|
|
1094
|
-
if (disposition) params.disposition = disposition;
|
|
1095
|
-
if (verdict) params.verdict = verdict;
|
|
1173
|
+
if (round !== undefined) {params.round = round;}
|
|
1174
|
+
if (phase) {params.phase = phase;}
|
|
1175
|
+
if (actor) {params.actor = actor;}
|
|
1176
|
+
if (disposition) {params.disposition = disposition;}
|
|
1177
|
+
if (verdict) {params.verdict = verdict;}
|
|
1096
1178
|
|
|
1097
1179
|
const readReviewStateFn = options.readReviewStateFn || readReviewState;
|
|
1098
1180
|
const { identityUser: stateReviewIdentity } = resolveReviewIdentity(slug, worktree, {
|
|
@@ -1152,6 +1234,12 @@ function createEventHandler(slug, args, options = {}) {
|
|
|
1152
1234
|
log(fmt.status('PASS', `Created review event at: ${result.path}`));
|
|
1153
1235
|
}
|
|
1154
1236
|
|
|
1237
|
+
/**
|
|
1238
|
+
* @param {string} slug
|
|
1239
|
+
* @param {string[]} args
|
|
1240
|
+
* @param {{log?: Function, error?: Function, exit?: Function, resolveWorktreeFn?: Function}} [options]
|
|
1241
|
+
* @returns {void}
|
|
1242
|
+
*/
|
|
1155
1243
|
function importLegacyHandler(slug, args, options = {}) {
|
|
1156
1244
|
const log = options.log || fmt.log.plain;
|
|
1157
1245
|
const error = options.error || fmt.log.plainError;
|
|
@@ -1161,21 +1249,27 @@ function importLegacyHandler(slug, args, options = {}) {
|
|
|
1161
1249
|
const tmpDir = flagValue(args, '--tmp-dir') || process.env.WORKFLOW_TMP_DIR || os.tmpdir();
|
|
1162
1250
|
const worktree = resolveWorktreeFn(slug) || process.cwd();
|
|
1163
1251
|
|
|
1164
|
-
const result = importAllLegacyArtifacts(slug, tmpDir,
|
|
1252
|
+
const result = importAllLegacyArtifacts(slug, { tmpDir, worktree, log, error });
|
|
1165
1253
|
|
|
1254
|
+
const errors = result.errors || [];
|
|
1166
1255
|
if (!result.ok) {
|
|
1167
|
-
error(fmt.status('FAIL', `Legacy import failed: ${
|
|
1256
|
+
error(fmt.status('FAIL', `Legacy import failed: ${errors.join(', ')}`));
|
|
1168
1257
|
exit(1);
|
|
1169
1258
|
return;
|
|
1170
1259
|
}
|
|
1171
1260
|
|
|
1172
|
-
log(fmt.status('PASS', `Imported ${result.imported} legacy artifacts for ${slug}`));
|
|
1261
|
+
log(fmt.status('PASS', `Imported ${result.imported ? result.imported.length : 0} legacy artifacts for ${slug}`));
|
|
1173
1262
|
}
|
|
1174
1263
|
|
|
1175
1264
|
// ============================================================================
|
|
1176
1265
|
// Main Dispatcher
|
|
1177
1266
|
// ============================================================================
|
|
1178
1267
|
|
|
1268
|
+
/**
|
|
1269
|
+
* @param {string[]} args
|
|
1270
|
+
* @param {{inferSlugFn?: Function, log?: Function, error?: Function, exit?: Function, verifyReviewFn?: Function, submitForReviewFn?: Function, consumeArtifactsFn?: Function, pushRoundFn?: Function, readCommentsFn?: Function, commentRoundFn?: Function, submitReviewRoundFn?: Function, closeMissionPrFn?: Function, startReviewLoopFn?: Function, recordStageStatsSafeFn?: Function, startAgentFn?: Function, resolveTaskFileFn?: Function, getTaskImplementerFn?: Function, getPrStatusFn?: Function, readReviewStateFn?: Function, performStaticReviewFn?: Function, postStaticReviewCommentFn?: Function, resolveWorktreeFn?: Function, run?: Function, missionPath?: string}} [options]
|
|
1271
|
+
* @returns {Promise<void>}
|
|
1272
|
+
*/
|
|
1179
1273
|
async function review(args, options = {}) {
|
|
1180
1274
|
const inferSlugFn = options.inferSlugFn || inferSlug;
|
|
1181
1275
|
const log = options.log || fmt.log.plain;
|
|
@@ -1322,7 +1416,7 @@ const recordStageStatsSafeFn = options.recordStageStatsSafeFn || require('./revi
|
|
|
1322
1416
|
log(fmt.status('WARN', `Static review found ${staticResult.findings.length} finding(s) but the implementer could not be resolved for ${fmt.slug(slug)} — not re-launching the implementer and not starting the review loop.`));
|
|
1323
1417
|
} else {
|
|
1324
1418
|
log(`\nStatic review found ${staticResult.findings.length} finding(s). Re-launching implementer (${fmt.agent(implementer)}) with a targeted fix prompt...`);
|
|
1325
|
-
const findingLines = staticResult.findings.map(f => `- ${f}`).join('\n');
|
|
1419
|
+
const findingLines = staticResult.findings.map((/** @type {string} */ f) => `- ${f}`).join('\n');
|
|
1326
1420
|
const prompt = `Static review of your mission branch found the following issue(s). Fix them and commit, then stop:\n${findingLines}`;
|
|
1327
1421
|
await startAgentFn('active', { prompt, worktree: worktreeForStatic, agent: implementer, slug });
|
|
1328
1422
|
}
|