@h-rig/cli-surface-plugin 0.0.6-alpha.157 → 0.0.6-alpha.159
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/src/app/drone-ui.d.ts +0 -11
- package/dist/src/app/drone-ui.js +0 -114
- package/dist/src/commands/_async-ui.d.ts +1 -1
- package/dist/src/commands/_cli-format.d.ts +0 -29
- package/dist/src/commands/_cli-format.js +59 -113
- package/dist/src/commands/_connection-state.d.ts +6 -33
- package/dist/src/commands/_connection-state.js +654 -138
- package/dist/src/commands/_doctor-checks.d.ts +2 -5
- package/dist/src/commands/_doctor-checks.js +10 -9
- package/dist/src/commands/_help-catalog.d.ts +2 -1
- package/dist/src/commands/_help-catalog.js +654 -7
- package/dist/src/commands/_inprocess-services.d.ts +5 -5
- package/dist/src/commands/_inprocess-services.js +1 -1
- package/dist/src/commands/_parsers.js +651 -12
- package/dist/src/commands/_paths.d.ts +0 -2
- package/dist/src/commands/_paths.js +2 -10
- package/dist/src/commands/_pi-install.d.ts +2 -12
- package/dist/src/commands/_pi-install.js +3 -36
- package/dist/src/commands/_policy.js +659 -20
- package/dist/src/commands/agent.d.ts +1 -1
- package/dist/src/commands/agent.js +675 -24
- package/dist/src/commands/config.d.ts +1 -1
- package/dist/src/commands/config.js +656 -21
- package/dist/src/commands/dist.d.ts +1 -1
- package/dist/src/commands/dist.js +828 -102
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.js +658 -12
- package/dist/src/commands/github.d.ts +1 -1
- package/dist/src/commands/github.js +658 -19
- package/dist/src/commands/inbox.d.ts +12 -8
- package/dist/src/commands/inbox.js +741 -22
- package/dist/src/commands/init.d.ts +17 -19
- package/dist/src/commands/init.js +836 -306
- package/dist/src/commands/inspect.d.ts +5 -6
- package/dist/src/commands/inspect.js +754 -42
- package/dist/src/commands/pi.d.ts +1 -1
- package/dist/src/commands/pi.js +655 -16
- package/dist/src/commands/plugin.d.ts +9 -9
- package/dist/src/commands/plugin.js +652 -13
- package/dist/src/commands/profile-and-review.d.ts +1 -1
- package/dist/src/commands/profile-and-review.js +655 -16
- package/dist/src/commands/queue.d.ts +1 -1
- package/dist/src/commands/queue.js +871 -12
- package/dist/src/commands/remote-client.d.ts +152 -0
- package/dist/src/commands/remote-client.js +475 -0
- package/dist/src/commands/remote.d.ts +1 -1
- package/dist/src/commands/remote.js +1100 -29
- package/dist/src/commands/repo-git-harness.d.ts +1 -1
- package/dist/src/commands/repo-git-harness.js +2321 -47
- package/dist/src/commands/run.d.ts +10 -6
- package/dist/src/commands/run.js +830 -50
- package/dist/src/commands/server.d.ts +1 -1
- package/dist/src/commands/server.js +649 -11
- package/dist/src/commands/setup.d.ts +2 -2
- package/dist/src/commands/setup.js +829 -18
- package/dist/src/commands/stats.d.ts +2 -4
- package/dist/src/commands/stats.js +1299 -20
- package/dist/src/commands/test.d.ts +1 -1
- package/dist/src/commands/test.js +648 -9
- package/dist/src/commands/triage.d.ts +2 -3
- package/dist/src/commands/triage.js +657 -11
- package/dist/src/commands/workspace.d.ts +1 -1
- package/dist/src/commands/workspace.js +1280 -15
- package/dist/src/control-plane/agent-binary-build.d.ts +9 -0
- package/dist/src/control-plane/agent-binary-build.js +88 -0
- package/dist/src/control-plane/embedded-native-assets.d.ts +7 -0
- package/dist/src/control-plane/embedded-native-assets.js +6 -0
- package/dist/src/control-plane/guard.d.ts +17 -0
- package/dist/src/control-plane/guard.js +684 -0
- package/dist/src/control-plane/harness-cli.d.ts +12 -0
- package/dist/src/control-plane/harness-cli.js +1623 -0
- package/dist/src/control-plane/native/git-ops.d.ts +67 -0
- package/dist/src/control-plane/native/git-ops.js +1381 -0
- package/dist/src/control-plane/native/github-auth-env.d.ts +1 -0
- package/dist/src/control-plane/native/github-auth-env.js +21 -0
- package/dist/src/control-plane/native/host-git.d.ts +4 -0
- package/dist/src/control-plane/native/host-git.js +51 -0
- package/dist/src/control-plane/priority-queue.d.ts +22 -0
- package/dist/src/control-plane/priority-queue.js +212 -0
- package/dist/src/control-plane/rigfig.d.ts +9 -0
- package/dist/src/control-plane/rigfig.js +70 -0
- package/dist/src/control-plane/scope.d.ts +3 -0
- package/dist/src/control-plane/scope.js +58 -0
- package/dist/src/control-plane/setup-status.d.ts +44 -0
- package/dist/src/control-plane/setup-status.js +164 -0
- package/dist/src/control-plane/task-data.d.ts +2 -0
- package/dist/src/control-plane/task-data.js +12 -0
- package/dist/src/control-plane/workspace-ops.d.ts +79 -0
- package/dist/src/control-plane/workspace-ops.js +639 -0
- package/dist/src/help-catalog-data.d.ts +7 -0
- package/dist/src/help-catalog-data.js +660 -0
- package/dist/src/kernel-dispatch.js +1 -3
- package/dist/src/plugin.js +10072 -30
- package/dist/src/runner.d.ts +7 -9
- package/dist/src/runner.js +750 -30
- package/package.json +12 -13
- package/dist/src/commands/_json-output.d.ts +0 -11
- package/dist/src/commands/_json-output.js +0 -54
- package/dist/src/commands/_pi-frontend.d.ts +0 -35
- package/dist/src/commands/_pi-frontend.js +0 -64
- package/dist/src/commands/_run-driver-helpers.d.ts +0 -26
- package/dist/src/commands/_run-driver-helpers.js +0 -132
- package/dist/src/commands/task-run-driver.d.ts +0 -93
- package/dist/src/commands/task-run-driver.js +0 -136
- package/dist/src/commands/task.d.ts +0 -46
- package/dist/src/commands/task.js +0 -555
- package/dist/src/provider-model.d.ts +0 -34
- package/dist/src/provider-model.js +0 -56
- package/dist/src/rig-config-package-deps.d.ts +0 -10
- package/dist/src/rig-config-package-deps.js +0 -272
- package/dist/src/version.d.ts +0 -8
- package/dist/src/version.js +0 -47
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function authStateToken(env?: Record<string, string | undefined>): string | null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli-surface-plugin/src/control-plane/native/github-auth-env.ts
|
|
3
|
+
import { existsSync, readFileSync } from "fs";
|
|
4
|
+
function cleanToken(value) {
|
|
5
|
+
const trimmed = value?.trim() ?? "";
|
|
6
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
7
|
+
}
|
|
8
|
+
function authStateToken(env = process.env) {
|
|
9
|
+
const file = env.RIG_GITHUB_AUTH_STATE_FILE?.trim();
|
|
10
|
+
if (!file || !existsSync(file))
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
14
|
+
return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
authStateToken
|
|
21
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli-surface-plugin/src/control-plane/native/host-git.ts
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { resolve } from "path";
|
|
5
|
+
function isRuntimeGatewayGitPath(candidate) {
|
|
6
|
+
return /\/\.rig\/bin\/git$/.test(candidate.replace(/\\/g, "/"));
|
|
7
|
+
}
|
|
8
|
+
function isRuntimeGatewayGhPath(candidate) {
|
|
9
|
+
return /\/\.rig\/bin\/gh$/.test(candidate.replace(/\\/g, "/"));
|
|
10
|
+
}
|
|
11
|
+
function resolveHostGitBinary() {
|
|
12
|
+
const candidates = [
|
|
13
|
+
process.env.RIG_GIT_BIN?.trim() || "",
|
|
14
|
+
"/usr/bin/git",
|
|
15
|
+
"/opt/homebrew/bin/git",
|
|
16
|
+
"/usr/local/bin/git"
|
|
17
|
+
];
|
|
18
|
+
const bunResolved = Bun.which("git");
|
|
19
|
+
if (bunResolved && !isRuntimeGatewayGitPath(bunResolved))
|
|
20
|
+
candidates.push(bunResolved);
|
|
21
|
+
for (const candidate of candidates) {
|
|
22
|
+
if (candidate && !isRuntimeGatewayGitPath(candidate) && existsSync(candidate))
|
|
23
|
+
return candidate;
|
|
24
|
+
}
|
|
25
|
+
return "git";
|
|
26
|
+
}
|
|
27
|
+
function resolveGithubCliBinary(options = {}) {
|
|
28
|
+
const candidates = new Set;
|
|
29
|
+
const explicit = process.env.RIG_GH_BIN?.trim();
|
|
30
|
+
if (explicit)
|
|
31
|
+
candidates.add(explicit);
|
|
32
|
+
for (const candidate of ["/usr/bin/gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh"])
|
|
33
|
+
candidates.add(candidate);
|
|
34
|
+
if (options.scanPath) {
|
|
35
|
+
for (const entry of (process.env.PATH || "").split(":").map((value) => value.trim()).filter(Boolean)) {
|
|
36
|
+
candidates.add(resolve(entry, "gh"));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const bunResolved = Bun.which("gh");
|
|
40
|
+
if (bunResolved)
|
|
41
|
+
candidates.add(bunResolved);
|
|
42
|
+
for (const candidate of candidates) {
|
|
43
|
+
if (candidate && existsSync(candidate) && !isRuntimeGatewayGhPath(candidate))
|
|
44
|
+
return candidate;
|
|
45
|
+
}
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
resolveHostGitBinary,
|
|
50
|
+
resolveGithubCliBinary
|
|
51
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type IsolationMode } from "@rig/contracts";
|
|
2
|
+
import type { RunnerContext } from "@rig/core/runtime-runner-context";
|
|
3
|
+
export type QueueRunResult = {
|
|
4
|
+
processed: number;
|
|
5
|
+
succeeded: number;
|
|
6
|
+
failed: number;
|
|
7
|
+
tasks: Array<{
|
|
8
|
+
taskId: string;
|
|
9
|
+
ok: boolean;
|
|
10
|
+
score: number;
|
|
11
|
+
unblockCount: number;
|
|
12
|
+
error?: string;
|
|
13
|
+
}>;
|
|
14
|
+
};
|
|
15
|
+
export declare function runPriorityQueue(context: RunnerContext, options: {
|
|
16
|
+
workers: number;
|
|
17
|
+
maxTasks: number;
|
|
18
|
+
action: "validate" | "verify" | "pipeline";
|
|
19
|
+
failFast: boolean;
|
|
20
|
+
isolation: "off" | IsolationMode;
|
|
21
|
+
reuseRuntime: boolean;
|
|
22
|
+
}): Promise<QueueRunResult>;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli-surface-plugin/src/control-plane/priority-queue.ts
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { resolve } from "path";
|
|
5
|
+
import { resolveRigLayout } from "@rig/core/layout";
|
|
6
|
+
import { agentId } from "@rig/core/safe-identifiers";
|
|
7
|
+
import {
|
|
8
|
+
ISOLATION_BACKEND,
|
|
9
|
+
TASK_DATA_SERVICE_CAPABILITY
|
|
10
|
+
} from "@rig/contracts";
|
|
11
|
+
import { defineCapability } from "@rig/core/capability";
|
|
12
|
+
import { requireCapabilityForRoot, requireInstalledCapability } from "@rig/core/capability-loaders";
|
|
13
|
+
var TaskDataCap = defineCapability(TASK_DATA_SERVICE_CAPABILITY);
|
|
14
|
+
var taskData = () => requireInstalledCapability(TaskDataCap, "task-data capability unavailable: load @rig/task-sources-plugin (default bundle) before running the queue.");
|
|
15
|
+
var IsolationBackendCap = defineCapability(ISOLATION_BACKEND);
|
|
16
|
+
var requireIsolationBackend = (projectRoot) => requireCapabilityForRoot(projectRoot, IsolationBackendCap, `No ISOLATION_BACKEND capability is registered for project root "${projectRoot}". ` + "Install @rig/isolation-plugin (it ships in the default bundle) so runtime provisioning can resolve a backend.");
|
|
17
|
+
async function runPriorityQueue(context, options) {
|
|
18
|
+
const trackerSnapshot = taskData().readSyncedTrackerState(context.projectRoot);
|
|
19
|
+
const rankedTasks = rankTasks(context.projectRoot, trackerSnapshot).slice(0, options.maxTasks);
|
|
20
|
+
const effectiveIsolation = context.dryRun ? "off" : options.isolation;
|
|
21
|
+
await context.emitEvent("queue.started", {
|
|
22
|
+
taskCount: rankedTasks.length,
|
|
23
|
+
workers: options.workers,
|
|
24
|
+
action: options.action,
|
|
25
|
+
isolation: effectiveIsolation,
|
|
26
|
+
reuseRuntime: options.reuseRuntime
|
|
27
|
+
});
|
|
28
|
+
const results = [];
|
|
29
|
+
let index = 0;
|
|
30
|
+
let halted = false;
|
|
31
|
+
async function workerLoop(workerId) {
|
|
32
|
+
let workerRuntime;
|
|
33
|
+
while (!halted) {
|
|
34
|
+
const currentIndex = index;
|
|
35
|
+
index += 1;
|
|
36
|
+
const task = rankedTasks[currentIndex];
|
|
37
|
+
if (!task) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
await context.emitEvent("queue.task.started", {
|
|
41
|
+
workerId,
|
|
42
|
+
taskId: task.id,
|
|
43
|
+
score: task.score,
|
|
44
|
+
unblockCount: task.unblockCount,
|
|
45
|
+
action: options.action,
|
|
46
|
+
isolation: effectiveIsolation
|
|
47
|
+
});
|
|
48
|
+
try {
|
|
49
|
+
if (effectiveIsolation !== "off") {
|
|
50
|
+
if (!workerRuntime || !options.reuseRuntime || workerRuntime.taskId !== task.id) {
|
|
51
|
+
const runtimeId = options.reuseRuntime ? `queue-w${workerId}-${effectiveIsolation}` : agentId(`queue-${workerId}`);
|
|
52
|
+
const isolationBackend = await requireIsolationBackend(context.projectRoot);
|
|
53
|
+
workerRuntime = await isolationBackend.ensureAgentRuntime({
|
|
54
|
+
projectRoot: context.projectRoot,
|
|
55
|
+
id: runtimeId,
|
|
56
|
+
taskId: task.id,
|
|
57
|
+
mode: effectiveIsolation
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
let result;
|
|
62
|
+
if (options.action === "pipeline") {
|
|
63
|
+
result = await runTaskStep(context, task.id, "validate", effectiveIsolation, workerRuntime);
|
|
64
|
+
result = await runTaskStep(context, task.id, "verify", effectiveIsolation, workerRuntime);
|
|
65
|
+
} else {
|
|
66
|
+
result = await runTaskStep(context, task.id, options.action, effectiveIsolation, workerRuntime);
|
|
67
|
+
}
|
|
68
|
+
results.push({
|
|
69
|
+
taskId: task.id,
|
|
70
|
+
ok: result.exitCode === 0,
|
|
71
|
+
score: task.score,
|
|
72
|
+
unblockCount: task.unblockCount
|
|
73
|
+
});
|
|
74
|
+
await context.emitEvent("queue.task.finished", {
|
|
75
|
+
workerId,
|
|
76
|
+
taskId: task.id,
|
|
77
|
+
ok: result.exitCode === 0,
|
|
78
|
+
action: options.action,
|
|
79
|
+
exitCode: result.exitCode
|
|
80
|
+
});
|
|
81
|
+
} catch (error) {
|
|
82
|
+
results.push({
|
|
83
|
+
taskId: task.id,
|
|
84
|
+
ok: false,
|
|
85
|
+
score: task.score,
|
|
86
|
+
unblockCount: task.unblockCount,
|
|
87
|
+
error: `${error}`
|
|
88
|
+
});
|
|
89
|
+
await context.emitEvent("queue.task.finished", {
|
|
90
|
+
workerId,
|
|
91
|
+
taskId: task.id,
|
|
92
|
+
ok: false,
|
|
93
|
+
action: options.action,
|
|
94
|
+
error: `${error}`
|
|
95
|
+
});
|
|
96
|
+
if (options.failFast) {
|
|
97
|
+
halted = true;
|
|
98
|
+
}
|
|
99
|
+
} finally {}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const workerCount = Math.max(1, options.workers);
|
|
103
|
+
await Promise.all(Array.from({ length: workerCount }, (_, i) => workerLoop(i + 1)));
|
|
104
|
+
const summary = {
|
|
105
|
+
processed: results.length,
|
|
106
|
+
succeeded: results.filter((item) => item.ok).length,
|
|
107
|
+
failed: results.filter((item) => !item.ok).length,
|
|
108
|
+
tasks: results
|
|
109
|
+
};
|
|
110
|
+
await context.emitEvent("queue.finished", {
|
|
111
|
+
processed: summary.processed,
|
|
112
|
+
succeeded: summary.succeeded,
|
|
113
|
+
failed: summary.failed
|
|
114
|
+
});
|
|
115
|
+
return summary;
|
|
116
|
+
}
|
|
117
|
+
async function runTaskStep(context, taskId, action, isolation, runtime) {
|
|
118
|
+
const localTaskCommand = resolveTaskCommand(context.projectRoot, action, taskId);
|
|
119
|
+
if (context.dryRun) {
|
|
120
|
+
const now = new Date().toISOString();
|
|
121
|
+
return {
|
|
122
|
+
command: localTaskCommand,
|
|
123
|
+
formattedCommand: localTaskCommand.join(" "),
|
|
124
|
+
exitCode: 0,
|
|
125
|
+
durationMs: 0,
|
|
126
|
+
startedAt: now,
|
|
127
|
+
finishedAt: now
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (isolation === "off") {
|
|
131
|
+
return context.runCommand(localTaskCommand);
|
|
132
|
+
}
|
|
133
|
+
if (!runtime) {
|
|
134
|
+
throw new Error(`Missing runtime for isolated queue action ${action} (${taskId})`);
|
|
135
|
+
}
|
|
136
|
+
const startedAt = new Date;
|
|
137
|
+
const isolatedTaskCommand = resolveTaskCommand(runtime.workspaceDir, action, taskId, context.projectRoot);
|
|
138
|
+
const isolationBackend = await requireIsolationBackend(context.projectRoot);
|
|
139
|
+
const result = await isolationBackend.runInAgentRuntime({
|
|
140
|
+
projectRoot: context.projectRoot,
|
|
141
|
+
runtime,
|
|
142
|
+
command: isolatedTaskCommand,
|
|
143
|
+
inheritStdio: context.outputMode === "text"
|
|
144
|
+
});
|
|
145
|
+
const finishedAt = new Date;
|
|
146
|
+
if (result.exitCode !== 0) {
|
|
147
|
+
throw new Error(`Isolated ${action} failed for ${taskId} in runtime ${runtime.id} with exit code ${result.exitCode}${result.stderr ? `
|
|
148
|
+
${result.stderr.trim()}` : ""}`);
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
command: isolatedTaskCommand,
|
|
152
|
+
formattedCommand: isolatedTaskCommand.join(" "),
|
|
153
|
+
exitCode: result.exitCode,
|
|
154
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
155
|
+
startedAt: startedAt.toISOString(),
|
|
156
|
+
finishedAt: finishedAt.toISOString(),
|
|
157
|
+
...result.stdout !== undefined ? { stdout: result.stdout } : {},
|
|
158
|
+
...result.stderr !== undefined ? { stderr: result.stderr } : {}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function resolveTaskCommand(projectRoot, action, taskId, hostProjectRoot = projectRoot) {
|
|
162
|
+
try {
|
|
163
|
+
const compiledRig = resolve(resolveRigLayout(projectRoot).binDir, "rig");
|
|
164
|
+
if (existsSync(compiledRig)) {
|
|
165
|
+
return [compiledRig, "task", action, "--task", taskId];
|
|
166
|
+
}
|
|
167
|
+
} catch {}
|
|
168
|
+
return ["bun", "run", resolve(hostProjectRoot, "packages/cli/bin/rig.ts"), "task", action, "--task", taskId];
|
|
169
|
+
}
|
|
170
|
+
function rankTasks(projectRoot, snapshot) {
|
|
171
|
+
const readyTaskIds = taskData().listReadyTaskIdsFromTracker(snapshot);
|
|
172
|
+
let taskConfig = {};
|
|
173
|
+
const issues = snapshot.issues.filter((issue) => issue.issueType === "task");
|
|
174
|
+
try {
|
|
175
|
+
taskConfig = taskData().readTaskConfig(projectRoot);
|
|
176
|
+
} catch {
|
|
177
|
+
taskConfig = {};
|
|
178
|
+
}
|
|
179
|
+
const activeTasks = new Map(issues.filter((issue) => issue.status === "open" || issue.status === "ready").map((issue) => [issue.id, issue]));
|
|
180
|
+
const reverseDeps = new Map;
|
|
181
|
+
for (const issue of issues) {
|
|
182
|
+
if (issue.status !== "open" && issue.status !== "ready") {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
for (const dep of issue.dependencies ?? []) {
|
|
186
|
+
if (dep.type !== "blocks" || !dep.dependsOnId) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
reverseDeps.set(dep.dependsOnId, (reverseDeps.get(dep.dependsOnId) || 0) + 1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return readyTaskIds.map((id) => {
|
|
193
|
+
const issue = activeTasks.get(id);
|
|
194
|
+
const taskMeta = taskConfig[id];
|
|
195
|
+
const priority = issue?.priority ?? 3;
|
|
196
|
+
const unblockCount = reverseDeps.get(id) ?? 0;
|
|
197
|
+
const roleWeight = taskMeta?.role === "architect" ? 25 : taskMeta?.role === "extractor" ? 10 : 0;
|
|
198
|
+
const criticalityWeight = taskMeta?.criticality === "core" ? 20 : taskMeta?.criticality === "high" ? 10 : 0;
|
|
199
|
+
const validationWeight = taskMeta?.validation?.some((entry) => entry.includes("test-contract") || entry.includes("test-boundary")) ? 8 : 0;
|
|
200
|
+
const customWeight = taskMeta?.queue_weight ?? 0;
|
|
201
|
+
const score = unblockCount * 100 + (10 - priority) + roleWeight + criticalityWeight + validationWeight + customWeight;
|
|
202
|
+
return {
|
|
203
|
+
id,
|
|
204
|
+
priority,
|
|
205
|
+
unblockCount,
|
|
206
|
+
score
|
|
207
|
+
};
|
|
208
|
+
}).sort((a, b) => b.score - a.score || a.priority - b.priority || a.id.localeCompare(b.id));
|
|
209
|
+
}
|
|
210
|
+
export {
|
|
211
|
+
runPriorityQueue
|
|
212
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ConfigDefaultsContext, RigPlugin } from "@rig/core/config";
|
|
2
|
+
export declare function rigfigConfigPath(projectRoot: string): string;
|
|
3
|
+
export declare function composeDeclarativeConfig(plugins: readonly RigPlugin[], context: ConfigDefaultsContext): Record<string, unknown>;
|
|
4
|
+
export declare function writeRigfigConfig(projectRoot: string, config: Record<string, unknown>): string;
|
|
5
|
+
export declare function composeAndWriteRigfig(projectRoot: string, options?: {
|
|
6
|
+
readonly repoSlug?: string;
|
|
7
|
+
readonly overlay?: Record<string, unknown>;
|
|
8
|
+
}): string;
|
|
9
|
+
export declare function configFileAlreadyExists(projectRoot: string): boolean;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli-surface-plugin/src/control-plane/rigfig.ts
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
4
|
+
import { resolve } from "path";
|
|
5
|
+
import { stringify as stringifyToml } from "smol-toml";
|
|
6
|
+
import { getStandardPluginsResolver } from "@rig/core/embedded-plugins";
|
|
7
|
+
function rigfigConfigPath(projectRoot) {
|
|
8
|
+
return resolve(projectRoot, ".rig", "rigfig.toml");
|
|
9
|
+
}
|
|
10
|
+
function isPlainObject(value) {
|
|
11
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
function deepMerge(base, overlay) {
|
|
14
|
+
const out = { ...base };
|
|
15
|
+
for (const [key, value] of Object.entries(overlay)) {
|
|
16
|
+
const previous = out[key];
|
|
17
|
+
out[key] = isPlainObject(previous) && isPlainObject(value) ? deepMerge(previous, value) : value;
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
function composeDeclarativeConfig(plugins, context) {
|
|
22
|
+
let merged = {};
|
|
23
|
+
for (const plugin of plugins) {
|
|
24
|
+
const fragment = plugin.contributes?.config?.defaults?.(context);
|
|
25
|
+
if (isPlainObject(fragment))
|
|
26
|
+
merged = deepMerge(merged, fragment);
|
|
27
|
+
}
|
|
28
|
+
return merged;
|
|
29
|
+
}
|
|
30
|
+
var HEADER = [
|
|
31
|
+
"# Declarative rig configuration \u2014 the happy path.",
|
|
32
|
+
"# Plain DATA, composed from the standard plugins' defaults. No top-level",
|
|
33
|
+
"# rig.config.ts and no @h-rig/* install: the global rig binary resolves",
|
|
34
|
+
"# plugins from its embedded standard collection. rig created this for you;",
|
|
35
|
+
"# edit it freely (each plugin owns the section it needs).",
|
|
36
|
+
"",
|
|
37
|
+
""
|
|
38
|
+
].join(`
|
|
39
|
+
`);
|
|
40
|
+
function writeRigfigConfig(projectRoot, config) {
|
|
41
|
+
mkdirSync(resolve(projectRoot, ".rig"), { recursive: true });
|
|
42
|
+
const path = rigfigConfigPath(projectRoot);
|
|
43
|
+
writeFileSync(path, `${HEADER}${stringifyToml(config)}
|
|
44
|
+
`, "utf-8");
|
|
45
|
+
return path;
|
|
46
|
+
}
|
|
47
|
+
function composeAndWriteRigfig(projectRoot, options = {}) {
|
|
48
|
+
const resolver = getStandardPluginsResolver();
|
|
49
|
+
if (!resolver) {
|
|
50
|
+
throw new Error("Cannot write rig config: embedded standard plugins are not registered (seed wiring error).");
|
|
51
|
+
}
|
|
52
|
+
const context = options.repoSlug ? { projectRoot, repoSlug: options.repoSlug } : { projectRoot };
|
|
53
|
+
let composed = composeDeclarativeConfig(resolver(), context);
|
|
54
|
+
if (options.overlay) {
|
|
55
|
+
if (isPlainObject(options.overlay.taskSource) && "taskSource" in composed)
|
|
56
|
+
delete composed.taskSource;
|
|
57
|
+
composed = deepMerge(composed, options.overlay);
|
|
58
|
+
}
|
|
59
|
+
return writeRigfigConfig(projectRoot, composed);
|
|
60
|
+
}
|
|
61
|
+
function configFileAlreadyExists(projectRoot) {
|
|
62
|
+
return existsSync(resolve(projectRoot, "rig.config.ts")) || existsSync(resolve(projectRoot, "rig.config.mts")) || existsSync(resolve(projectRoot, "rig.config.json")) || existsSync(rigfigConfigPath(projectRoot));
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
writeRigfigConfig,
|
|
66
|
+
rigfigConfigPath,
|
|
67
|
+
configFileAlreadyExists,
|
|
68
|
+
composeDeclarativeConfig,
|
|
69
|
+
composeAndWriteRigfig
|
|
70
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli-surface-plugin/src/control-plane/scope.ts
|
|
3
|
+
import { getScopeRules } from "@rig/core/scope-rules";
|
|
4
|
+
var scopeRegexCache = new Map;
|
|
5
|
+
function unique(values) {
|
|
6
|
+
return [...new Set(values)];
|
|
7
|
+
}
|
|
8
|
+
function normalizeRelativeScopePath(inputPath) {
|
|
9
|
+
let normalized = inputPath.replace(/^\.\//, "");
|
|
10
|
+
const rules = getScopeRules();
|
|
11
|
+
if (rules?.stripPrefixes) {
|
|
12
|
+
for (const prefix of rules.stripPrefixes) {
|
|
13
|
+
if (normalized.startsWith(prefix)) {
|
|
14
|
+
normalized = normalized.slice(prefix.length);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return normalized;
|
|
19
|
+
}
|
|
20
|
+
function normalizePathToScope(projectRoot, monorepoRoot, inputPath) {
|
|
21
|
+
let normalized = inputPath.replace(/^\.\//, "");
|
|
22
|
+
if (normalized.startsWith(projectRoot + "/")) {
|
|
23
|
+
normalized = normalized.slice(projectRoot.length + 1);
|
|
24
|
+
}
|
|
25
|
+
if (normalized.startsWith(monorepoRoot + "/")) {
|
|
26
|
+
normalized = normalized.slice(monorepoRoot.length + 1);
|
|
27
|
+
}
|
|
28
|
+
return normalizeRelativeScopePath(normalized);
|
|
29
|
+
}
|
|
30
|
+
function scopeGlobToRegex(glob) {
|
|
31
|
+
const cached = scopeRegexCache.get(glob);
|
|
32
|
+
if (cached) {
|
|
33
|
+
return cached;
|
|
34
|
+
}
|
|
35
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "__GLOBSTAR__").replace(/\*/g, "[^/]*").replace(/__GLOBSTAR__/g, ".*");
|
|
36
|
+
const compiled = new RegExp(`^${escaped}$`);
|
|
37
|
+
scopeRegexCache.set(glob, compiled);
|
|
38
|
+
return compiled;
|
|
39
|
+
}
|
|
40
|
+
function scopeMatches(path, scopes) {
|
|
41
|
+
const pathVariants = unique([path, normalizeRelativeScopePath(path)]);
|
|
42
|
+
for (const scope of scopes) {
|
|
43
|
+
const scopeVariants = unique([scope, normalizeRelativeScopePath(scope)]);
|
|
44
|
+
for (const candidatePath of pathVariants) {
|
|
45
|
+
for (const candidateScope of scopeVariants) {
|
|
46
|
+
if (candidatePath === candidateScope || scopeGlobToRegex(candidateScope).test(candidatePath)) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
scopeMatches,
|
|
56
|
+
scopeGlobToRegex,
|
|
57
|
+
normalizePathToScope
|
|
58
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { type GitHubAuthStatus } from "@rig/github-lib";
|
|
3
|
+
export type RigConfigStatus = {
|
|
4
|
+
readonly exists: boolean;
|
|
5
|
+
readonly valid: boolean;
|
|
6
|
+
readonly path: string;
|
|
7
|
+
readonly slug: string | null;
|
|
8
|
+
readonly reason?: string;
|
|
9
|
+
};
|
|
10
|
+
export type RigStateStatus = {
|
|
11
|
+
readonly valid: boolean;
|
|
12
|
+
readonly selected: string | null;
|
|
13
|
+
readonly project: string | null;
|
|
14
|
+
readonly reason?: string;
|
|
15
|
+
};
|
|
16
|
+
export type RigAuthValidation = {
|
|
17
|
+
readonly ok: boolean;
|
|
18
|
+
readonly source: "stored-token" | "gh" | "missing";
|
|
19
|
+
readonly login?: string | null;
|
|
20
|
+
readonly detail: string;
|
|
21
|
+
readonly status?: GitHubAuthStatus;
|
|
22
|
+
};
|
|
23
|
+
export type RigSetupStatus = {
|
|
24
|
+
readonly configured: boolean;
|
|
25
|
+
readonly projectRoot: string;
|
|
26
|
+
readonly slug: string | null;
|
|
27
|
+
readonly config: RigConfigStatus;
|
|
28
|
+
readonly state: RigStateStatus;
|
|
29
|
+
readonly auth: RigAuthValidation;
|
|
30
|
+
readonly reasons: readonly string[];
|
|
31
|
+
};
|
|
32
|
+
type RigSetupDeps = {
|
|
33
|
+
readonly spawn?: typeof spawnSync;
|
|
34
|
+
};
|
|
35
|
+
export declare function parseRepoSlug(value: string): {
|
|
36
|
+
owner: string;
|
|
37
|
+
repo: string;
|
|
38
|
+
slug: string;
|
|
39
|
+
};
|
|
40
|
+
export declare function detectStartupStatus(input: {
|
|
41
|
+
projectRoot: string;
|
|
42
|
+
deps?: RigSetupDeps | undefined;
|
|
43
|
+
}): Promise<RigSetupStatus>;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli-surface-plugin/src/control-plane/setup-status.ts
|
|
3
|
+
import { spawnSync } from "child_process";
|
|
4
|
+
import { existsSync, readFileSync } from "fs";
|
|
5
|
+
import { resolve as resolve2 } from "path";
|
|
6
|
+
import { createGitHubAuthStore, resolveGitHubAuthStatus } from "@rig/github-lib";
|
|
7
|
+
|
|
8
|
+
// packages/cli-surface-plugin/src/control-plane/rigfig.ts
|
|
9
|
+
import { resolve } from "path";
|
|
10
|
+
import { stringify as stringifyToml } from "smol-toml";
|
|
11
|
+
import { getStandardPluginsResolver } from "@rig/core/embedded-plugins";
|
|
12
|
+
function rigfigConfigPath(projectRoot) {
|
|
13
|
+
return resolve(projectRoot, ".rig", "rigfig.toml");
|
|
14
|
+
}
|
|
15
|
+
var HEADER = [
|
|
16
|
+
"# Declarative rig configuration \u2014 the happy path.",
|
|
17
|
+
"# Plain DATA, composed from the standard plugins' defaults. No top-level",
|
|
18
|
+
"# rig.config.ts and no @h-rig/* install: the global rig binary resolves",
|
|
19
|
+
"# plugins from its embedded standard collection. rig created this for you;",
|
|
20
|
+
"# edit it freely (each plugin owns the section it needs).",
|
|
21
|
+
"",
|
|
22
|
+
""
|
|
23
|
+
].join(`
|
|
24
|
+
`);
|
|
25
|
+
|
|
26
|
+
// packages/cli-surface-plugin/src/control-plane/setup-status.ts
|
|
27
|
+
function cleanString(value) {
|
|
28
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
29
|
+
}
|
|
30
|
+
function runSyncCommand(command, input = {}) {
|
|
31
|
+
const executable = command[0];
|
|
32
|
+
if (!executable)
|
|
33
|
+
throw new Error("command is required");
|
|
34
|
+
return (input.spawn ?? spawnSync)(executable, [...command.slice(1)], {
|
|
35
|
+
cwd: input.cwd,
|
|
36
|
+
encoding: "utf8",
|
|
37
|
+
timeout: input.timeoutMs ?? 1e4,
|
|
38
|
+
env: input.env ?? process.env
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function parseRepoSlug(value) {
|
|
42
|
+
const match = value.trim().match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
43
|
+
if (!match)
|
|
44
|
+
throw new Error(`Invalid GitHub repo slug "${value}". Expected owner/repo.`);
|
|
45
|
+
return { owner: match[1], repo: match[2], slug: `${match[1]}/${match[2]}` };
|
|
46
|
+
}
|
|
47
|
+
function parseRepoSlugFromRemote(remoteUrl) {
|
|
48
|
+
const trimmed = remoteUrl.trim();
|
|
49
|
+
const match = trimmed.match(/github\.com[:/]([^/\s]+)\/([^/\s.]+)(?:\.git)?$/i);
|
|
50
|
+
return match ? `${match[1]}/${match[2]}` : null;
|
|
51
|
+
}
|
|
52
|
+
function detectOriginRepoSlug(projectRoot, deps = {}) {
|
|
53
|
+
const result = runSyncCommand(["git", "-C", projectRoot, "remote", "get-url", "origin"], { timeoutMs: 5000, spawn: deps.spawn });
|
|
54
|
+
if (result.status !== 0 || result.error)
|
|
55
|
+
return null;
|
|
56
|
+
return parseRepoSlugFromRemote(result.stdout.trim());
|
|
57
|
+
}
|
|
58
|
+
function readJsonRecord(path) {
|
|
59
|
+
if (!existsSync(path))
|
|
60
|
+
return null;
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
63
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function readRigConfigStatus(projectRoot) {
|
|
69
|
+
const path = rigfigConfigPath(projectRoot);
|
|
70
|
+
if (!existsSync(path))
|
|
71
|
+
return { exists: false, valid: false, path, slug: null, reason: "missing .rig/rigfig.toml" };
|
|
72
|
+
try {
|
|
73
|
+
const source = readFileSync(path, "utf-8");
|
|
74
|
+
const owner = source.match(/^\s*owner\s*=\s*["']([^"']+)["']/m)?.[1] ?? null;
|
|
75
|
+
const repoValues = [...source.matchAll(/^\s*repo\s*=\s*["']([^"']+)["']/gm)].map((match) => match[1]).filter(Boolean);
|
|
76
|
+
const taskRepo = repoValues.find((value) => !value.includes("/")) ?? null;
|
|
77
|
+
const projectRepo = repoValues.find((value) => value.includes("/")) ?? null;
|
|
78
|
+
const githubIssues = /^\s*kind\s*=\s*["']github-issues["']/m.test(source);
|
|
79
|
+
const slug = owner && taskRepo ? `${owner}/${taskRepo}` : projectRepo;
|
|
80
|
+
if (!githubIssues || !slug)
|
|
81
|
+
return { exists: true, valid: false, path, slug: slug ?? null, reason: ".rig/rigfig.toml is not a GitHub Issues Rig config" };
|
|
82
|
+
parseRepoSlug(slug);
|
|
83
|
+
return { exists: true, valid: true, path, slug };
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return { exists: true, valid: false, path, slug: null, reason: error instanceof Error ? error.message : String(error) };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function connectionStatePath(projectRoot) {
|
|
89
|
+
return resolve2(projectRoot, ".rig", "state", "connection.json");
|
|
90
|
+
}
|
|
91
|
+
function readRigConnectionStatus(projectRoot) {
|
|
92
|
+
const stateDir = resolve2(projectRoot, ".rig", "state");
|
|
93
|
+
if (!existsSync(stateDir))
|
|
94
|
+
return { valid: false, selected: null, project: null, reason: "missing .rig/state" };
|
|
95
|
+
const connection = readJsonRecord(connectionStatePath(projectRoot));
|
|
96
|
+
if (!connection)
|
|
97
|
+
return { valid: false, selected: null, project: null, reason: "missing or invalid .rig/state/connection.json" };
|
|
98
|
+
const selected = cleanString(connection.selected);
|
|
99
|
+
const project = cleanString(connection.project);
|
|
100
|
+
if (!selected)
|
|
101
|
+
return { valid: false, selected: null, project, reason: "connection.json is missing selected placement" };
|
|
102
|
+
if (!project)
|
|
103
|
+
return { valid: false, selected, project: null, reason: "connection.json is missing project slug" };
|
|
104
|
+
try {
|
|
105
|
+
parseRepoSlug(project);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return { valid: false, selected, project, reason: error instanceof Error ? error.message : String(error) };
|
|
108
|
+
}
|
|
109
|
+
return { valid: true, selected, project };
|
|
110
|
+
}
|
|
111
|
+
function detectGhAuth(projectRoot, slug, deps = {}) {
|
|
112
|
+
const user = runSyncCommand(["gh", "api", "user", "--jq", ".login"], { cwd: projectRoot, timeoutMs: 5000, spawn: deps.spawn });
|
|
113
|
+
if (user.status !== 0 || user.error || !user.stdout.trim())
|
|
114
|
+
return null;
|
|
115
|
+
const repo = runSyncCommand(["gh", "repo", "view", slug, "--json", "nameWithOwner", "--jq", ".nameWithOwner"], { cwd: projectRoot, timeoutMs: 5000, spawn: deps.spawn });
|
|
116
|
+
if (repo.status !== 0 || repo.error)
|
|
117
|
+
return { ok: false, source: "gh", login: user.stdout.trim(), detail: (repo.stderr || repo.stdout || "gh cannot access the selected repository").trim() };
|
|
118
|
+
return { ok: true, source: "gh", login: user.stdout.trim(), detail: "gh CLI authentication can access the selected repository" };
|
|
119
|
+
}
|
|
120
|
+
async function validateGitHubAuth(projectRoot, slug, deps = {}) {
|
|
121
|
+
if (!slug)
|
|
122
|
+
return { ok: false, source: "missing", detail: "GitHub repo slug is unknown" };
|
|
123
|
+
const status = resolveGitHubAuthStatus({ projectRoot, oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
|
|
124
|
+
if (status.signedIn) {
|
|
125
|
+
const store = createGitHubAuthStore(projectRoot);
|
|
126
|
+
if (!status.selectedRepo) {
|
|
127
|
+
store.saveSelectedRepo(slug);
|
|
128
|
+
return { ok: true, source: "stored-token", login: status.login, detail: "stored Rig GitHub token selected for this repo", status: store.status({ oauthConfigured: status.oauthConfigured }) };
|
|
129
|
+
}
|
|
130
|
+
if (status.selectedRepo !== slug)
|
|
131
|
+
return { ok: false, source: "stored-token", login: status.login, detail: `stored GitHub token is scoped to ${status.selectedRepo}, not ${slug}`, status };
|
|
132
|
+
return { ok: true, source: "stored-token", login: status.login, detail: "stored Rig GitHub token is present", status };
|
|
133
|
+
}
|
|
134
|
+
const gh = detectGhAuth(projectRoot, slug, deps);
|
|
135
|
+
if (gh)
|
|
136
|
+
return gh;
|
|
137
|
+
return { ok: false, source: "missing", detail: "Sign in with `gh auth login`, choose Setup \u2192 GitHub auth, or paste a token." };
|
|
138
|
+
}
|
|
139
|
+
async function detectStartupStatus(input) {
|
|
140
|
+
const projectRoot = input.projectRoot;
|
|
141
|
+
const config = readRigConfigStatus(projectRoot);
|
|
142
|
+
const state = readRigConnectionStatus(projectRoot);
|
|
143
|
+
const detectedSlug = detectOriginRepoSlug(projectRoot, input.deps);
|
|
144
|
+
const slug = config.slug ?? state.project ?? detectedSlug;
|
|
145
|
+
const reasons = [];
|
|
146
|
+
if (!detectedSlug)
|
|
147
|
+
reasons.push("git origin does not point at a GitHub owner/repo remote");
|
|
148
|
+
if (!config.exists || !config.valid)
|
|
149
|
+
reasons.push(config.reason ?? "rig.config.ts is invalid");
|
|
150
|
+
if (!state.valid)
|
|
151
|
+
reasons.push(state.reason ?? ".rig/state/connection.json is invalid");
|
|
152
|
+
if (config.slug && state.project && config.slug !== state.project)
|
|
153
|
+
reasons.push(`rig.config.ts repo ${config.slug} does not match connection project ${state.project}`);
|
|
154
|
+
if (slug && detectedSlug && slug !== detectedSlug)
|
|
155
|
+
reasons.push(`configured repo ${slug} does not match git origin ${detectedSlug}`);
|
|
156
|
+
const auth = await validateGitHubAuth(projectRoot, slug, input.deps);
|
|
157
|
+
if (!auth.ok)
|
|
158
|
+
reasons.push(auth.detail);
|
|
159
|
+
return { configured: reasons.length === 0, projectRoot, slug, config, state, auth, reasons };
|
|
160
|
+
}
|
|
161
|
+
export {
|
|
162
|
+
parseRepoSlug,
|
|
163
|
+
detectStartupStatus
|
|
164
|
+
};
|