@h-rig/cli 0.0.6-alpha.1 → 0.0.6-alpha.11
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 +481 -226
- 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 +47 -9
- 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 +185 -67
- 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 +125 -16
- package/dist/src/commands/task.js +28 -10
- package/dist/src/commands.js +469 -217
- package/dist/src/index.js +481 -226
- package/dist/src/launcher.js +4 -2
- package/dist/src/runner.js +3 -2
- package/package.json +5 -4
|
@@ -62,6 +62,8 @@ function normalizeRuntimeAdapter(value) {
|
|
|
62
62
|
|
|
63
63
|
// packages/cli/src/commands/_server-client.ts
|
|
64
64
|
import { spawnSync } from "child_process";
|
|
65
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
66
|
+
import { resolve as resolve2 } from "path";
|
|
65
67
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
66
68
|
|
|
67
69
|
// packages/cli/src/commands/_connection-state.ts
|
|
@@ -152,9 +154,25 @@ function cleanToken(value) {
|
|
|
152
154
|
const trimmed = value?.trim();
|
|
153
155
|
return trimmed ? trimmed : null;
|
|
154
156
|
}
|
|
155
|
-
function
|
|
157
|
+
function readPrivateRemoteSessionToken(projectRoot) {
|
|
158
|
+
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
159
|
+
if (!existsSync2(path))
|
|
160
|
+
return null;
|
|
161
|
+
try {
|
|
162
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
163
|
+
return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
156
169
|
if (cachedGitHubBearerToken !== undefined)
|
|
157
170
|
return cachedGitHubBearerToken;
|
|
171
|
+
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
172
|
+
if (privateSession) {
|
|
173
|
+
cachedGitHubBearerToken = privateSession;
|
|
174
|
+
return cachedGitHubBearerToken;
|
|
175
|
+
}
|
|
158
176
|
const envToken = cleanToken(process.env.RIG_GITHUB_TOKEN) ?? cleanToken(process.env.GITHUB_TOKEN) ?? cleanToken(process.env.GH_TOKEN);
|
|
159
177
|
if (envToken) {
|
|
160
178
|
cachedGitHubBearerToken = envToken;
|
|
@@ -174,7 +192,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
174
192
|
if (selected?.connection.kind === "remote") {
|
|
175
193
|
return {
|
|
176
194
|
baseUrl: selected.connection.baseUrl,
|
|
177
|
-
authToken: readGitHubBearerTokenForRemote(),
|
|
195
|
+
authToken: readGitHubBearerTokenForRemote(projectRoot),
|
|
178
196
|
connectionKind: "remote"
|
|
179
197
|
};
|
|
180
198
|
}
|
|
@@ -281,7 +299,7 @@ async function executeServer(context, args, options) {
|
|
|
281
299
|
const authTokenResult = takeOption(pending, "--auth-token");
|
|
282
300
|
pending = authTokenResult.rest;
|
|
283
301
|
requireNoExtraArgs(pending, "bun run rig server start [--host <host>] [--port <n>] [--poll-ms <n>] [--auth-token <token>]");
|
|
284
|
-
const commandParts = ["
|
|
302
|
+
const commandParts = ["rig-server", "start"];
|
|
285
303
|
if (hostResult.value) {
|
|
286
304
|
commandParts.push("--host", hostResult.value);
|
|
287
305
|
}
|
|
@@ -304,7 +322,7 @@ async function executeServer(context, args, options) {
|
|
|
304
322
|
const eventResult = takeOption(pending, "--event");
|
|
305
323
|
pending = eventResult.rest;
|
|
306
324
|
requireNoExtraArgs(pending, "bun run rig server notify-test [--event <type>]");
|
|
307
|
-
const commandParts = ["
|
|
325
|
+
const commandParts = ["rig-server", "notify-test"];
|
|
308
326
|
if (eventResult.value) {
|
|
309
327
|
commandParts.push("--event", eventResult.value);
|
|
310
328
|
}
|
|
@@ -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
|
}
|
|
@@ -1056,8 +1082,11 @@ function stringArrayField(record, key) {
|
|
|
1056
1082
|
}
|
|
1057
1083
|
async function executeRigOwnedTaskRun(context, input) {
|
|
1058
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();
|
|
1059
1088
|
const sourceTask = readRunSourceTaskContract(context.projectRoot, input.runId, input.taskId);
|
|
1060
|
-
|
|
1089
|
+
let prompt = buildRunPrompt({
|
|
1061
1090
|
projectRoot: context.projectRoot,
|
|
1062
1091
|
taskId: input.taskId,
|
|
1063
1092
|
fallbackTitle: input.title,
|
|
@@ -1111,14 +1140,14 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1111
1140
|
taskId: runtimeTaskId,
|
|
1112
1141
|
createdAt: startedAt,
|
|
1113
1142
|
runtimeAdapter: input.runtimeAdapter,
|
|
1114
|
-
status: "created"
|
|
1143
|
+
status: resumeMode ? "preparing" : "created"
|
|
1115
1144
|
});
|
|
1116
1145
|
patchAuthorityRun(context.projectRoot, input.runId, {
|
|
1117
1146
|
status: "preparing",
|
|
1118
1147
|
startedAt,
|
|
1119
1148
|
completedAt: null,
|
|
1120
1149
|
errorText: null,
|
|
1121
|
-
artifactRoot: null,
|
|
1150
|
+
artifactRoot: resumeMode ? existingRunRecord?.artifactRoot ?? null : null,
|
|
1122
1151
|
runtimeAdapter: input.runtimeAdapter,
|
|
1123
1152
|
runtimeMode: input.runtimeMode,
|
|
1124
1153
|
interactionMode: input.interactionMode,
|
|
@@ -1134,9 +1163,9 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1134
1163
|
detail: input.taskId ?? input.title ?? runtimeTaskId
|
|
1135
1164
|
});
|
|
1136
1165
|
appendRunLog(context.projectRoot, input.runId, {
|
|
1137
|
-
id: `log:${input.runId}:start`,
|
|
1138
|
-
title: "Rig task run started",
|
|
1139
|
-
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,
|
|
1140
1169
|
tone: "info",
|
|
1141
1170
|
status: "preparing",
|
|
1142
1171
|
createdAt: startedAt
|
|
@@ -1157,7 +1186,22 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1157
1186
|
const loadedAutomationConfig = await loadTaskRunAutomationConfig(context.projectRoot);
|
|
1158
1187
|
const automationConfig = input.prMode ? { ...loadedAutomationConfig ?? {}, pr: { ...loadedAutomationConfig?.pr ?? {}, mode: input.prMode } } : loadedAutomationConfig;
|
|
1159
1188
|
const planningClassification = classifyPlanningNeed({ config: automationConfig, sourceTask });
|
|
1160
|
-
|
|
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."}`;
|
|
1161
1205
|
if (input.runtimeAdapter === "pi") {
|
|
1162
1206
|
for (const stage of ["Connect", "GitHub/task sync", "Prepare workspace", "Launch Pi", "Plan", "Implement"]) {
|
|
1163
1207
|
appendPiStageLog({
|
|
@@ -1199,11 +1243,11 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1199
1243
|
let reviewAction;
|
|
1200
1244
|
let verificationStarted = false;
|
|
1201
1245
|
let reviewStarted = false;
|
|
1202
|
-
let latestRuntimeWorkspace = null;
|
|
1203
|
-
let latestSessionDir = null;
|
|
1204
|
-
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;
|
|
1205
1249
|
let latestProviderCommand = null;
|
|
1206
|
-
let latestRuntimeBranch = null;
|
|
1250
|
+
let latestRuntimeBranch = resumeMode && typeof existingRunRecord?.branch === "string" ? existingRunRecord.branch : null;
|
|
1207
1251
|
let snapshotSidecarPromise = null;
|
|
1208
1252
|
let dirtyBaselineApplied = false;
|
|
1209
1253
|
const childEnv = {
|
|
@@ -1221,9 +1265,14 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1221
1265
|
RIG_RUNTIME_ADAPTER: input.runtimeAdapter,
|
|
1222
1266
|
RIG_SERVER_RUN_ID: input.runId
|
|
1223
1267
|
},
|
|
1224
|
-
...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
|
+
} : {}
|
|
1225
1273
|
};
|
|
1226
1274
|
Object.assign(childEnv, buildTaskRunReviewEnv(automationConfig));
|
|
1275
|
+
Object.assign(childEnv, buildDirtyBaselineHandshakeEnv({ projectRoot: context.projectRoot, runId: input.runId, baselineMode: input.baselineMode }));
|
|
1227
1276
|
const automationLimits = resolveTaskRunAutomationLimits(automationConfig);
|
|
1228
1277
|
const maxAttempts = automationLimits.maxValidationAttempts;
|
|
1229
1278
|
const promoteToValidating = (detail) => {
|
|
@@ -1287,13 +1336,20 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1287
1336
|
if (!dirtyBaselineApplied && input.baselineMode === "dirty-snapshot" && latestRuntimeWorkspace) {
|
|
1288
1337
|
dirtyBaselineApplied = true;
|
|
1289
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
|
+
}
|
|
1290
1345
|
appendRunLog(context.projectRoot, input.runId, {
|
|
1291
1346
|
id: `log:${input.runId}:dirty-baseline`,
|
|
1292
1347
|
title: "Dirty baseline snapshot",
|
|
1293
1348
|
detail: dirty.detail,
|
|
1294
1349
|
tone: dirty.applied ? "tool" : "info",
|
|
1295
1350
|
status: dirty.applied ? "completed" : "skipped",
|
|
1296
|
-
createdAt: new Date().toISOString()
|
|
1351
|
+
createdAt: new Date().toISOString(),
|
|
1352
|
+
payload: readyFile ? { readyFile } : undefined
|
|
1297
1353
|
});
|
|
1298
1354
|
emitServerRunEvent({ type: "log", runId: input.runId, title: "Dirty baseline snapshot" });
|
|
1299
1355
|
}
|
|
@@ -1519,7 +1575,36 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
1519
1575
|
let reviewFailureDetail = null;
|
|
1520
1576
|
const stderrLines = [];
|
|
1521
1577
|
const piAcceptedArtifactExitGraceMs = resolvePiAcceptedArtifactExitGraceMs(childEnv);
|
|
1522
|
-
|
|
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) {
|
|
1523
1608
|
const attemptHostAgentCommand = buildHostAgentCommand(currentPrompt);
|
|
1524
1609
|
const child = spawn(attemptHostAgentCommand[0], attemptHostAgentCommand.slice(1), {
|
|
1525
1610
|
cwd: context.projectRoot,
|
|
@@ -1765,6 +1850,29 @@ Failed to update task source for ${input.taskId ?? runtimeTaskId} to failed: ${e
|
|
|
1765
1850
|
});
|
|
1766
1851
|
throw new CliError2(terminalFailureDetail, exit.code ?? 1);
|
|
1767
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
|
+
}
|
|
1768
1876
|
const runPiPrFeedbackFix = async (message) => {
|
|
1769
1877
|
appendPiStageLog({
|
|
1770
1878
|
projectRoot: context.projectRoot,
|
|
@@ -1935,5 +2043,6 @@ export {
|
|
|
1935
2043
|
buildTaskRunReviewEnv,
|
|
1936
2044
|
buildPiValidationRetrySteeringPrompt,
|
|
1937
2045
|
buildPiRigBridgeEnv,
|
|
2046
|
+
buildDirtyBaselineHandshakeEnv,
|
|
1938
2047
|
applyDirtyBaselineSnapshot
|
|
1939
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
|
}
|