@h-rig/cli 0.0.6-alpha.3 → 0.0.6-alpha.5

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/dist/bin/rig.js CHANGED
@@ -3446,6 +3446,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
3446
3446
  const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
3447
3447
  const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
3448
3448
  const next = {
3449
+ ...existing ?? {},
3449
3450
  runId: input.runId,
3450
3451
  projectRoot,
3451
3452
  workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
@@ -6118,6 +6119,7 @@ import {
6118
6119
  listOpenEpics,
6119
6120
  resolveDefaultEpic,
6120
6121
  runResume,
6122
+ runRestart,
6121
6123
  runStatus,
6122
6124
  runStop,
6123
6125
  startRun,
@@ -6226,6 +6228,17 @@ async function attachRunOperatorView(context, input) {
6226
6228
  }
6227
6229
 
6228
6230
  // packages/cli/src/commands/run.ts
6231
+ function normalizeRemoteRunDetails(payload) {
6232
+ const run = payload.run;
6233
+ if (!run || typeof run !== "object" || Array.isArray(run))
6234
+ return null;
6235
+ return {
6236
+ ...run,
6237
+ ...Array.isArray(payload.timeline) ? { timeline: payload.timeline } : {},
6238
+ ...Array.isArray(payload.approvals) ? { approvals: payload.approvals } : {},
6239
+ ...Array.isArray(payload.userInputs) ? { userInputs: payload.userInputs } : {}
6240
+ };
6241
+ }
6229
6242
  function shouldPromptForEpicSelection(context, command, promptEpic, noEpicPrompt) {
6230
6243
  if (noEpicPrompt) {
6231
6244
  return false;
@@ -6364,7 +6377,7 @@ async function executeRun(context, args) {
6364
6377
  if (!run.value) {
6365
6378
  throw new CliError2("run show requires --run <id>.");
6366
6379
  }
6367
- const record = readAuthorityRun4(context.projectRoot, run.value);
6380
+ const record = readAuthorityRun4(context.projectRoot, run.value) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, run.value).catch(() => ({})));
6368
6381
  if (!record) {
6369
6382
  throw new CliError2(`Run not found: ${run.value}`, 2);
6370
6383
  }
@@ -6545,6 +6558,20 @@ async function executeRun(context, args) {
6545
6558
  }
6546
6559
  return { ok: true, group: "run", command, details: resumed };
6547
6560
  }
6561
+ case "restart": {
6562
+ requireNoExtraArgs(rest, "bun run rig run restart");
6563
+ if (context.dryRun) {
6564
+ if (context.outputMode === "text") {
6565
+ console.log("[dry-run] rig run restart");
6566
+ }
6567
+ return { ok: true, group: "run", command };
6568
+ }
6569
+ const restarted = await runRestart(context.projectRoot, runtimeContext);
6570
+ if (context.outputMode === "text") {
6571
+ console.log(`Restarted run: ${restarted.runId}`);
6572
+ }
6573
+ return { ok: true, group: "run", command, details: restarted };
6574
+ }
6548
6575
  case "stop": {
6549
6576
  const runOption = takeOption(rest, "--run");
6550
6577
  const positionalRunId = runOption.rest.length > 0 ? runOption.rest[0] : undefined;
@@ -7945,6 +7972,9 @@ function stringArrayField(record, key) {
7945
7972
  }
7946
7973
  async function executeRigOwnedTaskRun(context, input) {
7947
7974
  const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
7975
+ const existingRunRecord = readAuthorityRun5(context.projectRoot, input.runId);
7976
+ const resumeMode = process.env.RIG_RUN_RESUME === "1";
7977
+ const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
7948
7978
  const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
7949
7979
  let prompt = buildRunPrompt({
7950
7980
  projectRoot: context.projectRoot,
@@ -8000,14 +8030,14 @@ async function executeRigOwnedTaskRun(context, input) {
8000
8030
  taskId: runtimeTaskId,
8001
8031
  createdAt: startedAt,
8002
8032
  runtimeAdapter: input.runtimeAdapter,
8003
- status: "created"
8033
+ status: resumeMode ? "preparing" : "created"
8004
8034
  });
8005
8035
  patchAuthorityRun(context.projectRoot, input.runId, {
8006
8036
  status: "preparing",
8007
8037
  startedAt,
8008
8038
  completedAt: null,
8009
8039
  errorText: null,
8010
- artifactRoot: null,
8040
+ artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
8011
8041
  runtimeAdapter: input.runtimeAdapter,
8012
8042
  runtimeMode: input.runtimeMode,
8013
8043
  interactionMode: input.interactionMode,
@@ -8023,9 +8053,9 @@ async function executeRigOwnedTaskRun(context, input) {
8023
8053
  detail: input.taskId ?? input.title ?? runtimeTaskId
8024
8054
  });
8025
8055
  appendRunLog(context.projectRoot, input.runId, {
8026
- id: `log:${input.runId}:start`,
8027
- title: "Rig task run started",
8028
- detail: input.taskId ?? input.title ?? runtimeTaskId,
8056
+ id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
8057
+ title: resumeMode ? "Rig task run resumed" : "Rig task run started",
8058
+ detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
8029
8059
  tone: "info",
8030
8060
  status: "preparing",
8031
8061
  createdAt: startedAt
@@ -8103,11 +8133,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8103
8133
  let reviewAction;
8104
8134
  let verificationStarted = false;
8105
8135
  let reviewStarted = false;
8106
- let latestRuntimeWorkspace = null;
8107
- let latestSessionDir = null;
8108
- let latestLogsDir = null;
8136
+ let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
8137
+ let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve22(existingRunRecord.sessionPath, "..") : null;
8138
+ let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
8109
8139
  let latestProviderCommand = null;
8110
- let latestRuntimeBranch = null;
8140
+ let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
8111
8141
  let snapshotSidecarPromise = null;
8112
8142
  let dirtyBaselineApplied = false;
8113
8143
  const childEnv = {
@@ -8125,7 +8155,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8125
8155
  RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
8126
8156
  RIG_SERVER_RUN_ID: input.runId
8127
8157
  },
8128
- ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
8158
+ ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {},
8159
+ ...resumeMode ? {
8160
+ RIG_RUN_RESUME: "1",
8161
+ RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
8162
+ } : {}
8129
8163
  };
8130
8164
  Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
8131
8165
  Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
@@ -8431,7 +8465,36 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8431
8465
  let reviewFailureDetail = null;
8432
8466
  const stderrLines = [];
8433
8467
  const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
8434
- for (let attempt = 1;attempt <= maxAttempts; attempt += 1) {
8468
+ if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
8469
+ appendRunLog(context.projectRoot, input.runId, {
8470
+ id: `log:${input.runId}:resume-closeout-phase`,
8471
+ title: "Resume continuing from closeout phase",
8472
+ detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
8473
+ tone: "info",
8474
+ status: resumePreviousStatus,
8475
+ createdAt: new Date().toISOString()
8476
+ });
8477
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
8478
+ exit = { code: 0, signal: null };
8479
+ } else if (resumeMode && latestRuntimeWorkspace) {
8480
+ const acceptedArtifactState = readTaskRunAcceptedArtifactState({
8481
+ taskId: input.taskId ?? runtimeTaskId,
8482
+ workspaceDir: latestRuntimeWorkspace
8483
+ });
8484
+ if (acceptedArtifactState.accepted) {
8485
+ appendRunLog(context.projectRoot, input.runId, {
8486
+ id: `log:${input.runId}:resume-accepted-artifacts`,
8487
+ title: "Resume found accepted artifacts; continuing closeout",
8488
+ detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
8489
+ tone: "info",
8490
+ status: "validating",
8491
+ createdAt: new Date().toISOString()
8492
+ });
8493
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
8494
+ exit = { code: 0, signal: null };
8495
+ }
8496
+ }
8497
+ for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
8435
8498
  const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
8436
8499
  const child = spawn2(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
8437
8500
  cwd: context.projectRoot,
@@ -67,6 +67,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
67
67
  const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
68
68
  const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
69
69
  const next = {
70
+ ...existing ?? {},
70
71
  runId: input.runId,
71
72
  projectRoot,
72
73
  workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
@@ -126,6 +126,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
126
126
  const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
127
127
  const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
128
128
  const next = {
129
+ ...existing ?? {},
129
130
  runId: input.runId,
130
131
  projectRoot,
131
132
  workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
@@ -64,6 +64,7 @@ import {
64
64
  listOpenEpics,
65
65
  resolveDefaultEpic,
66
66
  runResume,
67
+ runRestart,
67
68
  runStatus,
68
69
  runStop,
69
70
  startRun,
@@ -406,6 +407,17 @@ async function attachRunOperatorView(context, input) {
406
407
  }
407
408
 
408
409
  // packages/cli/src/commands/run.ts
410
+ function normalizeRemoteRunDetails(payload) {
411
+ const run = payload.run;
412
+ if (!run || typeof run !== "object" || Array.isArray(run))
413
+ return null;
414
+ return {
415
+ ...run,
416
+ ...Array.isArray(payload.timeline) ? { timeline: payload.timeline } : {},
417
+ ...Array.isArray(payload.approvals) ? { approvals: payload.approvals } : {},
418
+ ...Array.isArray(payload.userInputs) ? { userInputs: payload.userInputs } : {}
419
+ };
420
+ }
409
421
  function shouldPromptForEpicSelection(context, command, promptEpic, noEpicPrompt) {
410
422
  if (noEpicPrompt) {
411
423
  return false;
@@ -544,7 +556,7 @@ async function executeRun(context, args) {
544
556
  if (!run.value) {
545
557
  throw new CliError2("run show requires --run <id>.");
546
558
  }
547
- const record = readAuthorityRun(context.projectRoot, run.value);
559
+ const record = readAuthorityRun(context.projectRoot, run.value) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, run.value).catch(() => ({})));
548
560
  if (!record) {
549
561
  throw new CliError2(`Run not found: ${run.value}`, 2);
550
562
  }
@@ -725,6 +737,20 @@ async function executeRun(context, args) {
725
737
  }
726
738
  return { ok: true, group: "run", command, details: resumed };
727
739
  }
740
+ case "restart": {
741
+ requireNoExtraArgs(rest, "bun run rig run restart");
742
+ if (context.dryRun) {
743
+ if (context.outputMode === "text") {
744
+ console.log("[dry-run] rig run restart");
745
+ }
746
+ return { ok: true, group: "run", command };
747
+ }
748
+ const restarted = await runRestart(context.projectRoot, runtimeContext);
749
+ if (context.outputMode === "text") {
750
+ console.log(`Restarted run: ${restarted.runId}`);
751
+ }
752
+ return { ok: true, group: "run", command, details: restarted };
753
+ }
728
754
  case "stop": {
729
755
  const runOption = takeOption(rest, "--run");
730
756
  const positionalRunId = runOption.rest.length > 0 ? runOption.rest[0] : undefined;
@@ -135,6 +135,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
135
135
  const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
136
136
  const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
137
137
  const next = {
138
+ ...existing ?? {},
138
139
  runId: input.runId,
139
140
  projectRoot,
140
141
  workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
@@ -1081,6 +1082,9 @@ function stringArrayField(record, key) {
1081
1082
  }
1082
1083
  async function executeRigOwnedTaskRun(context, input) {
1083
1084
  const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
1085
+ const existingRunRecord = readAuthorityRun3(context.projectRoot, input.runId);
1086
+ const resumeMode = process.env.RIG_RUN_RESUME === "1";
1087
+ const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
1084
1088
  const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
1085
1089
  let prompt = buildRunPrompt({
1086
1090
  projectRoot: context.projectRoot,
@@ -1136,14 +1140,14 @@ async function executeRigOwnedTaskRun(context, input) {
1136
1140
  taskId: runtimeTaskId,
1137
1141
  createdAt: startedAt,
1138
1142
  runtimeAdapter: input.runtimeAdapter,
1139
- status: "created"
1143
+ status: resumeMode ? "preparing" : "created"
1140
1144
  });
1141
1145
  patchAuthorityRun(context.projectRoot, input.runId, {
1142
1146
  status: "preparing",
1143
1147
  startedAt,
1144
1148
  completedAt: null,
1145
1149
  errorText: null,
1146
- artifactRoot: null,
1150
+ artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
1147
1151
  runtimeAdapter: input.runtimeAdapter,
1148
1152
  runtimeMode: input.runtimeMode,
1149
1153
  interactionMode: input.interactionMode,
@@ -1159,9 +1163,9 @@ async function executeRigOwnedTaskRun(context, input) {
1159
1163
  detail: input.taskId ?? input.title ?? runtimeTaskId
1160
1164
  });
1161
1165
  appendRunLog(context.projectRoot, input.runId, {
1162
- id: `log:${input.runId}:start`,
1163
- title: "Rig task run started",
1164
- detail: input.taskId ?? input.title ?? runtimeTaskId,
1166
+ id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
1167
+ title: resumeMode ? "Rig task run resumed" : "Rig task run started",
1168
+ detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
1165
1169
  tone: "info",
1166
1170
  status: "preparing",
1167
1171
  createdAt: startedAt
@@ -1239,11 +1243,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1239
1243
  let reviewAction;
1240
1244
  let verificationStarted = false;
1241
1245
  let reviewStarted = false;
1242
- let latestRuntimeWorkspace = null;
1243
- let latestSessionDir = null;
1244
- let latestLogsDir = null;
1246
+ let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
1247
+ let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve4(existingRunRecord.sessionPath, "..") : null;
1248
+ let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
1245
1249
  let latestProviderCommand = null;
1246
- let latestRuntimeBranch = null;
1250
+ let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
1247
1251
  let snapshotSidecarPromise = null;
1248
1252
  let dirtyBaselineApplied = false;
1249
1253
  const childEnv = {
@@ -1261,7 +1265,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1261
1265
  RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
1262
1266
  RIG_SERVER_RUN_ID: input.runId
1263
1267
  },
1264
- ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
1268
+ ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {},
1269
+ ...resumeMode ? {
1270
+ RIG_RUN_RESUME: "1",
1271
+ RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
1272
+ } : {}
1265
1273
  };
1266
1274
  Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
1267
1275
  Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
@@ -1567,7 +1575,36 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
1567
1575
  let reviewFailureDetail = null;
1568
1576
  const stderrLines = [];
1569
1577
  const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
1570
- for (let attempt = 1;attempt <= maxAttempts; attempt += 1) {
1578
+ if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
1579
+ appendRunLog(context.projectRoot, input.runId, {
1580
+ id: `log:${input.runId}:resume-closeout-phase`,
1581
+ title: "Resume continuing from closeout phase",
1582
+ detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
1583
+ tone: "info",
1584
+ status: resumePreviousStatus,
1585
+ createdAt: new Date().toISOString()
1586
+ });
1587
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
1588
+ exit = { code: 0, signal: null };
1589
+ } else if (resumeMode && latestRuntimeWorkspace) {
1590
+ const acceptedArtifactState = readTaskRunAcceptedArtifactState({
1591
+ taskId: input.taskId ?? runtimeTaskId,
1592
+ workspaceDir: latestRuntimeWorkspace
1593
+ });
1594
+ if (acceptedArtifactState.accepted) {
1595
+ appendRunLog(context.projectRoot, input.runId, {
1596
+ id: `log:${input.runId}:resume-accepted-artifacts`,
1597
+ title: "Resume found accepted artifacts; continuing closeout",
1598
+ detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
1599
+ tone: "info",
1600
+ status: "validating",
1601
+ createdAt: new Date().toISOString()
1602
+ });
1603
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
1604
+ exit = { code: 0, signal: null };
1605
+ }
1606
+ }
1607
+ for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
1571
1608
  const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
1572
1609
  const child = spawn(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
1573
1610
  cwd: context.projectRoot,
@@ -3240,6 +3240,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
3240
3240
  const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
3241
3241
  const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
3242
3242
  const next = {
3243
+ ...existing ?? {},
3243
3244
  runId: input.runId,
3244
3245
  projectRoot,
3245
3246
  workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
@@ -5912,6 +5913,7 @@ import {
5912
5913
  listOpenEpics,
5913
5914
  resolveDefaultEpic,
5914
5915
  runResume,
5916
+ runRestart,
5915
5917
  runStatus,
5916
5918
  runStop,
5917
5919
  startRun,
@@ -6020,6 +6022,17 @@ async function attachRunOperatorView(context, input) {
6020
6022
  }
6021
6023
 
6022
6024
  // packages/cli/src/commands/run.ts
6025
+ function normalizeRemoteRunDetails(payload) {
6026
+ const run = payload.run;
6027
+ if (!run || typeof run !== "object" || Array.isArray(run))
6028
+ return null;
6029
+ return {
6030
+ ...run,
6031
+ ...Array.isArray(payload.timeline) ? { timeline: payload.timeline } : {},
6032
+ ...Array.isArray(payload.approvals) ? { approvals: payload.approvals } : {},
6033
+ ...Array.isArray(payload.userInputs) ? { userInputs: payload.userInputs } : {}
6034
+ };
6035
+ }
6023
6036
  function shouldPromptForEpicSelection(context, command, promptEpic, noEpicPrompt) {
6024
6037
  if (noEpicPrompt) {
6025
6038
  return false;
@@ -6158,7 +6171,7 @@ async function executeRun(context, args) {
6158
6171
  if (!run.value) {
6159
6172
  throw new CliError2("run show requires --run <id>.");
6160
6173
  }
6161
- const record = readAuthorityRun4(context.projectRoot, run.value);
6174
+ const record = readAuthorityRun4(context.projectRoot, run.value) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, run.value).catch(() => ({})));
6162
6175
  if (!record) {
6163
6176
  throw new CliError2(`Run not found: ${run.value}`, 2);
6164
6177
  }
@@ -6339,6 +6352,20 @@ async function executeRun(context, args) {
6339
6352
  }
6340
6353
  return { ok: true, group: "run", command, details: resumed };
6341
6354
  }
6355
+ case "restart": {
6356
+ requireNoExtraArgs(rest, "bun run rig run restart");
6357
+ if (context.dryRun) {
6358
+ if (context.outputMode === "text") {
6359
+ console.log("[dry-run] rig run restart");
6360
+ }
6361
+ return { ok: true, group: "run", command };
6362
+ }
6363
+ const restarted = await runRestart(context.projectRoot, runtimeContext);
6364
+ if (context.outputMode === "text") {
6365
+ console.log(`Restarted run: ${restarted.runId}`);
6366
+ }
6367
+ return { ok: true, group: "run", command, details: restarted };
6368
+ }
6342
6369
  case "stop": {
6343
6370
  const runOption = takeOption(rest, "--run");
6344
6371
  const positionalRunId = runOption.rest.length > 0 ? runOption.rest[0] : undefined;
@@ -7739,6 +7766,9 @@ function stringArrayField(record, key) {
7739
7766
  }
7740
7767
  async function executeRigOwnedTaskRun(context, input) {
7741
7768
  const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
7769
+ const existingRunRecord = readAuthorityRun5(context.projectRoot, input.runId);
7770
+ const resumeMode = process.env.RIG_RUN_RESUME === "1";
7771
+ const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
7742
7772
  const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
7743
7773
  let prompt = buildRunPrompt({
7744
7774
  projectRoot: context.projectRoot,
@@ -7794,14 +7824,14 @@ async function executeRigOwnedTaskRun(context, input) {
7794
7824
  taskId: runtimeTaskId,
7795
7825
  createdAt: startedAt,
7796
7826
  runtimeAdapter: input.runtimeAdapter,
7797
- status: "created"
7827
+ status: resumeMode ? "preparing" : "created"
7798
7828
  });
7799
7829
  patchAuthorityRun(context.projectRoot, input.runId, {
7800
7830
  status: "preparing",
7801
7831
  startedAt,
7802
7832
  completedAt: null,
7803
7833
  errorText: null,
7804
- artifactRoot: null,
7834
+ artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
7805
7835
  runtimeAdapter: input.runtimeAdapter,
7806
7836
  runtimeMode: input.runtimeMode,
7807
7837
  interactionMode: input.interactionMode,
@@ -7817,9 +7847,9 @@ async function executeRigOwnedTaskRun(context, input) {
7817
7847
  detail: input.taskId ?? input.title ?? runtimeTaskId
7818
7848
  });
7819
7849
  appendRunLog(context.projectRoot, input.runId, {
7820
- id: `log:${input.runId}:start`,
7821
- title: "Rig task run started",
7822
- detail: input.taskId ?? input.title ?? runtimeTaskId,
7850
+ id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
7851
+ title: resumeMode ? "Rig task run resumed" : "Rig task run started",
7852
+ detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
7823
7853
  tone: "info",
7824
7854
  status: "preparing",
7825
7855
  createdAt: startedAt
@@ -7897,11 +7927,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
7897
7927
  let reviewAction;
7898
7928
  let verificationStarted = false;
7899
7929
  let reviewStarted = false;
7900
- let latestRuntimeWorkspace = null;
7901
- let latestSessionDir = null;
7902
- let latestLogsDir = null;
7930
+ let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
7931
+ let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve21(existingRunRecord.sessionPath, "..") : null;
7932
+ let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
7903
7933
  let latestProviderCommand = null;
7904
- let latestRuntimeBranch = null;
7934
+ let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
7905
7935
  let snapshotSidecarPromise = null;
7906
7936
  let dirtyBaselineApplied = false;
7907
7937
  const childEnv = {
@@ -7919,7 +7949,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
7919
7949
  RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
7920
7950
  RIG_SERVER_RUN_ID: input.runId
7921
7951
  },
7922
- ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
7952
+ ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {},
7953
+ ...resumeMode ? {
7954
+ RIG_RUN_RESUME: "1",
7955
+ RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
7956
+ } : {}
7923
7957
  };
7924
7958
  Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
7925
7959
  Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
@@ -8225,7 +8259,36 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8225
8259
  let reviewFailureDetail = null;
8226
8260
  const stderrLines = [];
8227
8261
  const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
8228
- for (let attempt = 1;attempt <= maxAttempts; attempt += 1) {
8262
+ if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
8263
+ appendRunLog(context.projectRoot, input.runId, {
8264
+ id: `log:${input.runId}:resume-closeout-phase`,
8265
+ title: "Resume continuing from closeout phase",
8266
+ detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
8267
+ tone: "info",
8268
+ status: resumePreviousStatus,
8269
+ createdAt: new Date().toISOString()
8270
+ });
8271
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
8272
+ exit = { code: 0, signal: null };
8273
+ } else if (resumeMode && latestRuntimeWorkspace) {
8274
+ const acceptedArtifactState = readTaskRunAcceptedArtifactState({
8275
+ taskId: input.taskId ?? runtimeTaskId,
8276
+ workspaceDir: latestRuntimeWorkspace
8277
+ });
8278
+ if (acceptedArtifactState.accepted) {
8279
+ appendRunLog(context.projectRoot, input.runId, {
8280
+ id: `log:${input.runId}:resume-accepted-artifacts`,
8281
+ title: "Resume found accepted artifacts; continuing closeout",
8282
+ detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
8283
+ tone: "info",
8284
+ status: "validating",
8285
+ createdAt: new Date().toISOString()
8286
+ });
8287
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
8288
+ exit = { code: 0, signal: null };
8289
+ }
8290
+ }
8291
+ for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
8229
8292
  const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
8230
8293
  const child = spawn2(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
8231
8294
  cwd: context.projectRoot,
package/dist/src/index.js CHANGED
@@ -3442,6 +3442,7 @@ function upsertAgentAuthorityRun(projectRoot, input) {
3442
3442
  const runtimeAdapter = normalizeRuntimeAdapter(input.runtimeAdapter ?? existing?.runtimeAdapter ?? "claude-code");
3443
3443
  const title = resolveTaskTitleForAuthorityRun(projectRoot, input.taskId) ?? input.taskId;
3444
3444
  const next = {
3445
+ ...existing ?? {},
3445
3446
  runId: input.runId,
3446
3447
  projectRoot,
3447
3448
  workspaceId: existing?.workspaceId ?? RIG_WORKSPACE_ID,
@@ -6114,6 +6115,7 @@ import {
6114
6115
  listOpenEpics,
6115
6116
  resolveDefaultEpic,
6116
6117
  runResume,
6118
+ runRestart,
6117
6119
  runStatus,
6118
6120
  runStop,
6119
6121
  startRun,
@@ -6222,6 +6224,17 @@ async function attachRunOperatorView(context, input) {
6222
6224
  }
6223
6225
 
6224
6226
  // packages/cli/src/commands/run.ts
6227
+ function normalizeRemoteRunDetails(payload) {
6228
+ const run = payload.run;
6229
+ if (!run || typeof run !== "object" || Array.isArray(run))
6230
+ return null;
6231
+ return {
6232
+ ...run,
6233
+ ...Array.isArray(payload.timeline) ? { timeline: payload.timeline } : {},
6234
+ ...Array.isArray(payload.approvals) ? { approvals: payload.approvals } : {},
6235
+ ...Array.isArray(payload.userInputs) ? { userInputs: payload.userInputs } : {}
6236
+ };
6237
+ }
6225
6238
  function shouldPromptForEpicSelection(context, command, promptEpic, noEpicPrompt) {
6226
6239
  if (noEpicPrompt) {
6227
6240
  return false;
@@ -6360,7 +6373,7 @@ async function executeRun(context, args) {
6360
6373
  if (!run.value) {
6361
6374
  throw new CliError2("run show requires --run <id>.");
6362
6375
  }
6363
- const record = readAuthorityRun4(context.projectRoot, run.value);
6376
+ const record = readAuthorityRun4(context.projectRoot, run.value) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, run.value).catch(() => ({})));
6364
6377
  if (!record) {
6365
6378
  throw new CliError2(`Run not found: ${run.value}`, 2);
6366
6379
  }
@@ -6541,6 +6554,20 @@ async function executeRun(context, args) {
6541
6554
  }
6542
6555
  return { ok: true, group: "run", command, details: resumed };
6543
6556
  }
6557
+ case "restart": {
6558
+ requireNoExtraArgs(rest, "bun run rig run restart");
6559
+ if (context.dryRun) {
6560
+ if (context.outputMode === "text") {
6561
+ console.log("[dry-run] rig run restart");
6562
+ }
6563
+ return { ok: true, group: "run", command };
6564
+ }
6565
+ const restarted = await runRestart(context.projectRoot, runtimeContext);
6566
+ if (context.outputMode === "text") {
6567
+ console.log(`Restarted run: ${restarted.runId}`);
6568
+ }
6569
+ return { ok: true, group: "run", command, details: restarted };
6570
+ }
6544
6571
  case "stop": {
6545
6572
  const runOption = takeOption(rest, "--run");
6546
6573
  const positionalRunId = runOption.rest.length > 0 ? runOption.rest[0] : undefined;
@@ -7941,6 +7968,9 @@ function stringArrayField(record, key) {
7941
7968
  }
7942
7969
  async function executeRigOwnedTaskRun(context, input) {
7943
7970
  const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
7971
+ const existingRunRecord = readAuthorityRun5(context.projectRoot, input.runId);
7972
+ const resumeMode = process.env.RIG_RUN_RESUME === "1";
7973
+ const resumePreviousStatus = String(existingRunRecord?.status ?? "").toLowerCase();
7944
7974
  const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
7945
7975
  let prompt = buildRunPrompt({
7946
7976
  projectRoot: context.projectRoot,
@@ -7996,14 +8026,14 @@ async function executeRigOwnedTaskRun(context, input) {
7996
8026
  taskId: runtimeTaskId,
7997
8027
  createdAt: startedAt,
7998
8028
  runtimeAdapter: input.runtimeAdapter,
7999
- status: "created"
8029
+ status: resumeMode ? "preparing" : "created"
8000
8030
  });
8001
8031
  patchAuthorityRun(context.projectRoot, input.runId, {
8002
8032
  status: "preparing",
8003
8033
  startedAt,
8004
8034
  completedAt: null,
8005
8035
  errorText: null,
8006
- artifactRoot: null,
8036
+ artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
8007
8037
  runtimeAdapter: input.runtimeAdapter,
8008
8038
  runtimeMode: input.runtimeMode,
8009
8039
  interactionMode: input.interactionMode,
@@ -8019,9 +8049,9 @@ async function executeRigOwnedTaskRun(context, input) {
8019
8049
  detail: input.taskId ?? input.title ?? runtimeTaskId
8020
8050
  });
8021
8051
  appendRunLog(context.projectRoot, input.runId, {
8022
- id: `log:${input.runId}:start`,
8023
- title: "Rig task run started",
8024
- detail: input.taskId ?? input.title ?? runtimeTaskId,
8052
+ id: `log:${input.runId}:${resumeMode ? "resume" : "start"}`,
8053
+ title: resumeMode ? "Rig task run resumed" : "Rig task run started",
8054
+ detail: resumeMode ? `Continuing the same run lifecycle for ${input.taskId ?? input.title ?? runtimeTaskId}.` : input.taskId ?? input.title ?? runtimeTaskId,
8025
8055
  tone: "info",
8026
8056
  status: "preparing",
8027
8057
  createdAt: startedAt
@@ -8099,11 +8129,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8099
8129
  let reviewAction;
8100
8130
  let verificationStarted = false;
8101
8131
  let reviewStarted = false;
8102
- let latestRuntimeWorkspace = null;
8103
- let latestSessionDir = null;
8104
- let latestLogsDir = null;
8132
+ let latestRuntimeWorkspace = resumeMode && typeof existingRunRecord?.worktreePath === "string" ? existingRunRecord.worktreePath : null;
8133
+ let latestSessionDir = resumeMode && typeof existingRunRecord?.sessionPath === "string" ? resolve22(existingRunRecord.sessionPath, "..") : null;
8134
+ let latestLogsDir = resumeMode && typeof existingRunRecord?.logRoot === "string" ? existingRunRecord.logRoot : null;
8105
8135
  let latestProviderCommand = null;
8106
- let latestRuntimeBranch = null;
8136
+ let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
8107
8137
  let snapshotSidecarPromise = null;
8108
8138
  let dirtyBaselineApplied = false;
8109
8139
  const childEnv = {
@@ -8121,7 +8151,11 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8121
8151
  RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
8122
8152
  RIG_SERVER_RUN_ID: input.runId
8123
8153
  },
8124
- ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
8154
+ ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {},
8155
+ ...resumeMode ? {
8156
+ RIG_RUN_RESUME: "1",
8157
+ RIG_RUNTIME_ARTIFACT_CLEANUP: "preserve"
8158
+ } : {}
8125
8159
  };
8126
8160
  Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
8127
8161
  Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
@@ -8427,7 +8461,36 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
8427
8461
  let reviewFailureDetail = null;
8428
8462
  const stderrLines = [];
8429
8463
  const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
8430
- for (let attempt = 1;attempt <= maxAttempts; attempt += 1) {
8464
+ if (resumeMode && ["validating", "reviewing"].includes(resumePreviousStatus)) {
8465
+ appendRunLog(context.projectRoot, input.runId, {
8466
+ id: `log:${input.runId}:resume-closeout-phase`,
8467
+ title: "Resume continuing from closeout phase",
8468
+ detail: `Previous run status was ${resumePreviousStatus}; skipping agent relaunch and continuing validation/PR/merge closeout for the same run.`,
8469
+ tone: "info",
8470
+ status: resumePreviousStatus,
8471
+ createdAt: new Date().toISOString()
8472
+ });
8473
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume continuing from closeout phase" });
8474
+ exit = { code: 0, signal: null };
8475
+ } else if (resumeMode && latestRuntimeWorkspace) {
8476
+ const acceptedArtifactState = readTaskRunAcceptedArtifactState({
8477
+ taskId: input.taskId ?? runtimeTaskId,
8478
+ workspaceDir: latestRuntimeWorkspace
8479
+ });
8480
+ if (acceptedArtifactState.accepted) {
8481
+ appendRunLog(context.projectRoot, input.runId, {
8482
+ id: `log:${input.runId}:resume-accepted-artifacts`,
8483
+ title: "Resume found accepted artifacts; continuing closeout",
8484
+ detail: acceptedArtifactState.reason ?? "Accepted task artifacts are present from the previous run process.",
8485
+ tone: "info",
8486
+ status: "validating",
8487
+ createdAt: new Date().toISOString()
8488
+ });
8489
+ emitServerRunEvent({ type: "log", runId: input.runId, title: "Resume found accepted artifacts; continuing closeout" });
8490
+ exit = { code: 0, signal: null };
8491
+ }
8492
+ }
8493
+ for (let attempt = 1;!exit && attempt <= maxAttempts; attempt += 1) {
8431
8494
  const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
8432
8495
  const child = spawn2(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
8433
8496
  cwd: context.projectRoot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/cli",
3
- "version": "0.0.6-alpha.3",
3
+ "version": "0.0.6-alpha.5",
4
4
  "type": "module",
5
5
  "description": "Rig package",
6
6
  "license": "UNLICENSED",
@@ -23,9 +23,9 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@clack/prompts": "^1.2.0",
26
- "@rig/core": "npm:@h-rig/core@0.0.6-alpha.3",
27
- "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.3",
28
- "@rig/client": "npm:@h-rig/client@0.0.6-alpha.3",
26
+ "@rig/core": "npm:@h-rig/core@0.0.6-alpha.5",
27
+ "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.5",
28
+ "@rig/client": "npm:@h-rig/client@0.0.6-alpha.5",
29
29
  "picocolors": "^1.1.1"
30
30
  }
31
31
  }