@h-rig/cli-surface-plugin 0.0.6-alpha.156 → 0.0.6-alpha.158

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.
Files changed (112) hide show
  1. package/dist/src/app/drone-ui.d.ts +0 -11
  2. package/dist/src/app/drone-ui.js +0 -114
  3. package/dist/src/commands/_async-ui.d.ts +1 -1
  4. package/dist/src/commands/_cli-format.d.ts +0 -29
  5. package/dist/src/commands/_cli-format.js +59 -113
  6. package/dist/src/commands/_connection-state.d.ts +6 -33
  7. package/dist/src/commands/_connection-state.js +654 -138
  8. package/dist/src/commands/_doctor-checks.d.ts +2 -5
  9. package/dist/src/commands/_doctor-checks.js +10 -9
  10. package/dist/src/commands/_help-catalog.d.ts +2 -1
  11. package/dist/src/commands/_help-catalog.js +654 -7
  12. package/dist/src/commands/_inprocess-services.d.ts +5 -5
  13. package/dist/src/commands/_inprocess-services.js +1 -1
  14. package/dist/src/commands/_parsers.js +651 -12
  15. package/dist/src/commands/_paths.d.ts +0 -2
  16. package/dist/src/commands/_paths.js +2 -10
  17. package/dist/src/commands/_pi-install.d.ts +2 -12
  18. package/dist/src/commands/_pi-install.js +3 -36
  19. package/dist/src/commands/_policy.js +659 -20
  20. package/dist/src/commands/agent.d.ts +1 -1
  21. package/dist/src/commands/agent.js +675 -24
  22. package/dist/src/commands/config.d.ts +1 -1
  23. package/dist/src/commands/config.js +656 -21
  24. package/dist/src/commands/dist.d.ts +1 -1
  25. package/dist/src/commands/dist.js +828 -102
  26. package/dist/src/commands/doctor.d.ts +1 -1
  27. package/dist/src/commands/doctor.js +658 -12
  28. package/dist/src/commands/github.d.ts +1 -1
  29. package/dist/src/commands/github.js +658 -19
  30. package/dist/src/commands/inbox.d.ts +12 -8
  31. package/dist/src/commands/inbox.js +741 -22
  32. package/dist/src/commands/init.d.ts +17 -19
  33. package/dist/src/commands/init.js +836 -306
  34. package/dist/src/commands/inspect.d.ts +5 -6
  35. package/dist/src/commands/inspect.js +754 -42
  36. package/dist/src/commands/pi.d.ts +1 -1
  37. package/dist/src/commands/pi.js +655 -16
  38. package/dist/src/commands/plugin.d.ts +9 -9
  39. package/dist/src/commands/plugin.js +652 -13
  40. package/dist/src/commands/profile-and-review.d.ts +1 -1
  41. package/dist/src/commands/profile-and-review.js +655 -16
  42. package/dist/src/commands/queue.d.ts +1 -1
  43. package/dist/src/commands/queue.js +871 -12
  44. package/dist/src/commands/remote-client.d.ts +152 -0
  45. package/dist/src/commands/remote-client.js +475 -0
  46. package/dist/src/commands/remote.d.ts +1 -1
  47. package/dist/src/commands/remote.js +1100 -29
  48. package/dist/src/commands/repo-git-harness.d.ts +1 -1
  49. package/dist/src/commands/repo-git-harness.js +2321 -47
  50. package/dist/src/commands/run.d.ts +10 -6
  51. package/dist/src/commands/run.js +830 -50
  52. package/dist/src/commands/server.d.ts +1 -1
  53. package/dist/src/commands/server.js +649 -11
  54. package/dist/src/commands/setup.d.ts +2 -2
  55. package/dist/src/commands/setup.js +829 -18
  56. package/dist/src/commands/stats.d.ts +2 -4
  57. package/dist/src/commands/stats.js +1299 -20
  58. package/dist/src/commands/test.d.ts +1 -1
  59. package/dist/src/commands/test.js +648 -9
  60. package/dist/src/commands/triage.d.ts +2 -3
  61. package/dist/src/commands/triage.js +657 -11
  62. package/dist/src/commands/workspace.d.ts +1 -1
  63. package/dist/src/commands/workspace.js +1280 -15
  64. package/dist/src/control-plane/agent-binary-build.d.ts +9 -0
  65. package/dist/src/control-plane/agent-binary-build.js +88 -0
  66. package/dist/src/control-plane/embedded-native-assets.d.ts +7 -0
  67. package/dist/src/control-plane/embedded-native-assets.js +6 -0
  68. package/dist/src/control-plane/guard.d.ts +17 -0
  69. package/dist/src/control-plane/guard.js +684 -0
  70. package/dist/src/control-plane/harness-cli.d.ts +12 -0
  71. package/dist/src/control-plane/harness-cli.js +1623 -0
  72. package/dist/src/control-plane/native/git-ops.d.ts +67 -0
  73. package/dist/src/control-plane/native/git-ops.js +1381 -0
  74. package/dist/src/control-plane/native/github-auth-env.d.ts +1 -0
  75. package/dist/src/control-plane/native/github-auth-env.js +21 -0
  76. package/dist/src/control-plane/native/host-git.d.ts +4 -0
  77. package/dist/src/control-plane/native/host-git.js +51 -0
  78. package/dist/src/control-plane/priority-queue.d.ts +22 -0
  79. package/dist/src/control-plane/priority-queue.js +212 -0
  80. package/dist/src/control-plane/rigfig.d.ts +9 -0
  81. package/dist/src/control-plane/rigfig.js +70 -0
  82. package/dist/src/control-plane/scope.d.ts +3 -0
  83. package/dist/src/control-plane/scope.js +58 -0
  84. package/dist/src/control-plane/setup-status.d.ts +44 -0
  85. package/dist/src/control-plane/setup-status.js +164 -0
  86. package/dist/src/control-plane/task-data.d.ts +2 -0
  87. package/dist/src/control-plane/task-data.js +12 -0
  88. package/dist/src/control-plane/workspace-ops.d.ts +79 -0
  89. package/dist/src/control-plane/workspace-ops.js +639 -0
  90. package/dist/src/help-catalog-data.d.ts +7 -0
  91. package/dist/src/help-catalog-data.js +660 -0
  92. package/dist/src/kernel-dispatch.js +1 -3
  93. package/dist/src/plugin.js +10072 -30
  94. package/dist/src/runner.d.ts +7 -9
  95. package/dist/src/runner.js +750 -30
  96. package/package.json +12 -13
  97. package/dist/src/commands/_json-output.d.ts +0 -11
  98. package/dist/src/commands/_json-output.js +0 -54
  99. package/dist/src/commands/_pi-frontend.d.ts +0 -35
  100. package/dist/src/commands/_pi-frontend.js +0 -64
  101. package/dist/src/commands/_run-driver-helpers.d.ts +0 -26
  102. package/dist/src/commands/_run-driver-helpers.js +0 -132
  103. package/dist/src/commands/task-run-driver.d.ts +0 -93
  104. package/dist/src/commands/task-run-driver.js +0 -136
  105. package/dist/src/commands/task.d.ts +0 -46
  106. package/dist/src/commands/task.js +0 -555
  107. package/dist/src/provider-model.d.ts +0 -34
  108. package/dist/src/provider-model.js +0 -56
  109. package/dist/src/rig-config-package-deps.d.ts +0 -10
  110. package/dist/src/rig-config-package-deps.js +0 -272
  111. package/dist/src/version.d.ts +0 -8
  112. package/dist/src/version.js +0 -47
@@ -1,21 +1,822 @@
1
1
  // @bun
2
2
  // packages/cli-surface-plugin/src/commands/setup.ts
3
+ import { PROJECT_SETUP } from "@rig/contracts";
3
4
  import { createPluginHost } from "@rig/core";
4
- import { detectStartupStatus, ensureGitHubAuth, parseRepoSlug, runSetup, validateGitHubAuth } from "@rig/init-plugin";
5
+ import { defineCapability as defineCapability2 } from "@rig/core/capability";
6
+ import { loadCapabilityForRoot as loadCapabilityForRoot2 } from "@rig/core/capability-loaders";
7
+
8
+ // packages/cli-surface-plugin/src/control-plane/setup-status.ts
9
+ import { spawnSync } from "child_process";
10
+ import { existsSync, readFileSync } from "fs";
11
+ import { resolve as resolve2 } from "path";
12
+ import { createGitHubAuthStore, resolveGitHubAuthStatus } from "@rig/github-lib";
13
+
14
+ // packages/cli-surface-plugin/src/control-plane/rigfig.ts
15
+ import { resolve } from "path";
16
+ import { stringify as stringifyToml } from "smol-toml";
17
+ import { getStandardPluginsResolver } from "@rig/core/embedded-plugins";
18
+ function rigfigConfigPath(projectRoot) {
19
+ return resolve(projectRoot, ".rig", "rigfig.toml");
20
+ }
21
+ var HEADER = [
22
+ "# Declarative rig configuration \u2014 the happy path.",
23
+ "# Plain DATA, composed from the standard plugins' defaults. No top-level",
24
+ "# rig.config.ts and no @h-rig/* install: the global rig binary resolves",
25
+ "# plugins from its embedded standard collection. rig created this for you;",
26
+ "# edit it freely (each plugin owns the section it needs).",
27
+ "",
28
+ ""
29
+ ].join(`
30
+ `);
31
+
32
+ // packages/cli-surface-plugin/src/control-plane/setup-status.ts
33
+ function cleanString(value) {
34
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
35
+ }
36
+ function runSyncCommand(command, input = {}) {
37
+ const executable = command[0];
38
+ if (!executable)
39
+ throw new Error("command is required");
40
+ return (input.spawn ?? spawnSync)(executable, [...command.slice(1)], {
41
+ cwd: input.cwd,
42
+ encoding: "utf8",
43
+ timeout: input.timeoutMs ?? 1e4,
44
+ env: input.env ?? process.env
45
+ });
46
+ }
47
+ function parseRepoSlug(value) {
48
+ const match = value.trim().match(/^([^/\s]+)\/([^/\s]+)$/);
49
+ if (!match)
50
+ throw new Error(`Invalid GitHub repo slug "${value}". Expected owner/repo.`);
51
+ return { owner: match[1], repo: match[2], slug: `${match[1]}/${match[2]}` };
52
+ }
53
+ function parseRepoSlugFromRemote(remoteUrl) {
54
+ const trimmed = remoteUrl.trim();
55
+ const match = trimmed.match(/github\.com[:/]([^/\s]+)\/([^/\s.]+)(?:\.git)?$/i);
56
+ return match ? `${match[1]}/${match[2]}` : null;
57
+ }
58
+ function detectOriginRepoSlug(projectRoot, deps = {}) {
59
+ const result = runSyncCommand(["git", "-C", projectRoot, "remote", "get-url", "origin"], { timeoutMs: 5000, spawn: deps.spawn });
60
+ if (result.status !== 0 || result.error)
61
+ return null;
62
+ return parseRepoSlugFromRemote(result.stdout.trim());
63
+ }
64
+ function readJsonRecord(path) {
65
+ if (!existsSync(path))
66
+ return null;
67
+ try {
68
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
69
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+ function readRigConfigStatus(projectRoot) {
75
+ const path = rigfigConfigPath(projectRoot);
76
+ if (!existsSync(path))
77
+ return { exists: false, valid: false, path, slug: null, reason: "missing .rig/rigfig.toml" };
78
+ try {
79
+ const source = readFileSync(path, "utf-8");
80
+ const owner = source.match(/^\s*owner\s*=\s*["']([^"']+)["']/m)?.[1] ?? null;
81
+ const repoValues = [...source.matchAll(/^\s*repo\s*=\s*["']([^"']+)["']/gm)].map((match) => match[1]).filter(Boolean);
82
+ const taskRepo = repoValues.find((value) => !value.includes("/")) ?? null;
83
+ const projectRepo = repoValues.find((value) => value.includes("/")) ?? null;
84
+ const githubIssues = /^\s*kind\s*=\s*["']github-issues["']/m.test(source);
85
+ const slug = owner && taskRepo ? `${owner}/${taskRepo}` : projectRepo;
86
+ if (!githubIssues || !slug)
87
+ return { exists: true, valid: false, path, slug: slug ?? null, reason: ".rig/rigfig.toml is not a GitHub Issues Rig config" };
88
+ parseRepoSlug(slug);
89
+ return { exists: true, valid: true, path, slug };
90
+ } catch (error) {
91
+ return { exists: true, valid: false, path, slug: null, reason: error instanceof Error ? error.message : String(error) };
92
+ }
93
+ }
94
+ function connectionStatePath(projectRoot) {
95
+ return resolve2(projectRoot, ".rig", "state", "connection.json");
96
+ }
97
+ function readRigConnectionStatus(projectRoot) {
98
+ const stateDir = resolve2(projectRoot, ".rig", "state");
99
+ if (!existsSync(stateDir))
100
+ return { valid: false, selected: null, project: null, reason: "missing .rig/state" };
101
+ const connection = readJsonRecord(connectionStatePath(projectRoot));
102
+ if (!connection)
103
+ return { valid: false, selected: null, project: null, reason: "missing or invalid .rig/state/connection.json" };
104
+ const selected = cleanString(connection.selected);
105
+ const project = cleanString(connection.project);
106
+ if (!selected)
107
+ return { valid: false, selected: null, project, reason: "connection.json is missing selected placement" };
108
+ if (!project)
109
+ return { valid: false, selected, project: null, reason: "connection.json is missing project slug" };
110
+ try {
111
+ parseRepoSlug(project);
112
+ } catch (error) {
113
+ return { valid: false, selected, project, reason: error instanceof Error ? error.message : String(error) };
114
+ }
115
+ return { valid: true, selected, project };
116
+ }
117
+ function detectGhAuth(projectRoot, slug, deps = {}) {
118
+ const user = runSyncCommand(["gh", "api", "user", "--jq", ".login"], { cwd: projectRoot, timeoutMs: 5000, spawn: deps.spawn });
119
+ if (user.status !== 0 || user.error || !user.stdout.trim())
120
+ return null;
121
+ const repo = runSyncCommand(["gh", "repo", "view", slug, "--json", "nameWithOwner", "--jq", ".nameWithOwner"], { cwd: projectRoot, timeoutMs: 5000, spawn: deps.spawn });
122
+ if (repo.status !== 0 || repo.error)
123
+ return { ok: false, source: "gh", login: user.stdout.trim(), detail: (repo.stderr || repo.stdout || "gh cannot access the selected repository").trim() };
124
+ return { ok: true, source: "gh", login: user.stdout.trim(), detail: "gh CLI authentication can access the selected repository" };
125
+ }
126
+ async function validateGitHubAuth(projectRoot, slug, deps = {}) {
127
+ if (!slug)
128
+ return { ok: false, source: "missing", detail: "GitHub repo slug is unknown" };
129
+ const status = resolveGitHubAuthStatus({ projectRoot, oauthConfigured: Boolean(process.env.RIG_GITHUB_OAUTH_CLIENT_ID?.trim()) });
130
+ if (status.signedIn) {
131
+ const store = createGitHubAuthStore(projectRoot);
132
+ if (!status.selectedRepo) {
133
+ store.saveSelectedRepo(slug);
134
+ return { ok: true, source: "stored-token", login: status.login, detail: "stored Rig GitHub token selected for this repo", status: store.status({ oauthConfigured: status.oauthConfigured }) };
135
+ }
136
+ if (status.selectedRepo !== slug)
137
+ return { ok: false, source: "stored-token", login: status.login, detail: `stored GitHub token is scoped to ${status.selectedRepo}, not ${slug}`, status };
138
+ return { ok: true, source: "stored-token", login: status.login, detail: "stored Rig GitHub token is present", status };
139
+ }
140
+ const gh = detectGhAuth(projectRoot, slug, deps);
141
+ if (gh)
142
+ return gh;
143
+ return { ok: false, source: "missing", detail: "Sign in with `gh auth login`, choose Setup \u2192 GitHub auth, or paste a token." };
144
+ }
145
+ async function detectStartupStatus(input) {
146
+ const projectRoot = input.projectRoot;
147
+ const config = readRigConfigStatus(projectRoot);
148
+ const state = readRigConnectionStatus(projectRoot);
149
+ const detectedSlug = detectOriginRepoSlug(projectRoot, input.deps);
150
+ const slug = config.slug ?? state.project ?? detectedSlug;
151
+ const reasons = [];
152
+ if (!detectedSlug)
153
+ reasons.push("git origin does not point at a GitHub owner/repo remote");
154
+ if (!config.exists || !config.valid)
155
+ reasons.push(config.reason ?? "rig.config.ts is invalid");
156
+ if (!state.valid)
157
+ reasons.push(state.reason ?? ".rig/state/connection.json is invalid");
158
+ if (config.slug && state.project && config.slug !== state.project)
159
+ reasons.push(`rig.config.ts repo ${config.slug} does not match connection project ${state.project}`);
160
+ if (slug && detectedSlug && slug !== detectedSlug)
161
+ reasons.push(`configured repo ${slug} does not match git origin ${detectedSlug}`);
162
+ const auth = await validateGitHubAuth(projectRoot, slug, input.deps);
163
+ if (!auth.ok)
164
+ reasons.push(auth.detail);
165
+ return { configured: reasons.length === 0, projectRoot, slug, config, state, auth, reasons };
166
+ }
5
167
 
6
168
  // packages/cli-surface-plugin/src/runner.ts
7
- import { EventBus } from "@rig/runtime/control-plane/runtime/events";
8
- import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
9
- import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
10
- import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
169
+ import { EventBus } from "@rig/core/runtime-events";
170
+ import { CliError as RuntimeCliError } from "@rig/contracts";
171
+
172
+ // packages/cli-surface-plugin/src/control-plane/guard.ts
173
+ import { optimizeNextInvocation } from "bun:jsc";
174
+ import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "fs";
175
+ import { resolve as resolve3 } from "path";
176
+
177
+ // packages/cli-surface-plugin/src/control-plane/scope.ts
178
+ import { getScopeRules } from "@rig/core/scope-rules";
179
+ var scopeRegexCache = new Map;
180
+ function unique(values) {
181
+ return [...new Set(values)];
182
+ }
183
+ function normalizeRelativeScopePath(inputPath) {
184
+ let normalized = inputPath.replace(/^\.\//, "");
185
+ const rules = getScopeRules();
186
+ if (rules?.stripPrefixes) {
187
+ for (const prefix of rules.stripPrefixes) {
188
+ if (normalized.startsWith(prefix)) {
189
+ normalized = normalized.slice(prefix.length);
190
+ }
191
+ }
192
+ }
193
+ return normalized;
194
+ }
195
+ function normalizePathToScope(projectRoot, monorepoRoot, inputPath) {
196
+ let normalized = inputPath.replace(/^\.\//, "");
197
+ if (normalized.startsWith(projectRoot + "/")) {
198
+ normalized = normalized.slice(projectRoot.length + 1);
199
+ }
200
+ if (normalized.startsWith(monorepoRoot + "/")) {
201
+ normalized = normalized.slice(monorepoRoot.length + 1);
202
+ }
203
+ return normalizeRelativeScopePath(normalized);
204
+ }
205
+ function scopeGlobToRegex(glob) {
206
+ const cached = scopeRegexCache.get(glob);
207
+ if (cached) {
208
+ return cached;
209
+ }
210
+ const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "__GLOBSTAR__").replace(/\*/g, "[^/]*").replace(/__GLOBSTAR__/g, ".*");
211
+ const compiled = new RegExp(`^${escaped}$`);
212
+ scopeRegexCache.set(glob, compiled);
213
+ return compiled;
214
+ }
215
+ function scopeMatches(path, scopes) {
216
+ const pathVariants = unique([path, normalizeRelativeScopePath(path)]);
217
+ for (const scope of scopes) {
218
+ const scopeVariants = unique([scope, normalizeRelativeScopePath(scope)]);
219
+ for (const candidatePath of pathVariants) {
220
+ for (const candidateScope of scopeVariants) {
221
+ if (candidatePath === candidateScope || scopeGlobToRegex(candidateScope).test(candidatePath)) {
222
+ return true;
223
+ }
224
+ }
225
+ }
226
+ }
227
+ return false;
228
+ }
11
229
 
230
+ // packages/cli-surface-plugin/src/control-plane/guard.ts
231
+ import {
232
+ POLICY_VERSION
233
+ } from "@rig/contracts";
234
+ var DEFAULT_SCOPE = {
235
+ fail_closed: true,
236
+ harness_paths_exempt: true,
237
+ runtime_paths_exempt: true
238
+ };
239
+ var DEFAULT_SANDBOX = {
240
+ mode: "enforce",
241
+ network: true,
242
+ read_deny: [],
243
+ write_allow_from_runtime: true
244
+ };
245
+ var DEFAULT_ISOLATION = {
246
+ default_mode: "worktree",
247
+ repo_symlink_fallback: false,
248
+ strict_provisioning: true,
249
+ fail_closed_on_provision_error: true
250
+ };
251
+ var DEFAULT_COMPLETION = {
252
+ derive_checks_from_scope: true,
253
+ checks: [],
254
+ typescript_config_probe: ["tsconfig.json"],
255
+ eslint_config_probe: [".eslintrc.js", ".eslintrc.json", "eslint.config.js"]
256
+ };
257
+ var DEFAULT_RUNTIME_IMAGE = {
258
+ deps: {
259
+ monorepo_install: false,
260
+ hp_next_install: false
261
+ },
262
+ plugins_require_binaries: true
263
+ };
264
+ var DEFAULT_RUNTIME_SNAPSHOT = {
265
+ enabled: true
266
+ };
267
+ function defaultPolicy() {
268
+ return {
269
+ version: POLICY_VERSION,
270
+ mode: "enforce",
271
+ scope: { ...DEFAULT_SCOPE },
272
+ rules: [],
273
+ sandbox: { ...DEFAULT_SANDBOX },
274
+ isolation: { ...DEFAULT_ISOLATION },
275
+ completion: { ...DEFAULT_COMPLETION },
276
+ runtime_image: {
277
+ deps: { ...DEFAULT_RUNTIME_IMAGE.deps },
278
+ plugins_require_binaries: DEFAULT_RUNTIME_IMAGE.plugins_require_binaries
279
+ },
280
+ runtime_snapshot: { ...DEFAULT_RUNTIME_SNAPSHOT }
281
+ };
282
+ }
283
+ var policyCache = null;
284
+ var policyCachePath = null;
285
+ var seededPolicyConfig = null;
286
+ var compiledRegexCache = new Map;
287
+ function loadPolicy(projectRoot) {
288
+ if (seededPolicyConfig) {
289
+ return seededPolicyConfig;
290
+ }
291
+ const configPath = resolve3(projectRoot, "rig/policy/policy.json");
292
+ if (!existsSync2(configPath)) {
293
+ return defaultPolicy();
294
+ }
295
+ let mtimeMs;
296
+ try {
297
+ mtimeMs = statSync(configPath).mtimeMs;
298
+ } catch {
299
+ return defaultPolicy();
300
+ }
301
+ if (policyCache && policyCachePath === configPath && policyCache.mtimeMs === mtimeMs) {
302
+ return policyCache.config;
303
+ }
304
+ let parsed;
305
+ try {
306
+ parsed = JSON.parse(readFileSync2(configPath, "utf-8"));
307
+ } catch {
308
+ return defaultPolicy();
309
+ }
310
+ const config = mergeWithDefaults(parsed);
311
+ policyCache = { mtimeMs, config };
312
+ policyCachePath = configPath;
313
+ return config;
314
+ }
315
+ function mergeWithDefaults(parsed) {
316
+ const base = defaultPolicy();
317
+ if (typeof parsed.mode === "string" && isValidMode(parsed.mode)) {
318
+ base.mode = parsed.mode;
319
+ }
320
+ if (parsed.scope && typeof parsed.scope === "object" && !Array.isArray(parsed.scope)) {
321
+ const s = parsed.scope;
322
+ if (typeof s.fail_closed === "boolean")
323
+ base.scope.fail_closed = s.fail_closed;
324
+ if (typeof s.harness_paths_exempt === "boolean")
325
+ base.scope.harness_paths_exempt = s.harness_paths_exempt;
326
+ if (typeof s.runtime_paths_exempt === "boolean")
327
+ base.scope.runtime_paths_exempt = s.runtime_paths_exempt;
328
+ }
329
+ if (Array.isArray(parsed.rules)) {
330
+ base.rules = precompilePolicyRuleRegexes(parsed.rules.filter(isValidRule));
331
+ }
332
+ if (Array.isArray(parsed.deny) && base.rules.length === 0) {
333
+ base.rules = precompilePolicyRuleRegexes(migrateLegacyDeny(parsed.deny));
334
+ }
335
+ if (parsed.sandbox && typeof parsed.sandbox === "object" && !Array.isArray(parsed.sandbox)) {
336
+ const sb = parsed.sandbox;
337
+ if (typeof sb.mode === "string" && isValidMode(sb.mode))
338
+ base.sandbox.mode = sb.mode;
339
+ if (typeof sb.network === "boolean")
340
+ base.sandbox.network = sb.network;
341
+ if (Array.isArray(sb.read_deny))
342
+ base.sandbox.read_deny = sb.read_deny.filter((v) => typeof v === "string");
343
+ if (typeof sb.write_allow_from_runtime === "boolean")
344
+ base.sandbox.write_allow_from_runtime = sb.write_allow_from_runtime;
345
+ }
346
+ if (parsed.isolation && typeof parsed.isolation === "object" && !Array.isArray(parsed.isolation)) {
347
+ const iso = parsed.isolation;
348
+ if (iso.default_mode === "worktree")
349
+ base.isolation.default_mode = iso.default_mode;
350
+ if (typeof iso.repo_symlink_fallback === "boolean")
351
+ base.isolation.repo_symlink_fallback = iso.repo_symlink_fallback;
352
+ if (typeof iso.strict_provisioning === "boolean")
353
+ base.isolation.strict_provisioning = iso.strict_provisioning;
354
+ if (typeof iso.fail_closed_on_provision_error === "boolean")
355
+ base.isolation.fail_closed_on_provision_error = iso.fail_closed_on_provision_error;
356
+ }
357
+ if (parsed.completion && typeof parsed.completion === "object" && !Array.isArray(parsed.completion)) {
358
+ const comp = parsed.completion;
359
+ if (typeof comp.derive_checks_from_scope === "boolean")
360
+ base.completion.derive_checks_from_scope = comp.derive_checks_from_scope;
361
+ if (Array.isArray(comp.checks))
362
+ base.completion.checks = comp.checks.filter((v) => typeof v === "string");
363
+ if (Array.isArray(comp.typescript_config_probe))
364
+ base.completion.typescript_config_probe = comp.typescript_config_probe.filter((v) => typeof v === "string");
365
+ if (Array.isArray(comp.eslint_config_probe))
366
+ base.completion.eslint_config_probe = comp.eslint_config_probe.filter((v) => typeof v === "string");
367
+ }
368
+ if (parsed.runtime_image && typeof parsed.runtime_image === "object" && !Array.isArray(parsed.runtime_image)) {
369
+ const runtimeImage = parsed.runtime_image;
370
+ if (runtimeImage.deps && typeof runtimeImage.deps === "object" && !Array.isArray(runtimeImage.deps)) {
371
+ const deps = runtimeImage.deps;
372
+ if (typeof deps.monorepo_install === "boolean") {
373
+ base.runtime_image.deps.monorepo_install = deps.monorepo_install;
374
+ }
375
+ if (typeof deps.hp_next_install === "boolean") {
376
+ base.runtime_image.deps.hp_next_install = deps.hp_next_install;
377
+ }
378
+ }
379
+ if (typeof runtimeImage.plugins_require_binaries === "boolean") {
380
+ base.runtime_image.plugins_require_binaries = runtimeImage.plugins_require_binaries;
381
+ }
382
+ }
383
+ if (parsed.runtime_snapshot && typeof parsed.runtime_snapshot === "object" && !Array.isArray(parsed.runtime_snapshot)) {
384
+ const runtimeSnapshot = parsed.runtime_snapshot;
385
+ if (typeof runtimeSnapshot.enabled === "boolean") {
386
+ base.runtime_snapshot.enabled = runtimeSnapshot.enabled;
387
+ }
388
+ }
389
+ return base;
390
+ }
391
+ function isValidMode(value) {
392
+ return value === "off" || value === "observe" || value === "enforce";
393
+ }
394
+ function isValidRule(value) {
395
+ if (!value || typeof value !== "object" || Array.isArray(value))
396
+ return false;
397
+ const r = value;
398
+ return typeof r.id === "string" && typeof r.category === "string" && r.match != null && typeof r.match === "object";
399
+ }
400
+ function migrateLegacyDeny(deny) {
401
+ const rules = [];
402
+ for (const entry of deny) {
403
+ if (typeof entry.id !== "string")
404
+ continue;
405
+ const match = {};
406
+ if (typeof entry.pattern === "string")
407
+ match.pattern = entry.pattern;
408
+ if (typeof entry.regex === "string")
409
+ match.regex = entry.regex;
410
+ if (!match.pattern && !match.regex)
411
+ continue;
412
+ rules.push({
413
+ id: entry.id,
414
+ category: "command",
415
+ match,
416
+ action: "block",
417
+ ...typeof entry.description === "string" ? { description: entry.description } : {}
418
+ });
419
+ }
420
+ return rules;
421
+ }
422
+ function precompilePolicyRuleRegexes(rules) {
423
+ return rules.map((rule) => {
424
+ const compiledRegex = rule.match.regex ? compileSafeRegex(rule.match.regex, `rules.${rule.id}.match.regex`, true) : undefined;
425
+ const compiledUnlessRegex = rule.unless?.regex ? compileSafeRegex(rule.unless.regex, `rules.${rule.id}.unless.regex`, true) : undefined;
426
+ return {
427
+ ...rule,
428
+ ...compiledRegex ? { compiledRegex } : {},
429
+ ...compiledUnlessRegex ? { compiledUnlessRegex } : {}
430
+ };
431
+ });
432
+ }
433
+ function getRegexUnsafeReason(pattern) {
434
+ if (pattern.length > 512) {
435
+ return "pattern exceeds max safe length (512 chars)";
436
+ }
437
+ if (/\\[1-9]/.test(pattern)) {
438
+ return "pattern uses backreferences";
439
+ }
440
+ if (/\((?:[^()\\]|\\.)*[+*](?:[^()\\]|\\.)*\)\s*[*+{]/.test(pattern)) {
441
+ return "pattern contains nested quantifiers";
442
+ }
443
+ if (/\((?:[^()\\]|\\.)*\.\\?[+*](?:[^()\\]|\\.)*\)\s*[*+{]/.test(pattern)) {
444
+ return "pattern contains nested broad quantifiers";
445
+ }
446
+ return null;
447
+ }
448
+ function compileSafeRegex(pattern, sourceLabel, logOnFailure) {
449
+ const cached = compiledRegexCache.get(pattern);
450
+ if (cached !== undefined) {
451
+ return cached ?? undefined;
452
+ }
453
+ const unsafeReason = getRegexUnsafeReason(pattern);
454
+ if (unsafeReason) {
455
+ if (logOnFailure) {
456
+ console.warn(`[policy] Skipping unsafe regex in ${sourceLabel}: ${unsafeReason}`);
457
+ }
458
+ compiledRegexCache.set(pattern, null);
459
+ return;
460
+ }
461
+ try {
462
+ const compiled = new RegExp(pattern);
463
+ compiledRegexCache.set(pattern, compiled);
464
+ return compiled;
465
+ } catch (error) {
466
+ if (logOnFailure) {
467
+ const message = error instanceof Error ? error.message : String(error);
468
+ console.warn(`[policy] Skipping invalid regex in ${sourceLabel}: ${message}`);
469
+ }
470
+ compiledRegexCache.set(pattern, null);
471
+ return;
472
+ }
473
+ }
474
+ function matchRule(rule, input) {
475
+ const { match } = rule;
476
+ if (match.pattern && input.includes(match.pattern)) {
477
+ return true;
478
+ }
479
+ if (match.regex) {
480
+ const compiled = rule.compiledRegex || compileSafeRegex(match.regex, `rules.${rule.id}.match.regex`, false);
481
+ if (!compiled) {
482
+ return false;
483
+ }
484
+ try {
485
+ return compiled.test(input);
486
+ } catch {
487
+ return false;
488
+ }
489
+ }
490
+ return false;
491
+ }
492
+ function matchRuleUnless(rule, command, taskId) {
493
+ if (!rule.unless)
494
+ return false;
495
+ if (rule.unless.regex) {
496
+ const compiled = rule.compiledUnlessRegex || compileSafeRegex(rule.unless.regex, `rules.${rule.id}.unless.regex`, false);
497
+ if (!compiled) {
498
+ return false;
499
+ }
500
+ try {
501
+ if (compiled.test(command))
502
+ return true;
503
+ } catch {}
504
+ }
505
+ if (rule.unless.task_in && taskId) {
506
+ if (rule.unless.task_in.includes(taskId))
507
+ return true;
508
+ }
509
+ return false;
510
+ }
511
+ function resolveAction(mode, matched) {
512
+ if (matched.length === 0)
513
+ return "allow";
514
+ if (mode === "off")
515
+ return "allow";
516
+ if (mode === "observe")
517
+ return "warn";
518
+ return "block";
519
+ }
520
+ function resolveAbsolutePath(projectRoot, rawPath) {
521
+ if (rawPath.startsWith("/"))
522
+ return resolve3(rawPath);
523
+ return resolve3(projectRoot, rawPath);
524
+ }
525
+ function isHarnessPath(projectRoot, rawPath) {
526
+ const absPath = resolveAbsolutePath(projectRoot, rawPath);
527
+ const managedRoots = [
528
+ resolve3(projectRoot, "rig"),
529
+ resolve3(projectRoot, ".rig"),
530
+ resolve3(projectRoot, "artifacts")
531
+ ];
532
+ return managedRoots.some((root) => absPath === root || absPath.startsWith(root + "/"));
533
+ }
534
+ function isRuntimePath(projectRoot, rawPath, taskWorkspace) {
535
+ const absPath = resolveAbsolutePath(projectRoot, rawPath);
536
+ if (taskWorkspace) {
537
+ const workspaceRigRoot = resolve3(taskWorkspace, ".rig");
538
+ const workspaceArtifactsRoot = resolve3(taskWorkspace, "artifacts");
539
+ if (absPath === workspaceRigRoot || absPath.startsWith(workspaceRigRoot + "/") || absPath === workspaceArtifactsRoot || absPath.startsWith(workspaceArtifactsRoot + "/")) {
540
+ return true;
541
+ }
542
+ }
543
+ const runtimeRoot = resolve3(projectRoot, ".rig/runtime/agents");
544
+ return absPath === runtimeRoot || absPath.startsWith(runtimeRoot + "/");
545
+ }
546
+ function isTestFile(path) {
547
+ return /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(path) || /\/(__tests__|tests|test)\//.test(path);
548
+ }
549
+ function evaluate(context) {
550
+ const policy = loadPolicy(context.projectRoot);
551
+ switch (context.evaluation.type) {
552
+ case "tool-call":
553
+ return evaluateToolCall(policy, context);
554
+ case "command":
555
+ return evaluateCommand(policy, context);
556
+ case "content-write":
557
+ return evaluateContent(policy, context);
558
+ case "file-access":
559
+ return evaluateScope(policy, context, context.evaluation.file_path, context.evaluation.access);
560
+ }
561
+ }
562
+ function evaluateScope(policy, context, filePath, access) {
563
+ const allowed = () => ({
564
+ allowed: true,
565
+ matchedRules: [],
566
+ action: "allow",
567
+ failClosed: false
568
+ });
569
+ if (policy.scope.harness_paths_exempt && isHarnessPath(context.projectRoot, filePath)) {
570
+ return allowed();
571
+ }
572
+ if (policy.scope.runtime_paths_exempt && isRuntimePath(context.projectRoot, filePath, context.taskWorkspace)) {
573
+ return allowed();
574
+ }
575
+ if (!context.taskId) {
576
+ if (access === "write" && policy.scope.fail_closed) {
577
+ return {
578
+ allowed: false,
579
+ matchedRules: [],
580
+ action: resolveAction(policy.mode, [{ id: "scope:no-task", category: "command", reason: "No active task; fail-closed for write operations" }]),
581
+ failClosed: true
582
+ };
583
+ }
584
+ return allowed();
585
+ }
586
+ const scopes = context.taskScopes || [];
587
+ if (scopes.length === 0) {
588
+ return allowed();
589
+ }
590
+ if (context.taskWorkspace && context.taskWorkspace !== context.projectRoot && filePath.startsWith("/")) {
591
+ const absPath = resolve3(filePath);
592
+ if (!absPath.startsWith(context.taskWorkspace + "/") && !isHarnessPath(context.projectRoot, filePath)) {
593
+ const reason2 = `Absolute path '${filePath}' is outside task runtime boundary. Allowed root: ${context.taskWorkspace}`;
594
+ const matched2 = [{ id: "scope:workspace-boundary", category: "command", reason: reason2 }];
595
+ return {
596
+ allowed: policy.mode !== "enforce",
597
+ matchedRules: matched2,
598
+ action: resolveAction(policy.mode, matched2),
599
+ failClosed: false
600
+ };
601
+ }
602
+ }
603
+ const monorepoRoot = context.monorepoRoot || process.env.MONOREPO_ROOT?.trim() || context.taskWorkspace || context.projectRoot;
604
+ let normalizedPath = filePath;
605
+ if (context.taskWorkspace && context.taskWorkspace !== context.projectRoot && filePath.startsWith(context.taskWorkspace + "/")) {
606
+ normalizedPath = filePath.slice(context.taskWorkspace.length + 1);
607
+ }
608
+ normalizedPath = normalizePathToScope(context.projectRoot, monorepoRoot, normalizedPath);
609
+ if (scopeMatches(filePath, scopes) || scopeMatches(normalizedPath, scopes)) {
610
+ return allowed();
611
+ }
612
+ const reason = `File '${filePath}' (normalized: '${normalizedPath}') is outside scope of task ${context.taskId}`;
613
+ const matched = [{ id: "scope:out-of-scope", category: "command", reason }];
614
+ return {
615
+ allowed: policy.mode !== "enforce",
616
+ matchedRules: matched,
617
+ action: resolveAction(policy.mode, matched),
618
+ failClosed: false
619
+ };
620
+ }
621
+ function evaluateCommand(policy, context) {
622
+ const evaluation = context.evaluation;
623
+ if (evaluation.type !== "command") {
624
+ return { allowed: true, matchedRules: [], action: "allow", failClosed: false };
625
+ }
626
+ const command = evaluation.command;
627
+ const matchedRules = [];
628
+ for (const rule of policy.rules) {
629
+ if (rule.category !== "command")
630
+ continue;
631
+ if (!matchRule(rule, command))
632
+ continue;
633
+ if (matchRuleUnless(rule, command, context.taskId))
634
+ continue;
635
+ matchedRules.push({
636
+ id: rule.id,
637
+ category: rule.category,
638
+ ...rule.description !== undefined ? { description: rule.description } : {},
639
+ reason: rule.description || `Matched rule ${rule.id}`
640
+ });
641
+ }
642
+ const writeTarget = extractWriteTarget(command);
643
+ if (writeTarget && !/^\/dev\//.test(writeTarget) && !/^\/proc\//.test(writeTarget)) {
644
+ const scopeResult = evaluateScope(policy, context, writeTarget, "write");
645
+ if (!scopeResult.allowed || scopeResult.matchedRules.length > 0) {
646
+ matchedRules.push(...scopeResult.matchedRules);
647
+ }
648
+ }
649
+ const action = resolveAction(policy.mode, matchedRules);
650
+ return {
651
+ allowed: action !== "block",
652
+ matchedRules,
653
+ action,
654
+ failClosed: false
655
+ };
656
+ }
657
+ function extractWriteTarget(command) {
658
+ const redirect = command.match(/>>?\s+([^\s;|&]+)/);
659
+ if (redirect?.[1])
660
+ return redirect[1];
661
+ const tee = command.match(/tee\s+(-a\s+)?([^\s;|&]+)/);
662
+ if (tee?.[2])
663
+ return tee[2];
664
+ return "";
665
+ }
666
+ function evaluateContent(policy, context) {
667
+ const evaluation = context.evaluation;
668
+ if (evaluation.type !== "content-write") {
669
+ return { allowed: true, matchedRules: [], action: "allow", failClosed: false };
670
+ }
671
+ const { content, file_path } = evaluation;
672
+ const matchedRules = [];
673
+ const scopeResult = evaluateScope(policy, context, file_path, "write");
674
+ if (scopeResult.matchedRules.length > 0) {
675
+ matchedRules.push(...scopeResult.matchedRules);
676
+ }
677
+ for (const rule of policy.rules) {
678
+ if (rule.category !== "content" && rule.category !== "import" && rule.category !== "test-integrity")
679
+ continue;
680
+ if (rule.applies_to === "test-files" && !isTestFile(file_path))
681
+ continue;
682
+ if (!matchRule(rule, content))
683
+ continue;
684
+ if (matchRuleUnless(rule, content, context.taskId))
685
+ continue;
686
+ matchedRules.push({
687
+ id: rule.id,
688
+ category: rule.category,
689
+ ...rule.description !== undefined ? { description: rule.description } : {},
690
+ reason: rule.description || `Matched rule ${rule.id}`
691
+ });
692
+ }
693
+ const action = resolveAction(policy.mode, matchedRules);
694
+ return {
695
+ allowed: action !== "block",
696
+ matchedRules,
697
+ action,
698
+ failClosed: false
699
+ };
700
+ }
701
+ function evaluateToolCall(policy, context) {
702
+ const evaluation = context.evaluation;
703
+ if (evaluation.type !== "tool-call") {
704
+ return { allowed: true, matchedRules: [], action: "allow", failClosed: false };
705
+ }
706
+ const { tool_name, tool_input } = evaluation;
707
+ const allMatched = [];
708
+ const filePaths = extractFilePathsFromToolInput(tool_name, tool_input);
709
+ for (const fp of filePaths) {
710
+ const access = isWriteTool(tool_name) ? "write" : "read";
711
+ const scopeResult = evaluateScope(policy, context, fp, access);
712
+ if (scopeResult.matchedRules.length > 0) {
713
+ allMatched.push(...scopeResult.matchedRules);
714
+ }
715
+ }
716
+ const content = extractContentFromToolInput(tool_input);
717
+ if (content) {
718
+ const filePath = filePaths[0] || "";
719
+ const contentContext = {
720
+ ...context,
721
+ evaluation: { type: "content-write", file_path: filePath, content }
722
+ };
723
+ const contentPolicy = loadPolicy(context.projectRoot);
724
+ for (const rule of contentPolicy.rules) {
725
+ if (rule.category !== "content" && rule.category !== "import" && rule.category !== "test-integrity")
726
+ continue;
727
+ if (rule.applies_to === "test-files" && !isTestFile(filePath))
728
+ continue;
729
+ if (!matchRule(rule, content))
730
+ continue;
731
+ if (matchRuleUnless(rule, content, context.taskId))
732
+ continue;
733
+ allMatched.push({
734
+ id: rule.id,
735
+ category: rule.category,
736
+ ...rule.description !== undefined ? { description: rule.description } : {},
737
+ reason: rule.description || `Matched rule ${rule.id}`
738
+ });
739
+ }
740
+ }
741
+ if (tool_name === "Bash") {
742
+ const command = String(tool_input.command || tool_input.cmd || "");
743
+ if (command) {
744
+ const cmdContext = {
745
+ ...context,
746
+ evaluation: { type: "command", command }
747
+ };
748
+ const cmdResult = evaluateCommand(policy, cmdContext);
749
+ if (cmdResult.matchedRules.length > 0) {
750
+ allMatched.push(...cmdResult.matchedRules);
751
+ }
752
+ }
753
+ }
754
+ const seen = new Set;
755
+ const deduplicated = [];
756
+ for (const rule of allMatched) {
757
+ if (!seen.has(rule.id)) {
758
+ seen.add(rule.id);
759
+ deduplicated.push(rule);
760
+ }
761
+ }
762
+ const action = resolveAction(policy.mode, deduplicated);
763
+ return {
764
+ allowed: action !== "block",
765
+ matchedRules: deduplicated,
766
+ action,
767
+ failClosed: false
768
+ };
769
+ }
770
+ function isWriteTool(toolName) {
771
+ return toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit";
772
+ }
773
+ function extractFilePathsFromToolInput(toolName, input) {
774
+ const paths = [];
775
+ const add = (value) => {
776
+ if (typeof value === "string" && value.trim()) {
777
+ paths.push(value.trim());
778
+ }
779
+ };
780
+ if (toolName === "Read" || toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") {
781
+ add(input.file_path);
782
+ add(input.path);
783
+ } else if (toolName === "Glob") {
784
+ add(input.path);
785
+ } else if (toolName === "Grep") {
786
+ add(input.path);
787
+ } else {
788
+ add(input.file_path);
789
+ add(input.path);
790
+ }
791
+ return paths;
792
+ }
793
+ function extractContentFromToolInput(input) {
794
+ if (typeof input.content === "string")
795
+ return input.content;
796
+ if (typeof input.new_string === "string")
797
+ return input.new_string;
798
+ return "";
799
+ }
800
+ var guardHotPathPrimed = false;
801
+ function primeGuardHotPaths() {
802
+ if (guardHotPathPrimed) {
803
+ return;
804
+ }
805
+ guardHotPathPrimed = true;
806
+ try {
807
+ optimizeNextInvocation(matchRule);
808
+ optimizeNextInvocation(evaluate);
809
+ } catch {}
810
+ }
811
+ primeGuardHotPaths();
812
+
813
+ // packages/cli-surface-plugin/src/control-plane/agent-binary-build.ts
814
+ import { runtimeProvisioningEnv } from "@rig/core/runtime-provisioning-env";
815
+
816
+ // packages/cli-surface-plugin/src/runner.ts
12
817
  class CliError extends RuntimeCliError {
13
- hint;
14
818
  constructor(message, exitCode = 1, options = {}) {
15
- super(message, exitCode);
16
- if (options.hint?.trim()) {
17
- this.hint = options.hint.trim();
18
- }
819
+ super(message, exitCode, options);
19
820
  }
20
821
  }
21
822
  function takeFlag(args, flag) {
@@ -97,10 +898,17 @@ function withMutedConsole(mute, fn) {
97
898
  }
98
899
 
99
900
  // packages/cli-surface-plugin/src/commands/_doctor-checks.ts
100
- import { countDoctorFailures } from "@rig/contracts";
101
- import { loadDoctorService } from "@rig/runtime/control-plane/doctor-service-port";
901
+ import { DOCTOR } from "@rig/contracts";
902
+ import { defineCapability } from "@rig/core/capability";
903
+ import { loadCapabilityForRoot } from "@rig/core/capability-loaders";
904
+ function doctorLevelToStatus(level) {
905
+ return level === "ok" ? "pass" : level;
906
+ }
907
+ function countDoctorFailures(checks) {
908
+ return checks.filter((entry) => (entry.status ?? doctorLevelToStatus(entry.level ?? "warn")) === "fail").length;
909
+ }
102
910
  async function runRigDoctorChecks(options) {
103
- const service = await loadDoctorService(options.projectRoot);
911
+ const service = await loadCapabilityForRoot(options.projectRoot, defineCapability(DOCTOR));
104
912
  if (!service) {
105
913
  return [
106
914
  {
@@ -257,7 +1065,7 @@ function buildTaskSourceKindSetupCheck(config) {
257
1065
  return {
258
1066
  ok: isRegistered,
259
1067
  label: `task source kind "${kind}" registered`,
260
- hint: isRegistered ? undefined : `registered kinds: ${formatTaskSourceKinds(registeredKinds)}; load a plugin that contributes an executable task source factory for "${kind}"`
1068
+ ...isRegistered ? {} : { hint: `registered kinds: ${formatTaskSourceKinds(registeredKinds)}; load a plugin that contributes an executable task source factory for "${kind}"` }
261
1069
  };
262
1070
  }
263
1071
  async function executeSetup(context, args) {
@@ -298,7 +1106,8 @@ async function runBootstrap(projectRoot) {
298
1106
  }
299
1107
  async function runSetupCheck(projectRoot, outputMode = "text") {
300
1108
  const status = await detectStartupStatus({ projectRoot });
301
- await validateGitHubAuth(projectRoot, status.slug);
1109
+ if (!status.auth.ok)
1110
+ throw new CliError(status.auth.detail, 1);
302
1111
  const doctorChecks = await withSpinner("Running setup checks\u2026", (update) => runRigDoctorChecks({ projectRoot, onProgress: update }), { outputMode });
303
1112
  console.log(formatDoctorChecks(doctorChecks));
304
1113
  const failures = countDoctorFailures(doctorChecks);
@@ -336,16 +1145,18 @@ async function runSetupCommand(context, args) {
336
1145
  if (!slug)
337
1146
  throw new CliError("rig setup setup requires --repo <owner/repo> when no GitHub origin/config is detectable.", 2);
338
1147
  const repo = parseRepoSlug(slug);
339
- await ensureGitHubAuth({ projectRoot: context.projectRoot, slug: repo.slug, token: githubTokenResult.value, importGhToken: importGhToken.value });
340
1148
  const placement = setupPlacement(placementResult.value, hostResult.value, portResult.value, tokenResult.value);
341
- const result = await runSetup({
1149
+ const setup = await loadCapabilityForRoot2(context.projectRoot, defineCapability2(PROJECT_SETUP));
1150
+ if (!setup)
1151
+ throw new CliError("Project setup capability unavailable: load @rig/init-plugin before running setup.", 2);
1152
+ const result = await setup.runSetup({
342
1153
  projectRoot: context.projectRoot,
343
1154
  slug: repo.slug,
344
1155
  placement,
345
1156
  rewriteConfig: !noConfig.value,
346
1157
  ensurePi: !noPi.value,
347
1158
  ensureLabels: !noLabels.value,
348
- githubToken: githubTokenResult.value,
1159
+ ...githubTokenResult.value !== undefined ? { githubToken: githubTokenResult.value } : {},
349
1160
  importGhToken: importGhToken.value
350
1161
  });
351
1162
  if (context.outputMode === "text")