atris 3.74.26 → 3.74.27

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/commands/task.js CHANGED
@@ -575,6 +575,20 @@ function landingFlags(args) {
575
575
  };
576
576
  }
577
577
 
578
+ // The pull request and commit a flight merged on its own, so the job record
579
+ // names what landed. Only a merge commit makes it count.
580
+ function readyMergeFlags(args) {
581
+ const commit = textFlag(args, ['--merge-commit']);
582
+ if (!commit) return null;
583
+ return {
584
+ merge_commit: commit,
585
+ number: textFlag(args, ['--pr-number']),
586
+ url: textFlag(args, ['--pr-url']),
587
+ merged_at: textFlag(args, ['--merged-at']),
588
+ self_landed: hasFlag(args, '--self-landed'),
589
+ };
590
+ }
591
+
578
592
  function normalizedLandingSentence(value) {
579
593
  return String(value || '').replace(/\s+/g, ' ').trim();
580
594
  }
@@ -9650,6 +9664,7 @@ function cmdReady(args) {
9650
9664
  const lesson = flag(args, '--lesson') || '';
9651
9665
  const nextTaskInput = normalizeReviewNextTaskInput(typeof flag(args, '--next') === 'string' ? flag(args, '--next') : '');
9652
9666
  const landing = landingFlags(args);
9667
+ const merge = readyMergeFlags(args);
9653
9668
  guardExplicitActor('atris task ready', flag(args, '--as'));
9654
9669
  const actor = String(flag(args, '--as') || DEFAULT_OWNER);
9655
9670
  const resultFields = {
@@ -9697,6 +9712,7 @@ function cmdReady(args) {
9697
9712
  nextTask: nextTaskInput.nextTask,
9698
9713
  resultTrace: resultTrace && resultTrace.trace,
9699
9714
  landing,
9715
+ merge,
9700
9716
  result: resultSentence,
9701
9717
  });
9702
9718
  if (!result.ready) {
@@ -1125,7 +1125,7 @@ function shipWorktree(args) {
1125
1125
  }
1126
1126
  // A merge queue accepts the request before merging. Deleting its head
1127
1127
  // here closes the PR and cancels that request, so confirm the outcome.
1128
- const confirmation = spawnSync('gh', ['pr', 'view', mergeRef, '--json', 'state,mergedAt'], { cwd: root, encoding: 'utf8' });
1128
+ const confirmation = spawnSync('gh', ['pr', 'view', mergeRef, '--json', 'state,mergedAt,number,url,mergeCommit'], { cwd: root, encoding: 'utf8' });
1129
1129
  if (confirmation.status !== 0) {
1130
1130
  throw new Error('could not confirm the merge; the source branch was kept');
1131
1131
  }
@@ -1141,6 +1141,10 @@ function shipWorktree(args) {
1141
1141
  throw new Error('the change was not merged; the source branch was kept');
1142
1142
  }
1143
1143
  console.log('merge: merged');
1144
+ // One machine-readable line, so a caller can save which pull request
1145
+ // and commit landed. The rating only counts a landing it can point at.
1146
+ const record = shipMergeRecord(state);
1147
+ if (record) console.log(`merge: record ${JSON.stringify(record)}`);
1144
1148
  const deleted = runGit(['push', 'origin', '--delete', branch], { cwd: root, check: false });
1145
1149
  if (deleted.status === 0) {
1146
1150
  console.log(`merge: remote branch deleted ${branch}`);
@@ -1165,6 +1169,17 @@ function shipWorktree(args) {
1165
1169
  return 0;
1166
1170
  }
1167
1171
 
1172
+ // The landed pull request from `gh pr view --json`, or null without a merge commit.
1173
+ function shipMergeRecord(state) {
1174
+ const commit = state && state.mergeCommit && typeof state.mergeCommit === 'object' ? state.mergeCommit.oid : null;
1175
+ if (typeof commit !== 'string' || !/^[0-9a-f]{7,64}$/i.test(commit)) return null;
1176
+ const record = { merge_commit: commit };
1177
+ if (Number.isInteger(state.number) && state.number > 0) record.number = state.number;
1178
+ if (typeof state.url === 'string' && state.url) record.url = state.url;
1179
+ if (typeof state.mergedAt === 'string' && state.mergedAt) record.merged_at = state.mergedAt;
1180
+ return record;
1181
+ }
1182
+
1168
1183
  function guard(args) {
1169
1184
  const root = repoRoot();
1170
1185
  const worktrees = listWorktrees(root);
package/lib/fleet.js CHANGED
@@ -1483,6 +1483,28 @@ function shipFailureDetail(shipped, { head = 400, tail = 500 } = {}) {
1483
1483
  return `${cause}${headTail(combined.trim(), head, tail)}`;
1484
1484
  }
1485
1485
 
1486
+ // The `merge: record {...}` line worktree ship prints after a confirmed merge.
1487
+ function shipMergeRecord(stdout) {
1488
+ const line = String(stdout || '').match(/^merge: record (\{.*\})\s*$/m);
1489
+ if (!line) return null;
1490
+ try {
1491
+ const record = JSON.parse(line[1]);
1492
+ return record && typeof record === 'object' && typeof record.merge_commit === 'string' ? record : null;
1493
+ } catch {
1494
+ return null;
1495
+ }
1496
+ }
1497
+
1498
+ // task ready flags that name the pull request a flight merged by itself.
1499
+ function landedMergeFlags(merge) {
1500
+ if (!merge || !merge.merge_commit) return [];
1501
+ const flags = ['--merge-commit', merge.merge_commit, '--self-landed'];
1502
+ if (merge.number) flags.push('--pr-number', String(merge.number));
1503
+ if (merge.url) flags.push('--pr-url', merge.url);
1504
+ if (merge.merged_at) flags.push('--merged-at', merge.merged_at);
1505
+ return flags;
1506
+ }
1507
+
1486
1508
  function fleetShipArgs(entry, check, targetRef = resolveDispatchLandTarget(entry.worktreePath || process.cwd())) {
1487
1509
  return [
1488
1510
  'worktree', 'ship',
@@ -1629,6 +1651,8 @@ module.exports = {
1629
1651
  runInReapedProcessGroup,
1630
1652
  shipWithRetry,
1631
1653
  shipFailureDetail,
1654
+ shipMergeRecord,
1655
+ landedMergeFlags,
1632
1656
  get FLEET_CAPABLE() { return FLEET_CAPABLE; },
1633
1657
  get DISPATCH_CAPABLE() { return DISPATCH_CAPABLE; },
1634
1658
  get runFleetFlight() { return runFleetFlight; },
@@ -3272,7 +3296,7 @@ async function runFleetFlight({
3272
3296
  if (shipped.status !== 0 || !/done: worktree shipped/.test(shipped.stdout)) {
3273
3297
  return { ok: false, stage: 'ship', detail: shipFailureDetail(shipped) };
3274
3298
  }
3275
- return { ok: true, stage: 'shipped' };
3299
+ return { ok: true, stage: 'shipped', merge: shipMergeRecord(shipped.stdout) };
3276
3300
  });
3277
3301
 
3278
3302
  for (const { entry, result } of results) {
@@ -3309,6 +3333,7 @@ async function runFleetFlight({
3309
3333
  'task', 'ready', String(entry.task.display_id),
3310
3334
  '--proof', `Built by ${activeEngine} engine in fleet flight.${restaffProof} Landed via worktree ship gate (rebase-before-ship, verify re-run). Receipt saved at ${path.relative(root, receiptPath)}. Report tail: ${headTail(String(result.report || '').replace(/\n/g, ' '))}`,
3311
3335
  '--result', 'Operators can now review fleet-shipped work faster because the worktree was verified before landing.',
3336
+ ...landedMergeFlags(landed.merge),
3312
3337
  '--as', `fleet-${activeEngine}`,
3313
3338
  ], { flight, landedRow, worktree: entry.worktreePath, save: () => atomicWriteFlightReceipt(flight, receiptPath) });
3314
3339
  if (!reviewRecorded) {
@@ -3795,6 +3820,7 @@ async function runDispatchFlight({
3795
3820
  return {
3796
3821
  ok: true,
3797
3822
  stage: 'shipped',
3823
+ merge: shipMergeRecord(shipped.stdout),
3798
3824
  check,
3799
3825
  verifyOutput: `${verified.stdout}${verified.stderr}`,
3800
3826
  verifier_result: verifierResult,
@@ -4195,6 +4221,7 @@ async function runDispatchFlight({
4195
4221
  '--result', 'Operators can now review engine-built work faster because dispatch reran the verifier before landing.',
4196
4222
  '--checked', `${landed.check} passed before landing`,
4197
4223
  '--tested', 'The requested behavior passed its declared verifier.',
4224
+ ...landedMergeFlags(landed.merge),
4198
4225
  '--as', readyActor,
4199
4226
  ], { flight, landedRow, worktree: entry.worktreePath, save: () => writeDispatchReceipt(flight, receiptPath, { ids, reviewOnly: proofOnly, enforceRemoteBoundary }) });
4200
4227
  if (!reviewRecorded) {
package/lib/gm-season.js CHANGED
@@ -301,7 +301,7 @@ function parseJson(text) {
301
301
  }
302
302
 
303
303
  function landedBy(row, payload) {
304
- if (payload && payload.auto_accepted === true) return 'autoland';
304
+ if (payload && (payload.auto_accepted === true || payload.self_landed === true)) return 'autoland';
305
305
  return /auto/i.test(String(row.actor || '')) ? 'autoland' : 'you';
306
306
  }
307
307
 
package/lib/task-db.js CHANGED
@@ -1044,7 +1044,23 @@ function recordReviewPassActor(metadata, claimedBy, rawActor) {
1044
1044
  return { builder: builder || null, independent };
1045
1045
  }
1046
1046
 
1047
- function readyTask(db, { id, actor, proof, lesson, nextTask, landing = {}, resultTrace, result: resultSentence, reason, workRoot }) {
1047
+ // A merge the caller confirmed. The commit id is required; the other fields
1048
+ // are kept only when they look right.
1049
+ function cleanMergeRecord(merge) {
1050
+ if (!merge || typeof merge !== 'object') return null;
1051
+ const commit = String(merge.merge_commit || '').trim();
1052
+ if (!/^[0-9a-f]{7,64}$/i.test(commit)) return null;
1053
+ const record = { merge_commit: commit.toLowerCase() };
1054
+ const number = Number(merge.number);
1055
+ if (Number.isInteger(number) && number > 0) record.number = number;
1056
+ const url = String(merge.url || '').trim();
1057
+ if (/^https?:\/\//i.test(url)) record.url = url;
1058
+ const mergedAt = String(merge.merged_at || '').trim();
1059
+ if (mergedAt && Number.isFinite(Date.parse(mergedAt))) record.merged_at = mergedAt;
1060
+ return record;
1061
+ }
1062
+
1063
+ function readyTask(db, { id, actor, proof, lesson, nextTask, landing = {}, merge = null, resultTrace, result: resultSentence, reason, workRoot }) {
1048
1064
  if (!id) throw new Error('id required');
1049
1065
  const text = String(proof || '').trim();
1050
1066
  if (!text) throw new Error('proof required');
@@ -1073,6 +1089,8 @@ function readyTask(db, { id, actor, proof, lesson, nextTask, landing = {}, resul
1073
1089
  const cleaned = cleanLandingText(landingInput[key]);
1074
1090
  if (cleaned) metadata[`landing_${key}`] = cleaned;
1075
1091
  }
1092
+ const mergeRecord = cleanMergeRecord(merge);
1093
+ if (mergeRecord) metadata.merge_commit = mergeRecord.merge_commit;
1076
1094
  // Only the builder's own ready names the work's commit. A reviewer or the
1077
1095
  // autoland verifier re-runs the check from its own checkout; stamping that
1078
1096
  // checkout's HEAD would name a commit the builder never made. No actor, or
@@ -1121,6 +1139,12 @@ function readyTask(db, { id, actor, proof, lesson, nextTask, landing = {}, resul
1121
1139
  tested: metadata.landing_tested || null,
1122
1140
  decision: metadata.landing_decision || null,
1123
1141
  };
1142
+ if (mergeRecord) {
1143
+ payload.landing.merge_commit = mergeRecord.merge_commit;
1144
+ payload.pull_request = { state: 'merged', ...mergeRecord };
1145
+ // Landed by the flight with no person in the loop.
1146
+ if (merge.self_landed === true) payload.self_landed = true;
1147
+ }
1124
1148
  const event = appendTaskEvent(db, {
1125
1149
  taskId: id,
1126
1150
  workspaceRoot: updated.workspace_root,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.74.26",
3
+ "version": "3.74.27",
4
4
  "description": "you say what you want in plain words. atris builds it, checks it, and shows you proof.",
5
5
  "mcpName": "io.github.atrislabs/atris",
6
6
  "main": "bin/atris.js",