@h-rig/cli 0.0.6-alpha.87 → 0.0.6-alpha.89
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 +1380 -865
- package/dist/src/app/board.js +462 -48
- package/dist/src/app-opentui/adapters/common.d.ts +3 -0
- package/dist/src/app-opentui/adapters/common.js +4 -0
- package/dist/src/app-opentui/adapters/doctor.js +458 -46
- package/dist/src/app-opentui/adapters/family.js +701 -151
- package/dist/src/app-opentui/adapters/fleet.js +477 -46
- package/dist/src/app-opentui/adapters/inbox.js +477 -46
- package/dist/src/app-opentui/adapters/init.js +497 -74
- package/dist/src/app-opentui/adapters/inspect.js +477 -46
- package/dist/src/app-opentui/adapters/pi-attach.d.ts +7 -0
- package/dist/src/app-opentui/adapters/pi-attach.js +1004 -519
- package/dist/src/app-opentui/adapters/run-detail.js +477 -46
- package/dist/src/app-opentui/adapters/server.d.ts +26 -0
- package/dist/src/app-opentui/adapters/server.js +676 -59
- package/dist/src/app-opentui/adapters/tasks.js +621 -549
- package/dist/src/app-opentui/autocomplete.js +4 -2
- package/dist/src/app-opentui/bootstrap.js +1376 -861
- package/dist/src/app-opentui/command-palette.js +37 -10
- package/dist/src/app-opentui/index.js +632 -528
- package/dist/src/app-opentui/intent.js +33 -8
- package/dist/src/app-opentui/keymap.js +43 -414
- package/dist/src/app-opentui/pi-host-child.js +496 -57
- package/dist/src/app-opentui/pi-pty-host.d.ts +14 -64
- package/dist/src/app-opentui/pi-pty-host.js +3 -397
- package/dist/src/app-opentui/react/App.js +144 -469
- package/dist/src/app-opentui/react/ChromeHost.js +44 -415
- package/dist/src/app-opentui/react/launch.js +659 -552
- package/dist/src/app-opentui/react/nav.js +1 -1
- package/dist/src/app-opentui/registry.js +1181 -742
- package/dist/src/app-opentui/remote-link.d.ts +10 -0
- package/dist/src/app-opentui/remote-link.js +47 -0
- package/dist/src/app-opentui/render/terminal-handoff.d.ts +16 -0
- package/dist/src/app-opentui/render/terminal-handoff.js +14 -0
- package/dist/src/app-opentui/runtime.js +632 -528
- package/dist/src/app-opentui/scenes/doctor.js +1 -1
- package/dist/src/app-opentui/scenes/error.js +50 -4
- package/dist/src/app-opentui/scenes/family.js +60 -6
- package/dist/src/app-opentui/scenes/fleet.js +65 -13
- package/dist/src/app-opentui/scenes/help.js +4 -2
- package/dist/src/app-opentui/scenes/init.js +12 -12
- package/dist/src/app-opentui/scenes/main.js +7 -7
- package/dist/src/app-opentui/scenes/server.js +83 -11
- package/dist/src/app-opentui/scenes/tasks.js +79 -16
- package/dist/src/app-opentui/state.js +25 -5
- package/dist/src/app-opentui/surface-catalog.js +4 -2
- package/dist/src/app-opentui/types.d.ts +1 -1
- package/dist/src/commands/_cli-format.d.ts +10 -1
- package/dist/src/commands/_cli-format.js +5 -2
- package/dist/src/commands/_connection-state.d.ts +11 -1
- package/dist/src/commands/_connection-state.js +50 -5
- package/dist/src/commands/_doctor-checks.js +458 -46
- package/dist/src/commands/_help-catalog.js +4 -2
- package/dist/src/commands/_json-output.js +4 -0
- package/dist/src/commands/_operator-view.js +496 -57
- package/dist/src/commands/_pi-frontend.d.ts +25 -0
- package/dist/src/commands/_pi-frontend.js +497 -57
- package/dist/src/commands/_preflight.js +509 -72
- package/dist/src/commands/_server-client.d.ts +33 -0
- package/dist/src/commands/_server-client.js +477 -46
- package/dist/src/commands/_server-events.js +446 -41
- package/dist/src/commands/_snapshot-upload.js +460 -48
- package/dist/src/commands/connect.js +620 -15
- package/dist/src/commands/doctor.js +458 -46
- package/dist/src/commands/github.js +462 -50
- package/dist/src/commands/inbox.js +458 -46
- package/dist/src/commands/init.js +497 -74
- package/dist/src/commands/inspect.js +458 -46
- package/dist/src/commands/run.js +496 -57
- package/dist/src/commands/server.js +647 -163
- package/dist/src/commands/setup.js +463 -51
- package/dist/src/commands/stats.js +462 -48
- package/dist/src/commands/task-run-driver.js +464 -52
- package/dist/src/commands/task.js +551 -85
- package/dist/src/commands.js +701 -151
- package/dist/src/index.js +705 -151
- package/dist/src/launcher.js +4 -0
- package/package.json +8 -8
|
@@ -183,12 +183,13 @@ var init__help_catalog = __esm(() => {
|
|
|
183
183
|
{
|
|
184
184
|
name: "server",
|
|
185
185
|
summary: "Choose, inspect, and start the Rig server that owns tasks and runs.",
|
|
186
|
-
usage: ["rig server <status|list|add|use|start> [options]"],
|
|
186
|
+
usage: ["rig server <status|list|add|use|repair-link|start> [options]"],
|
|
187
187
|
commands: [
|
|
188
|
-
{ command: "status", description: "Show the selected server for this repo.", primary: true },
|
|
188
|
+
{ command: "status", description: "Show the selected server and remote project-root link for this repo.", primary: true },
|
|
189
189
|
{ command: "use local", description: "Switch this repo to the local Rig server.", primary: true },
|
|
190
190
|
{ command: "add <alias> <url>", description: "Save a remote Rig server URL.", primary: true },
|
|
191
191
|
{ command: "use <alias>", description: "Select a saved remote server alias.", primary: true },
|
|
192
|
+
{ command: "repair-link [--prepare|--backfill-only] [--repo owner/repo]", description: "Backfill or prepare the selected remote's server-host checkout link.", primary: true },
|
|
192
193
|
{ command: "list", description: "List saved local/remote server aliases.", primary: true },
|
|
193
194
|
{ command: "start [--host <host>] [--port <n>]", description: "Start a local rig-server process." }
|
|
194
195
|
],
|
|
@@ -196,6 +197,7 @@ var init__help_catalog = __esm(() => {
|
|
|
196
197
|
"rig server status",
|
|
197
198
|
"rig server add prod https://where.rig-does.work",
|
|
198
199
|
"rig server use prod",
|
|
200
|
+
"rig server repair-link --repo owner/repo",
|
|
199
201
|
"rig server use local",
|
|
200
202
|
"rig server start --port 3773"
|
|
201
203
|
],
|
|
@@ -3546,12 +3548,28 @@ function readRepoConnection(projectRoot) {
|
|
|
3546
3548
|
selected,
|
|
3547
3549
|
project: typeof record.project === "string" ? record.project : undefined,
|
|
3548
3550
|
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
3549
|
-
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
3551
|
+
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined,
|
|
3552
|
+
serverProjectRootAlias: typeof record.serverProjectRootAlias === "string" && record.serverProjectRootAlias.trim() ? record.serverProjectRootAlias.trim() : undefined,
|
|
3553
|
+
serverProjectRootBaseUrl: typeof record.serverProjectRootBaseUrl === "string" && record.serverProjectRootBaseUrl.trim() ? record.serverProjectRootBaseUrl.trim().replace(/\/+$/, "") : undefined
|
|
3550
3554
|
};
|
|
3551
3555
|
}
|
|
3552
3556
|
function writeRepoConnection(projectRoot, state) {
|
|
3553
3557
|
writeJsonFile2(resolveRepoConnectionPath(projectRoot), state);
|
|
3554
3558
|
}
|
|
3559
|
+
function rootAllowedForSelection(repo, connection) {
|
|
3560
|
+
const root = repo.serverProjectRoot?.trim();
|
|
3561
|
+
if (!root)
|
|
3562
|
+
return;
|
|
3563
|
+
if (connection.kind === "remote") {
|
|
3564
|
+
if (repo.serverProjectRootAlias !== repo.selected)
|
|
3565
|
+
return;
|
|
3566
|
+
if (repo.serverProjectRootBaseUrl !== connection.baseUrl)
|
|
3567
|
+
return;
|
|
3568
|
+
} else if (repo.serverProjectRootAlias && repo.serverProjectRootAlias !== repo.selected) {
|
|
3569
|
+
return;
|
|
3570
|
+
}
|
|
3571
|
+
return root;
|
|
3572
|
+
}
|
|
3555
3573
|
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
3556
3574
|
const repo = readRepoConnection(projectRoot);
|
|
3557
3575
|
if (!repo)
|
|
@@ -3563,13 +3581,41 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
3563
3581
|
if (!connection) {
|
|
3564
3582
|
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
3565
3583
|
}
|
|
3566
|
-
return { alias: repo.selected, connection, serverProjectRoot: repo
|
|
3584
|
+
return { alias: repo.selected, connection, serverProjectRoot: rootAllowedForSelection(repo, connection) };
|
|
3585
|
+
}
|
|
3586
|
+
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot, metadata = {}) {
|
|
3587
|
+
const repo = readRepoConnection(projectRoot);
|
|
3588
|
+
if (!repo)
|
|
3589
|
+
return;
|
|
3590
|
+
let inferred = metadata;
|
|
3591
|
+
if (!inferred.alias || !inferred.baseUrl) {
|
|
3592
|
+
try {
|
|
3593
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
3594
|
+
if (selected?.connection.kind === "remote") {
|
|
3595
|
+
inferred = {
|
|
3596
|
+
alias: inferred.alias ?? selected.alias,
|
|
3597
|
+
baseUrl: inferred.baseUrl ?? selected.connection.baseUrl
|
|
3598
|
+
};
|
|
3599
|
+
}
|
|
3600
|
+
} catch {}
|
|
3601
|
+
}
|
|
3602
|
+
writeRepoConnection(projectRoot, {
|
|
3603
|
+
...repo,
|
|
3604
|
+
...metadata.project ? { project: metadata.project } : {},
|
|
3605
|
+
serverProjectRoot,
|
|
3606
|
+
...inferred.alias ? { serverProjectRootAlias: inferred.alias } : {},
|
|
3607
|
+
...inferred.baseUrl ? { serverProjectRootBaseUrl: inferred.baseUrl.replace(/\/+$/, "") } : {}
|
|
3608
|
+
});
|
|
3567
3609
|
}
|
|
3568
|
-
function
|
|
3610
|
+
function clearRepoServerProjectRoot(projectRoot) {
|
|
3569
3611
|
const repo = readRepoConnection(projectRoot);
|
|
3570
3612
|
if (!repo)
|
|
3571
3613
|
return;
|
|
3572
|
-
writeRepoConnection(projectRoot, {
|
|
3614
|
+
writeRepoConnection(projectRoot, {
|
|
3615
|
+
selected: repo.selected,
|
|
3616
|
+
...repo.project ? { project: repo.project } : {},
|
|
3617
|
+
...repo.linkedAt ? { linkedAt: repo.linkedAt } : {}
|
|
3618
|
+
});
|
|
3573
3619
|
}
|
|
3574
3620
|
function isRemoteConnectionSelected(projectRoot) {
|
|
3575
3621
|
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
@@ -3579,8 +3625,8 @@ var init__connection_state = __esm(() => {
|
|
|
3579
3625
|
});
|
|
3580
3626
|
|
|
3581
3627
|
// packages/cli/src/commands/_server-client.ts
|
|
3582
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
3583
|
-
import { resolve as resolve10 } from "path";
|
|
3628
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
3629
|
+
import { dirname as dirname3, isAbsolute, resolve as resolve10 } from "path";
|
|
3584
3630
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
3585
3631
|
function setServerPhaseListener(listener) {
|
|
3586
3632
|
const previous = serverPhaseListener;
|
|
@@ -3626,11 +3672,10 @@ function readStoredGitHubAuthToken(projectRoot) {
|
|
|
3626
3672
|
const parsed = readRemoteAuthState(projectRoot);
|
|
3627
3673
|
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
3628
3674
|
}
|
|
3629
|
-
function
|
|
3630
|
-
|
|
3631
|
-
const slug = repo?.project?.trim();
|
|
3632
|
-
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
3675
|
+
function inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug) {
|
|
3676
|
+
if (!/^[^/]+\/[^/]+$/.test(repoSlug))
|
|
3633
3677
|
return null;
|
|
3678
|
+
const repo = readRepoConnection(projectRoot);
|
|
3634
3679
|
const auth = readRemoteAuthState(projectRoot);
|
|
3635
3680
|
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
3636
3681
|
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
@@ -3639,25 +3684,419 @@ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
|
3639
3684
|
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
3640
3685
|
if (!checkoutBaseDir)
|
|
3641
3686
|
return null;
|
|
3642
|
-
|
|
3643
|
-
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
3644
|
-
return inferred;
|
|
3687
|
+
return `${checkoutBaseDir.replace(/\/+$/, "")}/${repoSlug}`;
|
|
3645
3688
|
}
|
|
3646
3689
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
3647
3690
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
3648
3691
|
}
|
|
3692
|
+
function normalizeRepoSlug(value) {
|
|
3693
|
+
const slug = value?.trim();
|
|
3694
|
+
return slug && /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
|
|
3695
|
+
}
|
|
3696
|
+
function readProjectLinkSlug(projectRoot) {
|
|
3697
|
+
const path = resolve10(projectRoot, ".rig", "state", "project-link.json");
|
|
3698
|
+
if (!existsSync6(path))
|
|
3699
|
+
return null;
|
|
3700
|
+
try {
|
|
3701
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
3702
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
3703
|
+
return null;
|
|
3704
|
+
return normalizeRepoSlug(typeof parsed.repoSlug === "string" ? String(parsed.repoSlug) : null);
|
|
3705
|
+
} catch {
|
|
3706
|
+
return null;
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
function writeProjectLinkConnection(projectRoot, repoSlug, alias) {
|
|
3710
|
+
const path = resolve10(projectRoot, ".rig", "state", "project-link.json");
|
|
3711
|
+
mkdirSync6(dirname3(path), { recursive: true });
|
|
3712
|
+
writeFileSync5(path, `${JSON.stringify({ repoSlug, connection: alias, linkedAt: new Date().toISOString() }, null, 2)}
|
|
3713
|
+
`, "utf8");
|
|
3714
|
+
}
|
|
3715
|
+
function remoteLinkRepairCommand(repoSlug) {
|
|
3716
|
+
return repoSlug ? `rig server repair-link --repo ${repoSlug}` : "rig server repair-link --repo owner/repo";
|
|
3717
|
+
}
|
|
3718
|
+
function formatRemoteProjectLinkHint(resolution) {
|
|
3719
|
+
const repair = remoteLinkRepairCommand(resolution.repoSlug);
|
|
3720
|
+
if (resolution.status === "auth_required") {
|
|
3721
|
+
return `Authenticate the selected remote${resolution.alias ? ` (${resolution.alias})` : ""}, then run \`${repair}\`. Use \`rig github auth import-gh\` or \`rig init --repair --server remote --github-auth device${resolution.repoSlug ? ` --repo ${resolution.repoSlug}` : ""}\`.`;
|
|
3722
|
+
}
|
|
3723
|
+
if (resolution.status === "missing_project") {
|
|
3724
|
+
return `Record the repo slug, then run \`${repair}\` (or \`rig init --repo owner/repo --server remote --github-auth device\`).`;
|
|
3725
|
+
}
|
|
3726
|
+
if (resolution.status === "not_remote")
|
|
3727
|
+
return "Select a remote server with `rig server use <alias>` before repairing a remote project link.";
|
|
3728
|
+
return `Run \`${repair}\` to backfill or prepare a server-host checkout for the selected remote${resolution.alias ? ` (${resolution.alias})` : ""}.`;
|
|
3729
|
+
}
|
|
3730
|
+
function remoteProjectLinkFailure(input) {
|
|
3731
|
+
const partial = {
|
|
3732
|
+
status: input.status,
|
|
3733
|
+
alias: input.alias,
|
|
3734
|
+
baseUrl: input.baseUrl,
|
|
3735
|
+
repoSlug: input.repoSlug
|
|
3736
|
+
};
|
|
3737
|
+
return {
|
|
3738
|
+
ok: false,
|
|
3739
|
+
...input,
|
|
3740
|
+
hint: formatRemoteProjectLinkHint(partial),
|
|
3741
|
+
next: remoteLinkRepairCommand(input.repoSlug)
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
3744
|
+
function remoteProjectLinkSuccess(input) {
|
|
3745
|
+
return {
|
|
3746
|
+
ok: true,
|
|
3747
|
+
status: input.status,
|
|
3748
|
+
alias: input.alias,
|
|
3749
|
+
baseUrl: input.baseUrl,
|
|
3750
|
+
repoSlug: input.repoSlug,
|
|
3751
|
+
serverProjectRoot: input.serverProjectRoot,
|
|
3752
|
+
source: input.source,
|
|
3753
|
+
prepared: input.prepared ?? input.status === "prepared",
|
|
3754
|
+
validated: input.validated ?? false,
|
|
3755
|
+
message: input.message ?? `Remote project link ready for ${input.repoSlug}.`,
|
|
3756
|
+
hint: "Remote-scoped task/run/status requests will send x-rig-project-root for this server-host checkout.",
|
|
3757
|
+
next: "rig task list"
|
|
3758
|
+
};
|
|
3759
|
+
}
|
|
3760
|
+
function localCheckoutPathCandidate(projectRoot, candidate) {
|
|
3761
|
+
try {
|
|
3762
|
+
const local = resolve10(projectRoot);
|
|
3763
|
+
const resolved = resolve10(candidate);
|
|
3764
|
+
return resolved === local || resolved.startsWith(`${local}/`);
|
|
3765
|
+
} catch {
|
|
3766
|
+
return false;
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
async function requestRemoteProjectLinkJson(baseUrl, pathname, authToken, init = {}) {
|
|
3770
|
+
const requestUrl = new URL(`${baseUrl}${pathname}`);
|
|
3771
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
3772
|
+
requestUrl.searchParams.set("rt", authToken);
|
|
3773
|
+
const response = await fetch(requestUrl, {
|
|
3774
|
+
...init,
|
|
3775
|
+
headers: mergeHeaders(init.headers, authToken)
|
|
3776
|
+
});
|
|
3777
|
+
const text = await response.text();
|
|
3778
|
+
const payload = text.trim().length > 0 ? (() => {
|
|
3779
|
+
try {
|
|
3780
|
+
return JSON.parse(text);
|
|
3781
|
+
} catch {
|
|
3782
|
+
return null;
|
|
3783
|
+
}
|
|
3784
|
+
})() : null;
|
|
3785
|
+
return { ok: response.ok, status: response.status, payload, text };
|
|
3786
|
+
}
|
|
3787
|
+
function payloadError(payload, fallback) {
|
|
3788
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
3789
|
+
const record = payload;
|
|
3790
|
+
const value = record.error ?? record.message ?? record.reason;
|
|
3791
|
+
if (typeof value === "string" && value.trim())
|
|
3792
|
+
return value.trim();
|
|
3793
|
+
}
|
|
3794
|
+
return fallback;
|
|
3795
|
+
}
|
|
3796
|
+
function checkoutPathsFromProjectPayload(payload) {
|
|
3797
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
3798
|
+
return [];
|
|
3799
|
+
const project = payload.project;
|
|
3800
|
+
if (!project || typeof project !== "object" || Array.isArray(project))
|
|
3801
|
+
return [];
|
|
3802
|
+
const checkouts = project.checkouts;
|
|
3803
|
+
if (!Array.isArray(checkouts))
|
|
3804
|
+
return [];
|
|
3805
|
+
return [...checkouts].reverse().flatMap((entry) => {
|
|
3806
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
3807
|
+
return [];
|
|
3808
|
+
const path = entry.path;
|
|
3809
|
+
return typeof path === "string" && path.trim() ? [path.trim()] : [];
|
|
3810
|
+
});
|
|
3811
|
+
}
|
|
3812
|
+
function checkoutPathFromPreparePayload(payload) {
|
|
3813
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
3814
|
+
return null;
|
|
3815
|
+
const checkout = payload.checkout;
|
|
3816
|
+
if (!checkout || typeof checkout !== "object" || Array.isArray(checkout))
|
|
3817
|
+
return null;
|
|
3818
|
+
const path = checkout.path;
|
|
3819
|
+
return typeof path === "string" && path.trim() ? path.trim() : null;
|
|
3820
|
+
}
|
|
3821
|
+
async function validateAndPersistRemoteRoot(input) {
|
|
3822
|
+
const candidate = input.candidate.trim();
|
|
3823
|
+
if (!candidate || !isAbsolute(candidate)) {
|
|
3824
|
+
return remoteProjectLinkFailure({
|
|
3825
|
+
status: "invalid_root",
|
|
3826
|
+
alias: input.alias,
|
|
3827
|
+
baseUrl: input.baseUrl,
|
|
3828
|
+
repoSlug: input.repoSlug,
|
|
3829
|
+
message: `Remote project root candidate is not an absolute server-host path: ${candidate || "(empty)"}.`
|
|
3830
|
+
});
|
|
3831
|
+
}
|
|
3832
|
+
if (localCheckoutPathCandidate(input.projectRoot, candidate)) {
|
|
3833
|
+
return remoteProjectLinkFailure({
|
|
3834
|
+
status: "invalid_root",
|
|
3835
|
+
alias: input.alias,
|
|
3836
|
+
baseUrl: input.baseUrl,
|
|
3837
|
+
repoSlug: input.repoSlug,
|
|
3838
|
+
message: `Refusing to use local checkout path ${candidate} as a remote server project root.`
|
|
3839
|
+
});
|
|
3840
|
+
}
|
|
3841
|
+
let response;
|
|
3842
|
+
try {
|
|
3843
|
+
response = await requestRemoteProjectLinkJson(input.baseUrl, "/api/server/project-root", input.authToken, {
|
|
3844
|
+
method: "POST",
|
|
3845
|
+
headers: { "content-type": "application/json" },
|
|
3846
|
+
body: JSON.stringify({ projectRoot: candidate })
|
|
3847
|
+
});
|
|
3848
|
+
} catch (error) {
|
|
3849
|
+
return remoteProjectLinkFailure({
|
|
3850
|
+
status: "error",
|
|
3851
|
+
alias: input.alias,
|
|
3852
|
+
baseUrl: input.baseUrl,
|
|
3853
|
+
repoSlug: input.repoSlug,
|
|
3854
|
+
message: `Could not validate remote project root ${candidate}: ${error instanceof Error ? error.message : String(error)}`
|
|
3855
|
+
});
|
|
3856
|
+
}
|
|
3857
|
+
if (response.status === 401 || response.status === 403) {
|
|
3858
|
+
return remoteProjectLinkFailure({
|
|
3859
|
+
status: "auth_required",
|
|
3860
|
+
alias: input.alias,
|
|
3861
|
+
baseUrl: input.baseUrl,
|
|
3862
|
+
repoSlug: input.repoSlug,
|
|
3863
|
+
statusCode: response.status,
|
|
3864
|
+
message: `Remote server ${input.alias} requires authentication before validating project root ${candidate}.`
|
|
3865
|
+
});
|
|
3866
|
+
}
|
|
3867
|
+
if (!response.ok) {
|
|
3868
|
+
return remoteProjectLinkFailure({
|
|
3869
|
+
status: "invalid_root",
|
|
3870
|
+
alias: input.alias,
|
|
3871
|
+
baseUrl: input.baseUrl,
|
|
3872
|
+
repoSlug: input.repoSlug,
|
|
3873
|
+
statusCode: response.status,
|
|
3874
|
+
message: `Remote server rejected project root ${candidate} (${response.status}): ${payloadError(response.payload, response.text || "project-root validation failed")}`
|
|
3875
|
+
});
|
|
3876
|
+
}
|
|
3877
|
+
const record = response.payload && typeof response.payload === "object" && !Array.isArray(response.payload) ? response.payload : {};
|
|
3878
|
+
if (record.ok !== true) {
|
|
3879
|
+
return remoteProjectLinkFailure({
|
|
3880
|
+
status: "invalid_root",
|
|
3881
|
+
alias: input.alias,
|
|
3882
|
+
baseUrl: input.baseUrl,
|
|
3883
|
+
repoSlug: input.repoSlug,
|
|
3884
|
+
message: `Remote server did not accept project root ${candidate}: ${payloadError(response.payload, "project-root validation failed")}`
|
|
3885
|
+
});
|
|
3886
|
+
}
|
|
3887
|
+
const accepted = typeof record.projectRoot === "string" && record.projectRoot.trim() ? record.projectRoot.trim() : typeof record.requestedProjectRoot === "string" && record.requestedProjectRoot.trim() ? record.requestedProjectRoot.trim() : candidate;
|
|
3888
|
+
writeRepoServerProjectRoot(input.projectRoot, accepted, { alias: input.alias, baseUrl: input.baseUrl, project: input.repoSlug });
|
|
3889
|
+
writeProjectLinkConnection(input.projectRoot, input.repoSlug, input.alias);
|
|
3890
|
+
const status = input.status ?? (input.prepared ? "prepared" : "backfilled");
|
|
3891
|
+
return remoteProjectLinkSuccess({
|
|
3892
|
+
status,
|
|
3893
|
+
alias: input.alias,
|
|
3894
|
+
baseUrl: input.baseUrl,
|
|
3895
|
+
repoSlug: input.repoSlug,
|
|
3896
|
+
serverProjectRoot: accepted,
|
|
3897
|
+
source: input.source,
|
|
3898
|
+
prepared: input.prepared,
|
|
3899
|
+
validated: true,
|
|
3900
|
+
message: status === "ready" ? `Remote project link already points to ${accepted} and was validated on ${input.alias}.` : input.prepared ? `Prepared and linked remote checkout ${accepted} for ${input.repoSlug}.` : `Backfilled remote checkout ${accepted} for ${input.repoSlug}.`
|
|
3901
|
+
});
|
|
3902
|
+
}
|
|
3903
|
+
async function ensureRemoteProjectRootLink(projectRoot, options = {}) {
|
|
3904
|
+
let selected;
|
|
3905
|
+
try {
|
|
3906
|
+
selected = resolveSelectedConnection(projectRoot);
|
|
3907
|
+
} catch (error) {
|
|
3908
|
+
return remoteProjectLinkFailure({ status: "error", message: error instanceof Error ? error.message : String(error) });
|
|
3909
|
+
}
|
|
3910
|
+
if (!selected || selected.connection.kind !== "remote") {
|
|
3911
|
+
return remoteProjectLinkFailure({ status: "not_remote", message: "No remote Rig server is selected for this repo." });
|
|
3912
|
+
}
|
|
3913
|
+
const repo = readRepoConnection(projectRoot);
|
|
3914
|
+
const explicitRepoRequested = options.repoSlug !== undefined;
|
|
3915
|
+
const explicitRepoSlug = explicitRepoRequested ? normalizeRepoSlug(options.repoSlug) : null;
|
|
3916
|
+
const repoProjectSlug = normalizeRepoSlug(repo?.project);
|
|
3917
|
+
const repoSlug = explicitRepoSlug ?? repoProjectSlug ?? readProjectLinkSlug(projectRoot);
|
|
3918
|
+
const alias = selected.alias;
|
|
3919
|
+
const baseUrl = selected.connection.baseUrl;
|
|
3920
|
+
const authToken = options.authToken === undefined ? readGitHubBearerTokenForRemote(projectRoot) : options.authToken;
|
|
3921
|
+
const mode = options.mode ?? "backfill-only";
|
|
3922
|
+
if (explicitRepoRequested && !explicitRepoSlug) {
|
|
3923
|
+
return remoteProjectLinkFailure({
|
|
3924
|
+
status: "missing_project",
|
|
3925
|
+
alias,
|
|
3926
|
+
baseUrl,
|
|
3927
|
+
message: `Invalid --repo value ${JSON.stringify(options.repoSlug)}. Expected owner/repo.`
|
|
3928
|
+
});
|
|
3929
|
+
}
|
|
3930
|
+
if (!repoSlug) {
|
|
3931
|
+
return remoteProjectLinkFailure({
|
|
3932
|
+
status: "missing_project",
|
|
3933
|
+
alias,
|
|
3934
|
+
baseUrl,
|
|
3935
|
+
message: `Remote server ${alias} is selected, but this checkout has no GitHub repo slug recorded.`
|
|
3936
|
+
});
|
|
3937
|
+
}
|
|
3938
|
+
const skippedCandidates = [];
|
|
3939
|
+
const storedRoot = selected.serverProjectRoot?.trim() || null;
|
|
3940
|
+
const storedRootMatchesResolvedRepo = repoProjectSlug === repoSlug;
|
|
3941
|
+
if (storedRoot && storedRootMatchesResolvedRepo) {
|
|
3942
|
+
if (localCheckoutPathCandidate(projectRoot, storedRoot)) {
|
|
3943
|
+
clearRepoServerProjectRoot(projectRoot);
|
|
3944
|
+
skippedCandidates.push(storedRoot);
|
|
3945
|
+
} else {
|
|
3946
|
+
const storedResult = await validateAndPersistRemoteRoot({
|
|
3947
|
+
projectRoot,
|
|
3948
|
+
alias,
|
|
3949
|
+
baseUrl,
|
|
3950
|
+
authToken,
|
|
3951
|
+
repoSlug,
|
|
3952
|
+
candidate: storedRoot,
|
|
3953
|
+
source: "stored",
|
|
3954
|
+
status: "ready"
|
|
3955
|
+
});
|
|
3956
|
+
if (storedResult.ok || storedResult.status === "auth_required" || storedResult.status === "error")
|
|
3957
|
+
return storedResult;
|
|
3958
|
+
clearRepoServerProjectRoot(projectRoot);
|
|
3959
|
+
skippedCandidates.push(storedRoot);
|
|
3960
|
+
}
|
|
3961
|
+
} else if (storedRoot) {
|
|
3962
|
+
skippedCandidates.push(storedRoot);
|
|
3963
|
+
}
|
|
3964
|
+
const authCandidate = inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug);
|
|
3965
|
+
if (authCandidate) {
|
|
3966
|
+
const authResult = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: authCandidate, source: "auth-state" });
|
|
3967
|
+
if (authResult.ok || authResult.status === "auth_required")
|
|
3968
|
+
return authResult;
|
|
3969
|
+
}
|
|
3970
|
+
let registryResponse;
|
|
3971
|
+
try {
|
|
3972
|
+
registryResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}`, authToken);
|
|
3973
|
+
} catch (error) {
|
|
3974
|
+
return remoteProjectLinkFailure({
|
|
3975
|
+
status: "error",
|
|
3976
|
+
alias,
|
|
3977
|
+
baseUrl,
|
|
3978
|
+
repoSlug,
|
|
3979
|
+
message: `Could not read remote project registry for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
|
|
3980
|
+
});
|
|
3981
|
+
}
|
|
3982
|
+
if (registryResponse.status === 401 || registryResponse.status === 403) {
|
|
3983
|
+
return remoteProjectLinkFailure({
|
|
3984
|
+
status: "auth_required",
|
|
3985
|
+
alias,
|
|
3986
|
+
baseUrl,
|
|
3987
|
+
repoSlug,
|
|
3988
|
+
statusCode: registryResponse.status,
|
|
3989
|
+
message: `Remote server ${alias} requires authentication before reading the ${repoSlug} project registry.`
|
|
3990
|
+
});
|
|
3991
|
+
}
|
|
3992
|
+
if (registryResponse.ok) {
|
|
3993
|
+
const candidates = checkoutPathsFromProjectPayload(registryResponse.payload);
|
|
3994
|
+
for (const candidate of candidates) {
|
|
3995
|
+
const result = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate, source: "registry" });
|
|
3996
|
+
if (result.ok || result.status === "auth_required")
|
|
3997
|
+
return result;
|
|
3998
|
+
skippedCandidates.push(candidate);
|
|
3999
|
+
}
|
|
4000
|
+
if (mode === "backfill-only") {
|
|
4001
|
+
return remoteProjectLinkFailure({
|
|
4002
|
+
status: candidates.length > 0 ? "invalid_root" : "no_server_checkout",
|
|
4003
|
+
alias,
|
|
4004
|
+
baseUrl,
|
|
4005
|
+
repoSlug,
|
|
4006
|
+
message: candidates.length > 0 ? `Remote registry has checkout candidates for ${repoSlug}, but none validated for ${alias}.` : `Remote registry has ${repoSlug}, but no server checkout path is linked yet.`,
|
|
4007
|
+
skippedCandidates
|
|
4008
|
+
});
|
|
4009
|
+
}
|
|
4010
|
+
} else if (registryResponse.status === 404) {
|
|
4011
|
+
if (mode === "backfill-only") {
|
|
4012
|
+
return remoteProjectLinkFailure({
|
|
4013
|
+
status: "project_not_registered",
|
|
4014
|
+
alias,
|
|
4015
|
+
baseUrl,
|
|
4016
|
+
repoSlug,
|
|
4017
|
+
statusCode: registryResponse.status,
|
|
4018
|
+
message: `Remote server ${alias} has no registered project record for ${repoSlug}.`
|
|
4019
|
+
});
|
|
4020
|
+
}
|
|
4021
|
+
} else {
|
|
4022
|
+
return remoteProjectLinkFailure({
|
|
4023
|
+
status: "error",
|
|
4024
|
+
alias,
|
|
4025
|
+
baseUrl,
|
|
4026
|
+
repoSlug,
|
|
4027
|
+
statusCode: registryResponse.status,
|
|
4028
|
+
message: `Could not backfill ${repoSlug} from remote registry (${registryResponse.status}): ${payloadError(registryResponse.payload, registryResponse.text || "registry lookup failed")}`,
|
|
4029
|
+
skippedCandidates
|
|
4030
|
+
});
|
|
4031
|
+
}
|
|
4032
|
+
let prepareResponse;
|
|
4033
|
+
try {
|
|
4034
|
+
prepareResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}/prepare-checkout`, authToken, {
|
|
4035
|
+
method: "POST",
|
|
4036
|
+
headers: { "content-type": "application/json" },
|
|
4037
|
+
body: JSON.stringify({ checkout: { kind: "managed-clone" }, repoUrl: `https://github.com/${repoSlug}.git` })
|
|
4038
|
+
});
|
|
4039
|
+
} catch (error) {
|
|
4040
|
+
return remoteProjectLinkFailure({
|
|
4041
|
+
status: "error",
|
|
4042
|
+
alias,
|
|
4043
|
+
baseUrl,
|
|
4044
|
+
repoSlug,
|
|
4045
|
+
message: `Could not prepare remote checkout for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
|
|
4046
|
+
});
|
|
4047
|
+
}
|
|
4048
|
+
if (prepareResponse.status === 401 || prepareResponse.status === 403) {
|
|
4049
|
+
return remoteProjectLinkFailure({
|
|
4050
|
+
status: "auth_required",
|
|
4051
|
+
alias,
|
|
4052
|
+
baseUrl,
|
|
4053
|
+
repoSlug,
|
|
4054
|
+
statusCode: prepareResponse.status,
|
|
4055
|
+
message: `Remote server ${alias} requires authentication before preparing ${repoSlug}.`
|
|
4056
|
+
});
|
|
4057
|
+
}
|
|
4058
|
+
if (!prepareResponse.ok) {
|
|
4059
|
+
return remoteProjectLinkFailure({
|
|
4060
|
+
status: "error",
|
|
4061
|
+
alias,
|
|
4062
|
+
baseUrl,
|
|
4063
|
+
repoSlug,
|
|
4064
|
+
statusCode: prepareResponse.status,
|
|
4065
|
+
message: `Remote checkout prepare failed for ${repoSlug} (${prepareResponse.status}): ${payloadError(prepareResponse.payload, prepareResponse.text || "prepare-checkout failed")}`,
|
|
4066
|
+
skippedCandidates
|
|
4067
|
+
});
|
|
4068
|
+
}
|
|
4069
|
+
const preparedPath = checkoutPathFromPreparePayload(prepareResponse.payload);
|
|
4070
|
+
if (!preparedPath) {
|
|
4071
|
+
return remoteProjectLinkFailure({
|
|
4072
|
+
status: "invalid_root",
|
|
4073
|
+
alias,
|
|
4074
|
+
baseUrl,
|
|
4075
|
+
repoSlug,
|
|
4076
|
+
message: `Remote checkout prepare for ${repoSlug} did not return a checkout.path.`,
|
|
4077
|
+
skippedCandidates
|
|
4078
|
+
});
|
|
4079
|
+
}
|
|
4080
|
+
return validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: preparedPath, source: "prepare", prepared: true });
|
|
4081
|
+
}
|
|
4082
|
+
async function repairRemoteProjectRootLink(context, options = {}) {
|
|
4083
|
+
const resolution = await ensureRemoteProjectRootLink(context.projectRoot, { mode: options.mode ?? "prepare-if-missing", repoSlug: options.repoSlug });
|
|
4084
|
+
if (!resolution.ok)
|
|
4085
|
+
throw new CliError(resolution.message, 1, { hint: resolution.hint });
|
|
4086
|
+
return resolution;
|
|
4087
|
+
}
|
|
3649
4088
|
async function ensureServerForCli(projectRoot) {
|
|
3650
4089
|
try {
|
|
3651
4090
|
const selected = resolveSelectedConnection(projectRoot);
|
|
3652
4091
|
if (selected?.connection.kind === "remote") {
|
|
3653
4092
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
3654
4093
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
3655
|
-
const
|
|
4094
|
+
const link = await ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only", authToken });
|
|
3656
4095
|
return {
|
|
3657
4096
|
baseUrl: selected.connection.baseUrl,
|
|
3658
4097
|
authToken,
|
|
3659
4098
|
connectionKind: "remote",
|
|
3660
|
-
serverProjectRoot
|
|
4099
|
+
serverProjectRoot: link.ok ? link.serverProjectRoot ?? null : null
|
|
3661
4100
|
};
|
|
3662
4101
|
}
|
|
3663
4102
|
reportServerPhase("Starting local Rig server\u2026");
|
|
@@ -3675,32 +4114,6 @@ async function ensureServerForCli(projectRoot) {
|
|
|
3675
4114
|
throw error;
|
|
3676
4115
|
}
|
|
3677
4116
|
}
|
|
3678
|
-
async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
|
|
3679
|
-
const repo = readRepoConnection(projectRoot);
|
|
3680
|
-
const slug = repo?.project?.trim();
|
|
3681
|
-
if (!slug)
|
|
3682
|
-
return null;
|
|
3683
|
-
try {
|
|
3684
|
-
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
3685
|
-
if (authToken && queryAuthFallbackEnabled())
|
|
3686
|
-
url.searchParams.set("rt", authToken);
|
|
3687
|
-
const response = await fetch(url, {
|
|
3688
|
-
headers: mergeHeaders(undefined, authToken)
|
|
3689
|
-
});
|
|
3690
|
-
if (!response.ok)
|
|
3691
|
-
return null;
|
|
3692
|
-
const payload = await response.json();
|
|
3693
|
-
const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
|
|
3694
|
-
const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
|
|
3695
|
-
const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
|
|
3696
|
-
const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
|
|
3697
|
-
if (path)
|
|
3698
|
-
writeRepoServerProjectRoot(projectRoot, path);
|
|
3699
|
-
return path;
|
|
3700
|
-
} catch {
|
|
3701
|
-
return null;
|
|
3702
|
-
}
|
|
3703
|
-
}
|
|
3704
4117
|
function appendTaskFilterParams(url, filters) {
|
|
3705
4118
|
if (filters.assignee)
|
|
3706
4119
|
url.searchParams.set("assignee", filters.assignee);
|
|
@@ -3801,13 +4214,20 @@ function canUseRemoteWithoutProjectRoot(pathname) {
|
|
|
3801
4214
|
async function requestServerJson(context, pathname, init = {}) {
|
|
3802
4215
|
const server = await ensureServerForCli(context.projectRoot);
|
|
3803
4216
|
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
4217
|
+
let scopedServerProjectRoot = server.serverProjectRoot;
|
|
4218
|
+
if (server.connectionKind === "remote" && !scopedServerProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
4219
|
+
const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: server.authToken });
|
|
4220
|
+
if (link.ok && link.serverProjectRoot) {
|
|
4221
|
+
scopedServerProjectRoot = link.serverProjectRoot;
|
|
4222
|
+
} else {
|
|
4223
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
4224
|
+
const target = link.alias && link.baseUrl ? `${link.alias} (${link.baseUrl})` : server.baseUrl;
|
|
4225
|
+
throw new CliError(`Remote server ${target} is selected for ${link.repoSlug ?? repo?.project ?? "this repo"}, but this checkout has no server-host project root link. ${link.message}`, 1, { hint: link.hint });
|
|
4226
|
+
}
|
|
3807
4227
|
}
|
|
3808
4228
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
3809
|
-
if (
|
|
3810
|
-
headers.set("x-rig-project-root",
|
|
4229
|
+
if (scopedServerProjectRoot)
|
|
4230
|
+
headers.set("x-rig-project-root", scopedServerProjectRoot);
|
|
3811
4231
|
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
3812
4232
|
requestUrl.searchParams.set("rt", server.authToken);
|
|
3813
4233
|
}
|
|
@@ -4173,6 +4593,14 @@ function permissionAllowsPr(payload) {
|
|
|
4173
4593
|
function isNotFoundError(error) {
|
|
4174
4594
|
return /\b(404|not found)\b/i.test(message(error));
|
|
4175
4595
|
}
|
|
4596
|
+
function isLoopbackBaseUrl(baseUrl) {
|
|
4597
|
+
try {
|
|
4598
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
4599
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
4600
|
+
} catch {
|
|
4601
|
+
return false;
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4176
4604
|
function projectCheckoutReady(payload) {
|
|
4177
4605
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
4178
4606
|
return null;
|
|
@@ -4227,25 +4655,37 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
4227
4655
|
}
|
|
4228
4656
|
const repo = readRepoConnection(context.projectRoot);
|
|
4229
4657
|
const remoteWithoutProjectLink = selectedServer?.connectionKind === "remote" && !repo?.project;
|
|
4230
|
-
|
|
4658
|
+
let remoteRootBlocked = false;
|
|
4659
|
+
checks.push(repo ? preflightCheck("project-link", "project linked to selected server", repo.project ? "pass" : remoteWithoutProjectLink ? "fail" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : " (missing project link)"}`, remoteWithoutProjectLink ? "Run `rig server repair-link --repo owner/repo` or `rig init --repo owner/repo --server remote` to restore the remote project link." : "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to selected server", legacyServerCompatibility ? "warn" : "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
|
|
4660
|
+
if (selectedServer?.connectionKind === "remote" && repo?.project && !selectedServer.serverProjectRoot) {
|
|
4661
|
+
const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: selectedServer.authToken });
|
|
4662
|
+
const loopbackDevRemote = !selectedServer.authToken && isLoopbackBaseUrl(selectedServer.baseUrl);
|
|
4663
|
+
checks.push(preflightCheck("remote-project-root-link", "remote server checkout/root linked", link.ok ? "pass" : loopbackDevRemote ? "warn" : "fail", link.ok ? `${link.repoSlug} -> ${link.serverProjectRoot}` : link.message, link.ok ? undefined : loopbackDevRemote ? "Loopback development remotes may continue without x-rig-project-root; production remotes must run `rig server repair-link`." : link.hint));
|
|
4664
|
+
remoteRootBlocked = !link.ok && !loopbackDevRemote;
|
|
4665
|
+
}
|
|
4231
4666
|
try {
|
|
4232
4667
|
const auth = await request("/api/github/auth/status");
|
|
4233
4668
|
checks.push(isAuthenticated(auth) ? preflightCheck("github-auth", "GitHub auth valid", "pass") : preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
|
|
4234
4669
|
} catch (error) {
|
|
4235
4670
|
checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
4236
4671
|
}
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4672
|
+
if (!remoteRootBlocked) {
|
|
4673
|
+
try {
|
|
4674
|
+
const projection = await request("/api/workspace/task-projection");
|
|
4675
|
+
checks.push(preflightCheck("task-projection", "task projection ready", "pass", JSON.stringify(projection).slice(0, 120)));
|
|
4676
|
+
} catch (error) {
|
|
4677
|
+
checks.push(preflightCheck("task-projection", "task projection ready", "warn", message(error), "Refresh task projection with `rig task list` before launching."));
|
|
4678
|
+
}
|
|
4679
|
+
try {
|
|
4680
|
+
const permissions = await request("/api/github/repo/permissions");
|
|
4681
|
+
const allowed = permissionAllowsPr(permissions);
|
|
4682
|
+
checks.push(allowed === false ? preflightCheck("github-repo-permissions", "GitHub PR permissions", "fail", JSON.stringify(permissions).slice(0, 120), "Grant the selected GitHub token permission to push branches and open PRs.") : preflightCheck("github-repo-permissions", "GitHub PR permissions", allowed === true ? "pass" : "warn", JSON.stringify(permissions).slice(0, 120), "Confirm the selected token can push branches and open PRs."));
|
|
4683
|
+
} catch (error) {
|
|
4684
|
+
checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", message(error), "Ensure the selected token can push branches and open PRs."));
|
|
4685
|
+
}
|
|
4686
|
+
} else {
|
|
4687
|
+
checks.push(preflightCheck("task-projection", "task projection ready", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
|
|
4688
|
+
checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
|
|
4249
4689
|
}
|
|
4250
4690
|
if (repo?.project) {
|
|
4251
4691
|
try {
|
|
@@ -4257,19 +4697,24 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
4257
4697
|
}
|
|
4258
4698
|
}
|
|
4259
4699
|
if (taskId) {
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4700
|
+
if (!remoteRootBlocked) {
|
|
4701
|
+
try {
|
|
4702
|
+
const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
|
|
4703
|
+
const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
|
|
4704
|
+
checks.push(found ? preflightCheck("issue", "task/issue accessible", "pass", taskId) : preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", taskId, "Confirm the issue exists and matches the configured task filters."));
|
|
4705
|
+
} catch (error) {
|
|
4706
|
+
checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
|
|
4707
|
+
}
|
|
4708
|
+
try {
|
|
4709
|
+
const runs = await request("/api/runs?limit=200");
|
|
4710
|
+
const duplicate = activeDuplicateRun(runs, taskId);
|
|
4711
|
+
checks.push(duplicate ? preflightCheck("duplicate-active-run", "one active run per task", "fail", String(duplicate.runId ?? taskId), "Attach to or stop the existing run before starting another one for this task.") : preflightCheck("duplicate-active-run", "one active run per task", "pass", taskId));
|
|
4712
|
+
} catch (error) {
|
|
4713
|
+
checks.push(preflightCheck("duplicate-active-run", "one active run per task", "warn", message(error), "Could not list active runs; the server will still reject conflicting runs if configured."));
|
|
4714
|
+
}
|
|
4715
|
+
} else {
|
|
4716
|
+
checks.push(preflightCheck("issue", "task/issue accessible", "fail", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
|
|
4717
|
+
checks.push(preflightCheck("duplicate-active-run", "one active run per task", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
|
|
4273
4718
|
}
|
|
4274
4719
|
}
|
|
4275
4720
|
if ((options.runtimeAdapter ?? "pi") === "pi") {
|
|
@@ -4750,7 +5195,7 @@ import {
|
|
|
4750
5195
|
chmodSync,
|
|
4751
5196
|
copyFileSync as copyFileSync2,
|
|
4752
5197
|
existsSync as existsSync8,
|
|
4753
|
-
mkdirSync as
|
|
5198
|
+
mkdirSync as mkdirSync7,
|
|
4754
5199
|
readdirSync,
|
|
4755
5200
|
readlinkSync,
|
|
4756
5201
|
rmSync as rmSync3,
|
|
@@ -4855,7 +5300,7 @@ async function executeDist(context, args) {
|
|
|
4855
5300
|
requireNoExtraArgs(pending, "rig dist install [--scope user|system] [--path <dir>]");
|
|
4856
5301
|
const scope = parseInstallScope(scopeResult.value);
|
|
4857
5302
|
const installDir = resolveInstallDir(scope, pathResult.value);
|
|
4858
|
-
|
|
5303
|
+
mkdirSync7(installDir, { recursive: true });
|
|
4859
5304
|
let source = await findLatestDistBinary(context.projectRoot);
|
|
4860
5305
|
let buildDir = null;
|
|
4861
5306
|
if (!source) {
|
|
@@ -4910,7 +5355,7 @@ async function executeDist(context, args) {
|
|
|
4910
5355
|
const fp = await computeRuntimeImageFingerprint(context.projectRoot);
|
|
4911
5356
|
const currentId = computeRuntimeImageId(fp);
|
|
4912
5357
|
const imagesDir = resolve13(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
|
|
4913
|
-
|
|
5358
|
+
mkdirSync7(imagesDir, { recursive: true });
|
|
4914
5359
|
let pruned = 0;
|
|
4915
5360
|
for (const entry of readdirSync(imagesDir, { withFileTypes: true })) {
|
|
4916
5361
|
if (entry.isDirectory() && entry.name !== currentId) {
|
|
@@ -4922,9 +5367,9 @@ async function executeDist(context, args) {
|
|
|
4922
5367
|
console.log(`Pruned ${pruned} stale image(s).`);
|
|
4923
5368
|
}
|
|
4924
5369
|
const imageDir = resolve13(imagesDir, currentId);
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
5370
|
+
mkdirSync7(resolve13(imageDir, "bin/hooks"), { recursive: true });
|
|
5371
|
+
mkdirSync7(resolve13(imageDir, "bin/plugins"), { recursive: true });
|
|
5372
|
+
mkdirSync7(resolve13(imageDir, "bin/validators"), { recursive: true });
|
|
4928
5373
|
const hookNames = [
|
|
4929
5374
|
"scope-guard",
|
|
4930
5375
|
"import-guard",
|
|
@@ -4949,8 +5394,8 @@ async function executeDist(context, args) {
|
|
|
4949
5394
|
const binPluginsDir = resolve13(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
|
|
4950
5395
|
const validatorsRoot = resolve13(hostProjectRoot, "packages/runtime/src/control-plane/validators");
|
|
4951
5396
|
const binValidatorsDir = resolve13(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
|
|
4952
|
-
|
|
4953
|
-
|
|
5397
|
+
mkdirSync7(binPluginsDir, { recursive: true });
|
|
5398
|
+
mkdirSync7(binValidatorsDir, { recursive: true });
|
|
4954
5399
|
if (existsSync8(pluginsDir)) {
|
|
4955
5400
|
for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
|
|
4956
5401
|
const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
|
|
@@ -5389,16 +5834,19 @@ function formatConnectionList(connections) {
|
|
|
5389
5834
|
return [formatSection("Rig servers", `${rows.length} available`), `${pc4.bold(pad("ALIAS", aliasWidth))} ${pc4.bold("KIND")} ${pc4.bold("TARGET")}`, ...lines, "", ...formatNextSteps(["Select one: `rig server use <alias|local>`"])].join(`
|
|
5390
5835
|
`);
|
|
5391
5836
|
}
|
|
5392
|
-
function formatConnectionStatus(selected, connections) {
|
|
5837
|
+
function formatConnectionStatus(selected, connections, repo, remoteProjectLink) {
|
|
5393
5838
|
const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
|
|
5394
5839
|
const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
|
|
5840
|
+
const rootLabel = remoteProjectLink ? remoteProjectLink.ok ? `ready \xB7 ${remoteProjectLink.serverProjectRoot ?? repo?.serverProjectRoot ?? "linked"}` : `${remoteProjectLink.status ?? "needs repair"} \xB7 ${remoteProjectLink.hint ?? remoteProjectLink.message ?? "run rig server repair-link"}` : repo?.serverProjectRoot ? repo.serverProjectRoot : "not required";
|
|
5395
5841
|
return [
|
|
5396
5842
|
formatSection("Rig server", "selected for this repo"),
|
|
5397
5843
|
`${themeFaint("\u2502")} ${themeDim("selected ")} ${pc4.bold(selected)}`,
|
|
5398
5844
|
`${themeFaint("\u2502")} ${themeDim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
|
|
5399
5845
|
`${themeFaint("\u2502")} ${themeDim("target ")} ${target ?? "not configured"}`,
|
|
5846
|
+
...repo?.project ? [`${themeFaint("\u2502")} ${themeDim("project ")} ${repo.project}`] : [],
|
|
5847
|
+
...connection?.kind === "remote" ? [`${themeFaint("\u2502")} ${themeDim("root ")} ${rootLabel}`] : [],
|
|
5400
5848
|
"",
|
|
5401
|
-
...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
|
|
5849
|
+
...formatNextSteps(remoteProjectLink && !remoteProjectLink.ok ? ["Repair remote link: `rig server repair-link`", "Authenticate first if needed: `rig github auth import-gh`"] : ["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
|
|
5402
5850
|
].join(`
|
|
5403
5851
|
`);
|
|
5404
5852
|
}
|
|
@@ -5858,7 +6306,7 @@ var init__pi_install = () => {};
|
|
|
5858
6306
|
|
|
5859
6307
|
// packages/cli/src/commands/_snapshot-upload.ts
|
|
5860
6308
|
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
5861
|
-
import { dirname as
|
|
6309
|
+
import { dirname as dirname4, resolve as resolve15, relative, sep } from "path";
|
|
5862
6310
|
function toPosixPath(path) {
|
|
5863
6311
|
return path.split(sep).join("/");
|
|
5864
6312
|
}
|
|
@@ -6187,7 +6635,7 @@ var init__doctor_checks = __esm(() => {
|
|
|
6187
6635
|
});
|
|
6188
6636
|
|
|
6189
6637
|
// packages/cli/src/commands/init.ts
|
|
6190
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as
|
|
6638
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
6191
6639
|
import { spawnSync } from "child_process";
|
|
6192
6640
|
import { basename as basename2, resolve as resolve17 } from "path";
|
|
6193
6641
|
import { buildRigInitConfigSource } from "@rig/core";
|
|
@@ -6211,14 +6659,14 @@ function parseRepoSlug(value) {
|
|
|
6211
6659
|
}
|
|
6212
6660
|
function ensureRigPrivateDirs(projectRoot) {
|
|
6213
6661
|
const rigDir = resolve17(projectRoot, ".rig");
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6662
|
+
mkdirSync8(resolve17(rigDir, "state"), { recursive: true });
|
|
6663
|
+
mkdirSync8(resolve17(rigDir, "logs"), { recursive: true });
|
|
6664
|
+
mkdirSync8(resolve17(rigDir, "runs"), { recursive: true });
|
|
6665
|
+
mkdirSync8(resolve17(rigDir, "tmp"), { recursive: true });
|
|
6666
|
+
mkdirSync8(resolve17(projectRoot, "artifacts"), { recursive: true });
|
|
6219
6667
|
const taskConfigPath = resolve17(rigDir, "task-config.json");
|
|
6220
6668
|
if (!existsSync11(taskConfigPath))
|
|
6221
|
-
|
|
6669
|
+
writeFileSync6(taskConfigPath, `{}
|
|
6222
6670
|
`, "utf-8");
|
|
6223
6671
|
}
|
|
6224
6672
|
function ensureGitignoreEntries(projectRoot) {
|
|
@@ -6246,7 +6694,7 @@ function ensureRigConfigPackageDependencies(projectRoot) {
|
|
|
6246
6694
|
...existsSync11(path) ? existing : { name: "rig-project", private: true },
|
|
6247
6695
|
devDependencies
|
|
6248
6696
|
};
|
|
6249
|
-
|
|
6697
|
+
writeFileSync6(path, `${JSON.stringify(next, null, 2)}
|
|
6250
6698
|
`, "utf8");
|
|
6251
6699
|
}
|
|
6252
6700
|
function applyGitHubProjectConfig(source, options) {
|
|
@@ -6272,9 +6720,9 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
|
|
|
6272
6720
|
const selected = strategy ?? { kind: "managed-clone" };
|
|
6273
6721
|
switch (selected.kind) {
|
|
6274
6722
|
case "managed-clone":
|
|
6275
|
-
return { kind: "managed-clone"
|
|
6723
|
+
return { kind: "managed-clone" };
|
|
6276
6724
|
case "current-ref":
|
|
6277
|
-
return { kind: "current-ref",
|
|
6725
|
+
return { kind: "current-ref", ...selected.ref ? { ref: selected.ref } : {} };
|
|
6278
6726
|
case "uploaded-snapshot":
|
|
6279
6727
|
return { kind: "uploaded-snapshot", path: projectRoot, source: "local-working-tree" };
|
|
6280
6728
|
case "existing-path":
|
|
@@ -6527,7 +6975,7 @@ function remoteGitHubAuthMetadata(payload) {
|
|
|
6527
6975
|
};
|
|
6528
6976
|
}
|
|
6529
6977
|
function writeRemoteGitHubAuthState(projectRoot, input) {
|
|
6530
|
-
|
|
6978
|
+
writeFileSync6(resolve17(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
|
|
6531
6979
|
authenticated: true,
|
|
6532
6980
|
source: input.source,
|
|
6533
6981
|
storedOnServer: true,
|
|
@@ -6575,7 +7023,7 @@ function runLocalFilesInit(context, options) {
|
|
|
6575
7023
|
console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
|
|
6576
7024
|
} else {
|
|
6577
7025
|
const projectName = basename2(projectRoot) || "rig-project";
|
|
6578
|
-
|
|
7026
|
+
writeFileSync6(configTsPath, buildRigInitConfigSource({
|
|
6579
7027
|
projectName,
|
|
6580
7028
|
taskSource: { kind: "files", path: "tasks" },
|
|
6581
7029
|
useStandardPlugin: true
|
|
@@ -6584,8 +7032,8 @@ function runLocalFilesInit(context, options) {
|
|
|
6584
7032
|
ensureRigConfigPackageDependencies(projectRoot);
|
|
6585
7033
|
const tasksDir = resolve17(projectRoot, "tasks");
|
|
6586
7034
|
if (!existsSync11(tasksDir)) {
|
|
6587
|
-
|
|
6588
|
-
|
|
7035
|
+
mkdirSync8(tasksDir, { recursive: true });
|
|
7036
|
+
writeFileSync6(resolve17(tasksDir, "T-1.json"), `${JSON.stringify({ id: "T-1", title: "My first Rig task", body: "Describe the change you want an agent to make." }, null, 2)}
|
|
6589
7037
|
`, "utf-8");
|
|
6590
7038
|
}
|
|
6591
7039
|
if (context.outputMode !== "json") {
|
|
@@ -6609,7 +7057,7 @@ function runDemoInit(context, options) {
|
|
|
6609
7057
|
console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it for the demo.");
|
|
6610
7058
|
}
|
|
6611
7059
|
} else {
|
|
6612
|
-
|
|
7060
|
+
writeFileSync6(configTsPath, buildRigInitConfigSource({
|
|
6613
7061
|
projectName: basename2(projectRoot) || "rig-demo",
|
|
6614
7062
|
taskSource: { kind: "files", path: DEMO_TASKS_RELATIVE_DIR },
|
|
6615
7063
|
useStandardPlugin: true
|
|
@@ -6618,14 +7066,14 @@ function runDemoInit(context, options) {
|
|
|
6618
7066
|
}
|
|
6619
7067
|
ensureRigConfigPackageDependencies(projectRoot);
|
|
6620
7068
|
const demoTasksDir = resolve17(projectRoot, DEMO_TASKS_RELATIVE_DIR);
|
|
6621
|
-
|
|
7069
|
+
mkdirSync8(demoTasksDir, { recursive: true });
|
|
6622
7070
|
const taskIds = [];
|
|
6623
7071
|
for (const task of DEMO_TASKS) {
|
|
6624
7072
|
const id = String(task.id);
|
|
6625
7073
|
taskIds.push(id);
|
|
6626
7074
|
const taskPath = resolve17(demoTasksDir, `${id}.json`);
|
|
6627
7075
|
if (!existsSync11(taskPath)) {
|
|
6628
|
-
|
|
7076
|
+
writeFileSync6(taskPath, `${JSON.stringify(task, null, 2)}
|
|
6629
7077
|
`, "utf-8");
|
|
6630
7078
|
}
|
|
6631
7079
|
}
|
|
@@ -6653,21 +7101,32 @@ function runDemoInit(context, options) {
|
|
|
6653
7101
|
}
|
|
6654
7102
|
async function runControlPlaneInit(context, options) {
|
|
6655
7103
|
const projectRoot = context.projectRoot;
|
|
6656
|
-
const
|
|
7104
|
+
const existingRepoConnection = readRepoConnection(projectRoot);
|
|
7105
|
+
const selectedConnection = (() => {
|
|
7106
|
+
try {
|
|
7107
|
+
return resolveSelectedConnection(projectRoot);
|
|
7108
|
+
} catch {
|
|
7109
|
+
return null;
|
|
7110
|
+
}
|
|
7111
|
+
})();
|
|
7112
|
+
const selectedRemote = selectedConnection?.connection.kind === "remote" ? { alias: selectedConnection.alias, connection: selectedConnection.connection } : null;
|
|
7113
|
+
const detectedSlug = options.repoSlug ?? existingRepoConnection?.project ?? detectOriginRepoSlug(projectRoot);
|
|
7114
|
+
const serverKind = options.server ?? (selectedRemote ? "remote" : "local");
|
|
6657
7115
|
if (!detectedSlug) {
|
|
6658
7116
|
const authMethod2 = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
|
|
6659
|
-
if (
|
|
7117
|
+
if (serverKind === "local" && authMethod2 === "skip") {
|
|
6660
7118
|
return runLocalFilesInit(context, options);
|
|
6661
7119
|
}
|
|
6662
7120
|
throw new CliError("Could not detect GitHub repo slug from origin. Pass --repo owner/repo \u2014 or run `rig init --yes --server local --github-auth skip` for a local files-source project without GitHub.", 1);
|
|
6663
7121
|
}
|
|
6664
7122
|
const repo = parseRepoSlug(detectedSlug);
|
|
6665
|
-
const
|
|
6666
|
-
const
|
|
7123
|
+
const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : selectedRemote?.alias ?? "remote");
|
|
7124
|
+
const selectedRemoteUrl = selectedRemote && (!options.connectionAlias || options.connectionAlias === selectedRemote.alias) ? selectedRemote.connection.baseUrl : undefined;
|
|
7125
|
+
const remoteUrl = serverKind === "remote" ? (options.remoteUrl ?? selectedRemoteUrl)?.replace(/\/+$/, "") : undefined;
|
|
6667
7126
|
if (serverKind === "remote") {
|
|
6668
|
-
if (!
|
|
6669
|
-
throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server
|
|
6670
|
-
upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl:
|
|
7127
|
+
if (!remoteUrl)
|
|
7128
|
+
throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server`, or first select a saved remote with `rig server use <alias>`." });
|
|
7129
|
+
upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: remoteUrl });
|
|
6671
7130
|
}
|
|
6672
7131
|
writeRepoConnection(projectRoot, {
|
|
6673
7132
|
selected: connectionAlias,
|
|
@@ -6690,11 +7149,11 @@ async function runControlPlaneInit(context, options) {
|
|
|
6690
7149
|
taskSource: { kind: "github-issues", owner: repo.owner, repo: repo.repo },
|
|
6691
7150
|
useStandardPlugin: true
|
|
6692
7151
|
}), options);
|
|
6693
|
-
|
|
7152
|
+
writeFileSync6(configTsPath, source, "utf-8");
|
|
6694
7153
|
}
|
|
6695
7154
|
ensureRigConfigPackageDependencies(projectRoot);
|
|
6696
7155
|
}
|
|
6697
|
-
|
|
7156
|
+
writeFileSync6(resolve17(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
|
|
6698
7157
|
`, "utf8");
|
|
6699
7158
|
const checkout = checkoutForInit(projectRoot, serverKind, options.remoteCheckout);
|
|
6700
7159
|
let uploadedSnapshot = null;
|
|
@@ -6709,7 +7168,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
6709
7168
|
let githubAuth = null;
|
|
6710
7169
|
let deviceAuth = null;
|
|
6711
7170
|
const authMethod = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
|
|
6712
|
-
const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${
|
|
7171
|
+
const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${remoteUrl ?? "the remote Rig server"}.` : null;
|
|
6713
7172
|
if (remoteGhTokenWarning && !options.yes) {
|
|
6714
7173
|
throw new CliError(`${remoteGhTokenWarning} Re-run with --yes to confirm this explicit token transfer.`, 1);
|
|
6715
7174
|
}
|
|
@@ -7089,8 +7548,8 @@ var init_init = __esm(() => {
|
|
|
7089
7548
|
|
|
7090
7549
|
// packages/cli/src/commands/github.ts
|
|
7091
7550
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
7092
|
-
import { mkdirSync as
|
|
7093
|
-
import { dirname as
|
|
7551
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
7552
|
+
import { dirname as dirname5, resolve as resolve18 } from "path";
|
|
7094
7553
|
function printPayload(context, payload, fallback) {
|
|
7095
7554
|
if (context.outputMode === "json")
|
|
7096
7555
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -7129,8 +7588,8 @@ function persistRemoteAuthSession(context, source, result, fallbackToken) {
|
|
|
7129
7588
|
return;
|
|
7130
7589
|
const repo = readRepoConnection(context.projectRoot);
|
|
7131
7590
|
const path = resolve18(context.projectRoot, ".rig", "state", "github-auth.json");
|
|
7132
|
-
|
|
7133
|
-
|
|
7591
|
+
mkdirSync9(dirname5(path), { recursive: true });
|
|
7592
|
+
writeFileSync7(path, `${JSON.stringify({
|
|
7134
7593
|
authenticated: true,
|
|
7135
7594
|
source,
|
|
7136
7595
|
storedOnServer: true,
|
|
@@ -8633,7 +9092,7 @@ var init__operator_surface = __esm(() => {
|
|
|
8633
9092
|
});
|
|
8634
9093
|
|
|
8635
9094
|
// packages/cli/src/commands/_pi-frontend.ts
|
|
8636
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
9095
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync10, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
8637
9096
|
import { homedir as homedir6, tmpdir } from "os";
|
|
8638
9097
|
import { join as join3 } from "path";
|
|
8639
9098
|
import { main as runPiMain } from "@earendil-works/pi-coding-agent";
|
|
@@ -8685,7 +9144,7 @@ function statusFromRunDetails(run) {
|
|
|
8685
9144
|
async function prepareOperatorConsole(context, runId, tempSessionDir) {
|
|
8686
9145
|
const server = await ensureServerForCli(context.projectRoot);
|
|
8687
9146
|
const localCwd = join3(homedir6(), ".rig", "drones", runId.slice(0, 8));
|
|
8688
|
-
|
|
9147
|
+
mkdirSync10(localCwd, { recursive: true });
|
|
8689
9148
|
trustDroneCwd(localCwd);
|
|
8690
9149
|
installRigPiTheme();
|
|
8691
9150
|
let sessionFileArg = [];
|
|
@@ -8707,7 +9166,7 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
|
|
|
8707
9166
|
return line;
|
|
8708
9167
|
}).join(`
|
|
8709
9168
|
`);
|
|
8710
|
-
|
|
9169
|
+
writeFileSync8(localSessionPath, content);
|
|
8711
9170
|
sessionFileArg = ["--session", localSessionPath];
|
|
8712
9171
|
}
|
|
8713
9172
|
} catch {}
|
|
@@ -8723,12 +9182,12 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
|
|
|
8723
9182
|
function trustDroneCwd(localCwd) {
|
|
8724
9183
|
try {
|
|
8725
9184
|
const agentDir = join3(homedir6(), ".pi", "agent");
|
|
8726
|
-
|
|
9185
|
+
mkdirSync10(agentDir, { recursive: true });
|
|
8727
9186
|
const trustPath = join3(agentDir, "trust.json");
|
|
8728
9187
|
const store = existsSync14(trustPath) ? JSON.parse(readFileSync10(trustPath, "utf8")) : {};
|
|
8729
9188
|
if (store[localCwd] !== true) {
|
|
8730
9189
|
store[localCwd] = true;
|
|
8731
|
-
|
|
9190
|
+
writeFileSync8(trustPath, `${JSON.stringify(store, null, "\t")}
|
|
8732
9191
|
`);
|
|
8733
9192
|
}
|
|
8734
9193
|
} catch {}
|
|
@@ -8736,15 +9195,38 @@ function trustDroneCwd(localCwd) {
|
|
|
8736
9195
|
function installRigPiTheme() {
|
|
8737
9196
|
try {
|
|
8738
9197
|
const themesDir = join3(homedir6(), ".pi", "agent", "themes");
|
|
8739
|
-
|
|
9198
|
+
mkdirSync10(themesDir, { recursive: true });
|
|
8740
9199
|
const themePath = join3(themesDir, "rig.json");
|
|
8741
9200
|
const next = `${JSON.stringify(RIG_PI_THEME, null, "\t")}
|
|
8742
9201
|
`;
|
|
8743
9202
|
if (!existsSync14(themePath) || readFileSync10(themePath, "utf8") !== next) {
|
|
8744
|
-
|
|
9203
|
+
writeFileSync8(themePath, next);
|
|
8745
9204
|
}
|
|
8746
9205
|
} catch {}
|
|
8747
9206
|
}
|
|
9207
|
+
async function runWithProcessExitGuard(body) {
|
|
9208
|
+
const realExit = process.exit;
|
|
9209
|
+
let exitCode = 0;
|
|
9210
|
+
let signalQuit = () => {};
|
|
9211
|
+
const quit = new Promise((resolve22) => {
|
|
9212
|
+
signalQuit = resolve22;
|
|
9213
|
+
});
|
|
9214
|
+
const guardedExit = (code) => {
|
|
9215
|
+
exitCode = typeof code === "number" ? code : 0;
|
|
9216
|
+
signalQuit();
|
|
9217
|
+
return;
|
|
9218
|
+
};
|
|
9219
|
+
process.exit = guardedExit;
|
|
9220
|
+
try {
|
|
9221
|
+
await Promise.race([Promise.resolve().then(body), quit]);
|
|
9222
|
+
} finally {
|
|
9223
|
+
process.exit = realExit;
|
|
9224
|
+
}
|
|
9225
|
+
return exitCode;
|
|
9226
|
+
}
|
|
9227
|
+
function runPiMainReturningOnQuit(args, options) {
|
|
9228
|
+
return runWithProcessExitGuard(() => runPiMain(args, options));
|
|
9229
|
+
}
|
|
8748
9230
|
async function attachRunBundledPiFrontend(context, input) {
|
|
8749
9231
|
const tempSessionDir = mkdtempSync(join3(tmpdir(), "rig-pi-frontend-sessions-"));
|
|
8750
9232
|
const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
|
|
@@ -8760,16 +9242,20 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
8760
9242
|
};
|
|
8761
9243
|
let detached = false;
|
|
8762
9244
|
try {
|
|
8763
|
-
|
|
9245
|
+
const piArgs = [
|
|
8764
9246
|
"--offline",
|
|
8765
9247
|
"--no-extensions",
|
|
8766
9248
|
"--no-skills",
|
|
8767
9249
|
"--no-prompt-templates",
|
|
8768
9250
|
"--no-context-files",
|
|
8769
9251
|
...sessionFileArg
|
|
8770
|
-
]
|
|
8771
|
-
|
|
8772
|
-
|
|
9252
|
+
];
|
|
9253
|
+
const piOptions = { extensionFactories: [piRigExtensionFactory] };
|
|
9254
|
+
if (input.returnOnQuit) {
|
|
9255
|
+
await runPiMainReturningOnQuit(piArgs, piOptions);
|
|
9256
|
+
} else {
|
|
9257
|
+
await runPiMain(piArgs, piOptions);
|
|
9258
|
+
}
|
|
8773
9259
|
detached = true;
|
|
8774
9260
|
} finally {
|
|
8775
9261
|
restoreEnv();
|
|
@@ -9498,13 +9984,46 @@ function parseConnection(alias, value, options) {
|
|
|
9498
9984
|
}
|
|
9499
9985
|
return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
|
|
9500
9986
|
}
|
|
9501
|
-
function printJsonOrText(context,
|
|
9502
|
-
if (context.outputMode
|
|
9503
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
9504
|
-
} else {
|
|
9987
|
+
function printJsonOrText(context, _payload, text2) {
|
|
9988
|
+
if (context.outputMode !== "json") {
|
|
9505
9989
|
console.log(text2);
|
|
9506
9990
|
}
|
|
9507
9991
|
}
|
|
9992
|
+
function formatRemoteProjectLinkText(result) {
|
|
9993
|
+
return formatSuccessCard(result.ok ? "Remote project link ready" : "Remote project link needs repair", [
|
|
9994
|
+
["selected", result.alias ?? "remote"],
|
|
9995
|
+
["target", result.baseUrl ?? "unknown"],
|
|
9996
|
+
["repo", result.repoSlug ?? "owner/repo"],
|
|
9997
|
+
["status", result.status],
|
|
9998
|
+
...result.serverProjectRoot ? [["server root", result.serverProjectRoot]] : [],
|
|
9999
|
+
["next", result.ok ? result.next ?? "rig task list" : result.hint]
|
|
10000
|
+
]);
|
|
10001
|
+
}
|
|
10002
|
+
function parseRepairLinkArgs(rest, usage) {
|
|
10003
|
+
let repoSlug;
|
|
10004
|
+
let mode = "prepare-if-missing";
|
|
10005
|
+
const pending = [...rest];
|
|
10006
|
+
while (pending.length > 0) {
|
|
10007
|
+
const token = pending.shift();
|
|
10008
|
+
if (token === "--repo") {
|
|
10009
|
+
const value = pending.shift()?.trim();
|
|
10010
|
+
if (!value)
|
|
10011
|
+
throw new CliError(`Missing --repo value. Usage: ${usage}`, 1);
|
|
10012
|
+
const normalized = normalizeRepoSlug(value);
|
|
10013
|
+
if (!normalized)
|
|
10014
|
+
throw new CliError(`Invalid --repo value: ${value}`, 1, { hint: "Use the canonical owner/repo slug, e.g. `--repo humanity-org/humanwork`." });
|
|
10015
|
+
repoSlug = normalized;
|
|
10016
|
+
} else if (token === "--backfill-only") {
|
|
10017
|
+
mode = "backfill-only";
|
|
10018
|
+
} else if (token === "--prepare") {
|
|
10019
|
+
mode = "prepare-if-missing";
|
|
10020
|
+
} else {
|
|
10021
|
+
throw new CliError(`Unexpected argument: ${String(token)}
|
|
10022
|
+
Usage: ${usage}`, 1);
|
|
10023
|
+
}
|
|
10024
|
+
}
|
|
10025
|
+
return { repoSlug, mode };
|
|
10026
|
+
}
|
|
9508
10027
|
async function promptForConnectionAlias(context) {
|
|
9509
10028
|
const state = readGlobalConnections();
|
|
9510
10029
|
const repo = readRepoConnection(context.projectRoot);
|
|
@@ -9558,37 +10077,67 @@ async function executeConnectionCommand(context, args, options) {
|
|
|
9558
10077
|
}
|
|
9559
10078
|
if (!alias)
|
|
9560
10079
|
throw new CliError(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
|
|
10080
|
+
let selectedConnection = { kind: "local", mode: "auto" };
|
|
9561
10081
|
if (alias !== "local") {
|
|
9562
10082
|
const state = readGlobalConnections();
|
|
9563
|
-
|
|
10083
|
+
const connection = state.connections[alias];
|
|
10084
|
+
if (!connection)
|
|
9564
10085
|
throw new CliError(`Unknown Rig server: ${alias}`, 1, { hint: "Run `rig server list` to see saved aliases, or save one with `rig server add <alias> <url>`." });
|
|
10086
|
+
selectedConnection = connection;
|
|
9565
10087
|
}
|
|
9566
10088
|
const previousRepo = readRepoConnection(context.projectRoot);
|
|
10089
|
+
const preserveRemoteRoot = Boolean(previousRepo?.serverProjectRoot && previousRepo.selected === alias && selectedConnection.kind === "remote" && previousRepo.serverProjectRootAlias === alias && previousRepo.serverProjectRootBaseUrl === selectedConnection.baseUrl);
|
|
9567
10090
|
const repoState = {
|
|
9568
10091
|
selected: alias,
|
|
9569
10092
|
...previousRepo?.project ? { project: previousRepo.project } : {},
|
|
9570
10093
|
linkedAt: new Date().toISOString(),
|
|
9571
|
-
...
|
|
10094
|
+
...preserveRemoteRoot ? {
|
|
10095
|
+
serverProjectRoot: previousRepo.serverProjectRoot,
|
|
10096
|
+
serverProjectRootAlias: previousRepo.serverProjectRootAlias,
|
|
10097
|
+
serverProjectRootBaseUrl: previousRepo.serverProjectRootBaseUrl
|
|
10098
|
+
} : {}
|
|
9572
10099
|
};
|
|
9573
10100
|
writeRepoConnection(context.projectRoot, repoState);
|
|
9574
|
-
|
|
10101
|
+
const remoteProjectLink = alias !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
|
|
10102
|
+
ok: false,
|
|
10103
|
+
status: "error",
|
|
10104
|
+
message: error instanceof Error ? error.message : String(error),
|
|
10105
|
+
hint: "Run `rig server repair-link` after authenticating the selected remote."
|
|
10106
|
+
})) : null;
|
|
10107
|
+
const latestRepo = readRepoConnection(context.projectRoot);
|
|
10108
|
+
const details = { selected: latestRepo?.selected ?? alias, repo: latestRepo, remoteProjectLink };
|
|
10109
|
+
printJsonOrText(context, details, formatSuccessCard("Rig server selected", [
|
|
9575
10110
|
["selected", alias],
|
|
9576
10111
|
["scope", "this repo"],
|
|
9577
|
-
["
|
|
10112
|
+
...remoteProjectLink ? [["remote link", remoteProjectLink.ok ? `ready (${remoteProjectLink.serverProjectRoot ?? "linked"})` : `${remoteProjectLink.status}: ${remoteProjectLink.hint}`]] : [],
|
|
10113
|
+
["next", remoteProjectLink && !remoteProjectLink.ok ? "rig server repair-link" : "rig task list"]
|
|
9578
10114
|
]));
|
|
9579
|
-
return { ok: true, group: options.group, command: "use", details
|
|
10115
|
+
return { ok: true, group: options.group, command: "use", details };
|
|
10116
|
+
}
|
|
10117
|
+
case "repair-link": {
|
|
10118
|
+
const parsed = parseRepairLinkArgs(rest, `${usageName(options)} repair-link [--prepare|--backfill-only] [--repo owner/repo]`);
|
|
10119
|
+
const result = await repairRemoteProjectRootLink(context, parsed);
|
|
10120
|
+
printJsonOrText(context, result, formatRemoteProjectLinkText(result));
|
|
10121
|
+
return { ok: true, group: options.group, command: "repair-link", details: result };
|
|
9580
10122
|
}
|
|
9581
10123
|
case "status": {
|
|
9582
10124
|
requireNoExtraArgs(rest, `${usageName(options)} status`);
|
|
9583
10125
|
const repo = readRepoConnection(context.projectRoot);
|
|
9584
10126
|
const global = readGlobalConnections();
|
|
9585
|
-
const
|
|
9586
|
-
|
|
10127
|
+
const remoteProjectLink = repo?.selected && repo.selected !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
|
|
10128
|
+
ok: false,
|
|
10129
|
+
status: "error",
|
|
10130
|
+
message: error instanceof Error ? error.message : String(error),
|
|
10131
|
+
hint: "Run `rig server repair-link` after authenticating the selected remote."
|
|
10132
|
+
})) : null;
|
|
10133
|
+
const latestRepo = readRepoConnection(context.projectRoot) ?? repo;
|
|
10134
|
+
const details = { selected: latestRepo?.selected ?? "local", repo: latestRepo, connections: global.connections, remoteProjectLink };
|
|
10135
|
+
printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections, latestRepo ?? null, remoteProjectLink));
|
|
9587
10136
|
return { ok: true, group: options.group, command: "status", details };
|
|
9588
10137
|
}
|
|
9589
10138
|
default:
|
|
9590
10139
|
throw new CliError(`Unknown ${options.group} command: ${String(command)}
|
|
9591
|
-
Usage: ${usageName(options)} <list|add|use|status>`, 1);
|
|
10140
|
+
Usage: ${usageName(options)} <list|add|use|status|repair-link>`, 1);
|
|
9592
10141
|
}
|
|
9593
10142
|
}
|
|
9594
10143
|
var init_connect = __esm(() => {
|
|
@@ -9596,13 +10145,14 @@ var init_connect = __esm(() => {
|
|
|
9596
10145
|
init_runner();
|
|
9597
10146
|
init__connection_state();
|
|
9598
10147
|
init__cli_format();
|
|
10148
|
+
init__server_client();
|
|
9599
10149
|
});
|
|
9600
10150
|
|
|
9601
10151
|
// packages/cli/src/commands/server.ts
|
|
9602
10152
|
import { resolveRigServerCommand } from "@rig/runtime/local-server";
|
|
9603
10153
|
async function executeServer(context, args, options) {
|
|
9604
10154
|
const [command = "status", ...rest] = args;
|
|
9605
|
-
if (["status", "list", "add", "use"].includes(command)) {
|
|
10155
|
+
if (["status", "list", "add", "use", "repair-link"].includes(command)) {
|
|
9606
10156
|
return executeConnectionCommand(context, [command, ...rest], { group: "server", interactiveUse: true });
|
|
9607
10157
|
}
|
|
9608
10158
|
switch (command) {
|
|
@@ -10384,7 +10934,7 @@ var init_task = __esm(() => {
|
|
|
10384
10934
|
});
|
|
10385
10935
|
|
|
10386
10936
|
// packages/cli/src/commands/task-run-driver.ts
|
|
10387
|
-
import { copyFileSync as copyFileSync3, existsSync as existsSync15, mkdirSync as
|
|
10937
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync12, statSync as statSync2, writeFileSync as writeFileSync9 } from "fs";
|
|
10388
10938
|
import { resolve as resolve23 } from "path";
|
|
10389
10939
|
import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
|
|
10390
10940
|
import { createInterface as createLineInterface } from "readline";
|
|
@@ -10487,7 +11037,7 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
|
|
|
10487
11037
|
try {
|
|
10488
11038
|
if (!statSync2(sourcePath).isFile())
|
|
10489
11039
|
continue;
|
|
10490
|
-
|
|
11040
|
+
mkdirSync11(resolve23(targetPath, ".."), { recursive: true });
|
|
10491
11041
|
copyFileSync3(sourcePath, targetPath);
|
|
10492
11042
|
copied += 1;
|
|
10493
11043
|
} catch {}
|
|
@@ -11358,8 +11908,8 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
11358
11908
|
artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
|
|
11359
11909
|
classifiedAt: new Date().toISOString()
|
|
11360
11910
|
};
|
|
11361
|
-
|
|
11362
|
-
|
|
11911
|
+
mkdirSync11(resolve23(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
|
|
11912
|
+
writeFileSync9(resolve23(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
|
|
11363
11913
|
`, "utf8");
|
|
11364
11914
|
patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
|
|
11365
11915
|
prompt = `${prompt}
|
|
@@ -11506,8 +12056,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
11506
12056
|
const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
|
|
11507
12057
|
const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
|
|
11508
12058
|
if (readyFile) {
|
|
11509
|
-
|
|
11510
|
-
|
|
12059
|
+
mkdirSync11(resolve23(readyFile, ".."), { recursive: true });
|
|
12060
|
+
writeFileSync9(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
|
|
11511
12061
|
`, "utf8");
|
|
11512
12062
|
}
|
|
11513
12063
|
appendRunLog(context.projectRoot, input.runId, {
|
|
@@ -12453,7 +13003,7 @@ var init_test = __esm(() => {
|
|
|
12453
13003
|
});
|
|
12454
13004
|
|
|
12455
13005
|
// packages/cli/src/commands/setup.ts
|
|
12456
|
-
import { existsSync as existsSync16, mkdirSync as
|
|
13006
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync12, readdirSync as readdirSync3, writeFileSync as writeFileSync10 } from "fs";
|
|
12457
13007
|
import { resolve as resolve24 } from "path";
|
|
12458
13008
|
import { createPluginHost } from "@rig/core";
|
|
12459
13009
|
import {
|
|
@@ -12508,12 +13058,12 @@ function runSetupInit(projectRoot) {
|
|
|
12508
13058
|
const stateDir = resolveControlPlaneHostStateDir(projectRoot);
|
|
12509
13059
|
const logsDir = resolveControlPlaneHostLogsDir(projectRoot);
|
|
12510
13060
|
const artifactsDir = resolveControlPlaneArtifactsDir(projectRoot);
|
|
12511
|
-
|
|
12512
|
-
|
|
12513
|
-
|
|
13061
|
+
mkdirSync12(stateDir, { recursive: true });
|
|
13062
|
+
mkdirSync12(logsDir, { recursive: true });
|
|
13063
|
+
mkdirSync12(artifactsDir, { recursive: true });
|
|
12514
13064
|
const failuresPath = resolve24(stateDir, "failed_approaches.md");
|
|
12515
13065
|
if (!existsSync16(failuresPath)) {
|
|
12516
|
-
|
|
13066
|
+
writeFileSync10(failuresPath, `# Failed Approaches
|
|
12517
13067
|
|
|
12518
13068
|
`, "utf-8");
|
|
12519
13069
|
}
|