@kbediako/codex-orchestrator 0.1.27 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -213,6 +213,9 @@ Repo internals, development workflows, and deeper architecture notes (contributo
213
213
  - `docs/README.md`
214
214
  - `docs/diagnostics-prompt-guide.md` (first-run diagnostics prompt + expected outputs)
215
215
  - `docs/guides/collab-vs-mcp.md` (agent-first decision guide)
216
+ - `docs/guides/rlm-recursion-v2.md` (RLM recursion reference)
217
+ - `docs/guides/cloud-mode-preflight.md` (cloud-mode preflight + fallback guidance)
218
+ - `docs/guides/review-artifacts.md` (where `npm run review` writes prompt/output artifacts)
216
219
 
217
220
  ## RLM benchmark graphs
218
221
 
@@ -1234,6 +1234,11 @@ Commands:
1234
1234
  version | --version
1235
1235
 
1236
1236
  help Show this message.
1237
+
1238
+ Notes:
1239
+ RLM recursion guidance: docs/guides/rlm-recursion-v2.md
1240
+ Cloud-mode preflight/fallback guide: docs/guides/cloud-mode-preflight.md
1241
+ Review artifacts guide: docs/guides/review-artifacts.md
1237
1242
  `);
1238
1243
  }
1239
1244
  void main();
@@ -1296,6 +1301,9 @@ Subcommands:
1296
1301
  Examples:
1297
1302
  codex-orchestrator pr watch-merge --pr 211 --dry-run --quiet-minutes 10
1298
1303
  codex-orchestrator pr watch-merge --pr 211 --auto-merge --merge-method squash
1304
+
1305
+ Guide:
1306
+ Review artifacts (prompt + output log paths): docs/guides/review-artifacts.md
1299
1307
  `);
1300
1308
  }
1301
1309
  function printRlmHelp() {
@@ -9,6 +9,9 @@ const DEFAULT_MERGE_METHOD = 'squash';
9
9
  const CHECKRUN_PASS_CONCLUSIONS = new Set(['SUCCESS', 'SKIPPED', 'NEUTRAL']);
10
10
  const STATUS_CONTEXT_PASS_STATES = new Set(['SUCCESS']);
11
11
  const STATUS_CONTEXT_PENDING_STATES = new Set(['EXPECTED', 'PENDING']);
12
+ const REQUIRED_BUCKET_PASS = new Set(['pass']);
13
+ const REQUIRED_BUCKET_PENDING = new Set(['pending']);
14
+ const REQUIRED_BUCKET_FAILED = new Set(['fail', 'cancel', 'skipping']);
12
15
  const MERGEABLE_STATES = new Set(['CLEAN', 'HAS_HOOKS', 'UNSTABLE']);
13
16
  const BLOCKED_REVIEW_DECISIONS = new Set(['CHANGES_REQUESTED', 'REVIEW_REQUIRED']);
14
17
  const DO_NOT_MERGE_LABEL = /do[\s_-]*not[\s_-]*merge/i;
@@ -67,6 +70,9 @@ query($owner:String!, $repo:String!, $number:Int!) {
67
70
  function normalizeEnum(value) {
68
71
  return typeof value === 'string' ? value.trim().toUpperCase() : '';
69
72
  }
73
+ function normalizeBucket(value) {
74
+ return typeof value === 'string' ? value.trim().toLowerCase() : '';
75
+ }
70
76
  function formatDuration(ms) {
71
77
  if (ms <= 0) {
72
78
  return '0s';
@@ -299,7 +305,80 @@ function summarizeChecks(nodes) {
299
305
  }
300
306
  return summary;
301
307
  }
302
- function buildStatusSnapshot(response) {
308
+ export function summarizeRequiredChecks(entries) {
309
+ const summary = {
310
+ total: 0,
311
+ successCount: 0,
312
+ pending: [],
313
+ failed: []
314
+ };
315
+ if (!Array.isArray(entries)) {
316
+ return summary;
317
+ }
318
+ for (const entry of entries) {
319
+ if (!entry || typeof entry !== 'object') {
320
+ continue;
321
+ }
322
+ summary.total += 1;
323
+ const name = typeof entry.name === 'string' && entry.name.trim() ? entry.name.trim() : 'required-check';
324
+ const bucket = normalizeBucket(entry.bucket);
325
+ const state = normalizeEnum(entry.state);
326
+ const detailsUrl = typeof entry.link === 'string' ? entry.link : null;
327
+ if (REQUIRED_BUCKET_PASS.has(bucket)) {
328
+ summary.successCount += 1;
329
+ continue;
330
+ }
331
+ if (REQUIRED_BUCKET_PENDING.has(bucket)) {
332
+ summary.pending.push(name);
333
+ continue;
334
+ }
335
+ if (REQUIRED_BUCKET_FAILED.has(bucket)) {
336
+ summary.failed.push({
337
+ name,
338
+ state: state || bucket.toUpperCase() || 'UNKNOWN',
339
+ detailsUrl
340
+ });
341
+ continue;
342
+ }
343
+ if (STATUS_CONTEXT_PENDING_STATES.has(state)) {
344
+ summary.pending.push(name);
345
+ continue;
346
+ }
347
+ if (STATUS_CONTEXT_PASS_STATES.has(state)) {
348
+ summary.successCount += 1;
349
+ continue;
350
+ }
351
+ summary.failed.push({
352
+ name,
353
+ state: state || 'UNKNOWN',
354
+ detailsUrl
355
+ });
356
+ }
357
+ return summary;
358
+ }
359
+ function hasRequiredChecksSummary(summary) {
360
+ return Boolean(summary && typeof summary === 'object' && summary.total > 0);
361
+ }
362
+ export function resolveRequiredChecksSummary(freshSummary, previousSummary, fetchError = false) {
363
+ if (hasRequiredChecksSummary(freshSummary)) {
364
+ return freshSummary;
365
+ }
366
+ if (fetchError && hasRequiredChecksSummary(previousSummary)) {
367
+ return previousSummary;
368
+ }
369
+ return null;
370
+ }
371
+ export function resolveCachedRequiredChecksSummary(previousCache, currentHeadOid) {
372
+ if (!previousCache || typeof previousCache !== 'object') {
373
+ return null;
374
+ }
375
+ const cachedHeadOid = typeof previousCache.headOid === 'string' ? previousCache.headOid : null;
376
+ if (!cachedHeadOid || !currentHeadOid || cachedHeadOid !== currentHeadOid) {
377
+ return null;
378
+ }
379
+ return hasRequiredChecksSummary(previousCache.summary) ? previousCache.summary : null;
380
+ }
381
+ export function buildStatusSnapshot(response, requiredChecks = null) {
303
382
  const pr = response?.data?.repository?.pullRequest;
304
383
  if (!pr) {
305
384
  throw new Error('GraphQL response missing pullRequest payload.');
@@ -315,6 +394,9 @@ function buildStatusSnapshot(response) {
315
394
  const contexts = pr.commits?.nodes?.[0]?.commit?.statusCheckRollup?.contexts?.nodes;
316
395
  const checkNodes = Array.isArray(contexts) ? contexts : [];
317
396
  const checks = summarizeChecks(checkNodes);
397
+ const requiredCheckSummary = requiredChecks && typeof requiredChecks === 'object' && requiredChecks.total > 0 ? requiredChecks : null;
398
+ const gateChecks = requiredCheckSummary ?? checks;
399
+ const gateChecksSource = requiredCheckSummary ? 'required' : 'rollup';
318
400
  const reviewDecision = normalizeEnum(pr.reviewDecision);
319
401
  const mergeStateStatus = normalizeEnum(pr.mergeStateStatus);
320
402
  const state = normalizeEnum(pr.state);
@@ -329,8 +411,10 @@ function buildStatusSnapshot(response) {
329
411
  if (hasDoNotMergeLabel) {
330
412
  gateReasons.push('label:do-not-merge');
331
413
  }
332
- if (checks.pending.length > 0) {
333
- gateReasons.push(`checks_pending=${checks.pending.length}`);
414
+ if (gateChecks.pending.length > 0) {
415
+ gateReasons.push(gateChecksSource === 'required'
416
+ ? `required_checks_pending=${gateChecks.pending.length}`
417
+ : `checks_pending=${gateChecks.pending.length}`);
334
418
  }
335
419
  if (!MERGEABLE_STATES.has(mergeStateStatus)) {
336
420
  gateReasons.push(`merge_state=${mergeStateStatus || 'UNKNOWN'}`);
@@ -354,31 +438,70 @@ function buildStatusSnapshot(response) {
354
438
  hasDoNotMergeLabel,
355
439
  unresolvedThreadCount,
356
440
  checks,
441
+ requiredChecks: requiredCheckSummary,
442
+ gateChecksSource,
357
443
  gateReasons,
358
444
  readyToMerge: gateReasons.length === 0,
359
445
  headOid: pr.commits?.nodes?.[0]?.commit?.oid || null
360
446
  };
361
447
  }
362
448
  function formatStatusLine(snapshot, quietRemainingMs) {
449
+ const requiredChecks = snapshot.requiredChecks;
363
450
  const failedNames = snapshot.checks.failed.map((item) => `${item.name}:${item.state}`).join(', ') || '-';
364
451
  const pendingNames = snapshot.checks.pending.join(', ') || '-';
452
+ const requiredFailedNames = requiredChecks
453
+ ? requiredChecks.failed.map((item) => `${item.name}:${item.state}`).join(', ') || '-'
454
+ : '-';
455
+ const requiredPendingNames = requiredChecks ? requiredChecks.pending.join(', ') || '-' : '-';
365
456
  const reasons = snapshot.gateReasons.join(', ') || 'none';
366
457
  return [
367
458
  `PR #${snapshot.number}`,
368
459
  `state=${snapshot.state}`,
369
460
  `merge_state=${snapshot.mergeStateStatus}`,
370
461
  `review=${snapshot.reviewDecision}`,
462
+ `gate_checks=${snapshot.gateChecksSource}`,
371
463
  `checks_ok=${snapshot.checks.successCount}/${snapshot.checks.total}`,
372
464
  `checks_pending=${snapshot.checks.pending.length}`,
373
465
  `checks_failed=${snapshot.checks.failed.length}`,
466
+ `required_checks_ok=${requiredChecks ? `${requiredChecks.successCount}/${requiredChecks.total}` : 'n/a'}`,
467
+ `required_checks_pending=${requiredChecks ? requiredChecks.pending.length : 'n/a'}`,
468
+ `required_checks_failed=${requiredChecks ? requiredChecks.failed.length : 'n/a'}`,
374
469
  `unresolved_threads=${snapshot.unresolvedThreadCount}`,
375
470
  `quiet_remaining=${formatDuration(quietRemainingMs)}`,
376
471
  `blocked_by=${reasons}`,
377
472
  `pending=[${pendingNames}]`,
378
- `failed=[${failedNames}]`
473
+ `failed=[${failedNames}]`,
474
+ `required_pending=[${requiredPendingNames}]`,
475
+ `required_failed=[${requiredFailedNames}]`
379
476
  ].join(' | ');
380
477
  }
381
- async function fetchSnapshot(owner, repo, prNumber) {
478
+ async function fetchRequiredChecks(owner, repo, prNumber) {
479
+ try {
480
+ const result = await runGhJson([
481
+ 'pr',
482
+ 'checks',
483
+ String(prNumber),
484
+ '--required',
485
+ '--json',
486
+ 'name,state,link,bucket',
487
+ '--repo',
488
+ `${owner}/${repo}`
489
+ ]);
490
+ const entries = Array.isArray(result) ? result : [];
491
+ const summary = summarizeRequiredChecks(entries);
492
+ return {
493
+ summary: summary.total > 0 ? summary : null,
494
+ fetchError: false
495
+ };
496
+ }
497
+ catch {
498
+ return {
499
+ summary: null,
500
+ fetchError: true
501
+ };
502
+ }
503
+ }
504
+ async function fetchSnapshot(owner, repo, prNumber, previousRequiredChecksCache = null) {
382
505
  const response = await runGhJson([
383
506
  'api',
384
507
  'graphql',
@@ -391,7 +514,19 @@ async function fetchSnapshot(owner, repo, prNumber) {
391
514
  '-F',
392
515
  `number=${prNumber}`
393
516
  ]);
394
- return buildStatusSnapshot(response);
517
+ const currentHeadOid = response?.data?.repository?.pullRequest?.commits?.nodes?.[0]?.commit?.oid || null;
518
+ const previousRequiredChecks = resolveCachedRequiredChecksSummary(previousRequiredChecksCache, currentHeadOid);
519
+ const requiredChecksResult = await fetchRequiredChecks(owner, repo, prNumber);
520
+ const requiredChecks = resolveRequiredChecksSummary(requiredChecksResult.summary, previousRequiredChecks, requiredChecksResult.fetchError);
521
+ return {
522
+ snapshot: buildStatusSnapshot(response, requiredChecks),
523
+ requiredChecksForNextPoll: requiredChecks
524
+ ? {
525
+ headOid: currentHeadOid,
526
+ summary: requiredChecks
527
+ }
528
+ : null
529
+ };
395
530
  }
396
531
  async function attemptMerge({ prNumber, mergeMethod, deleteBranch, headOid }) {
397
532
  // gh pr merge has no --yes flag; rely on non-interactive stdio + explicit merge method.
@@ -472,10 +607,13 @@ async function runPrWatchMergeOrThrow(argv, options) {
472
607
  let quietWindowAnchorUpdatedAt = null;
473
608
  let quietWindowAnchorHeadOid = null;
474
609
  let lastMergeAttemptHeadOid = null;
610
+ let requiredChecksForNextPollCache = null;
475
611
  while (Date.now() <= deadline) {
476
612
  let snapshot;
477
613
  try {
478
- snapshot = await fetchSnapshot(owner, repo, prNumber);
614
+ const fetched = await fetchSnapshot(owner, repo, prNumber, requiredChecksForNextPollCache);
615
+ snapshot = fetched.snapshot;
616
+ requiredChecksForNextPollCache = fetched.requiredChecksForNextPoll;
479
617
  }
480
618
  catch (error) {
481
619
  log(`Polling error: ${error instanceof Error ? error.message : String(error)} (retrying).`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbediako/codex-orchestrator",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",