@h-rig/cli 0.0.6-alpha.0 → 0.0.6-alpha.10
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 +445 -222
- package/dist/src/commands/_authority-runs.js +1 -0
- package/dist/src/commands/_doctor-checks.js +31 -13
- package/dist/src/commands/_operator-view.js +20 -2
- package/dist/src/commands/_preflight.js +25 -7
- package/dist/src/commands/_server-client.js +22 -4
- package/dist/src/commands/_snapshot-upload.js +26 -8
- package/dist/src/commands/agent.js +1 -0
- package/dist/src/commands/doctor.js +31 -13
- package/dist/src/commands/github.js +21 -3
- package/dist/src/commands/init.js +142 -62
- package/dist/src/commands/run.js +53 -9
- package/dist/src/commands/server.js +22 -4
- package/dist/src/commands/setup.js +36 -18
- package/dist/src/commands/task-run-driver.js +132 -17
- package/dist/src/commands/task.js +28 -10
- package/dist/src/commands.js +433 -213
- package/dist/src/index.js +445 -222
- package/dist/src/launcher.js +4 -2
- package/dist/src/runner.js +3 -2
- package/package.json +5 -4
|
@@ -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
|
|
6
|
-
import { resolve as
|
|
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
|
|
175
|
-
import { resolve as
|
|
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
|
|
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 =
|
|
391
|
-
if (!
|
|
408
|
+
const path = resolve5(projectRoot, name);
|
|
409
|
+
if (!existsSync4(path))
|
|
392
410
|
continue;
|
|
393
411
|
try {
|
|
394
|
-
const source =
|
|
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) =>
|
|
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 =
|
|
642
|
-
if (!
|
|
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 =
|
|
661
|
-
if (
|
|
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 =
|
|
665
|
-
if (
|
|
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 =
|
|
671
|
-
if (
|
|
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";
|
|
@@ -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,
|
|
@@ -405,7 +406,24 @@ var PI_CANONICAL_RUN_STAGES = [
|
|
|
405
406
|
function canonicalPiRunStages() {
|
|
406
407
|
return [...PI_CANONICAL_RUN_STAGES];
|
|
407
408
|
}
|
|
409
|
+
function looksLikeGitHubToken(value) {
|
|
410
|
+
const token = value?.trim();
|
|
411
|
+
if (!token)
|
|
412
|
+
return false;
|
|
413
|
+
return /^(gh[opusr]_|github_pat_)/.test(token);
|
|
414
|
+
}
|
|
415
|
+
function githubBridgeEnv(token) {
|
|
416
|
+
const clean = token?.trim();
|
|
417
|
+
if (!clean)
|
|
418
|
+
return {};
|
|
419
|
+
return {
|
|
420
|
+
RIG_GITHUB_TOKEN: clean,
|
|
421
|
+
GITHUB_TOKEN: clean,
|
|
422
|
+
GH_TOKEN: clean
|
|
423
|
+
};
|
|
424
|
+
}
|
|
408
425
|
function buildPiRigBridgeEnv(input) {
|
|
426
|
+
const githubToken = input.githubToken?.trim() || (looksLikeGitHubToken(input.authToken) ? input.authToken.trim() : "");
|
|
409
427
|
return {
|
|
410
428
|
RIG_PROJECT_ROOT: input.projectRoot,
|
|
411
429
|
PROJECT_RIG_ROOT: input.projectRoot,
|
|
@@ -415,7 +433,7 @@ function buildPiRigBridgeEnv(input) {
|
|
|
415
433
|
RIG_RUNTIME_ADAPTER: "pi",
|
|
416
434
|
...input.serverUrl ? { RIG_SERVER_URL: input.serverUrl } : {},
|
|
417
435
|
...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
|
|
418
|
-
...
|
|
436
|
+
...githubBridgeEnv(githubToken)
|
|
419
437
|
};
|
|
420
438
|
}
|
|
421
439
|
function runGitSync(cwd, args, input) {
|
|
@@ -476,6 +494,14 @@ function buildTaskRunReviewEnv(config) {
|
|
|
476
494
|
...review?.provider ? { AI_REVIEW_PROVIDER: review.provider } : {}
|
|
477
495
|
};
|
|
478
496
|
}
|
|
497
|
+
function buildDirtyBaselineHandshakeEnv(input) {
|
|
498
|
+
if (input.baselineMode !== "dirty-snapshot")
|
|
499
|
+
return { RIG_BASELINE_MODE: input.baselineMode ?? "head" };
|
|
500
|
+
return {
|
|
501
|
+
RIG_BASELINE_MODE: "dirty-snapshot",
|
|
502
|
+
RIG_DIRTY_BASELINE_READY_FILE: resolve4(input.projectRoot, ".rig", "runs", input.runId, "dirty-baseline.ready.json")
|
|
503
|
+
};
|
|
504
|
+
}
|
|
479
505
|
function positiveInt(value, fallback) {
|
|
480
506
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
481
507
|
}
|
|
@@ -575,6 +601,12 @@ function appendPiStageLog(input) {
|
|
|
575
601
|
});
|
|
576
602
|
emitServerRunEvent({ type: "log", runId: input.runId, title: input.stage });
|
|
577
603
|
}
|
|
604
|
+
async function runCheckedCommand(command, args, cwd, label = "git") {
|
|
605
|
+
const result = await command(args, { cwd });
|
|
606
|
+
if (result.exitCode !== 0) {
|
|
607
|
+
throw new Error(`${label} ${args.join(" ")} failed (${result.exitCode}): ${result.stderr ?? result.stdout ?? ""}`.trim());
|
|
608
|
+
}
|
|
609
|
+
}
|
|
578
610
|
function createCommandRunner(binary) {
|
|
579
611
|
return async (args, options) => {
|
|
580
612
|
const child = spawn(binary, [...args], {
|
|
@@ -650,6 +682,7 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
650
682
|
command: gitCommand
|
|
651
683
|
});
|
|
652
684
|
stage("Commit", commit.committed ? "Committed run workspace changes." : "No workspace changes to commit.", "completed", "tool");
|
|
685
|
+
await runCheckedCommand(gitCommand, ["push", "--set-upstream", "origin", branch], workspace, "git");
|
|
653
686
|
stage("Open PR", `Opening PR from ${branch}.`, "running", "tool");
|
|
654
687
|
const pr = await prAutomation({
|
|
655
688
|
projectRoot: workspace,
|
|
@@ -660,7 +693,6 @@ async function runTaskRunPostValidationLifecycle(input) {
|
|
|
660
693
|
sourceTask: input.sourceTask,
|
|
661
694
|
uploadedSnapshot: input.uploadedSnapshot,
|
|
662
695
|
command: ghCommand,
|
|
663
|
-
gitCommand,
|
|
664
696
|
steerPi,
|
|
665
697
|
lifecycle: {
|
|
666
698
|
onPrOpened: async ({ prUrl }) => {
|
|
@@ -1050,8 +1082,11 @@ function stringArrayField(record, key) {
|
|
|
1050
1082
|
}
|
|
1051
1083
|
async function executeRigOwnedTaskRun(context, input) {
|
|
1052
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();
|
|
1053
1088
|
const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
|
|
1054
|
-
|
|
1089
|
+
let prompt = buildRunPrompt({
|
|
1055
1090
|
projectRoot: context.projectRoot,
|
|
1056
1091
|
taskId: input.taskId,
|
|
1057
1092
|
fallbackTitle: input.title,
|
|
@@ -1105,14 +1140,14 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1105
1140
|
taskId: runtimeTaskId,
|
|
1106
1141
|
createdAt: startedAt,
|
|
1107
1142
|
runtimeAdapter: input.runtimeAdapter,
|
|
1108
|
-
status: "created"
|
|
1143
|
+
status: resumeMode ? "preparing" : "created"
|
|
1109
1144
|
});
|
|
1110
1145
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
1111
1146
|
status: "preparing",
|
|
1112
1147
|
startedAt,
|
|
1113
1148
|
completedAt: null,
|
|
1114
1149
|
errorText: null,
|
|
1115
|
-
artifactRoot: null,
|
|
1150
|
+
artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
|
|
1116
1151
|
runtimeAdapter: input.runtimeAdapter,
|
|
1117
1152
|
runtimeMode: input.runtimeMode,
|
|
1118
1153
|
interactionMode: input.interactionMode,
|
|
@@ -1128,9 +1163,9 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1128
1163
|
detail: input.taskId ?? input.title ?? runtimeTaskId
|
|
1129
1164
|
});
|
|
1130
1165
|
appendRunLog(context.projectRoot, input.runId, {
|
|
1131
|
-
id: `log:${input.runId}:start`,
|
|
1132
|
-
title: "Rig task run started",
|
|
1133
|
-
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,
|
|
1134
1169
|
tone: "info",
|
|
1135
1170
|
status: "preparing",
|
|
1136
1171
|
createdAt: startedAt
|
|
@@ -1151,7 +1186,22 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1151
1186
|
const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
|
|
1152
1187
|
const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
|
|
1153
1188
|
const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
|
|
1154
|
-
|
|
1189
|
+
const planningArtifactPath = resolve4("artifacts", runtimeTaskId, "implementation-plan.md");
|
|
1190
|
+
const persistedPlanning = {
|
|
1191
|
+
...planningClassification,
|
|
1192
|
+
classifier: input.runtimeAdapter === "pi" ? "pi-rig-structured-policy" : "rig-structured-policy",
|
|
1193
|
+
artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
|
|
1194
|
+
classifiedAt: new Date().toISOString()
|
|
1195
|
+
};
|
|
1196
|
+
mkdirSync(resolve4(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
|
|
1197
|
+
writeFileSync(resolve4(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
|
|
1198
|
+
`, "utf8");
|
|
1199
|
+
patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
|
|
1200
|
+
prompt = `${prompt}
|
|
1201
|
+
|
|
1202
|
+
Rig planning classification:
|
|
1203
|
+
${JSON.stringify(persistedPlanning, null, 2)}
|
|
1204
|
+
${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
1205
|
if (input.runtimeAdapter === "pi") {
|
|
1156
1206
|
for (const stage of ["Connect", "GitHub/task sync", "Prepare workspace", "Launch Pi", "Plan", "Implement"]) {
|
|
1157
1207
|
appendPiStageLog({
|
|
@@ -1193,11 +1243,11 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1193
1243
|
let reviewAction;
|
|
1194
1244
|
let verificationStarted = false;
|
|
1195
1245
|
let reviewStarted = false;
|
|
1196
|
-
let latestRuntimeWorkspace = null;
|
|
1197
|
-
let latestSessionDir = null;
|
|
1198
|
-
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;
|
|
1199
1249
|
let latestProviderCommand = null;
|
|
1200
|
-
let latestRuntimeBranch = null;
|
|
1250
|
+
let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
|
|
1201
1251
|
let snapshotSidecarPromise = null;
|
|
1202
1252
|
let dirtyBaselineApplied = false;
|
|
1203
1253
|
const childEnv = {
|
|
@@ -1215,9 +1265,14 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1215
1265
|
RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
|
|
1216
1266
|
RIG_SERVER_RUN_ID: input.runId
|
|
1217
1267
|
},
|
|
1218
|
-
...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
|
+
} : {}
|
|
1219
1273
|
};
|
|
1220
1274
|
Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
|
|
1275
|
+
Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
|
|
1221
1276
|
const automationLimits = resolveTaskRunAutomationLimits(automationConfig);
|
|
1222
1277
|
const maxAttempts = automationLimits.maxValidationAttempts;
|
|
1223
1278
|
const promoteToValidating = (detail) => {
|
|
@@ -1281,13 +1336,20 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1281
1336
|
if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
|
|
1282
1337
|
dirtyBaselineApplied = true;
|
|
1283
1338
|
const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
|
|
1339
|
+
const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
|
|
1340
|
+
if (readyFile) {
|
|
1341
|
+
mkdirSync(resolve4(readyFile, ".."), { recursive: true });
|
|
1342
|
+
writeFileSync(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
|
|
1343
|
+
`, "utf8");
|
|
1344
|
+
}
|
|
1284
1345
|
appendRunLog(context.projectRoot, input.runId, {
|
|
1285
1346
|
id: `log:${input.runId}:dirty-baseline`,
|
|
1286
1347
|
title: "Dirty baseline snapshot",
|
|
1287
1348
|
detail: dirty.detail,
|
|
1288
1349
|
tone: dirty.applied ? "tool" : "info",
|
|
1289
1350
|
status: dirty.applied ? "completed" : "skipped",
|
|
1290
|
-
createdAt: new Date().toISOString()
|
|
1351
|
+
createdAt: new Date().toISOString(),
|
|
1352
|
+
payload: readyFile ? { readyFile } : undefined
|
|
1291
1353
|
});
|
|
1292
1354
|
emitServerRunEvent({ type: "log", runId: input.runId, title: "Dirty baseline snapshot" });
|
|
1293
1355
|
}
|
|
@@ -1513,7 +1575,36 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1513
1575
|
let reviewFailureDetail = null;
|
|
1514
1576
|
const stderrLines = [];
|
|
1515
1577
|
const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
|
|
1516
|
-
|
|
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) {
|
|
1517
1608
|
const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
|
|
1518
1609
|
const child = spawn(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
|
|
1519
1610
|
cwd: context.projectRoot,
|
|
@@ -1759,6 +1850,29 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
1759
1850
|
});
|
|
1760
1851
|
throw new CliError2(terminalFailureDetail, exit.code ?? 1);
|
|
1761
1852
|
}
|
|
1853
|
+
if (planningClassification.planningRequired) {
|
|
1854
|
+
const planWorkspace = latestRuntimeWorkspace ?? context.projectRoot;
|
|
1855
|
+
const expectedPlanPath = resolve4(planWorkspace, planningArtifactPath);
|
|
1856
|
+
if (!existsSync2(expectedPlanPath)) {
|
|
1857
|
+
const failedAt = new Date().toISOString();
|
|
1858
|
+
const failureDetail = `Planning was required (${planningClassification.reason}) but ${planningArtifactPath} was not written before implementation completed.`;
|
|
1859
|
+
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
1860
|
+
status: "needs_attention",
|
|
1861
|
+
completedAt: failedAt,
|
|
1862
|
+
errorText: failureDetail
|
|
1863
|
+
});
|
|
1864
|
+
appendRunLog(context.projectRoot, input.runId, {
|
|
1865
|
+
id: `log:${input.runId}:plan-artifact-missing`,
|
|
1866
|
+
title: "Required plan artifact missing",
|
|
1867
|
+
detail: failureDetail,
|
|
1868
|
+
tone: "error",
|
|
1869
|
+
status: "needs_attention",
|
|
1870
|
+
createdAt: failedAt
|
|
1871
|
+
});
|
|
1872
|
+
emitServerRunEvent({ type: "failed", runId: input.runId, error: failureDetail });
|
|
1873
|
+
throw new CliError2(failureDetail, 1);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1762
1876
|
const runPiPrFeedbackFix = async (message) => {
|
|
1763
1877
|
appendPiStageLog({
|
|
1764
1878
|
projectRoot: context.projectRoot,
|
|
@@ -1929,5 +2043,6 @@ export {
|
|
|
1929
2043
|
buildTaskRunReviewEnv,
|
|
1930
2044
|
buildPiValidationRetrySteeringPrompt,
|
|
1931
2045
|
buildPiRigBridgeEnv,
|
|
2046
|
+
buildDirtyBaselineHandshakeEnv,
|
|
1932
2047
|
applyDirtyBaselineSnapshot
|
|
1933
2048
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli/src/commands/task.ts
|
|
3
|
-
import { readFileSync as
|
|
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
|
|
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
|
|
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
|
|
399
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
|
|
382
400
|
import { homedir as homedir2 } from "os";
|
|
383
|
-
import { resolve as
|
|
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
|
|
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 ??
|
|
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(
|
|
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 =
|
|
1122
|
+
content = readFileSync4(resolve4(context.projectRoot, fileFlag.value), "utf-8");
|
|
1105
1123
|
} else {
|
|
1106
1124
|
content = await readStdin();
|
|
1107
1125
|
}
|