@h-rig/cli 0.0.6-alpha.9 → 0.0.6-alpha.91

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 (116) hide show
  1. package/README.md +18 -19
  2. package/dist/bin/build-rig-binaries.d.ts +2 -0
  3. package/dist/bin/build-rig-binaries.js +22 -10
  4. package/dist/bin/rig.d.ts +72 -0
  5. package/dist/bin/rig.js +15637 -7344
  6. package/dist/config/rig-default-config.yml +5 -0
  7. package/dist/src/app/drone-ui.d.ts +34 -0
  8. package/dist/src/app/drone-ui.js +278 -0
  9. package/dist/src/commands/_async-ui.d.ts +10 -0
  10. package/dist/src/commands/_async-ui.js +121 -0
  11. package/dist/src/commands/_cli-format.d.ts +56 -0
  12. package/dist/src/commands/_cli-format.js +319 -0
  13. package/dist/src/commands/_connection-state.d.ts +54 -0
  14. package/dist/src/commands/_connection-state.js +75 -11
  15. package/dist/src/commands/_doctor-checks.d.ts +46 -0
  16. package/dist/src/commands/_doctor-checks.js +100 -267
  17. package/dist/src/commands/_help-catalog.d.ts +51 -0
  18. package/dist/src/commands/_help-catalog.js +502 -0
  19. package/dist/src/commands/_inprocess-services.d.ts +33 -0
  20. package/dist/src/commands/_inprocess-services.js +102 -0
  21. package/dist/src/commands/_json-output.d.ts +11 -0
  22. package/dist/src/commands/_json-output.js +60 -0
  23. package/dist/src/commands/_lazy-reconcile.d.ts +34 -0
  24. package/dist/src/commands/_lazy-reconcile.js +102 -0
  25. package/dist/src/commands/_parsers.d.ts +15 -0
  26. package/dist/src/commands/_parsers.js +18 -11
  27. package/dist/src/commands/_paths.d.ts +11 -0
  28. package/dist/src/commands/_paths.js +1 -1
  29. package/dist/src/commands/_pi-frontend.d.ts +35 -0
  30. package/dist/src/commands/_pi-frontend.js +64 -0
  31. package/dist/src/commands/_pi-install.d.ts +42 -0
  32. package/dist/src/commands/_pi-install.js +19 -36
  33. package/dist/src/commands/_policy.d.ts +8 -0
  34. package/dist/src/commands/_policy.js +67 -19
  35. package/dist/src/commands/_probes.d.ts +1 -0
  36. package/dist/src/commands/_run-bridge.d.ts +114 -0
  37. package/dist/src/commands/_run-bridge.js +387 -0
  38. package/dist/src/commands/_run-diagnostics.d.ts +9 -0
  39. package/dist/src/commands/_run-diagnostics.js +51 -0
  40. package/dist/src/commands/_run-driver-helpers.d.ts +26 -0
  41. package/dist/src/commands/_run-driver-helpers.js +79 -230
  42. package/dist/src/commands/_run-projection.d.ts +50 -0
  43. package/dist/src/commands/_run-projection.js +349 -0
  44. package/dist/src/commands/_run-subcommands.d.ts +3 -0
  45. package/dist/src/commands/_run-subcommands.js +31 -0
  46. package/dist/src/commands/_spinner.d.ts +25 -0
  47. package/dist/src/commands/_spinner.js +65 -0
  48. package/dist/src/commands/agent.d.ts +3 -0
  49. package/dist/src/commands/agent.js +8575 -236
  50. package/dist/src/commands/dist.d.ts +28 -0
  51. package/dist/src/commands/dist.js +44 -29
  52. package/dist/src/commands/doctor.d.ts +3 -0
  53. package/dist/src/commands/doctor.js +219 -268
  54. package/dist/src/commands/github.d.ts +3 -0
  55. package/dist/src/commands/github.js +236 -188
  56. package/dist/src/commands/inbox.d.ts +28 -0
  57. package/dist/src/commands/inbox.js +530 -111
  58. package/dist/src/commands/init.d.ts +64 -0
  59. package/dist/src/commands/init.js +912 -705
  60. package/dist/src/commands/inspect.d.ts +20 -0
  61. package/dist/src/commands/inspect.js +709 -119
  62. package/dist/src/commands/pi.d.ts +3 -0
  63. package/dist/src/commands/pi.js +177 -0
  64. package/dist/src/commands/plugin.d.ts +16 -0
  65. package/dist/src/commands/plugin.js +576 -29
  66. package/dist/src/commands/profile-and-review.d.ts +4 -0
  67. package/dist/src/commands/profile-and-review.js +112 -67
  68. package/dist/src/commands/queue.d.ts +3 -0
  69. package/dist/src/commands/queue.js +20 -20
  70. package/dist/src/commands/remote.d.ts +3 -0
  71. package/dist/src/commands/remote.js +880 -50
  72. package/dist/src/commands/repo-git-harness.d.ts +5 -0
  73. package/dist/src/commands/repo-git-harness.js +78 -28
  74. package/dist/src/commands/run.d.ts +21 -0
  75. package/dist/src/commands/run.js +17701 -661
  76. package/dist/src/commands/server.d.ts +3 -0
  77. package/dist/src/commands/server.js +166 -303
  78. package/dist/src/commands/setup.d.ts +16 -0
  79. package/dist/src/commands/setup.js +244 -299
  80. package/dist/src/commands/stats.d.ts +15 -0
  81. package/dist/src/commands/stats.js +979 -0
  82. package/dist/src/commands/task-run-driver.d.ts +94 -0
  83. package/dist/src/commands/task-run-driver.js +116 -1961
  84. package/dist/src/commands/task.d.ts +35 -0
  85. package/dist/src/commands/task.js +676 -1201
  86. package/dist/src/commands/test.d.ts +3 -0
  87. package/dist/src/commands/test.js +15 -8
  88. package/dist/src/commands/triage.d.ts +11 -0
  89. package/dist/src/commands/triage.js +227 -0
  90. package/dist/src/commands/workspace.d.ts +3 -0
  91. package/dist/src/commands/workspace.js +18 -11
  92. package/dist/src/commands.d.ts +13 -0
  93. package/dist/src/commands.js +16792 -8055
  94. package/dist/src/index.d.ts +4 -0
  95. package/dist/src/index.js +16862 -8450
  96. package/dist/src/launcher.d.ts +61 -0
  97. package/dist/src/launcher.js +81 -13
  98. package/dist/src/operator-cli.d.ts +2 -0
  99. package/dist/src/operator-cli.js +17837 -0
  100. package/dist/src/operator-entry.d.ts +1 -0
  101. package/dist/src/operator-entry.js +3 -0
  102. package/dist/src/runner.d.ts +47 -0
  103. package/dist/src/runner.js +16 -22
  104. package/dist/src/withMutedConsole.d.ts +2 -0
  105. package/package.json +21 -8
  106. package/dist/src/commands/_authority-runs.js +0 -111
  107. package/dist/src/commands/_operator-view.js +0 -340
  108. package/dist/src/commands/_preflight.js +0 -478
  109. package/dist/src/commands/_server-client.js +0 -382
  110. package/dist/src/commands/_snapshot-upload.js +0 -331
  111. package/dist/src/commands/_task-picker.js +0 -48
  112. package/dist/src/commands/browser.js +0 -890
  113. package/dist/src/commands/connect.js +0 -180
  114. package/dist/src/commands/inspector.js +0 -256
  115. package/dist/src/commands/task-report-bug.js +0 -1083
  116. package/dist/src/report-bug.js +0 -260
@@ -1,14 +1,25 @@
1
1
  // @bun
2
2
  var __require = import.meta.require;
3
3
 
4
+ // packages/cli/src/commands/plugin.ts
5
+ import { existsSync as existsSync6 } from "fs";
6
+ import { resolve as resolve4 } from "path";
7
+
4
8
  // packages/cli/src/runner.ts
5
9
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
6
- import { CliError } from "@rig/runtime/control-plane/errors";
10
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
7
11
  import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
8
- import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
9
- import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
10
12
  import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
11
- import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
13
+
14
+ class CliError extends RuntimeCliError {
15
+ hint;
16
+ constructor(message, exitCode = 1, options = {}) {
17
+ super(message, exitCode);
18
+ if (options.hint?.trim()) {
19
+ this.hint = options.hint.trim();
20
+ }
21
+ }
22
+ }
12
23
  function takeOption(args, option) {
13
24
  const rest = [];
14
25
  let value;
@@ -17,7 +28,7 @@ function takeOption(args, option) {
17
28
  if (current === option) {
18
29
  const next = args[index + 1];
19
30
  if (!next || next.startsWith("-")) {
20
- throw new CliError(`Missing value for ${option}`);
31
+ throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
21
32
  }
22
33
  value = next;
23
34
  index += 1;
@@ -43,11 +54,493 @@ Usage: ${usage}`);
43
54
  return taskId;
44
55
  }
45
56
 
57
+ // packages/runtime/src/control-plane/plugin-host-context.ts
58
+ import { existsSync as existsSync5 } from "fs";
59
+ import { resolve as resolvePath } from "path";
60
+ import {
61
+ createPluginHost
62
+ } from "@rig/core";
63
+ import { loadConfig } from "@rig/core/load-config";
64
+
65
+ // packages/runtime/src/control-plane/task-source.ts
66
+ function createTaskSourceRegistry() {
67
+ const byId = new Map;
68
+ const order = [];
69
+ return {
70
+ register(s) {
71
+ if (byId.has(s.id))
72
+ throw new Error(`task source already registered: ${s.id}`);
73
+ byId.set(s.id, s);
74
+ order.push(s);
75
+ },
76
+ resolveById(id) {
77
+ const s = byId.get(id);
78
+ if (!s)
79
+ throw new Error(`task source not registered: ${id}`);
80
+ return s;
81
+ },
82
+ resolveByKind(kind) {
83
+ for (const s of order)
84
+ if (s.kind === kind)
85
+ return s;
86
+ throw new Error(`no task source registered for kind: ${kind}`);
87
+ },
88
+ list: () => order
89
+ };
90
+ }
91
+
92
+ // packages/runtime/src/control-plane/task-source-bootstrap.ts
93
+ function formatRegisteredKinds(pluginHost) {
94
+ const kinds = pluginHost ? pluginHost.listExecutableTaskSources().map((source) => source.kind) : [];
95
+ return kinds.length > 0 ? kinds.join(", ") : "none";
96
+ }
97
+ function buildTaskSourceRegistry(config, pluginHost, context) {
98
+ const registry = createTaskSourceRegistry();
99
+ const taskSourceConfig = config.taskSource;
100
+ const factory = pluginHost?.resolveTaskSourceFactoryByKind(taskSourceConfig.kind);
101
+ if (!factory) {
102
+ throw new Error(`No task source factory registered for kind "${taskSourceConfig.kind}". ` + `Registered kinds: ${formatRegisteredKinds(pluginHost)}. ` + "Load a plugin that contributes an executable task source factory for this kind.");
103
+ }
104
+ registry.register(factory.factory(taskSourceConfig, context ? { ...context, rigConfig: config } : undefined));
105
+ return registry;
106
+ }
107
+
108
+ // packages/runtime/src/control-plane/repos/registry.ts
109
+ function createRepoRegistry(entries) {
110
+ const map = new Map;
111
+ for (const e of entries) {
112
+ if (map.has(e.id))
113
+ throw new Error(`repo already registered: ${e.id}`);
114
+ map.set(e.id, { ...e });
115
+ }
116
+ const ordered = Array.from(map.values());
117
+ return {
118
+ getById: (id) => map.get(id),
119
+ list: () => ordered
120
+ };
121
+ }
122
+ var MANAGED_REPOS = new Map;
123
+ function setManagedRepos(entries) {
124
+ const next = new Map;
125
+ for (const e of entries) {
126
+ if (next.has(e.id)) {
127
+ throw new Error(`managed repo already registered: ${e.id}`);
128
+ }
129
+ next.set(e.id, e);
130
+ }
131
+ MANAGED_REPOS = next;
132
+ }
133
+ function repoRegistrationToManagedEntry(reg) {
134
+ if (!reg.defaultBranch) {
135
+ return null;
136
+ }
137
+ return {
138
+ id: reg.id,
139
+ alias: reg.defaultPath ?? reg.id,
140
+ defaultBranch: reg.defaultBranch,
141
+ defaultRemoteUrl: reg.url,
142
+ remoteEnvVar: reg.remoteEnvVar,
143
+ checkoutEnvVar: reg.checkoutEnvVar
144
+ };
145
+ }
146
+
147
+ // packages/runtime/src/control-plane/agent-roles.ts
148
+ function createAgentRoleRegistry(pluginRoles, configOverrides) {
149
+ const map = new Map;
150
+ for (const r of pluginRoles) {
151
+ if (map.has(r.id))
152
+ throw new Error(`agent role already registered: ${r.id}`);
153
+ const override = configOverrides?.[r.id];
154
+ const model = override?.model ?? r.defaultModel;
155
+ if (!model) {
156
+ throw new Error(`agent role "${r.id}" has no model \u2014 provide defaultModel in plugin or model in config.runtime.agentRoles.${r.id}`);
157
+ }
158
+ map.set(r.id, { ...r, model });
159
+ }
160
+ return {
161
+ resolve(id) {
162
+ const r = map.get(id);
163
+ if (!r)
164
+ throw new Error(`agent role not registered: ${id}`);
165
+ return r;
166
+ },
167
+ list: () => Array.from(map.values())
168
+ };
169
+ }
170
+
171
+ // packages/runtime/src/control-plane/task-fields.ts
172
+ function createTaskFieldRegistry(extensions) {
173
+ const byId = new Map;
174
+ for (const e of extensions) {
175
+ if (byId.has(e.id))
176
+ throw new Error(`task field extension already registered: ${e.id}`);
177
+ byId.set(e.id, e);
178
+ }
179
+ return {
180
+ get: (id) => byId.get(id),
181
+ list: () => Array.from(byId.values()),
182
+ fieldNames: () => Array.from(byId.values()).map((e) => e.fieldName),
183
+ validateTaskFields(task) {
184
+ const errors = [];
185
+ for (const ext of byId.values()) {
186
+ let schema;
187
+ try {
188
+ schema = JSON.parse(ext.schemaJson);
189
+ } catch {
190
+ errors.push(`task field "${ext.id}": schemaJson is not valid JSON`);
191
+ continue;
192
+ }
193
+ const isRequired = typeof schema === "object" && schema !== null && schema.required === true;
194
+ if (!isRequired)
195
+ continue;
196
+ const value = task[ext.fieldName];
197
+ if (value === undefined || value === null || value === "") {
198
+ errors.push(`task field "${ext.fieldName}" (from extension "${ext.id}") is required but missing`);
199
+ }
200
+ }
201
+ return errors.length === 0 ? { ok: true } : { ok: false, errors };
202
+ }
203
+ };
204
+ }
205
+
206
+ // packages/runtime/src/control-plane/validators/runtime-registration.ts
207
+ import { existsSync } from "fs";
208
+ import { join } from "path";
209
+ function createValidatorRegistry() {
210
+ const map = new Map;
211
+ const order = [];
212
+ const registry = {
213
+ register(v) {
214
+ if (map.has(v.id))
215
+ throw new Error(`validator already registered: ${v.id}`);
216
+ map.set(v.id, v);
217
+ order.push(v);
218
+ },
219
+ resolve(id) {
220
+ const v = map.get(id);
221
+ if (!v)
222
+ throw new Error(`validator not registered: ${id}`);
223
+ return v;
224
+ },
225
+ list: () => order
226
+ };
227
+ registerBuiltInValidators(registry);
228
+ return registry;
229
+ }
230
+ function registerBuiltInValidators(registry) {
231
+ registry.register({
232
+ id: "std:typecheck",
233
+ category: "custom",
234
+ description: "Runs the package typecheck script when present.",
235
+ run: async (ctx) => runStdTypecheck(ctx)
236
+ });
237
+ }
238
+ async function runStdTypecheck(ctx) {
239
+ const packageJsonPath = join(ctx.workspaceRoot, "package.json");
240
+ if (!existsSync(packageJsonPath)) {
241
+ return {
242
+ id: "std:typecheck",
243
+ passed: false,
244
+ summary: `package.json not found at ${packageJsonPath}`
245
+ };
246
+ }
247
+ const proc = Bun.spawn(["bun", "run", "typecheck"], {
248
+ cwd: ctx.workspaceRoot,
249
+ env: process.env,
250
+ stdout: "pipe",
251
+ stderr: "pipe"
252
+ });
253
+ const [exitCode, stdout, stderr] = await Promise.all([
254
+ proc.exited,
255
+ new Response(proc.stdout).text(),
256
+ new Response(proc.stderr).text()
257
+ ]);
258
+ const output = `${stdout}${stderr}`.trim();
259
+ return {
260
+ id: "std:typecheck",
261
+ passed: exitCode === 0,
262
+ summary: exitCode === 0 ? "typecheck passed" : "typecheck failed",
263
+ ...output ? { details: output.slice(0, 4000) } : {}
264
+ };
265
+ }
266
+
267
+ // packages/runtime/src/control-plane/native/scope-rules.ts
268
+ var activeRules = null;
269
+ function setScopeRules(rules) {
270
+ activeRules = rules ?? null;
271
+ }
272
+
273
+ // packages/runtime/src/control-plane/hook-materializer.ts
274
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
275
+ import { dirname, resolve } from "path";
276
+ import { resolveBunCliInvocation } from "@rig/hook-kit";
277
+ var MARKER_PLUGIN = "_rigPlugin";
278
+ var MARKER_HOOK_ID = "_rigHookId";
279
+ function matcherToString(matcher) {
280
+ if (matcher.kind === "all")
281
+ return;
282
+ if (matcher.kind === "tool")
283
+ return matcher.name;
284
+ return matcher.pattern;
285
+ }
286
+ function isPluginOwned(cmd) {
287
+ return typeof cmd[MARKER_PLUGIN] === "string";
288
+ }
289
+ function shellQuote(value) {
290
+ return `'${value.replaceAll("'", `'\\''`)}'`;
291
+ }
292
+ function resolveHookRunnerPath() {
293
+ const sibling = resolve(import.meta.dirname, "hook-runner.ts");
294
+ if (existsSync2(sibling)) {
295
+ return sibling;
296
+ }
297
+ return "$CLAUDE_PROJECT_DIR/node_modules/@rig/runtime/src/control-plane/hook-runner.ts";
298
+ }
299
+ function buildTypedHookShimCommand(pluginName, hook, projectRoot) {
300
+ const runnerPath = resolveHookRunnerPath();
301
+ const runnerArg = runnerPath.startsWith("$CLAUDE_PROJECT_DIR") ? `"${runnerPath}"` : shellQuote(runnerPath);
302
+ const bun = resolveBunCliInvocation();
303
+ const envPrefix = Object.entries(bun.env).map(([key, value]) => `${key}=${shellQuote(value)}`).join(" ");
304
+ const parts = [
305
+ envPrefix,
306
+ shellQuote(bun.command),
307
+ runnerArg,
308
+ "--plugin",
309
+ shellQuote(pluginName),
310
+ "--hook",
311
+ shellQuote(hook.id),
312
+ "--event",
313
+ shellQuote(hook.event),
314
+ "--project-root",
315
+ shellQuote(projectRoot)
316
+ ].filter(Boolean);
317
+ return parts.join(" ");
318
+ }
319
+ function materializeHooks(projectRoot, entries) {
320
+ const settingsPath = resolve(projectRoot, ".claude", "settings.json");
321
+ const existing = existsSync2(settingsPath) ? safeReadJson(settingsPath) : {};
322
+ const hooks = existing.hooks ?? {};
323
+ for (const event of Object.keys(hooks)) {
324
+ const groups = hooks[event] ?? [];
325
+ const cleaned = [];
326
+ for (const group of groups) {
327
+ const operatorHooks = group.hooks.filter((h) => !isPluginOwned(h));
328
+ if (operatorHooks.length > 0) {
329
+ cleaned.push({ ...group, hooks: operatorHooks });
330
+ }
331
+ }
332
+ if (cleaned.length > 0) {
333
+ hooks[event] = cleaned;
334
+ } else {
335
+ delete hooks[event];
336
+ }
337
+ }
338
+ for (const { pluginName, hook, typed } of entries) {
339
+ const command = hook.command ?? (typed ? buildTypedHookShimCommand(pluginName, hook, projectRoot) : undefined);
340
+ if (!command) {
341
+ continue;
342
+ }
343
+ const event = hook.event;
344
+ const matcherString = matcherToString(hook.matcher);
345
+ const groups = hooks[event] ??= [];
346
+ let group = groups.find((g) => g.matcher === matcherString);
347
+ if (!group) {
348
+ group = matcherString === undefined ? { hooks: [] } : { matcher: matcherString, hooks: [] };
349
+ groups.push(group);
350
+ }
351
+ group.hooks.push({
352
+ type: "command",
353
+ command,
354
+ [MARKER_PLUGIN]: pluginName,
355
+ [MARKER_HOOK_ID]: hook.id
356
+ });
357
+ }
358
+ const next = { ...existing };
359
+ if (Object.keys(hooks).length > 0) {
360
+ next.hooks = hooks;
361
+ } else {
362
+ delete next.hooks;
363
+ }
364
+ mkdirSync(dirname(settingsPath), { recursive: true });
365
+ writeFileSync(settingsPath, `${JSON.stringify(next, null, 2)}
366
+ `, "utf-8");
367
+ return settingsPath;
368
+ }
369
+ function safeReadJson(path) {
370
+ try {
371
+ return JSON.parse(readFileSync(path, "utf-8"));
372
+ } catch {
373
+ return {};
374
+ }
375
+ }
376
+
377
+ // packages/runtime/src/control-plane/skill-materializer.ts
378
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
379
+ import { resolve as resolve2 } from "path";
380
+ import { loadSkill } from "@rig/skill-loader";
381
+ var MARKER_FILENAME = ".rig-plugin";
382
+ function skillDirName(id) {
383
+ return id.replace(/[^a-zA-Z0-9._-]+/g, "-");
384
+ }
385
+ async function materializeSkills(projectRoot, entries) {
386
+ const skillsRoot = resolve2(projectRoot, ".pi", "skills");
387
+ if (existsSync3(skillsRoot)) {
388
+ for (const name of readdirSync(skillsRoot)) {
389
+ const dir = resolve2(skillsRoot, name);
390
+ if (existsSync3(resolve2(dir, MARKER_FILENAME))) {
391
+ rmSync(dir, { recursive: true, force: true });
392
+ }
393
+ }
394
+ }
395
+ const written = [];
396
+ for (const { pluginName, skill } of entries) {
397
+ const sourcePath = resolve2(projectRoot, skill.path);
398
+ if (!existsSync3(sourcePath)) {
399
+ console.warn(`[plugin-host] skill "${skill.id}" from plugin "${pluginName}" not materialized: ${sourcePath} does not exist`);
400
+ continue;
401
+ }
402
+ let body;
403
+ try {
404
+ await loadSkill(sourcePath);
405
+ body = readFileSync2(sourcePath, "utf-8");
406
+ } catch (err) {
407
+ console.warn(`[plugin-host] skill "${skill.id}" from plugin "${pluginName}" not materialized: ${err instanceof Error ? err.message : err}`);
408
+ continue;
409
+ }
410
+ const dir = resolve2(skillsRoot, skillDirName(skill.id));
411
+ mkdirSync2(dir, { recursive: true });
412
+ writeFileSync2(resolve2(dir, "SKILL.md"), body, "utf-8");
413
+ writeFileSync2(resolve2(dir, MARKER_FILENAME), `${JSON.stringify({ plugin: pluginName, skillId: skill.id }, null, 2)}
414
+ `, "utf-8");
415
+ written.push({ id: skill.id, pluginName, directory: dir });
416
+ }
417
+ return written;
418
+ }
419
+
420
+ // packages/runtime/src/control-plane/pi-settings-materializer.ts
421
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
422
+ import { dirname as dirname2, resolve as resolve3 } from "path";
423
+ var SETTINGS_RELATIVE_PATH = ".pi/settings.json";
424
+ var MANAGED_RECORD_RELATIVE_PATH = ".rig/state/pi-managed-packages.json";
425
+ function readJson(path, fallback) {
426
+ if (!existsSync4(path))
427
+ return fallback;
428
+ try {
429
+ return JSON.parse(readFileSync3(path, "utf-8"));
430
+ } catch {
431
+ return fallback;
432
+ }
433
+ }
434
+ function packageKey(entry) {
435
+ if (typeof entry === "string")
436
+ return entry;
437
+ if (entry && typeof entry === "object" && typeof entry.source === "string") {
438
+ return entry.source;
439
+ }
440
+ return JSON.stringify(entry);
441
+ }
442
+ function materializePiPackages(projectRoot, declaredPackages) {
443
+ const settingsPath = resolve3(projectRoot, SETTINGS_RELATIVE_PATH);
444
+ const managedRecordPath = resolve3(projectRoot, MANAGED_RECORD_RELATIVE_PATH);
445
+ const settings = readJson(settingsPath, {});
446
+ const previouslyManaged = new Set(readJson(managedRecordPath, []));
447
+ const existing = Array.isArray(settings.packages) ? settings.packages : [];
448
+ const operatorEntries = existing.filter((entry) => !previouslyManaged.has(packageKey(entry)));
449
+ const operatorKeys = new Set(operatorEntries.map(packageKey));
450
+ const managedToAdd = declaredPackages.filter((pkg) => !operatorKeys.has(pkg));
451
+ const nextPackages = [...operatorEntries, ...managedToAdd];
452
+ if (nextPackages.length > 0 || existsSync4(settingsPath)) {
453
+ const nextSettings = { ...settings };
454
+ if (nextPackages.length > 0) {
455
+ nextSettings.packages = nextPackages;
456
+ } else {
457
+ delete nextSettings.packages;
458
+ }
459
+ mkdirSync3(dirname2(settingsPath), { recursive: true });
460
+ writeFileSync3(settingsPath, `${JSON.stringify(nextSettings, null, 2)}
461
+ `, "utf-8");
462
+ }
463
+ mkdirSync3(dirname2(managedRecordPath), { recursive: true });
464
+ writeFileSync3(managedRecordPath, `${JSON.stringify(managedToAdd, null, 2)}
465
+ `, "utf-8");
466
+ return { settingsPath, packages: managedToAdd };
467
+ }
468
+
469
+ // packages/runtime/src/control-plane/plugin-host-context.ts
470
+ async function buildPluginHostContext(projectRoot) {
471
+ let config;
472
+ try {
473
+ config = await loadConfig(projectRoot);
474
+ } catch (err) {
475
+ const msg = err instanceof Error ? err.message : String(err);
476
+ if (msg.includes("no rig.config")) {
477
+ return null;
478
+ }
479
+ throw err;
480
+ }
481
+ const pluginHost = createPluginHost(config.plugins);
482
+ setScopeRules(config.workspace.scopeNormalization);
483
+ if (config.workspace.sandbox && !process.env.RIG_RUNTIME_SANDBOX?.trim()) {
484
+ process.env.RIG_RUNTIME_SANDBOX = config.workspace.sandbox;
485
+ }
486
+ const validatorRegistry = createValidatorRegistry();
487
+ for (const impl of pluginHost.listExecutableValidators()) {
488
+ validatorRegistry.register(impl);
489
+ }
490
+ const taskSourceRegistry = buildTaskSourceRegistry(config, pluginHost, { projectRoot });
491
+ const repoRegistry = createRepoRegistry(pluginHost.listRepoSources());
492
+ const managedEntries = pluginHost.listRepoSources().map(repoRegistrationToManagedEntry).filter((e) => e !== null);
493
+ setManagedRepos(managedEntries);
494
+ const configRoleOverrides = config.runtime?.agentRoles;
495
+ const agentRoleRegistry = createAgentRoleRegistry(pluginHost.listAgentRoles(), configRoleOverrides);
496
+ const taskFieldRegistry = createTaskFieldRegistry(pluginHost.listTaskFieldExtensions());
497
+ try {
498
+ const hookEntries = config.plugins.flatMap((plugin) => (plugin.contributes?.hooks ?? []).map((hook) => ({
499
+ pluginName: plugin.name,
500
+ hook,
501
+ typed: Boolean(plugin.__runtime?.hooks?.[hook.id])
502
+ })));
503
+ if (hookEntries.length > 0) {
504
+ materializeHooks(projectRoot, hookEntries);
505
+ }
506
+ } catch (err) {
507
+ console.warn(`[plugin-host] hook materialization failed: ${err instanceof Error ? err.message : err}`);
508
+ }
509
+ try {
510
+ const skillEntries = config.plugins.flatMap((plugin) => (plugin.contributes?.skills ?? []).map((skill) => ({
511
+ pluginName: plugin.name,
512
+ skill
513
+ })));
514
+ if (skillEntries.length > 0) {
515
+ await materializeSkills(projectRoot, skillEntries);
516
+ }
517
+ } catch (err) {
518
+ console.warn(`[plugin-host] skill materialization failed: ${err instanceof Error ? err.message : err}`);
519
+ }
520
+ try {
521
+ const piPackages = config.runtime?.pi?.packages ?? [];
522
+ if (piPackages.length > 0 || existsSync5(resolvePath(projectRoot, ".rig/state/pi-managed-packages.json"))) {
523
+ materializePiPackages(projectRoot, piPackages);
524
+ }
525
+ } catch (err) {
526
+ console.warn(`[plugin-host] Pi package materialization failed: ${err instanceof Error ? err.message : err}`);
527
+ }
528
+ return {
529
+ config,
530
+ pluginHost,
531
+ validatorRegistry,
532
+ taskSourceRegistry,
533
+ repoRegistry,
534
+ agentRoleRegistry,
535
+ taskFieldRegistry
536
+ };
537
+ }
538
+
46
539
  // packages/cli/src/commands/_parsers.ts
47
540
  async function loadRigConfigOrNull(projectRoot) {
48
541
  try {
49
- const { loadConfig } = await import("@rig/core/load-config");
50
- return await loadConfig(projectRoot);
542
+ const { loadConfig: loadConfig2 } = await import("@rig/core/load-config");
543
+ return await loadConfig2(projectRoot);
51
544
  } catch {
52
545
  return null;
53
546
  }
@@ -58,8 +551,7 @@ async function executePlugin(context, args) {
58
551
  const [command = "list", ...rest] = args;
59
552
  switch (command) {
60
553
  case "list": {
61
- requireNoExtraArgs(rest, "bun run rig plugin list");
62
- const legacyPlugins = context.plugins.list();
554
+ requireNoExtraArgs(rest, "rig plugin list");
63
555
  const declarative = [];
64
556
  const config = await loadRigConfigOrNull(context.projectRoot);
65
557
  if (config && Array.isArray(config.plugins)) {
@@ -75,16 +567,16 @@ async function executePlugin(context, args) {
75
567
  taskSources: (c.taskSources ?? []).map((s) => s.id),
76
568
  skills: (c.skills ?? []).map((s) => s.id),
77
569
  taskFieldExtensions: (c.taskFieldSchemas ?? []).map((f) => f.id),
78
- cliCommands: (c.cliCommands ?? []).map((c2) => c2.id)
570
+ cliCommands: (c.cliCommands ?? []).map((entry) => entry.id)
79
571
  });
80
572
  }
81
573
  }
82
574
  if (context.outputMode === "text") {
83
- if (legacyPlugins.length === 0 && declarative.length === 0) {
84
- console.log("No plugins loaded.");
85
- }
86
- if (declarative.length > 0) {
87
- console.log("Declarative plugins (rig.config.ts):");
575
+ if (declarative.length === 0) {
576
+ const configExists = ["rig.config.ts", "rig.config.mts", "rig.config.json"].some((name) => existsSync6(resolve4(context.projectRoot, name)));
577
+ console.log(configExists ? "rig.config found but no plugins could be loaded. If the config imports @rig/* packages, run `bun install` in this project first." : "No plugins loaded. Declare plugins in rig.config.ts.");
578
+ } else {
579
+ console.log("Plugins (rig.config.ts):");
88
580
  for (const p of declarative) {
89
581
  console.log(` ${p.name}@${p.version}`);
90
582
  const lines = [];
@@ -103,31 +595,45 @@ async function executePlugin(context, args) {
103
595
  if (p.taskFieldExtensions.length)
104
596
  lines.push(` task-fields: ${p.taskFieldExtensions.join(", ")}`);
105
597
  if (p.cliCommands.length)
106
- lines.push(` cli-commands: ${p.cliCommands.join(", ")}`);
598
+ lines.push(` cli-commands: ${p.cliCommands.join(", ")} (run with \`rig plugin run <id>\`)`);
107
599
  for (const line of lines)
108
600
  console.log(line);
109
601
  }
110
602
  }
111
- if (legacyPlugins.length > 0) {
112
- console.log("Legacy disk-scan plugins (rig/plugins/):");
113
- for (const plugin of legacyPlugins) {
114
- const validators = plugin.validators.length > 0 ? plugin.validators.join(", ") : "none";
115
- console.log(` ${plugin.name} (validators: ${validators})`);
116
- }
117
- }
118
603
  }
119
604
  return {
120
605
  ok: true,
121
606
  group: "plugin",
122
607
  command,
123
- details: { declarative, legacy: legacyPlugins }
608
+ details: { declarative }
124
609
  };
125
610
  }
126
611
  case "validate": {
127
612
  const { value: task, rest: remaining } = takeOption(rest, "--task");
128
- requireNoExtraArgs(remaining, "bun run rig plugin validate --task <beads-id>");
129
- const taskId = requireTask(task, "bun run rig plugin validate --task <beads-id>");
130
- const results = await context.plugins.runValidators(taskId);
613
+ requireNoExtraArgs(remaining, "rig plugin validate --task <task-id>");
614
+ const taskId = requireTask(task, "rig plugin validate --task <task-id>");
615
+ const hostCtx = await buildPluginHostContext(context.projectRoot);
616
+ if (!hostCtx) {
617
+ throw new CliError(`No rig.config found at ${context.projectRoot}. Run \`rig init\` to set up plugins.`, 2);
618
+ }
619
+ const validators = hostCtx.validatorRegistry.list();
620
+ const results = [];
621
+ for (const validator of validators) {
622
+ try {
623
+ results.push(await validator.run({
624
+ taskId,
625
+ workspaceRoot: context.projectRoot,
626
+ scope: []
627
+ }));
628
+ } catch (error) {
629
+ results.push({
630
+ id: validator.id,
631
+ passed: false,
632
+ summary: `${validator.id} failed unexpectedly`,
633
+ details: `${error}`
634
+ });
635
+ }
636
+ }
131
637
  const passed = results.filter((result) => result.passed).length;
132
638
  const failed = results.length - passed;
133
639
  if (context.outputMode === "text") {
@@ -144,7 +650,7 @@ async function executePlugin(context, args) {
144
650
  }
145
651
  }
146
652
  if (failed > 0) {
147
- throw new CliError2(`Plugin validation failed for ${failed} validator(s).`, 2);
653
+ throw new CliError(`Plugin validation failed for ${failed} validator(s).`, 2, { hint: "Fix the reported validators, then re-run `rig plugin validate --task <id>`." });
148
654
  }
149
655
  return {
150
656
  ok: true,
@@ -158,10 +664,51 @@ async function executePlugin(context, args) {
158
664
  }
159
665
  };
160
666
  }
667
+ case "run": {
668
+ const [commandId, ...commandArgs] = rest;
669
+ if (!commandId) {
670
+ throw new CliError("Usage: rig plugin run <command-id> [args...]", 1, { hint: "Run `rig plugin list` to see plugin-contributed command ids." });
671
+ }
672
+ const hostCtx = await buildPluginHostContext(context.projectRoot);
673
+ if (!hostCtx) {
674
+ throw new CliError(`No rig.config found at ${context.projectRoot}. Run \`rig init\` to set up plugins.`, 2);
675
+ }
676
+ const registration = resolvePluginCliCommand(hostCtx.pluginHost.listCliCommands(), commandId);
677
+ if (!registration) {
678
+ const available = hostCtx.pluginHost.listCliCommands().map((entry) => entry.id);
679
+ throw new CliError(available.length > 0 ? `Unknown plugin command "${commandId}". Available: ${available.join(", ")}` : `No plugin CLI commands are registered. Plugins contribute them via contributes.cliCommands.`, 2);
680
+ }
681
+ if (context.dryRun) {
682
+ if (context.outputMode === "text") {
683
+ console.log(`[dry-run] ${registration.command}${commandArgs.length ? ` ${commandArgs.join(" ")}` : ""}`);
684
+ }
685
+ return { ok: true, group: "plugin", command, details: { id: registration.id, dryRun: true } };
686
+ }
687
+ const proc = Bun.spawn(["bash", "-c", `${registration.command} "$@"`, registration.id, ...commandArgs], {
688
+ cwd: context.projectRoot,
689
+ env: process.env,
690
+ stdin: "inherit",
691
+ stdout: "inherit",
692
+ stderr: "inherit"
693
+ });
694
+ const exitCode = await proc.exited;
695
+ if (exitCode !== 0) {
696
+ throw new CliError(`Plugin command "${registration.id}" exited with code ${exitCode}.`, exitCode, { hint: "Run `rig plugin list` to inspect the contributing plugin, or re-run with --json for details." });
697
+ }
698
+ return { ok: true, group: "plugin", command, details: { id: registration.id, exitCode } };
699
+ }
161
700
  default:
162
- throw new CliError2(`Unknown plugin command: ${command}`);
701
+ throw new CliError(`Unknown plugin command: ${command}`, 1, { hint: "Run `rig plugin --help` \u2014 commands are list|validate|run." });
163
702
  }
164
703
  }
704
+ function resolvePluginCliCommand(commands, requested) {
705
+ const exact = commands.find((entry) => entry.id === requested);
706
+ if (exact)
707
+ return exact;
708
+ const byLocalPart = commands.filter((entry) => entry.id.split(":").slice(1).join(":") === requested);
709
+ return byLocalPart.length === 1 ? byLocalPart[0] : undefined;
710
+ }
165
711
  export {
712
+ resolvePluginCliCommand,
166
713
  executePlugin
167
714
  };
@@ -0,0 +1,4 @@
1
+ import { type RunnerContext } from "../runner";
2
+ import type { CommandOutcome } from "@rig/runtime";
3
+ export declare function executeProfile(context: RunnerContext, args: string[]): Promise<CommandOutcome>;
4
+ export declare function executeReview(context: RunnerContext, args: string[]): Promise<CommandOutcome>;