@h-rig/cli 0.0.6-alpha.0 → 0.0.6-alpha.2

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.
@@ -2,8 +2,8 @@
2
2
  var __require = import.meta.require;
3
3
 
4
4
  // packages/cli/src/commands/setup.ts
5
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync, writeFileSync as writeFileSync2 } from "fs";
6
- import { resolve as resolve5 } from "path";
5
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync, writeFileSync as writeFileSync2 } from "fs";
6
+ import { resolve as resolve6 } from "path";
7
7
 
8
8
  // packages/cli/src/runner.ts
9
9
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
@@ -171,8 +171,8 @@ async function buildPiSetupChecks(input = {}) {
171
171
  }
172
172
 
173
173
  // packages/cli/src/commands/_doctor-checks.ts
174
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
175
- import { resolve as resolve4 } from "path";
174
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
175
+ import { resolve as resolve5 } from "path";
176
176
  import { isSupportedBunVersion, MIN_SUPPORTED_BUN_VERSION } from "@rig/runtime/control-plane/setup-version";
177
177
 
178
178
  // packages/cli/src/commands/_connection-state.ts
@@ -259,15 +259,33 @@ function resolveSelectedConnection(projectRoot, options = {}) {
259
259
 
260
260
  // packages/cli/src/commands/_server-client.ts
261
261
  import { spawnSync } from "child_process";
262
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
263
+ import { resolve as resolve4 } from "path";
262
264
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
263
265
  var cachedGitHubBearerToken;
264
266
  function cleanToken(value) {
265
267
  const trimmed = value?.trim();
266
268
  return trimmed ? trimmed : null;
267
269
  }
268
- function readGitHubBearerTokenForRemote() {
270
+ function readPrivateRemoteSessionToken(projectRoot) {
271
+ const path = resolve4(projectRoot, ".rig", "state", "github-auth.json");
272
+ if (!existsSync3(path))
273
+ return null;
274
+ try {
275
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
276
+ return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
277
+ } catch {
278
+ return null;
279
+ }
280
+ }
281
+ function readGitHubBearerTokenForRemote(projectRoot) {
269
282
  if (cachedGitHubBearerToken !== undefined)
270
283
  return cachedGitHubBearerToken;
284
+ const privateSession = readPrivateRemoteSessionToken(projectRoot);
285
+ if (privateSession) {
286
+ cachedGitHubBearerToken = privateSession;
287
+ return cachedGitHubBearerToken;
288
+ }
271
289
  const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
272
290
  if (envToken) {
273
291
  cachedGitHubBearerToken = envToken;
@@ -287,7 +305,7 @@ async function ensureServerForCli(projectRoot) {
287
305
  if (selected?.connection.kind === "remote") {
288
306
  return {
289
307
  baseUrl: selected.connection.baseUrl,
290
- authToken: readGitHubBearerTokenForRemote(),
308
+ authToken: readGitHubBearerTokenForRemote(projectRoot),
291
309
  connectionKind: "remote"
292
310
  };
293
311
  }
@@ -387,11 +405,11 @@ function repoSlugFromConfig(config) {
387
405
  function loadFallbackConfig(projectRoot) {
388
406
  const candidates = ["rig.config.ts", "rig.config.mts", "rig.config.json"];
389
407
  for (const name of candidates) {
390
- const path = resolve4(projectRoot, name);
391
- if (!existsSync3(path))
408
+ const path = resolve5(projectRoot, name);
409
+ if (!existsSync4(path))
392
410
  continue;
393
411
  try {
394
- const source = readFileSync3(path, "utf8");
412
+ const source = readFileSync4(path, "utf8");
395
413
  if (name.endsWith(".json"))
396
414
  return JSON.parse(source);
397
415
  const owner = source.match(/owner\s*:\s*["']([^"']+)["']/)?.[1];
@@ -470,7 +488,7 @@ async function runRigDoctorChecks(options) {
470
488
  checks.push(check("bun", `bun >= ${MIN_SUPPORTED_BUN_VERSION}`, isSupportedBunVersion(bunVersion) ? "pass" : "fail", `found ${bunVersion}`, `Install Bun ${MIN_SUPPORTED_BUN_VERSION} or newer.`), check("git", "git", which("git") ? "pass" : "fail", which("git") ?? undefined, "Install git and ensure it is on PATH."), check("jq", "jq", which("jq") ? "pass" : "warn", which("jq") ?? undefined, "Install jq (for example `brew install jq`)."));
471
489
  const loadedConfig = await loadConfig(projectRoot).catch(() => null);
472
490
  const config = loadedConfig ?? loadFallbackConfig(projectRoot);
473
- const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync3(resolve4(projectRoot, name)));
491
+ const hasConfigFile = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync4(resolve5(projectRoot, name)));
474
492
  checks.push(config ? check("config", "rig.config loadable", "pass") : check("config", "rig.config loadable", hasConfigFile ? "fail" : "fail", hasConfigFile ? "config file exists but failed to load" : "missing rig.config.ts/json", "Run `rig init` or fix the config error."));
475
493
  const taskSourceKind = config?.taskSource?.kind;
476
494
  checks.push(taskSourceKind ? check("task-source", "task source configured", "pass", taskSourceKind) : check("task-source", "task source configured", "fail", "missing taskSource", "Configure taskSource in rig.config.ts."));
@@ -638,8 +656,8 @@ function runSetupInit(projectRoot) {
638
656
  mkdirSync2(stateDir, { recursive: true });
639
657
  mkdirSync2(logsDir, { recursive: true });
640
658
  mkdirSync2(artifactsDir, { recursive: true });
641
- const failuresPath = resolve5(stateDir, "failed_approaches.md");
642
- if (!existsSync4(failuresPath)) {
659
+ const failuresPath = resolve6(stateDir, "failed_approaches.md");
660
+ if (!existsSync5(failuresPath)) {
643
661
  writeFileSync2(failuresPath, `# Failed Approaches
644
662
 
645
663
  `, "utf-8");
@@ -657,18 +675,18 @@ async function runSetupCheck(projectRoot) {
657
675
  }
658
676
  async function runSetupPreflight(projectRoot) {
659
677
  await runSetupCheck(projectRoot);
660
- const validationRoot = resolve5(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
661
- if (existsSync4(validationRoot)) {
678
+ const validationRoot = resolve6(resolveControlPlaneDefinitionRoot(projectRoot), "validation");
679
+ if (existsSync5(validationRoot)) {
662
680
  const validators = readdirSync(validationRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
663
681
  for (const validator of validators) {
664
- const script = resolve5(validationRoot, validator.name, "validate.sh");
665
- if (existsSync4(script)) {
682
+ const script = resolve6(validationRoot, validator.name, "validate.sh");
683
+ if (existsSync5(script)) {
666
684
  console.log(`OK: validator script ${script}`);
667
685
  }
668
686
  }
669
687
  }
670
- const hooksRoot = resolve5(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
671
- if (existsSync4(hooksRoot)) {
688
+ const hooksRoot = resolve6(resolveControlPlaneDefinitionRoot(projectRoot), "hooks");
689
+ if (existsSync5(hooksRoot)) {
672
690
  const hooks = readdirSync(hooksRoot).filter((name) => name.endsWith(".sh"));
673
691
  for (const hook of hooks) {
674
692
  console.log(`OK: hook ${hook}`);
@@ -1,6 +1,6 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/task-run-driver.ts
3
- import { copyFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, statSync } from "fs";
3
+ import { copyFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, statSync, writeFileSync } from "fs";
4
4
  import { resolve as resolve4 } from "path";
5
5
  import { spawn, spawnSync } from "child_process";
6
6
  import { createInterface as createLineInterface } from "readline";
@@ -476,6 +476,14 @@ function buildTaskRunReviewEnv(config) {
476
476
  ...review?.provider ? { AI_REVIEW_PROVIDER: review.provider } : {}
477
477
  };
478
478
  }
479
+ function buildDirtyBaselineHandshakeEnv(input) {
480
+ if (input.baselineMode !== "dirty-snapshot")
481
+ return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
482
+ return {
483
+ RIG_BASELINE_MODE: "dirty-snapshot",
484
+ RIG_DIRTY_BASELINE_READY_FILE: resolve4(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
485
+ };
486
+ }
479
487
  function positiveInt(value, fallback) {
480
488
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
481
489
  }
@@ -575,6 +583,12 @@ function appendPiStageLog(input) {
575
583
  });
576
584
  emitServerRunEvent({ type: "log", runId: input.runId, title: input.stage });
577
585
  }
586
+ async function runCheckedCommand(command, args, cwd, label = "git") {
587
+ const result = await command(args, { cwd });
588
+ if (result.exitCode !== 0) {
589
+ throw new Error(`${label} ${args.join(" ")} failed (${result.exitCode}): ${result.stderr ?? result.stdout ?? ""}`.trim());
590
+ }
591
+ }
578
592
  function createCommandRunner(binary) {
579
593
  return async (args, options) => {
580
594
  const child = spawn(binary, [...args], {
@@ -650,6 +664,7 @@ async function runTaskRunPostValidationLifecycle(input) {
650
664
  command: gitCommand
651
665
  });
652
666
  stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
667
+ await runCheckedCommand(gitCommand, ["push", "--set-upstream", "origin", branch], workspace, "git");
653
668
  stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
654
669
  const pr = await prAutomation({
655
670
  projectRoot: workspace,
@@ -660,7 +675,6 @@ async function runTaskRunPostValidationLifecycle(input) {
660
675
  sourceTask: input.sourceTask,
661
676
  uploadedSnapshot: input.uploadedSnapshot,
662
677
  command: ghCommand,
663
- gitCommand,
664
678
  steerPi,
665
679
  lifecycle: {
666
680
  onPrOpened: async ({ prUrl }) => {
@@ -1051,7 +1065,7 @@ function stringArrayField(record, key) {
1051
1065
  async function executeRigOwnedTaskRun(context, input) {
1052
1066
  const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
1053
1067
  const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
1054
- const prompt = buildRunPrompt({
1068
+ let prompt = buildRunPrompt({
1055
1069
  projectRoot: context.projectRoot,
1056
1070
  taskId: input.taskId,
1057
1071
  fallbackTitle: input.title,
@@ -1151,7 +1165,22 @@ async function executeRigOwnedTaskRun(context, input) {
1151
1165
  const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
1152
1166
  const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
1153
1167
  const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
1154
- patchAuthorityRun(context.projectRoot, input.runId, { planning: planningClassification });
1168
+ const planningArtifactPath = resolve4("artifacts", runtimeTaskId, "implementation-plan.md");
1169
+ const persistedPlanning = {
1170
+ ...planningClassification,
1171
+ classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
1172
+ artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
1173
+ classifiedAt: new Date().toISOString()
1174
+ };
1175
+ mkdirSync(resolve4(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
1176
+ writeFileSync(resolve4(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
1177
+ `, "utf8");
1178
+ patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
1179
+ prompt = `${prompt}
1180
+
1181
+ Rig planning classification:
1182
+ ${JSON.stringify(persistedPlanning, null, 2)}
1183
+ ${planningClassification.planningRequired ? `Before implementing, write a concise implementation plan to ${planningArtifactPath}. Treat that plan artifact as required acceptance evidence for the Plan stage.` : "Planning is not required for this run; briefly state why before implementation."}`;
1155
1184
  if (input.runtimeAdapter === "pi") {
1156
1185
  for (const stage of ["Connect", "GitHub/task sync", "Prepare workspace", "Launch Pi", "Plan", "Implement"]) {
1157
1186
  appendPiStageLog({
@@ -1218,6 +1247,7 @@ async function executeRigOwnedTaskRun(context, input) {
1218
1247
  ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
1219
1248
  };
1220
1249
  Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
1250
+ Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
1221
1251
  const automationLimits = resolveTaskRunAutomationLimits(automationConfig);
1222
1252
  const maxAttempts = automationLimits.maxValidationAttempts;
1223
1253
  const promoteToValidating = (detail) => {
@@ -1281,13 +1311,20 @@ async function executeRigOwnedTaskRun(context, input) {
1281
1311
  if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
1282
1312
  dirtyBaselineApplied = true;
1283
1313
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
1314
+ const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
1315
+ if (readyFile) {
1316
+ mkdirSync(resolve4(readyFile, ".."), { recursive: true });
1317
+ writeFileSync(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
1318
+ `, "utf8");
1319
+ }
1284
1320
  appendRunLog(context.projectRoot, input.runId, {
1285
1321
  id: `log:${input.runId}:dirty-baseline`,
1286
1322
  title: "Dirty baseline snapshot",
1287
1323
  detail: dirty.detail,
1288
1324
  tone: dirty.applied ? "tool" : "info",
1289
1325
  status: dirty.applied ? "completed" : "skipped",
1290
- createdAt: new Date().toISOString()
1326
+ createdAt: new Date().toISOString(),
1327
+ payload: readyFile ? { readyFile } : undefined
1291
1328
  });
1292
1329
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Dirty baseline snapshot" });
1293
1330
  }
@@ -1759,6 +1796,29 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1759
1796
  });
1760
1797
  throw new CliError2(terminalFailureDetail, exit.code ?? 1);
1761
1798
  }
1799
+ if (planningClassification.planningRequired) {
1800
+ const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
1801
+ const expectedPlanPath = resolve4(planWorkspace, planningArtifactPath);
1802
+ if (!existsSync2(expectedPlanPath)) {
1803
+ const failedAt = new Date().toISOString();
1804
+ const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
1805
+ patchAuthorityRun(context.projectRoot, input.runId, {
1806
+ status: "needs_attention",
1807
+ completedAt: failedAt,
1808
+ errorText: failureDetail
1809
+ });
1810
+ appendRunLog(context.projectRoot, input.runId, {
1811
+ id: `log:${input.runId}:plan-artifact-missing`,
1812
+ title: "Required plan artifact missing",
1813
+ detail: failureDetail,
1814
+ tone: "error",
1815
+ status: "needs_attention",
1816
+ createdAt: failedAt
1817
+ });
1818
+ emitServerRunEvent({ type: "failed", runId: input.runId, error: failureDetail });
1819
+ throw new CliError2(failureDetail, 1);
1820
+ }
1821
+ }
1762
1822
  const runPiPrFeedbackFix = async (message) => {
1763
1823
  appendPiStageLog({
1764
1824
  projectRoot: context.projectRoot,
@@ -1929,5 +1989,6 @@ export {
1929
1989
  buildTaskRunReviewEnv,
1930
1990
  buildPiValidationRetrySteeringPrompt,
1931
1991
  buildPiRigBridgeEnv,
1992
+ buildDirtyBaselineHandshakeEnv,
1932
1993
  applyDirtyBaselineSnapshot
1933
1994
  };
@@ -1,9 +1,9 @@
1
1
  // @bun
2
2
  // packages/cli/src/commands/task.ts
3
- import { readFileSync as readFileSync3 } from "fs";
3
+ import { readFileSync as readFileSync4 } from "fs";
4
4
  import { spawnSync as spawnSync2 } from "child_process";
5
5
  import { createInterface as createInterface3 } from "readline/promises";
6
- import { resolve as resolve3 } from "path";
6
+ import { resolve as resolve4 } from "path";
7
7
 
8
8
  // packages/cli/src/runner.ts
9
9
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
@@ -189,15 +189,33 @@ function resolveSelectedConnection(projectRoot, options = {}) {
189
189
 
190
190
  // packages/cli/src/commands/_server-client.ts
191
191
  import { spawnSync } from "child_process";
192
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
193
+ import { resolve as resolve2 } from "path";
192
194
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
193
195
  var cachedGitHubBearerToken;
194
196
  function cleanToken(value) {
195
197
  const trimmed = value?.trim();
196
198
  return trimmed ? trimmed : null;
197
199
  }
198
- function readGitHubBearerTokenForRemote() {
200
+ function readPrivateRemoteSessionToken(projectRoot) {
201
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
202
+ if (!existsSync2(path))
203
+ return null;
204
+ try {
205
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
206
+ return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
207
+ } catch {
208
+ return null;
209
+ }
210
+ }
211
+ function readGitHubBearerTokenForRemote(projectRoot) {
199
212
  if (cachedGitHubBearerToken !== undefined)
200
213
  return cachedGitHubBearerToken;
214
+ const privateSession = readPrivateRemoteSessionToken(projectRoot);
215
+ if (privateSession) {
216
+ cachedGitHubBearerToken = privateSession;
217
+ return cachedGitHubBearerToken;
218
+ }
201
219
  const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
202
220
  if (envToken) {
203
221
  cachedGitHubBearerToken = envToken;
@@ -217,7 +235,7 @@ async function ensureServerForCli(projectRoot) {
217
235
  if (selected?.connection.kind === "remote") {
218
236
  return {
219
237
  baseUrl: selected.connection.baseUrl,
220
- authToken: readGitHubBearerTokenForRemote(),
238
+ authToken: readGitHubBearerTokenForRemote(projectRoot),
221
239
  connectionKind: "remote"
222
240
  };
223
241
  }
@@ -378,9 +396,9 @@ async function submitTaskRunViaServer(context, input) {
378
396
  }
379
397
 
380
398
  // packages/cli/src/commands/_pi-install.ts
381
- import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync } from "fs";
399
+ import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
382
400
  import { homedir as homedir2 } from "os";
383
- import { resolve as resolve2 } from "path";
401
+ import { resolve as resolve3 } from "path";
384
402
  var PI_RIG_PACKAGE_NAME = "@rig/pi-rig";
385
403
  async function defaultCommandRunner(command, options = {}) {
386
404
  const proc = Bun.spawn(command, { cwd: options.cwd, stdout: "pipe", stderr: "pipe" });
@@ -392,7 +410,7 @@ async function defaultCommandRunner(command, options = {}) {
392
410
  return { exitCode, stdout, stderr };
393
411
  }
394
412
  function resolvePiRigExtensionPath(homeDir) {
395
- return resolve2(homeDir, ".pi", "agent", "extensions", "pi-rig");
413
+ return resolve3(homeDir, ".pi", "agent", "extensions", "pi-rig");
396
414
  }
397
415
  function resolvePiHomeDir(inputHomeDir) {
398
416
  return inputHomeDir ?? process.env.RIG_PI_HOME_DIR?.trim() ?? homedir2();
@@ -420,13 +438,13 @@ async function checkPiRigInstall(input = {}) {
420
438
  piRig: { ok: true, label: "pi-rig global extension", detail: extensionPath }
421
439
  };
422
440
  }
423
- const exists = input.exists ?? existsSync2;
441
+ const exists = input.exists ?? existsSync3;
424
442
  const runner = input.commandRunner ?? defaultCommandRunner;
425
443
  const piResult = await safeRun(runner, ["pi", "--version"]);
426
444
  const piListResult = piResult.exitCode === 0 ? await safeRun(runner, ["pi", "list"]) : { exitCode: 1, stdout: "", stderr: "" };
427
445
  const listedPiRig = piListResult.exitCode === 0 && piListContainsPiRig(`${piListResult.stdout}
428
446
  ${piListResult.stderr}`);
429
- const legacyBridge = exists(resolve2(extensionPath, "index.ts"));
447
+ const legacyBridge = exists(resolve3(extensionPath, "index.ts"));
430
448
  const hasPiRig = listedPiRig;
431
449
  return {
432
450
  extensionPath,
@@ -1101,7 +1119,7 @@ async function executeTask(context, args, options) {
1101
1119
  const fileFlag = takeOption(rest.slice(1), "--file");
1102
1120
  let content;
1103
1121
  if (fileFlag.value) {
1104
- content = readFileSync3(resolve3(context.projectRoot, fileFlag.value), "utf-8");
1122
+ content = readFileSync4(resolve4(context.projectRoot, fileFlag.value), "utf-8");
1105
1123
  } else {
1106
1124
  content = await readStdin();
1107
1125
  }