@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
package/dist/src/commands.js
CHANGED
|
@@ -864,12 +864,28 @@ function readRepoConnection(projectRoot) {
|
|
|
864
864
|
selected,
|
|
865
865
|
project: typeof record.project === "string" ? record.project : undefined,
|
|
866
866
|
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
867
|
-
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
867
|
+
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined,
|
|
868
|
+
serverProjectRootAlias: typeof record.serverProjectRootAlias === "string" && record.serverProjectRootAlias.trim() ? record.serverProjectRootAlias.trim() : undefined,
|
|
869
|
+
serverProjectRootBaseUrl: typeof record.serverProjectRootBaseUrl === "string" && record.serverProjectRootBaseUrl.trim() ? record.serverProjectRootBaseUrl.trim().replace(/\/+$/, "") : undefined
|
|
868
870
|
};
|
|
869
871
|
}
|
|
870
872
|
function writeRepoConnection(projectRoot, state) {
|
|
871
873
|
writeJsonFile2(resolveRepoConnectionPath(projectRoot), state);
|
|
872
874
|
}
|
|
875
|
+
function rootAllowedForSelection(repo, connection) {
|
|
876
|
+
const root = repo.serverProjectRoot?.trim();
|
|
877
|
+
if (!root)
|
|
878
|
+
return;
|
|
879
|
+
if (connection.kind === "remote") {
|
|
880
|
+
if (repo.serverProjectRootAlias !== repo.selected)
|
|
881
|
+
return;
|
|
882
|
+
if (repo.serverProjectRootBaseUrl !== connection.baseUrl)
|
|
883
|
+
return;
|
|
884
|
+
} else if (repo.serverProjectRootAlias && repo.serverProjectRootAlias !== repo.selected) {
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
return root;
|
|
888
|
+
}
|
|
873
889
|
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
874
890
|
const repo = readRepoConnection(projectRoot);
|
|
875
891
|
if (!repo)
|
|
@@ -881,13 +897,41 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
881
897
|
if (!connection) {
|
|
882
898
|
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
883
899
|
}
|
|
884
|
-
return { alias: repo.selected, connection, serverProjectRoot: repo
|
|
900
|
+
return { alias: repo.selected, connection, serverProjectRoot: rootAllowedForSelection(repo, connection) };
|
|
901
|
+
}
|
|
902
|
+
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot, metadata = {}) {
|
|
903
|
+
const repo = readRepoConnection(projectRoot);
|
|
904
|
+
if (!repo)
|
|
905
|
+
return;
|
|
906
|
+
let inferred = metadata;
|
|
907
|
+
if (!inferred.alias || !inferred.baseUrl) {
|
|
908
|
+
try {
|
|
909
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
910
|
+
if (selected?.connection.kind === "remote") {
|
|
911
|
+
inferred = {
|
|
912
|
+
alias: inferred.alias ?? selected.alias,
|
|
913
|
+
baseUrl: inferred.baseUrl ?? selected.connection.baseUrl
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
} catch {}
|
|
917
|
+
}
|
|
918
|
+
writeRepoConnection(projectRoot, {
|
|
919
|
+
...repo,
|
|
920
|
+
...metadata.project ? { project: metadata.project } : {},
|
|
921
|
+
serverProjectRoot,
|
|
922
|
+
...inferred.alias ? { serverProjectRootAlias: inferred.alias } : {},
|
|
923
|
+
...inferred.baseUrl ? { serverProjectRootBaseUrl: inferred.baseUrl.replace(/\/+$/, "") } : {}
|
|
924
|
+
});
|
|
885
925
|
}
|
|
886
|
-
function
|
|
926
|
+
function clearRepoServerProjectRoot(projectRoot) {
|
|
887
927
|
const repo = readRepoConnection(projectRoot);
|
|
888
928
|
if (!repo)
|
|
889
929
|
return;
|
|
890
|
-
writeRepoConnection(projectRoot, {
|
|
930
|
+
writeRepoConnection(projectRoot, {
|
|
931
|
+
selected: repo.selected,
|
|
932
|
+
...repo.project ? { project: repo.project } : {},
|
|
933
|
+
...repo.linkedAt ? { linkedAt: repo.linkedAt } : {}
|
|
934
|
+
});
|
|
891
935
|
}
|
|
892
936
|
function isRemoteConnectionSelected(projectRoot) {
|
|
893
937
|
return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
|
|
@@ -897,8 +941,8 @@ var init__connection_state = __esm(() => {
|
|
|
897
941
|
});
|
|
898
942
|
|
|
899
943
|
// packages/cli/src/commands/_server-client.ts
|
|
900
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
901
|
-
import { resolve as resolve10 } from "path";
|
|
944
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
945
|
+
import { dirname as dirname3, isAbsolute, resolve as resolve10 } from "path";
|
|
902
946
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
903
947
|
function setServerPhaseListener(listener) {
|
|
904
948
|
const previous = serverPhaseListener;
|
|
@@ -944,11 +988,10 @@ function readStoredGitHubAuthToken(projectRoot) {
|
|
|
944
988
|
const parsed = readRemoteAuthState(projectRoot);
|
|
945
989
|
return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
|
|
946
990
|
}
|
|
947
|
-
function
|
|
948
|
-
|
|
949
|
-
const slug = repo?.project?.trim();
|
|
950
|
-
if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
|
|
991
|
+
function inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug) {
|
|
992
|
+
if (!/^[^/]+\/[^/]+$/.test(repoSlug))
|
|
951
993
|
return null;
|
|
994
|
+
const repo = readRepoConnection(projectRoot);
|
|
952
995
|
const auth = readRemoteAuthState(projectRoot);
|
|
953
996
|
const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
|
|
954
997
|
const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
|
|
@@ -957,25 +1000,419 @@ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
|
|
|
957
1000
|
const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
|
|
958
1001
|
if (!checkoutBaseDir)
|
|
959
1002
|
return null;
|
|
960
|
-
|
|
961
|
-
writeRepoServerProjectRoot(projectRoot, inferred);
|
|
962
|
-
return inferred;
|
|
1003
|
+
return `${checkoutBaseDir.replace(/\/+$/, "")}/${repoSlug}`;
|
|
963
1004
|
}
|
|
964
1005
|
function readLocalConnectionFallbackToken(projectRoot) {
|
|
965
1006
|
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
966
1007
|
}
|
|
1008
|
+
function normalizeRepoSlug(value) {
|
|
1009
|
+
const slug = value?.trim();
|
|
1010
|
+
return slug && /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
|
|
1011
|
+
}
|
|
1012
|
+
function readProjectLinkSlug(projectRoot) {
|
|
1013
|
+
const path = resolve10(projectRoot, ".rig", "state", "project-link.json");
|
|
1014
|
+
if (!existsSync6(path))
|
|
1015
|
+
return null;
|
|
1016
|
+
try {
|
|
1017
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
1018
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
1019
|
+
return null;
|
|
1020
|
+
return normalizeRepoSlug(typeof parsed.repoSlug === "string" ? String(parsed.repoSlug) : null);
|
|
1021
|
+
} catch {
|
|
1022
|
+
return null;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
function writeProjectLinkConnection(projectRoot, repoSlug, alias) {
|
|
1026
|
+
const path = resolve10(projectRoot, ".rig", "state", "project-link.json");
|
|
1027
|
+
mkdirSync6(dirname3(path), { recursive: true });
|
|
1028
|
+
writeFileSync5(path, `${JSON.stringify({ repoSlug, connection: alias, linkedAt: new Date().toISOString() }, null, 2)}
|
|
1029
|
+
`, "utf8");
|
|
1030
|
+
}
|
|
1031
|
+
function remoteLinkRepairCommand(repoSlug) {
|
|
1032
|
+
return repoSlug ? `rig server repair-link --repo ${repoSlug}` : "rig server repair-link --repo owner/repo";
|
|
1033
|
+
}
|
|
1034
|
+
function formatRemoteProjectLinkHint(resolution) {
|
|
1035
|
+
const repair = remoteLinkRepairCommand(resolution.repoSlug);
|
|
1036
|
+
if (resolution.status === "auth_required") {
|
|
1037
|
+
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}` : ""}\`.`;
|
|
1038
|
+
}
|
|
1039
|
+
if (resolution.status === "missing_project") {
|
|
1040
|
+
return `Record the repo slug, then run \`${repair}\` (or \`rig init --repo owner/repo --server remote --github-auth device\`).`;
|
|
1041
|
+
}
|
|
1042
|
+
if (resolution.status === "not_remote")
|
|
1043
|
+
return "Select a remote server with `rig server use <alias>` before repairing a remote project link.";
|
|
1044
|
+
return `Run \`${repair}\` to backfill or prepare a server-host checkout for the selected remote${resolution.alias ? ` (${resolution.alias})` : ""}.`;
|
|
1045
|
+
}
|
|
1046
|
+
function remoteProjectLinkFailure(input) {
|
|
1047
|
+
const partial = {
|
|
1048
|
+
status: input.status,
|
|
1049
|
+
alias: input.alias,
|
|
1050
|
+
baseUrl: input.baseUrl,
|
|
1051
|
+
repoSlug: input.repoSlug
|
|
1052
|
+
};
|
|
1053
|
+
return {
|
|
1054
|
+
ok: false,
|
|
1055
|
+
...input,
|
|
1056
|
+
hint: formatRemoteProjectLinkHint(partial),
|
|
1057
|
+
next: remoteLinkRepairCommand(input.repoSlug)
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
function remoteProjectLinkSuccess(input) {
|
|
1061
|
+
return {
|
|
1062
|
+
ok: true,
|
|
1063
|
+
status: input.status,
|
|
1064
|
+
alias: input.alias,
|
|
1065
|
+
baseUrl: input.baseUrl,
|
|
1066
|
+
repoSlug: input.repoSlug,
|
|
1067
|
+
serverProjectRoot: input.serverProjectRoot,
|
|
1068
|
+
source: input.source,
|
|
1069
|
+
prepared: input.prepared ?? input.status === "prepared",
|
|
1070
|
+
validated: input.validated ?? false,
|
|
1071
|
+
message: input.message ?? `Remote project link ready for ${input.repoSlug}.`,
|
|
1072
|
+
hint: "Remote-scoped task/run/status requests will send x-rig-project-root for this server-host checkout.",
|
|
1073
|
+
next: "rig task list"
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
function localCheckoutPathCandidate(projectRoot, candidate) {
|
|
1077
|
+
try {
|
|
1078
|
+
const local = resolve10(projectRoot);
|
|
1079
|
+
const resolved = resolve10(candidate);
|
|
1080
|
+
return resolved === local || resolved.startsWith(`${local}/`);
|
|
1081
|
+
} catch {
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
async function requestRemoteProjectLinkJson(baseUrl, pathname, authToken, init = {}) {
|
|
1086
|
+
const requestUrl = new URL(`${baseUrl}${pathname}`);
|
|
1087
|
+
if (authToken && queryAuthFallbackEnabled())
|
|
1088
|
+
requestUrl.searchParams.set("rt", authToken);
|
|
1089
|
+
const response = await fetch(requestUrl, {
|
|
1090
|
+
...init,
|
|
1091
|
+
headers: mergeHeaders(init.headers, authToken)
|
|
1092
|
+
});
|
|
1093
|
+
const text = await response.text();
|
|
1094
|
+
const payload = text.trim().length > 0 ? (() => {
|
|
1095
|
+
try {
|
|
1096
|
+
return JSON.parse(text);
|
|
1097
|
+
} catch {
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
})() : null;
|
|
1101
|
+
return { ok: response.ok, status: response.status, payload, text };
|
|
1102
|
+
}
|
|
1103
|
+
function payloadError(payload, fallback) {
|
|
1104
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
1105
|
+
const record = payload;
|
|
1106
|
+
const value = record.error ?? record.message ?? record.reason;
|
|
1107
|
+
if (typeof value === "string" && value.trim())
|
|
1108
|
+
return value.trim();
|
|
1109
|
+
}
|
|
1110
|
+
return fallback;
|
|
1111
|
+
}
|
|
1112
|
+
function checkoutPathsFromProjectPayload(payload) {
|
|
1113
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
1114
|
+
return [];
|
|
1115
|
+
const project = payload.project;
|
|
1116
|
+
if (!project || typeof project !== "object" || Array.isArray(project))
|
|
1117
|
+
return [];
|
|
1118
|
+
const checkouts = project.checkouts;
|
|
1119
|
+
if (!Array.isArray(checkouts))
|
|
1120
|
+
return [];
|
|
1121
|
+
return [...checkouts].reverse().flatMap((entry) => {
|
|
1122
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
1123
|
+
return [];
|
|
1124
|
+
const path = entry.path;
|
|
1125
|
+
return typeof path === "string" && path.trim() ? [path.trim()] : [];
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
function checkoutPathFromPreparePayload(payload) {
|
|
1129
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
1130
|
+
return null;
|
|
1131
|
+
const checkout = payload.checkout;
|
|
1132
|
+
if (!checkout || typeof checkout !== "object" || Array.isArray(checkout))
|
|
1133
|
+
return null;
|
|
1134
|
+
const path = checkout.path;
|
|
1135
|
+
return typeof path === "string" && path.trim() ? path.trim() : null;
|
|
1136
|
+
}
|
|
1137
|
+
async function validateAndPersistRemoteRoot(input) {
|
|
1138
|
+
const candidate = input.candidate.trim();
|
|
1139
|
+
if (!candidate || !isAbsolute(candidate)) {
|
|
1140
|
+
return remoteProjectLinkFailure({
|
|
1141
|
+
status: "invalid_root",
|
|
1142
|
+
alias: input.alias,
|
|
1143
|
+
baseUrl: input.baseUrl,
|
|
1144
|
+
repoSlug: input.repoSlug,
|
|
1145
|
+
message: `Remote project root candidate is not an absolute server-host path: ${candidate || "(empty)"}.`
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
if (localCheckoutPathCandidate(input.projectRoot, candidate)) {
|
|
1149
|
+
return remoteProjectLinkFailure({
|
|
1150
|
+
status: "invalid_root",
|
|
1151
|
+
alias: input.alias,
|
|
1152
|
+
baseUrl: input.baseUrl,
|
|
1153
|
+
repoSlug: input.repoSlug,
|
|
1154
|
+
message: `Refusing to use local checkout path ${candidate} as a remote server project root.`
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
let response;
|
|
1158
|
+
try {
|
|
1159
|
+
response = await requestRemoteProjectLinkJson(input.baseUrl, "/api/server/project-root", input.authToken, {
|
|
1160
|
+
method: "POST",
|
|
1161
|
+
headers: { "content-type": "application/json" },
|
|
1162
|
+
body: JSON.stringify({ projectRoot: candidate })
|
|
1163
|
+
});
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
return remoteProjectLinkFailure({
|
|
1166
|
+
status: "error",
|
|
1167
|
+
alias: input.alias,
|
|
1168
|
+
baseUrl: input.baseUrl,
|
|
1169
|
+
repoSlug: input.repoSlug,
|
|
1170
|
+
message: `Could not validate remote project root ${candidate}: ${error instanceof Error ? error.message : String(error)}`
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
if (response.status === 401 || response.status === 403) {
|
|
1174
|
+
return remoteProjectLinkFailure({
|
|
1175
|
+
status: "auth_required",
|
|
1176
|
+
alias: input.alias,
|
|
1177
|
+
baseUrl: input.baseUrl,
|
|
1178
|
+
repoSlug: input.repoSlug,
|
|
1179
|
+
statusCode: response.status,
|
|
1180
|
+
message: `Remote server ${input.alias} requires authentication before validating project root ${candidate}.`
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
if (!response.ok) {
|
|
1184
|
+
return remoteProjectLinkFailure({
|
|
1185
|
+
status: "invalid_root",
|
|
1186
|
+
alias: input.alias,
|
|
1187
|
+
baseUrl: input.baseUrl,
|
|
1188
|
+
repoSlug: input.repoSlug,
|
|
1189
|
+
statusCode: response.status,
|
|
1190
|
+
message: `Remote server rejected project root ${candidate} (${response.status}): ${payloadError(response.payload, response.text || "project-root validation failed")}`
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
const record = response.payload && typeof response.payload === "object" && !Array.isArray(response.payload) ? response.payload : {};
|
|
1194
|
+
if (record.ok !== true) {
|
|
1195
|
+
return remoteProjectLinkFailure({
|
|
1196
|
+
status: "invalid_root",
|
|
1197
|
+
alias: input.alias,
|
|
1198
|
+
baseUrl: input.baseUrl,
|
|
1199
|
+
repoSlug: input.repoSlug,
|
|
1200
|
+
message: `Remote server did not accept project root ${candidate}: ${payloadError(response.payload, "project-root validation failed")}`
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
const accepted = typeof record.projectRoot === "string" && record.projectRoot.trim() ? record.projectRoot.trim() : typeof record.requestedProjectRoot === "string" && record.requestedProjectRoot.trim() ? record.requestedProjectRoot.trim() : candidate;
|
|
1204
|
+
writeRepoServerProjectRoot(input.projectRoot, accepted, { alias: input.alias, baseUrl: input.baseUrl, project: input.repoSlug });
|
|
1205
|
+
writeProjectLinkConnection(input.projectRoot, input.repoSlug, input.alias);
|
|
1206
|
+
const status = input.status ?? (input.prepared ? "prepared" : "backfilled");
|
|
1207
|
+
return remoteProjectLinkSuccess({
|
|
1208
|
+
status,
|
|
1209
|
+
alias: input.alias,
|
|
1210
|
+
baseUrl: input.baseUrl,
|
|
1211
|
+
repoSlug: input.repoSlug,
|
|
1212
|
+
serverProjectRoot: accepted,
|
|
1213
|
+
source: input.source,
|
|
1214
|
+
prepared: input.prepared,
|
|
1215
|
+
validated: true,
|
|
1216
|
+
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}.`
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
async function ensureRemoteProjectRootLink(projectRoot, options = {}) {
|
|
1220
|
+
let selected;
|
|
1221
|
+
try {
|
|
1222
|
+
selected = resolveSelectedConnection(projectRoot);
|
|
1223
|
+
} catch (error) {
|
|
1224
|
+
return remoteProjectLinkFailure({ status: "error", message: error instanceof Error ? error.message : String(error) });
|
|
1225
|
+
}
|
|
1226
|
+
if (!selected || selected.connection.kind !== "remote") {
|
|
1227
|
+
return remoteProjectLinkFailure({ status: "not_remote", message: "No remote Rig server is selected for this repo." });
|
|
1228
|
+
}
|
|
1229
|
+
const repo = readRepoConnection(projectRoot);
|
|
1230
|
+
const explicitRepoRequested = options.repoSlug !== undefined;
|
|
1231
|
+
const explicitRepoSlug = explicitRepoRequested ? normalizeRepoSlug(options.repoSlug) : null;
|
|
1232
|
+
const repoProjectSlug = normalizeRepoSlug(repo?.project);
|
|
1233
|
+
const repoSlug = explicitRepoSlug ?? repoProjectSlug ?? readProjectLinkSlug(projectRoot);
|
|
1234
|
+
const alias = selected.alias;
|
|
1235
|
+
const baseUrl = selected.connection.baseUrl;
|
|
1236
|
+
const authToken = options.authToken === undefined ? readGitHubBearerTokenForRemote(projectRoot) : options.authToken;
|
|
1237
|
+
const mode = options.mode ?? "backfill-only";
|
|
1238
|
+
if (explicitRepoRequested && !explicitRepoSlug) {
|
|
1239
|
+
return remoteProjectLinkFailure({
|
|
1240
|
+
status: "missing_project",
|
|
1241
|
+
alias,
|
|
1242
|
+
baseUrl,
|
|
1243
|
+
message: `Invalid --repo value ${JSON.stringify(options.repoSlug)}. Expected owner/repo.`
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
if (!repoSlug) {
|
|
1247
|
+
return remoteProjectLinkFailure({
|
|
1248
|
+
status: "missing_project",
|
|
1249
|
+
alias,
|
|
1250
|
+
baseUrl,
|
|
1251
|
+
message: `Remote server ${alias} is selected, but this checkout has no GitHub repo slug recorded.`
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
const skippedCandidates = [];
|
|
1255
|
+
const storedRoot = selected.serverProjectRoot?.trim() || null;
|
|
1256
|
+
const storedRootMatchesResolvedRepo = repoProjectSlug === repoSlug;
|
|
1257
|
+
if (storedRoot && storedRootMatchesResolvedRepo) {
|
|
1258
|
+
if (localCheckoutPathCandidate(projectRoot, storedRoot)) {
|
|
1259
|
+
clearRepoServerProjectRoot(projectRoot);
|
|
1260
|
+
skippedCandidates.push(storedRoot);
|
|
1261
|
+
} else {
|
|
1262
|
+
const storedResult = await validateAndPersistRemoteRoot({
|
|
1263
|
+
projectRoot,
|
|
1264
|
+
alias,
|
|
1265
|
+
baseUrl,
|
|
1266
|
+
authToken,
|
|
1267
|
+
repoSlug,
|
|
1268
|
+
candidate: storedRoot,
|
|
1269
|
+
source: "stored",
|
|
1270
|
+
status: "ready"
|
|
1271
|
+
});
|
|
1272
|
+
if (storedResult.ok || storedResult.status === "auth_required" || storedResult.status === "error")
|
|
1273
|
+
return storedResult;
|
|
1274
|
+
clearRepoServerProjectRoot(projectRoot);
|
|
1275
|
+
skippedCandidates.push(storedRoot);
|
|
1276
|
+
}
|
|
1277
|
+
} else if (storedRoot) {
|
|
1278
|
+
skippedCandidates.push(storedRoot);
|
|
1279
|
+
}
|
|
1280
|
+
const authCandidate = inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug);
|
|
1281
|
+
if (authCandidate) {
|
|
1282
|
+
const authResult = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: authCandidate, source: "auth-state" });
|
|
1283
|
+
if (authResult.ok || authResult.status === "auth_required")
|
|
1284
|
+
return authResult;
|
|
1285
|
+
}
|
|
1286
|
+
let registryResponse;
|
|
1287
|
+
try {
|
|
1288
|
+
registryResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}`, authToken);
|
|
1289
|
+
} catch (error) {
|
|
1290
|
+
return remoteProjectLinkFailure({
|
|
1291
|
+
status: "error",
|
|
1292
|
+
alias,
|
|
1293
|
+
baseUrl,
|
|
1294
|
+
repoSlug,
|
|
1295
|
+
message: `Could not read remote project registry for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
if (registryResponse.status === 401 || registryResponse.status === 403) {
|
|
1299
|
+
return remoteProjectLinkFailure({
|
|
1300
|
+
status: "auth_required",
|
|
1301
|
+
alias,
|
|
1302
|
+
baseUrl,
|
|
1303
|
+
repoSlug,
|
|
1304
|
+
statusCode: registryResponse.status,
|
|
1305
|
+
message: `Remote server ${alias} requires authentication before reading the ${repoSlug} project registry.`
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
if (registryResponse.ok) {
|
|
1309
|
+
const candidates = checkoutPathsFromProjectPayload(registryResponse.payload);
|
|
1310
|
+
for (const candidate of candidates) {
|
|
1311
|
+
const result = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate, source: "registry" });
|
|
1312
|
+
if (result.ok || result.status === "auth_required")
|
|
1313
|
+
return result;
|
|
1314
|
+
skippedCandidates.push(candidate);
|
|
1315
|
+
}
|
|
1316
|
+
if (mode === "backfill-only") {
|
|
1317
|
+
return remoteProjectLinkFailure({
|
|
1318
|
+
status: candidates.length > 0 ? "invalid_root" : "no_server_checkout",
|
|
1319
|
+
alias,
|
|
1320
|
+
baseUrl,
|
|
1321
|
+
repoSlug,
|
|
1322
|
+
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.`,
|
|
1323
|
+
skippedCandidates
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
} else if (registryResponse.status === 404) {
|
|
1327
|
+
if (mode === "backfill-only") {
|
|
1328
|
+
return remoteProjectLinkFailure({
|
|
1329
|
+
status: "project_not_registered",
|
|
1330
|
+
alias,
|
|
1331
|
+
baseUrl,
|
|
1332
|
+
repoSlug,
|
|
1333
|
+
statusCode: registryResponse.status,
|
|
1334
|
+
message: `Remote server ${alias} has no registered project record for ${repoSlug}.`
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
} else {
|
|
1338
|
+
return remoteProjectLinkFailure({
|
|
1339
|
+
status: "error",
|
|
1340
|
+
alias,
|
|
1341
|
+
baseUrl,
|
|
1342
|
+
repoSlug,
|
|
1343
|
+
statusCode: registryResponse.status,
|
|
1344
|
+
message: `Could not backfill ${repoSlug} from remote registry (${registryResponse.status}): ${payloadError(registryResponse.payload, registryResponse.text || "registry lookup failed")}`,
|
|
1345
|
+
skippedCandidates
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
let prepareResponse;
|
|
1349
|
+
try {
|
|
1350
|
+
prepareResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}/prepare-checkout`, authToken, {
|
|
1351
|
+
method: "POST",
|
|
1352
|
+
headers: { "content-type": "application/json" },
|
|
1353
|
+
body: JSON.stringify({ checkout: { kind: "managed-clone" }, repoUrl: `https://github.com/${repoSlug}.git` })
|
|
1354
|
+
});
|
|
1355
|
+
} catch (error) {
|
|
1356
|
+
return remoteProjectLinkFailure({
|
|
1357
|
+
status: "error",
|
|
1358
|
+
alias,
|
|
1359
|
+
baseUrl,
|
|
1360
|
+
repoSlug,
|
|
1361
|
+
message: `Could not prepare remote checkout for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
if (prepareResponse.status === 401 || prepareResponse.status === 403) {
|
|
1365
|
+
return remoteProjectLinkFailure({
|
|
1366
|
+
status: "auth_required",
|
|
1367
|
+
alias,
|
|
1368
|
+
baseUrl,
|
|
1369
|
+
repoSlug,
|
|
1370
|
+
statusCode: prepareResponse.status,
|
|
1371
|
+
message: `Remote server ${alias} requires authentication before preparing ${repoSlug}.`
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
if (!prepareResponse.ok) {
|
|
1375
|
+
return remoteProjectLinkFailure({
|
|
1376
|
+
status: "error",
|
|
1377
|
+
alias,
|
|
1378
|
+
baseUrl,
|
|
1379
|
+
repoSlug,
|
|
1380
|
+
statusCode: prepareResponse.status,
|
|
1381
|
+
message: `Remote checkout prepare failed for ${repoSlug} (${prepareResponse.status}): ${payloadError(prepareResponse.payload, prepareResponse.text || "prepare-checkout failed")}`,
|
|
1382
|
+
skippedCandidates
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
const preparedPath = checkoutPathFromPreparePayload(prepareResponse.payload);
|
|
1386
|
+
if (!preparedPath) {
|
|
1387
|
+
return remoteProjectLinkFailure({
|
|
1388
|
+
status: "invalid_root",
|
|
1389
|
+
alias,
|
|
1390
|
+
baseUrl,
|
|
1391
|
+
repoSlug,
|
|
1392
|
+
message: `Remote checkout prepare for ${repoSlug} did not return a checkout.path.`,
|
|
1393
|
+
skippedCandidates
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
return validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: preparedPath, source: "prepare", prepared: true });
|
|
1397
|
+
}
|
|
1398
|
+
async function repairRemoteProjectRootLink(context, options = {}) {
|
|
1399
|
+
const resolution = await ensureRemoteProjectRootLink(context.projectRoot, { mode: options.mode ?? "prepare-if-missing", repoSlug: options.repoSlug });
|
|
1400
|
+
if (!resolution.ok)
|
|
1401
|
+
throw new CliError(resolution.message, 1, { hint: resolution.hint });
|
|
1402
|
+
return resolution;
|
|
1403
|
+
}
|
|
967
1404
|
async function ensureServerForCli(projectRoot) {
|
|
968
1405
|
try {
|
|
969
1406
|
const selected = resolveSelectedConnection(projectRoot);
|
|
970
1407
|
if (selected?.connection.kind === "remote") {
|
|
971
1408
|
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
972
1409
|
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
973
|
-
const
|
|
1410
|
+
const link = await ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only", authToken });
|
|
974
1411
|
return {
|
|
975
1412
|
baseUrl: selected.connection.baseUrl,
|
|
976
1413
|
authToken,
|
|
977
1414
|
connectionKind: "remote",
|
|
978
|
-
serverProjectRoot
|
|
1415
|
+
serverProjectRoot: link.ok ? link.serverProjectRoot ?? null : null
|
|
979
1416
|
};
|
|
980
1417
|
}
|
|
981
1418
|
reportServerPhase("Starting local Rig server\u2026");
|
|
@@ -993,32 +1430,6 @@ async function ensureServerForCli(projectRoot) {
|
|
|
993
1430
|
throw error;
|
|
994
1431
|
}
|
|
995
1432
|
}
|
|
996
|
-
async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
|
|
997
|
-
const repo = readRepoConnection(projectRoot);
|
|
998
|
-
const slug = repo?.project?.trim();
|
|
999
|
-
if (!slug)
|
|
1000
|
-
return null;
|
|
1001
|
-
try {
|
|
1002
|
-
const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
|
|
1003
|
-
if (authToken && queryAuthFallbackEnabled())
|
|
1004
|
-
url.searchParams.set("rt", authToken);
|
|
1005
|
-
const response = await fetch(url, {
|
|
1006
|
-
headers: mergeHeaders(undefined, authToken)
|
|
1007
|
-
});
|
|
1008
|
-
if (!response.ok)
|
|
1009
|
-
return null;
|
|
1010
|
-
const payload = await response.json();
|
|
1011
|
-
const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
|
|
1012
|
-
const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
|
|
1013
|
-
const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
|
|
1014
|
-
const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
|
|
1015
|
-
if (path)
|
|
1016
|
-
writeRepoServerProjectRoot(projectRoot, path);
|
|
1017
|
-
return path;
|
|
1018
|
-
} catch {
|
|
1019
|
-
return null;
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
1433
|
function appendTaskFilterParams(url, filters) {
|
|
1023
1434
|
if (filters.assignee)
|
|
1024
1435
|
url.searchParams.set("assignee", filters.assignee);
|
|
@@ -1119,13 +1530,20 @@ function canUseRemoteWithoutProjectRoot(pathname) {
|
|
|
1119
1530
|
async function requestServerJson(context, pathname, init = {}) {
|
|
1120
1531
|
const server = await ensureServerForCli(context.projectRoot);
|
|
1121
1532
|
const requestUrl = new URL(`${server.baseUrl}${pathname}`);
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1533
|
+
let scopedServerProjectRoot = server.serverProjectRoot;
|
|
1534
|
+
if (server.connectionKind === "remote" && !scopedServerProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
|
|
1535
|
+
const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: server.authToken });
|
|
1536
|
+
if (link.ok && link.serverProjectRoot) {
|
|
1537
|
+
scopedServerProjectRoot = link.serverProjectRoot;
|
|
1538
|
+
} else {
|
|
1539
|
+
const repo = readRepoConnection(context.projectRoot);
|
|
1540
|
+
const target = link.alias && link.baseUrl ? `${link.alias} (${link.baseUrl})` : server.baseUrl;
|
|
1541
|
+
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 });
|
|
1542
|
+
}
|
|
1125
1543
|
}
|
|
1126
1544
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
1127
|
-
if (
|
|
1128
|
-
headers.set("x-rig-project-root",
|
|
1545
|
+
if (scopedServerProjectRoot)
|
|
1546
|
+
headers.set("x-rig-project-root", scopedServerProjectRoot);
|
|
1129
1547
|
if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
|
|
1130
1548
|
requestUrl.searchParams.set("rt", server.authToken);
|
|
1131
1549
|
}
|
|
@@ -1491,6 +1909,14 @@ function permissionAllowsPr(payload) {
|
|
|
1491
1909
|
function isNotFoundError(error) {
|
|
1492
1910
|
return /\b(404|not found)\b/i.test(message(error));
|
|
1493
1911
|
}
|
|
1912
|
+
function isLoopbackBaseUrl(baseUrl) {
|
|
1913
|
+
try {
|
|
1914
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
1915
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
1916
|
+
} catch {
|
|
1917
|
+
return false;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1494
1920
|
function projectCheckoutReady(payload) {
|
|
1495
1921
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
1496
1922
|
return null;
|
|
@@ -1545,25 +1971,37 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
1545
1971
|
}
|
|
1546
1972
|
const repo = readRepoConnection(context.projectRoot);
|
|
1547
1973
|
const remoteWithoutProjectLink = selectedServer?.connectionKind === "remote" && !repo?.project;
|
|
1548
|
-
|
|
1974
|
+
let remoteRootBlocked = false;
|
|
1975
|
+
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>`."));
|
|
1976
|
+
if (selectedServer?.connectionKind === "remote" && repo?.project && !selectedServer.serverProjectRoot) {
|
|
1977
|
+
const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: selectedServer.authToken });
|
|
1978
|
+
const loopbackDevRemote = !selectedServer.authToken && isLoopbackBaseUrl(selectedServer.baseUrl);
|
|
1979
|
+
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));
|
|
1980
|
+
remoteRootBlocked = !link.ok && !loopbackDevRemote;
|
|
1981
|
+
}
|
|
1549
1982
|
try {
|
|
1550
1983
|
const auth = await request("/api/github/auth/status");
|
|
1551
1984
|
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>`."));
|
|
1552
1985
|
} catch (error) {
|
|
1553
1986
|
checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
|
|
1554
1987
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1988
|
+
if (!remoteRootBlocked) {
|
|
1989
|
+
try {
|
|
1990
|
+
const projection = await request("/api/workspace/task-projection");
|
|
1991
|
+
checks.push(preflightCheck("task-projection", "task projection ready", "pass", JSON.stringify(projection).slice(0, 120)));
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
checks.push(preflightCheck("task-projection", "task projection ready", "warn", message(error), "Refresh task projection with `rig task list` before launching."));
|
|
1994
|
+
}
|
|
1995
|
+
try {
|
|
1996
|
+
const permissions = await request("/api/github/repo/permissions");
|
|
1997
|
+
const allowed = permissionAllowsPr(permissions);
|
|
1998
|
+
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."));
|
|
1999
|
+
} catch (error) {
|
|
2000
|
+
checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", message(error), "Ensure the selected token can push branches and open PRs."));
|
|
2001
|
+
}
|
|
2002
|
+
} else {
|
|
2003
|
+
checks.push(preflightCheck("task-projection", "task projection ready", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
|
|
2004
|
+
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."));
|
|
1567
2005
|
}
|
|
1568
2006
|
if (repo?.project) {
|
|
1569
2007
|
try {
|
|
@@ -1575,19 +2013,24 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
1575
2013
|
}
|
|
1576
2014
|
}
|
|
1577
2015
|
if (taskId) {
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
2016
|
+
if (!remoteRootBlocked) {
|
|
2017
|
+
try {
|
|
2018
|
+
const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
|
|
2019
|
+
const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
|
|
2020
|
+
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."));
|
|
2021
|
+
} catch (error) {
|
|
2022
|
+
checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
|
|
2023
|
+
}
|
|
2024
|
+
try {
|
|
2025
|
+
const runs = await request("/api/runs?limit=200");
|
|
2026
|
+
const duplicate = activeDuplicateRun(runs, taskId);
|
|
2027
|
+
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));
|
|
2028
|
+
} catch (error) {
|
|
2029
|
+
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."));
|
|
2030
|
+
}
|
|
2031
|
+
} else {
|
|
2032
|
+
checks.push(preflightCheck("issue", "task/issue accessible", "fail", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
|
|
2033
|
+
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."));
|
|
1591
2034
|
}
|
|
1592
2035
|
}
|
|
1593
2036
|
if ((options.runtimeAdapter ?? "pi") === "pi") {
|
|
@@ -2071,16 +2514,19 @@ function formatConnectionList(connections) {
|
|
|
2071
2514
|
return [formatSection("Rig servers", `${rows.length} available`), `${pc3.bold(pad("ALIAS", aliasWidth))} ${pc3.bold("KIND")} ${pc3.bold("TARGET")}`, ...lines, "", ...formatNextSteps(["Select one: `rig server use <alias|local>`"])].join(`
|
|
2072
2515
|
`);
|
|
2073
2516
|
}
|
|
2074
|
-
function formatConnectionStatus(selected, connections) {
|
|
2517
|
+
function formatConnectionStatus(selected, connections, repo, remoteProjectLink) {
|
|
2075
2518
|
const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
|
|
2076
2519
|
const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
|
|
2520
|
+
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";
|
|
2077
2521
|
return [
|
|
2078
2522
|
formatSection("Rig server", "selected for this repo"),
|
|
2079
2523
|
`${themeFaint("\u2502")} ${themeDim("selected ")} ${pc3.bold(selected)}`,
|
|
2080
2524
|
`${themeFaint("\u2502")} ${themeDim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
|
|
2081
2525
|
`${themeFaint("\u2502")} ${themeDim("target ")} ${target ?? "not configured"}`,
|
|
2526
|
+
...repo?.project ? [`${themeFaint("\u2502")} ${themeDim("project ")} ${repo.project}`] : [],
|
|
2527
|
+
...connection?.kind === "remote" ? [`${themeFaint("\u2502")} ${themeDim("root ")} ${rootLabel}`] : [],
|
|
2082
2528
|
"",
|
|
2083
|
-
...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
|
|
2529
|
+
...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`"])
|
|
2084
2530
|
].join(`
|
|
2085
2531
|
`);
|
|
2086
2532
|
}
|
|
@@ -2579,7 +3025,7 @@ var init__operator_surface = __esm(() => {
|
|
|
2579
3025
|
});
|
|
2580
3026
|
|
|
2581
3027
|
// packages/cli/src/commands/_pi-frontend.ts
|
|
2582
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
3028
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync10, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
2583
3029
|
import { homedir as homedir6, tmpdir } from "os";
|
|
2584
3030
|
import { join as join3 } from "path";
|
|
2585
3031
|
import { main as runPiMain } from "@earendil-works/pi-coding-agent";
|
|
@@ -2631,7 +3077,7 @@ function statusFromRunDetails(run) {
|
|
|
2631
3077
|
async function prepareOperatorConsole(context, runId, tempSessionDir) {
|
|
2632
3078
|
const server = await ensureServerForCli(context.projectRoot);
|
|
2633
3079
|
const localCwd = join3(homedir6(), ".rig", "drones", runId.slice(0, 8));
|
|
2634
|
-
|
|
3080
|
+
mkdirSync10(localCwd, { recursive: true });
|
|
2635
3081
|
trustDroneCwd(localCwd);
|
|
2636
3082
|
installRigPiTheme();
|
|
2637
3083
|
let sessionFileArg = [];
|
|
@@ -2653,7 +3099,7 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
|
|
|
2653
3099
|
return line;
|
|
2654
3100
|
}).join(`
|
|
2655
3101
|
`);
|
|
2656
|
-
|
|
3102
|
+
writeFileSync8(localSessionPath, content);
|
|
2657
3103
|
sessionFileArg = ["--session", localSessionPath];
|
|
2658
3104
|
}
|
|
2659
3105
|
} catch {}
|
|
@@ -2669,12 +3115,12 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
|
|
|
2669
3115
|
function trustDroneCwd(localCwd) {
|
|
2670
3116
|
try {
|
|
2671
3117
|
const agentDir = join3(homedir6(), ".pi", "agent");
|
|
2672
|
-
|
|
3118
|
+
mkdirSync10(agentDir, { recursive: true });
|
|
2673
3119
|
const trustPath = join3(agentDir, "trust.json");
|
|
2674
3120
|
const store = existsSync14(trustPath) ? JSON.parse(readFileSync10(trustPath, "utf8")) : {};
|
|
2675
3121
|
if (store[localCwd] !== true) {
|
|
2676
3122
|
store[localCwd] = true;
|
|
2677
|
-
|
|
3123
|
+
writeFileSync8(trustPath, `${JSON.stringify(store, null, "\t")}
|
|
2678
3124
|
`);
|
|
2679
3125
|
}
|
|
2680
3126
|
} catch {}
|
|
@@ -2682,15 +3128,38 @@ function trustDroneCwd(localCwd) {
|
|
|
2682
3128
|
function installRigPiTheme() {
|
|
2683
3129
|
try {
|
|
2684
3130
|
const themesDir = join3(homedir6(), ".pi", "agent", "themes");
|
|
2685
|
-
|
|
3131
|
+
mkdirSync10(themesDir, { recursive: true });
|
|
2686
3132
|
const themePath = join3(themesDir, "rig.json");
|
|
2687
3133
|
const next = `${JSON.stringify(RIG_PI_THEME, null, "\t")}
|
|
2688
3134
|
`;
|
|
2689
3135
|
if (!existsSync14(themePath) || readFileSync10(themePath, "utf8") !== next) {
|
|
2690
|
-
|
|
3136
|
+
writeFileSync8(themePath, next);
|
|
2691
3137
|
}
|
|
2692
3138
|
} catch {}
|
|
2693
3139
|
}
|
|
3140
|
+
async function runWithProcessExitGuard(body) {
|
|
3141
|
+
const realExit = process.exit;
|
|
3142
|
+
let exitCode = 0;
|
|
3143
|
+
let signalQuit = () => {};
|
|
3144
|
+
const quit = new Promise((resolve22) => {
|
|
3145
|
+
signalQuit = resolve22;
|
|
3146
|
+
});
|
|
3147
|
+
const guardedExit = (code) => {
|
|
3148
|
+
exitCode = typeof code === "number" ? code : 0;
|
|
3149
|
+
signalQuit();
|
|
3150
|
+
return;
|
|
3151
|
+
};
|
|
3152
|
+
process.exit = guardedExit;
|
|
3153
|
+
try {
|
|
3154
|
+
await Promise.race([Promise.resolve().then(body), quit]);
|
|
3155
|
+
} finally {
|
|
3156
|
+
process.exit = realExit;
|
|
3157
|
+
}
|
|
3158
|
+
return exitCode;
|
|
3159
|
+
}
|
|
3160
|
+
function runPiMainReturningOnQuit(args, options) {
|
|
3161
|
+
return runWithProcessExitGuard(() => runPiMain(args, options));
|
|
3162
|
+
}
|
|
2694
3163
|
async function attachRunBundledPiFrontend(context, input) {
|
|
2695
3164
|
const tempSessionDir = mkdtempSync(join3(tmpdir(), "rig-pi-frontend-sessions-"));
|
|
2696
3165
|
const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
|
|
@@ -2706,16 +3175,20 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
2706
3175
|
};
|
|
2707
3176
|
let detached = false;
|
|
2708
3177
|
try {
|
|
2709
|
-
|
|
3178
|
+
const piArgs = [
|
|
2710
3179
|
"--offline",
|
|
2711
3180
|
"--no-extensions",
|
|
2712
3181
|
"--no-skills",
|
|
2713
3182
|
"--no-prompt-templates",
|
|
2714
3183
|
"--no-context-files",
|
|
2715
3184
|
...sessionFileArg
|
|
2716
|
-
]
|
|
2717
|
-
|
|
2718
|
-
|
|
3185
|
+
];
|
|
3186
|
+
const piOptions = { extensionFactories: [piRigExtensionFactory] };
|
|
3187
|
+
if (input.returnOnQuit) {
|
|
3188
|
+
await runPiMainReturningOnQuit(piArgs, piOptions);
|
|
3189
|
+
} else {
|
|
3190
|
+
await runPiMain(piArgs, piOptions);
|
|
3191
|
+
}
|
|
2719
3192
|
detached = true;
|
|
2720
3193
|
} finally {
|
|
2721
3194
|
restoreEnv();
|
|
@@ -3088,12 +3561,13 @@ var init__help_catalog = __esm(() => {
|
|
|
3088
3561
|
{
|
|
3089
3562
|
name: "server",
|
|
3090
3563
|
summary: "Choose, inspect, and start the Rig server that owns tasks and runs.",
|
|
3091
|
-
usage: ["rig server <status|list|add|use|start> [options]"],
|
|
3564
|
+
usage: ["rig server <status|list|add|use|repair-link|start> [options]"],
|
|
3092
3565
|
commands: [
|
|
3093
|
-
{ command: "status", description: "Show the selected server for this repo.", primary: true },
|
|
3566
|
+
{ command: "status", description: "Show the selected server and remote project-root link for this repo.", primary: true },
|
|
3094
3567
|
{ command: "use local", description: "Switch this repo to the local Rig server.", primary: true },
|
|
3095
3568
|
{ command: "add <alias> <url>", description: "Save a remote Rig server URL.", primary: true },
|
|
3096
3569
|
{ command: "use <alias>", description: "Select a saved remote server alias.", primary: true },
|
|
3570
|
+
{ command: "repair-link [--prepare|--backfill-only] [--repo owner/repo]", description: "Backfill or prepare the selected remote's server-host checkout link.", primary: true },
|
|
3097
3571
|
{ command: "list", description: "List saved local/remote server aliases.", primary: true },
|
|
3098
3572
|
{ command: "start [--host <host>] [--port <n>]", description: "Start a local rig-server process." }
|
|
3099
3573
|
],
|
|
@@ -3101,6 +3575,7 @@ var init__help_catalog = __esm(() => {
|
|
|
3101
3575
|
"rig server status",
|
|
3102
3576
|
"rig server add prod https://where.rig-does.work",
|
|
3103
3577
|
"rig server use prod",
|
|
3578
|
+
"rig server repair-link --repo owner/repo",
|
|
3104
3579
|
"rig server use local",
|
|
3105
3580
|
"rig server start --port 3773"
|
|
3106
3581
|
],
|
|
@@ -7429,7 +7904,7 @@ import {
|
|
|
7429
7904
|
chmodSync,
|
|
7430
7905
|
copyFileSync as copyFileSync2,
|
|
7431
7906
|
existsSync as existsSync8,
|
|
7432
|
-
mkdirSync as
|
|
7907
|
+
mkdirSync as mkdirSync7,
|
|
7433
7908
|
readdirSync,
|
|
7434
7909
|
readlinkSync,
|
|
7435
7910
|
rmSync as rmSync3,
|
|
@@ -7546,7 +8021,7 @@ async function executeDist(context, args) {
|
|
|
7546
8021
|
requireNoExtraArgs(pending, "rig dist install [--scope user|system] [--path <dir>]");
|
|
7547
8022
|
const scope = parseInstallScope(scopeResult.value);
|
|
7548
8023
|
const installDir = resolveInstallDir(scope, pathResult.value);
|
|
7549
|
-
|
|
8024
|
+
mkdirSync7(installDir, { recursive: true });
|
|
7550
8025
|
let source = await findLatestDistBinary(context.projectRoot);
|
|
7551
8026
|
let buildDir = null;
|
|
7552
8027
|
if (!source) {
|
|
@@ -7601,7 +8076,7 @@ async function executeDist(context, args) {
|
|
|
7601
8076
|
const fp = await computeRuntimeImageFingerprint(context.projectRoot);
|
|
7602
8077
|
const currentId = computeRuntimeImageId(fp);
|
|
7603
8078
|
const imagesDir = resolve13(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
|
|
7604
|
-
|
|
8079
|
+
mkdirSync7(imagesDir, { recursive: true });
|
|
7605
8080
|
let pruned = 0;
|
|
7606
8081
|
for (const entry of readdirSync(imagesDir, { withFileTypes: true })) {
|
|
7607
8082
|
if (entry.isDirectory() && entry.name !== currentId) {
|
|
@@ -7613,9 +8088,9 @@ async function executeDist(context, args) {
|
|
|
7613
8088
|
console.log(`Pruned ${pruned} stale image(s).`);
|
|
7614
8089
|
}
|
|
7615
8090
|
const imageDir = resolve13(imagesDir, currentId);
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
8091
|
+
mkdirSync7(resolve13(imageDir, "bin/hooks"), { recursive: true });
|
|
8092
|
+
mkdirSync7(resolve13(imageDir, "bin/plugins"), { recursive: true });
|
|
8093
|
+
mkdirSync7(resolve13(imageDir, "bin/validators"), { recursive: true });
|
|
7619
8094
|
const hookNames = [
|
|
7620
8095
|
"scope-guard",
|
|
7621
8096
|
"import-guard",
|
|
@@ -7640,8 +8115,8 @@ async function executeDist(context, args) {
|
|
|
7640
8115
|
const binPluginsDir = resolve13(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
|
|
7641
8116
|
const validatorsRoot = resolve13(hostProjectRoot, "packages/runtime/src/control-plane/validators");
|
|
7642
8117
|
const binValidatorsDir = resolve13(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
|
|
7643
|
-
|
|
7644
|
-
|
|
8118
|
+
mkdirSync7(binPluginsDir, { recursive: true });
|
|
8119
|
+
mkdirSync7(binValidatorsDir, { recursive: true });
|
|
7645
8120
|
if (existsSync8(pluginsDir)) {
|
|
7646
8121
|
for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
|
|
7647
8122
|
const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
|
|
@@ -7748,7 +8223,7 @@ init_inbox();
|
|
|
7748
8223
|
init_runner();
|
|
7749
8224
|
init__connection_state();
|
|
7750
8225
|
init__server_client();
|
|
7751
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as
|
|
8226
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
7752
8227
|
import { spawnSync } from "child_process";
|
|
7753
8228
|
import { basename as basename2, resolve as resolve17 } from "path";
|
|
7754
8229
|
import { buildRigInitConfigSource } from "@rig/core";
|
|
@@ -7934,7 +8409,7 @@ async function buildPiSetupChecks(input = {}) {
|
|
|
7934
8409
|
// packages/cli/src/commands/_snapshot-upload.ts
|
|
7935
8410
|
init__server_client();
|
|
7936
8411
|
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
7937
|
-
import { dirname as
|
|
8412
|
+
import { dirname as dirname4, resolve as resolve15, relative, sep } from "path";
|
|
7938
8413
|
var SNAPSHOT_ARCHIVE_VERSION = 1;
|
|
7939
8414
|
var SNAPSHOT_ARCHIVE_CONTENT_TYPE = "application/vnd.rig.snapshot+json";
|
|
7940
8415
|
var DEFAULT_EXCLUDED_DIRECTORIES = new Set([
|
|
@@ -8283,14 +8758,14 @@ function parseRepoSlug(value) {
|
|
|
8283
8758
|
}
|
|
8284
8759
|
function ensureRigPrivateDirs(projectRoot) {
|
|
8285
8760
|
const rigDir = resolve17(projectRoot, ".rig");
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8761
|
+
mkdirSync8(resolve17(rigDir, "state"), { recursive: true });
|
|
8762
|
+
mkdirSync8(resolve17(rigDir, "logs"), { recursive: true });
|
|
8763
|
+
mkdirSync8(resolve17(rigDir, "runs"), { recursive: true });
|
|
8764
|
+
mkdirSync8(resolve17(rigDir, "tmp"), { recursive: true });
|
|
8765
|
+
mkdirSync8(resolve17(projectRoot, "artifacts"), { recursive: true });
|
|
8291
8766
|
const taskConfigPath = resolve17(rigDir, "task-config.json");
|
|
8292
8767
|
if (!existsSync11(taskConfigPath))
|
|
8293
|
-
|
|
8768
|
+
writeFileSync6(taskConfigPath, `{}
|
|
8294
8769
|
`, "utf-8");
|
|
8295
8770
|
}
|
|
8296
8771
|
function ensureGitignoreEntries(projectRoot) {
|
|
@@ -8318,7 +8793,7 @@ function ensureRigConfigPackageDependencies(projectRoot) {
|
|
|
8318
8793
|
...existsSync11(path) ? existing : { name: "rig-project", private: true },
|
|
8319
8794
|
devDependencies
|
|
8320
8795
|
};
|
|
8321
|
-
|
|
8796
|
+
writeFileSync6(path, `${JSON.stringify(next, null, 2)}
|
|
8322
8797
|
`, "utf8");
|
|
8323
8798
|
}
|
|
8324
8799
|
function applyGitHubProjectConfig(source, options) {
|
|
@@ -8344,9 +8819,9 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
|
|
|
8344
8819
|
const selected = strategy ?? { kind: "managed-clone" };
|
|
8345
8820
|
switch (selected.kind) {
|
|
8346
8821
|
case "managed-clone":
|
|
8347
|
-
return { kind: "managed-clone"
|
|
8822
|
+
return { kind: "managed-clone" };
|
|
8348
8823
|
case "current-ref":
|
|
8349
|
-
return { kind: "current-ref",
|
|
8824
|
+
return { kind: "current-ref", ...selected.ref ? { ref: selected.ref } : {} };
|
|
8350
8825
|
case "uploaded-snapshot":
|
|
8351
8826
|
return { kind: "uploaded-snapshot", path: projectRoot, source: "local-working-tree" };
|
|
8352
8827
|
case "existing-path":
|
|
@@ -8616,7 +9091,7 @@ function remoteGitHubAuthMetadata(payload) {
|
|
|
8616
9091
|
};
|
|
8617
9092
|
}
|
|
8618
9093
|
function writeRemoteGitHubAuthState(projectRoot, input) {
|
|
8619
|
-
|
|
9094
|
+
writeFileSync6(resolve17(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
|
|
8620
9095
|
authenticated: true,
|
|
8621
9096
|
source: input.source,
|
|
8622
9097
|
storedOnServer: true,
|
|
@@ -8664,7 +9139,7 @@ function runLocalFilesInit(context, options) {
|
|
|
8664
9139
|
console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
|
|
8665
9140
|
} else {
|
|
8666
9141
|
const projectName = basename2(projectRoot) || "rig-project";
|
|
8667
|
-
|
|
9142
|
+
writeFileSync6(configTsPath, buildRigInitConfigSource({
|
|
8668
9143
|
projectName,
|
|
8669
9144
|
taskSource: { kind: "files", path: "tasks" },
|
|
8670
9145
|
useStandardPlugin: true
|
|
@@ -8673,8 +9148,8 @@ function runLocalFilesInit(context, options) {
|
|
|
8673
9148
|
ensureRigConfigPackageDependencies(projectRoot);
|
|
8674
9149
|
const tasksDir = resolve17(projectRoot, "tasks");
|
|
8675
9150
|
if (!existsSync11(tasksDir)) {
|
|
8676
|
-
|
|
8677
|
-
|
|
9151
|
+
mkdirSync8(tasksDir, { recursive: true });
|
|
9152
|
+
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)}
|
|
8678
9153
|
`, "utf-8");
|
|
8679
9154
|
}
|
|
8680
9155
|
if (context.outputMode !== "json") {
|
|
@@ -8737,7 +9212,7 @@ function runDemoInit(context, options) {
|
|
|
8737
9212
|
console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it for the demo.");
|
|
8738
9213
|
}
|
|
8739
9214
|
} else {
|
|
8740
|
-
|
|
9215
|
+
writeFileSync6(configTsPath, buildRigInitConfigSource({
|
|
8741
9216
|
projectName: basename2(projectRoot) || "rig-demo",
|
|
8742
9217
|
taskSource: { kind: "files", path: DEMO_TASKS_RELATIVE_DIR },
|
|
8743
9218
|
useStandardPlugin: true
|
|
@@ -8746,14 +9221,14 @@ function runDemoInit(context, options) {
|
|
|
8746
9221
|
}
|
|
8747
9222
|
ensureRigConfigPackageDependencies(projectRoot);
|
|
8748
9223
|
const demoTasksDir = resolve17(projectRoot, DEMO_TASKS_RELATIVE_DIR);
|
|
8749
|
-
|
|
9224
|
+
mkdirSync8(demoTasksDir, { recursive: true });
|
|
8750
9225
|
const taskIds = [];
|
|
8751
9226
|
for (const task of DEMO_TASKS) {
|
|
8752
9227
|
const id = String(task.id);
|
|
8753
9228
|
taskIds.push(id);
|
|
8754
9229
|
const taskPath = resolve17(demoTasksDir, `${id}.json`);
|
|
8755
9230
|
if (!existsSync11(taskPath)) {
|
|
8756
|
-
|
|
9231
|
+
writeFileSync6(taskPath, `${JSON.stringify(task, null, 2)}
|
|
8757
9232
|
`, "utf-8");
|
|
8758
9233
|
}
|
|
8759
9234
|
}
|
|
@@ -8781,21 +9256,32 @@ function runDemoInit(context, options) {
|
|
|
8781
9256
|
}
|
|
8782
9257
|
async function runControlPlaneInit(context, options) {
|
|
8783
9258
|
const projectRoot = context.projectRoot;
|
|
8784
|
-
const
|
|
9259
|
+
const existingRepoConnection = readRepoConnection(projectRoot);
|
|
9260
|
+
const selectedConnection = (() => {
|
|
9261
|
+
try {
|
|
9262
|
+
return resolveSelectedConnection(projectRoot);
|
|
9263
|
+
} catch {
|
|
9264
|
+
return null;
|
|
9265
|
+
}
|
|
9266
|
+
})();
|
|
9267
|
+
const selectedRemote = selectedConnection?.connection.kind === "remote" ? { alias: selectedConnection.alias, connection: selectedConnection.connection } : null;
|
|
9268
|
+
const detectedSlug = options.repoSlug ?? existingRepoConnection?.project ?? detectOriginRepoSlug(projectRoot);
|
|
9269
|
+
const serverKind = options.server ?? (selectedRemote ? "remote" : "local");
|
|
8785
9270
|
if (!detectedSlug) {
|
|
8786
9271
|
const authMethod2 = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
|
|
8787
|
-
if (
|
|
9272
|
+
if (serverKind === "local" && authMethod2 === "skip") {
|
|
8788
9273
|
return runLocalFilesInit(context, options);
|
|
8789
9274
|
}
|
|
8790
9275
|
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);
|
|
8791
9276
|
}
|
|
8792
9277
|
const repo = parseRepoSlug(detectedSlug);
|
|
8793
|
-
const
|
|
8794
|
-
const
|
|
9278
|
+
const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : selectedRemote?.alias ?? "remote");
|
|
9279
|
+
const selectedRemoteUrl = selectedRemote && (!options.connectionAlias || options.connectionAlias === selectedRemote.alias) ? selectedRemote.connection.baseUrl : undefined;
|
|
9280
|
+
const remoteUrl = serverKind === "remote" ? (options.remoteUrl ?? selectedRemoteUrl)?.replace(/\/+$/, "") : undefined;
|
|
8795
9281
|
if (serverKind === "remote") {
|
|
8796
|
-
if (!
|
|
8797
|
-
throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server
|
|
8798
|
-
upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl:
|
|
9282
|
+
if (!remoteUrl)
|
|
9283
|
+
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>`." });
|
|
9284
|
+
upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: remoteUrl });
|
|
8799
9285
|
}
|
|
8800
9286
|
writeRepoConnection(projectRoot, {
|
|
8801
9287
|
selected: connectionAlias,
|
|
@@ -8818,11 +9304,11 @@ async function runControlPlaneInit(context, options) {
|
|
|
8818
9304
|
taskSource: { kind: "github-issues", owner: repo.owner, repo: repo.repo },
|
|
8819
9305
|
useStandardPlugin: true
|
|
8820
9306
|
}), options);
|
|
8821
|
-
|
|
9307
|
+
writeFileSync6(configTsPath, source, "utf-8");
|
|
8822
9308
|
}
|
|
8823
9309
|
ensureRigConfigPackageDependencies(projectRoot);
|
|
8824
9310
|
}
|
|
8825
|
-
|
|
9311
|
+
writeFileSync6(resolve17(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
|
|
8826
9312
|
`, "utf8");
|
|
8827
9313
|
const checkout = checkoutForInit(projectRoot, serverKind, options.remoteCheckout);
|
|
8828
9314
|
let uploadedSnapshot = null;
|
|
@@ -8837,7 +9323,7 @@ async function runControlPlaneInit(context, options) {
|
|
|
8837
9323
|
let githubAuth = null;
|
|
8838
9324
|
let deviceAuth = null;
|
|
8839
9325
|
const authMethod = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
|
|
8840
|
-
const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${
|
|
9326
|
+
const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${remoteUrl ?? "the remote Rig server"}.` : null;
|
|
8841
9327
|
if (remoteGhTokenWarning && !options.yes) {
|
|
8842
9328
|
throw new CliError(`${remoteGhTokenWarning} Re-run with --yes to confirm this explicit token transfer.`, 1);
|
|
8843
9329
|
}
|
|
@@ -9153,8 +9639,8 @@ init__server_client();
|
|
|
9153
9639
|
init__connection_state();
|
|
9154
9640
|
init__async_ui();
|
|
9155
9641
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
9156
|
-
import { mkdirSync as
|
|
9157
|
-
import { dirname as
|
|
9642
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
9643
|
+
import { dirname as dirname5, resolve as resolve18 } from "path";
|
|
9158
9644
|
function printPayload(context, payload, fallback) {
|
|
9159
9645
|
if (context.outputMode === "json")
|
|
9160
9646
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -9193,8 +9679,8 @@ function persistRemoteAuthSession(context, source, result, fallbackToken) {
|
|
|
9193
9679
|
return;
|
|
9194
9680
|
const repo = readRepoConnection(context.projectRoot);
|
|
9195
9681
|
const path = resolve18(context.projectRoot, ".rig", "state", "github-auth.json");
|
|
9196
|
-
|
|
9197
|
-
|
|
9682
|
+
mkdirSync9(dirname5(path), { recursive: true });
|
|
9683
|
+
writeFileSync7(path, `${JSON.stringify({
|
|
9198
9684
|
authenticated: true,
|
|
9199
9685
|
source,
|
|
9200
9686
|
storedOnServer: true,
|
|
@@ -10974,6 +11460,7 @@ init_drone_ui();
|
|
|
10974
11460
|
init_runner();
|
|
10975
11461
|
init__connection_state();
|
|
10976
11462
|
init__cli_format();
|
|
11463
|
+
init__server_client();
|
|
10977
11464
|
function usageName(options) {
|
|
10978
11465
|
return `rig ${options.group}`;
|
|
10979
11466
|
}
|
|
@@ -10993,13 +11480,46 @@ function parseConnection(alias, value, options) {
|
|
|
10993
11480
|
}
|
|
10994
11481
|
return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
|
|
10995
11482
|
}
|
|
10996
|
-
function printJsonOrText(context,
|
|
10997
|
-
if (context.outputMode
|
|
10998
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
10999
|
-
} else {
|
|
11483
|
+
function printJsonOrText(context, _payload, text2) {
|
|
11484
|
+
if (context.outputMode !== "json") {
|
|
11000
11485
|
console.log(text2);
|
|
11001
11486
|
}
|
|
11002
11487
|
}
|
|
11488
|
+
function formatRemoteProjectLinkText(result) {
|
|
11489
|
+
return formatSuccessCard(result.ok ? "Remote project link ready" : "Remote project link needs repair", [
|
|
11490
|
+
["selected", result.alias ?? "remote"],
|
|
11491
|
+
["target", result.baseUrl ?? "unknown"],
|
|
11492
|
+
["repo", result.repoSlug ?? "owner/repo"],
|
|
11493
|
+
["status", result.status],
|
|
11494
|
+
...result.serverProjectRoot ? [["server root", result.serverProjectRoot]] : [],
|
|
11495
|
+
["next", result.ok ? result.next ?? "rig task list" : result.hint]
|
|
11496
|
+
]);
|
|
11497
|
+
}
|
|
11498
|
+
function parseRepairLinkArgs(rest, usage) {
|
|
11499
|
+
let repoSlug;
|
|
11500
|
+
let mode = "prepare-if-missing";
|
|
11501
|
+
const pending = [...rest];
|
|
11502
|
+
while (pending.length > 0) {
|
|
11503
|
+
const token = pending.shift();
|
|
11504
|
+
if (token === "--repo") {
|
|
11505
|
+
const value = pending.shift()?.trim();
|
|
11506
|
+
if (!value)
|
|
11507
|
+
throw new CliError(`Missing --repo value. Usage: ${usage}`, 1);
|
|
11508
|
+
const normalized = normalizeRepoSlug(value);
|
|
11509
|
+
if (!normalized)
|
|
11510
|
+
throw new CliError(`Invalid --repo value: ${value}`, 1, { hint: "Use the canonical owner/repo slug, e.g. `--repo humanity-org/humanwork`." });
|
|
11511
|
+
repoSlug = normalized;
|
|
11512
|
+
} else if (token === "--backfill-only") {
|
|
11513
|
+
mode = "backfill-only";
|
|
11514
|
+
} else if (token === "--prepare") {
|
|
11515
|
+
mode = "prepare-if-missing";
|
|
11516
|
+
} else {
|
|
11517
|
+
throw new CliError(`Unexpected argument: ${String(token)}
|
|
11518
|
+
Usage: ${usage}`, 1);
|
|
11519
|
+
}
|
|
11520
|
+
}
|
|
11521
|
+
return { repoSlug, mode };
|
|
11522
|
+
}
|
|
11003
11523
|
async function promptForConnectionAlias(context) {
|
|
11004
11524
|
const state = readGlobalConnections();
|
|
11005
11525
|
const repo = readRepoConnection(context.projectRoot);
|
|
@@ -11053,37 +11573,67 @@ async function executeConnectionCommand(context, args, options) {
|
|
|
11053
11573
|
}
|
|
11054
11574
|
if (!alias)
|
|
11055
11575
|
throw new CliError(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
|
|
11576
|
+
let selectedConnection = { kind: "local", mode: "auto" };
|
|
11056
11577
|
if (alias !== "local") {
|
|
11057
11578
|
const state = readGlobalConnections();
|
|
11058
|
-
|
|
11579
|
+
const connection = state.connections[alias];
|
|
11580
|
+
if (!connection)
|
|
11059
11581
|
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>`." });
|
|
11582
|
+
selectedConnection = connection;
|
|
11060
11583
|
}
|
|
11061
11584
|
const previousRepo = readRepoConnection(context.projectRoot);
|
|
11585
|
+
const preserveRemoteRoot = Boolean(previousRepo?.serverProjectRoot && previousRepo.selected === alias && selectedConnection.kind === "remote" && previousRepo.serverProjectRootAlias === alias && previousRepo.serverProjectRootBaseUrl === selectedConnection.baseUrl);
|
|
11062
11586
|
const repoState = {
|
|
11063
11587
|
selected: alias,
|
|
11064
11588
|
...previousRepo?.project ? { project: previousRepo.project } : {},
|
|
11065
11589
|
linkedAt: new Date().toISOString(),
|
|
11066
|
-
...
|
|
11590
|
+
...preserveRemoteRoot ? {
|
|
11591
|
+
serverProjectRoot: previousRepo.serverProjectRoot,
|
|
11592
|
+
serverProjectRootAlias: previousRepo.serverProjectRootAlias,
|
|
11593
|
+
serverProjectRootBaseUrl: previousRepo.serverProjectRootBaseUrl
|
|
11594
|
+
} : {}
|
|
11067
11595
|
};
|
|
11068
11596
|
writeRepoConnection(context.projectRoot, repoState);
|
|
11069
|
-
|
|
11597
|
+
const remoteProjectLink = alias !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
|
|
11598
|
+
ok: false,
|
|
11599
|
+
status: "error",
|
|
11600
|
+
message: error instanceof Error ? error.message : String(error),
|
|
11601
|
+
hint: "Run `rig server repair-link` after authenticating the selected remote."
|
|
11602
|
+
})) : null;
|
|
11603
|
+
const latestRepo = readRepoConnection(context.projectRoot);
|
|
11604
|
+
const details = { selected: latestRepo?.selected ?? alias, repo: latestRepo, remoteProjectLink };
|
|
11605
|
+
printJsonOrText(context, details, formatSuccessCard("Rig server selected", [
|
|
11070
11606
|
["selected", alias],
|
|
11071
11607
|
["scope", "this repo"],
|
|
11072
|
-
["
|
|
11608
|
+
...remoteProjectLink ? [["remote link", remoteProjectLink.ok ? `ready (${remoteProjectLink.serverProjectRoot ?? "linked"})` : `${remoteProjectLink.status}: ${remoteProjectLink.hint}`]] : [],
|
|
11609
|
+
["next", remoteProjectLink && !remoteProjectLink.ok ? "rig server repair-link" : "rig task list"]
|
|
11073
11610
|
]));
|
|
11074
|
-
return { ok: true, group: options.group, command: "use", details
|
|
11611
|
+
return { ok: true, group: options.group, command: "use", details };
|
|
11612
|
+
}
|
|
11613
|
+
case "repair-link": {
|
|
11614
|
+
const parsed = parseRepairLinkArgs(rest, `${usageName(options)} repair-link [--prepare|--backfill-only] [--repo owner/repo]`);
|
|
11615
|
+
const result = await repairRemoteProjectRootLink(context, parsed);
|
|
11616
|
+
printJsonOrText(context, result, formatRemoteProjectLinkText(result));
|
|
11617
|
+
return { ok: true, group: options.group, command: "repair-link", details: result };
|
|
11075
11618
|
}
|
|
11076
11619
|
case "status": {
|
|
11077
11620
|
requireNoExtraArgs(rest, `${usageName(options)} status`);
|
|
11078
11621
|
const repo = readRepoConnection(context.projectRoot);
|
|
11079
11622
|
const global = readGlobalConnections();
|
|
11080
|
-
const
|
|
11081
|
-
|
|
11623
|
+
const remoteProjectLink = repo?.selected && repo.selected !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
|
|
11624
|
+
ok: false,
|
|
11625
|
+
status: "error",
|
|
11626
|
+
message: error instanceof Error ? error.message : String(error),
|
|
11627
|
+
hint: "Run `rig server repair-link` after authenticating the selected remote."
|
|
11628
|
+
})) : null;
|
|
11629
|
+
const latestRepo = readRepoConnection(context.projectRoot) ?? repo;
|
|
11630
|
+
const details = { selected: latestRepo?.selected ?? "local", repo: latestRepo, connections: global.connections, remoteProjectLink };
|
|
11631
|
+
printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections, latestRepo ?? null, remoteProjectLink));
|
|
11082
11632
|
return { ok: true, group: options.group, command: "status", details };
|
|
11083
11633
|
}
|
|
11084
11634
|
default:
|
|
11085
11635
|
throw new CliError(`Unknown ${options.group} command: ${String(command)}
|
|
11086
|
-
Usage: ${usageName(options)} <list|add|use|status>`, 1);
|
|
11636
|
+
Usage: ${usageName(options)} <list|add|use|status|repair-link>`, 1);
|
|
11087
11637
|
}
|
|
11088
11638
|
}
|
|
11089
11639
|
|
|
@@ -11091,7 +11641,7 @@ Usage: ${usageName(options)} <list|add|use|status>`, 1);
|
|
|
11091
11641
|
init__server_client();
|
|
11092
11642
|
async function executeServer(context, args, options) {
|
|
11093
11643
|
const [command = "status", ...rest] = args;
|
|
11094
|
-
if (["status", "list", "add", "use"].includes(command)) {
|
|
11644
|
+
if (["status", "list", "add", "use", "repair-link"].includes(command)) {
|
|
11095
11645
|
return executeConnectionCommand(context, [command, ...rest], { group: "server", interactiveUse: true });
|
|
11096
11646
|
}
|
|
11097
11647
|
switch (command) {
|
|
@@ -11299,7 +11849,7 @@ init_task();
|
|
|
11299
11849
|
|
|
11300
11850
|
// packages/cli/src/commands/task-run-driver.ts
|
|
11301
11851
|
init_runner();
|
|
11302
|
-
import { copyFileSync as copyFileSync3, existsSync as existsSync15, mkdirSync as
|
|
11852
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync12, statSync as statSync2, writeFileSync as writeFileSync9 } from "fs";
|
|
11303
11853
|
import { resolve as resolve23 } from "path";
|
|
11304
11854
|
import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
|
|
11305
11855
|
import { createInterface as createLineInterface } from "readline";
|
|
@@ -11404,7 +11954,7 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
|
|
|
11404
11954
|
try {
|
|
11405
11955
|
if (!statSync2(sourcePath).isFile())
|
|
11406
11956
|
continue;
|
|
11407
|
-
|
|
11957
|
+
mkdirSync11(resolve23(targetPath, ".."), { recursive: true });
|
|
11408
11958
|
copyFileSync3(sourcePath, targetPath);
|
|
11409
11959
|
copied += 1;
|
|
11410
11960
|
} catch {}
|
|
@@ -12296,8 +12846,8 @@ async function executeRigOwnedTaskRun(context, input) {
|
|
|
12296
12846
|
artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
|
|
12297
12847
|
classifiedAt: new Date().toISOString()
|
|
12298
12848
|
};
|
|
12299
|
-
|
|
12300
|
-
|
|
12849
|
+
mkdirSync11(resolve23(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
|
|
12850
|
+
writeFileSync9(resolve23(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
|
|
12301
12851
|
`, "utf8");
|
|
12302
12852
|
patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
|
|
12303
12853
|
prompt = `${prompt}
|
|
@@ -12444,8 +12994,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
|
|
|
12444
12994
|
const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
|
|
12445
12995
|
const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
|
|
12446
12996
|
if (readyFile) {
|
|
12447
|
-
|
|
12448
|
-
|
|
12997
|
+
mkdirSync11(resolve23(readyFile, ".."), { recursive: true });
|
|
12998
|
+
writeFileSync9(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
|
|
12449
12999
|
`, "utf8");
|
|
12450
13000
|
}
|
|
12451
13001
|
appendRunLog(context.projectRoot, input.runId, {
|
|
@@ -13365,7 +13915,7 @@ async function executeTest(context, args) {
|
|
|
13365
13915
|
init_runner();
|
|
13366
13916
|
init__parsers();
|
|
13367
13917
|
init__paths();
|
|
13368
|
-
import { existsSync as existsSync16, mkdirSync as
|
|
13918
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync12, readdirSync as readdirSync3, writeFileSync as writeFileSync10 } from "fs";
|
|
13369
13919
|
import { resolve as resolve24 } from "path";
|
|
13370
13920
|
import { createPluginHost } from "@rig/core";
|
|
13371
13921
|
import {
|
|
@@ -13421,12 +13971,12 @@ function runSetupInit(projectRoot) {
|
|
|
13421
13971
|
const stateDir = resolveControlPlaneHostStateDir(projectRoot);
|
|
13422
13972
|
const logsDir = resolveControlPlaneHostLogsDir(projectRoot);
|
|
13423
13973
|
const artifactsDir = resolveControlPlaneArtifactsDir(projectRoot);
|
|
13424
|
-
|
|
13425
|
-
|
|
13426
|
-
|
|
13974
|
+
mkdirSync12(stateDir, { recursive: true });
|
|
13975
|
+
mkdirSync12(logsDir, { recursive: true });
|
|
13976
|
+
mkdirSync12(artifactsDir, { recursive: true });
|
|
13427
13977
|
const failuresPath = resolve24(stateDir, "failed_approaches.md");
|
|
13428
13978
|
if (!existsSync16(failuresPath)) {
|
|
13429
|
-
|
|
13979
|
+
writeFileSync10(failuresPath, `# Failed Approaches
|
|
13430
13980
|
|
|
13431
13981
|
`, "utf-8");
|
|
13432
13982
|
}
|