@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.
Files changed (68) hide show
  1. package/config/integration-pipelines.json +9 -0
  2. package/config/workflow.config.schema.json +12 -0
  3. package/docs/adr/0032-mission-refinement-state-and-usage-budget-signals.md +10 -8
  4. package/docs/adr/0036-mission-sizing-and-dependency-wave-heuristics.md +14 -12
  5. package/docs/adr/0041-integration-pipeline-gates.md +1 -1
  6. package/docs/adr/0047-per-mission-change-size-budget.md +161 -0
  7. package/docs/adr/index.md +1 -0
  8. package/docs/use-cases.md +44 -0
  9. package/lib/agents/agents.js +149 -100
  10. package/lib/agents/claude-telemetry.js +14 -10
  11. package/lib/agents/claude.js +32 -11
  12. package/lib/agents/codex-telemetry.js +32 -19
  13. package/lib/agents/codex.js +36 -11
  14. package/lib/agents/limit-hit.js +42 -25
  15. package/lib/agents/mistral-telemetry.js +1 -1
  16. package/lib/agents/mistral.js +13 -3
  17. package/lib/agents/opencode-export.js +11 -5
  18. package/lib/agents/opencode-telemetry.js +75 -90
  19. package/lib/agents/opencode.js +86 -29
  20. package/lib/agents/stage-telemetry.js +3 -7
  21. package/lib/commands/active.js +139 -94
  22. package/lib/commands/checkpoint.js +3 -1
  23. package/lib/commands/config.js +5 -3
  24. package/lib/commands/coverage-gate.js +17 -10
  25. package/lib/commands/diff.js +11 -5
  26. package/lib/commands/draft.js +95 -55
  27. package/lib/commands/handoff.js +253 -65
  28. package/lib/commands/integrate.js +288 -188
  29. package/lib/commands/mission-start.js +39 -34
  30. package/lib/commands/rebase.js +32 -21
  31. package/lib/commands/repair-handoff.js +21 -18
  32. package/lib/commands/resolve-conflict.js +5 -2
  33. package/lib/commands/review.js +1 -0
  34. package/lib/commands/setup-review.js +1 -0
  35. package/lib/commands/stats-backfill.js +50 -41
  36. package/lib/commands/stats.js +653 -204
  37. package/lib/commands/status.js +33 -28
  38. package/lib/core/fmt.js +58 -15
  39. package/lib/core/git.js +7 -5
  40. package/lib/core/gitignore.js +6 -4
  41. package/lib/core/mission-utils.js +161 -88
  42. package/lib/core/nels.js +199 -0
  43. package/lib/core/persistent-data-migration.js +55 -23
  44. package/lib/core/product-config.js +44 -23
  45. package/lib/core/runtime-matrix.js +15 -2
  46. package/lib/core/spawn-tee.js +40 -13
  47. package/lib/core/state-map.js +32 -13
  48. package/lib/core/storage.js +19 -5
  49. package/lib/core/subagent-limit.js +28 -0
  50. package/lib/core/verification.js +29 -12
  51. package/lib/review/rebase.js +11 -3
  52. package/lib/review/review-adapter.js +93 -6
  53. package/lib/review/review-artifacts.js +89 -23
  54. package/lib/review/review-commands.js +139 -45
  55. package/lib/review/review-events.js +103 -52
  56. package/lib/review/review-loop.js +117 -87
  57. package/lib/review/review-polling.js +25 -5
  58. package/lib/review/review-prompts.js +36 -5
  59. package/lib/review/review-state.js +39 -15
  60. package/lib/tools/backlog.js +162 -71
  61. package/lib/tools/forgejo.js +290 -129
  62. package/lib/tools/gatekeeper.js +15 -0
  63. package/lib/tools/redgreen.js +220 -0
  64. package/lib/tools/sessions.js +12 -5
  65. package/lib/tools/setup-review.js +170 -70
  66. package/package.json +11 -3
  67. package/prompts/draft.md +6 -0
  68. 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 { git, run, getCurrentBranch } = require('../core/git');
11
- const { findMissionDir, findMissionArea, resolveWorktree, inferSlug, updateGraphifyKnowledgeGraph, findCheckpoints, isMissionArtifact, isWorkflowGeneratedArtifact, missionBranchName, missionBaseDir } = require('../core/mission-utils');
12
- const { resolveTaskFile, getTaskStatus, getAcceptanceCriteria, setTaskStatus, setTaskAssignee, enforceTaskAssignee, getTaskAssignee, getTaskImplementer, reportTaskResolution, transitionTask } = require('../tools/backlog');
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, getLatestReviewForPr, getLatestDispositionForPr, postComment, postReview, createPr, providerAvailable, getComments, closePr, resolveReviewUser, isProviderEnabled } = require('./review-adapter');
15
+ const { getPrStatus, readToken, postComment, createPr, getComments, closePr, resolveReviewUser, isProviderEnabled } = require('./review-adapter');
15
16
  const { buildAutonomousReviewMatrix, formatMatrixSummary } = require('../core/runtime-matrix');
16
- const { buildCompactReviewPrompt, buildCompactActOnReviewPrompt, buildReviewPrompt, buildActOnReviewPrompt } = require('./review-prompts');
17
- const { ReviewState, readReviewState, writeReviewState, resetReviewState, VALID_PHASES, resolveReviewIdentity } = require('./review-state');
18
- const { createEvent, importLegacyArtifact, importAllLegacyArtifacts, consumeHumanNotes,
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 { assertAgentSupported, workflowLauncherStatus, startAgent, eligibleAgentsForStep, selectAgent } = require('../agents/agents');
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, readArtifactFile, deleteArtifactFile, normalizeReviewVerdict, normalizeDisposition, postWorkflowComment, postWorkflowReview, consumeReviewerArtifacts, consumeImplementerArtifacts } = require('./review-artifacts');
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
- const unexpectedFiles = changedFiles.filter(f =>
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
- formatMatrixSummaryFn(buildAutonomousReviewMatrixFn()).forEach(line => log(line));
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 != null) label += ` round ${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, { worktree, log, error });
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: ${result.error}`));
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
  }