@h-rig/cli 0.0.6-alpha.1 → 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
  }
@@ -1057,7 +1065,7 @@ function stringArrayField(record, key) {
1057
1065
  async function executeRigOwnedTaskRun(context, input) {
1058
1066
  const runtimeTaskId = input.taskId?.trim() || `adhoc-${input.runId}`;
1059
1067
  const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
1060
- const prompt = buildRunPrompt({
1068
+ let prompt = buildRunPrompt({
1061
1069
  projectRoot: context.projectRoot,
1062
1070
  taskId: input.taskId,
1063
1071
  fallbackTitle: input.title,
@@ -1157,7 +1165,22 @@ async function executeRigOwnedTaskRun(context, input) {
1157
1165
  const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
1158
1166
  const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
1159
1167
  const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
1160
- 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."}`;
1161
1184
  if (input.runtimeAdapter === "pi") {
1162
1185
  for (const stage of ["Connect", "GitHub/task sync", "Prepare workspace", "Launch Pi", "Plan", "Implement"]) {
1163
1186
  appendPiStageLog({
@@ -1224,6 +1247,7 @@ async function executeRigOwnedTaskRun(context, input) {
1224
1247
  ...sourceTask ? { RIG_SOURCE_TASK_JSON: JSON.stringify(sourceTask) } : {}
1225
1248
  };
1226
1249
  Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
1250
+ Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
1227
1251
  const automationLimits = resolveTaskRunAutomationLimits(automationConfig);
1228
1252
  const maxAttempts = automationLimits.maxValidationAttempts;
1229
1253
  const promoteToValidating = (detail) => {
@@ -1287,13 +1311,20 @@ async function executeRigOwnedTaskRun(context, input) {
1287
1311
  if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
1288
1312
  dirtyBaselineApplied = true;
1289
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
+ }
1290
1320
  appendRunLog(context.projectRoot, input.runId, {
1291
1321
  id: `log:${input.runId}:dirty-baseline`,
1292
1322
  title: "Dirty baseline snapshot",
1293
1323
  detail: dirty.detail,
1294
1324
  tone: dirty.applied ? "tool" : "info",
1295
1325
  status: dirty.applied ? "completed" : "skipped",
1296
- createdAt: new Date().toISOString()
1326
+ createdAt: new Date().toISOString(),
1327
+ payload: readyFile ? { readyFile } : undefined
1297
1328
  });
1298
1329
  emitServerRunEvent({ type: "log", runId: input.runId, title: "Dirty baseline snapshot" });
1299
1330
  }
@@ -1765,6 +1796,29 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
1765
1796
  });
1766
1797
  throw new CliError2(terminalFailureDetail, exit.code ?? 1);
1767
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
+ }
1768
1822
  const runPiPrFeedbackFix = async (message) => {
1769
1823
  appendPiStageLog({
1770
1824
  projectRoot: context.projectRoot,
@@ -1935,5 +1989,6 @@ export {
1935
1989
  buildTaskRunReviewEnv,
1936
1990
  buildPiValidationRetrySteeringPrompt,
1937
1991
  buildPiRigBridgeEnv,
1992
+ buildDirtyBaselineHandshakeEnv,
1938
1993
  applyDirtyBaselineSnapshot
1939
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
  }