@h-rig/core 0.0.6-alpha.15 → 0.0.6-alpha.150

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.
@@ -0,0 +1,2 @@
1
+ import { RigConfig } from "@rig/contracts";
2
+ export declare function loadConfig(cwd: string): Promise<RigConfig>;
@@ -1,17 +1,321 @@
1
1
  // @bun
2
2
  // packages/core/src/load-config.ts
3
- import { existsSync, readFileSync } from "fs";
4
- import { join } from "path";
3
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync } from "fs";
4
+ import { isBuiltin } from "module";
5
+ import { dirname, isAbsolute, join, relative, resolve } from "path";
5
6
  import { pathToFileURL } from "url";
7
+ import { Schema as Schema2 } from "effect";
8
+ import { RigConfig as RigConfig2 } from "@rig/contracts";
9
+
10
+ // packages/core/src/define-config.ts
6
11
  import { Schema } from "effect";
7
12
  import { RigConfig } from "@rig/contracts";
13
+ function normalizeWorkspaceConfig(raw) {
14
+ const workspace = raw && typeof raw === "object" && !Array.isArray(raw) ? { ...raw } : { mainRepo: "." };
15
+ workspace.checkout = workspace.checkout ?? workspace.isolation ?? "worktree";
16
+ workspace.isolation = workspace.isolation ?? workspace.checkout;
17
+ workspace.sandbox = workspace.sandbox ?? "enforce";
18
+ return workspace;
19
+ }
20
+ function applyConfigDefaults(raw) {
21
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
22
+ return raw;
23
+ const record = raw;
24
+ return {
25
+ ...record,
26
+ plugins: Array.isArray(record.plugins) ? record.plugins : [],
27
+ workspace: normalizeWorkspaceConfig(record.workspace)
28
+ };
29
+ }
30
+
31
+ // packages/core/src/load-config.ts
8
32
  var TS_NAMES = ["rig.config.ts", "rig.config.mts"];
9
33
  var JSON_NAMES = ["rig.config.json"];
34
+ function isModuleResolutionError(error) {
35
+ const message = error instanceof Error ? error.message : String(error);
36
+ return message.includes("Cannot find module") || message.includes("Could not resolve");
37
+ }
38
+ function runningFromCompiledBinary() {
39
+ return import.meta.url.includes("$bunfs");
40
+ }
41
+ function packageNameAndSubpath(specifier) {
42
+ if (specifier.startsWith(".") || specifier.startsWith("/") || /^[a-zA-Z]+:/.test(specifier))
43
+ return null;
44
+ const parts = specifier.split("/");
45
+ const packageName = specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
46
+ if (!packageName)
47
+ return null;
48
+ const rest = parts.slice(specifier.startsWith("@") ? 2 : 1).join("/");
49
+ return { packageName, subpath: rest ? `./${rest}` : "." };
50
+ }
51
+ function exportTargetFromEntry(entry) {
52
+ if (typeof entry === "string")
53
+ return entry;
54
+ if (entry && typeof entry === "object" && !Array.isArray(entry)) {
55
+ const conditions = entry;
56
+ for (const key of ["bun", "import", "default", "require"]) {
57
+ if (typeof conditions[key] === "string")
58
+ return conditions[key];
59
+ }
60
+ }
61
+ return null;
62
+ }
63
+ function patternExportTarget(record, subpath) {
64
+ for (const [pattern, entry] of Object.entries(record)) {
65
+ if (!pattern.includes("*"))
66
+ continue;
67
+ const [prefix = "", suffix = ""] = pattern.split("*");
68
+ if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix))
69
+ continue;
70
+ const replacement = subpath.slice(prefix.length, subpath.length - suffix.length);
71
+ const target = exportTargetFromEntry(entry);
72
+ if (target)
73
+ return target.replace("*", replacement);
74
+ }
75
+ return null;
76
+ }
77
+ function exportTargetFromPackageJson(pkg, subpath) {
78
+ const exportsField = pkg.exports;
79
+ const target = (() => {
80
+ if (typeof exportsField === "string" && subpath === ".")
81
+ return exportsField;
82
+ if (!exportsField || typeof exportsField !== "object" || Array.isArray(exportsField))
83
+ return null;
84
+ const record = exportsField;
85
+ return exportTargetFromEntry(record[subpath] ?? (subpath === "." ? record["."] : undefined)) ?? patternExportTarget(record, subpath);
86
+ })();
87
+ if (target)
88
+ return target;
89
+ return subpath === "." && typeof pkg.module === "string" ? pkg.module : subpath === "." && typeof pkg.main === "string" ? pkg.main : null;
90
+ }
91
+ function resolvePackageDirFromBunStore(packageName, nodeModulesDir) {
92
+ const storeDir = join(nodeModulesDir, ".bun");
93
+ if (!existsSync(storeDir))
94
+ return null;
95
+ const encoded = packageName.replace("/", "+");
96
+ try {
97
+ const candidates = readdirSync(storeDir).filter((entry) => entry.startsWith(`${encoded}@`)).map((entry) => {
98
+ const candidateDir = join(storeDir, entry, "node_modules", packageName);
99
+ const packageJsonPath = join(candidateDir, "package.json");
100
+ if (!existsSync(packageJsonPath))
101
+ return null;
102
+ try {
103
+ const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
104
+ return {
105
+ dir: candidateDir,
106
+ sortKey: pkg.version?.trim() || entry
107
+ };
108
+ } catch {
109
+ return {
110
+ dir: candidateDir,
111
+ sortKey: entry
112
+ };
113
+ }
114
+ }).filter((candidate) => candidate !== null).sort((a, b) => b.sortKey.localeCompare(a.sortKey, undefined, { numeric: true, sensitivity: "base" }));
115
+ return candidates[0]?.dir ?? null;
116
+ } catch {
117
+ return null;
118
+ }
119
+ }
120
+ function resolveDirectoryModulePath(directoryPath) {
121
+ const packageJsonPath = join(directoryPath, "package.json");
122
+ if (existsSync(packageJsonPath)) {
123
+ try {
124
+ const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
125
+ const target = exportTargetFromPackageJson(pkg, ".");
126
+ if (target) {
127
+ const resolved = resolveModulePath(join(directoryPath, target));
128
+ if (resolved)
129
+ return resolved;
130
+ }
131
+ } catch {}
132
+ }
133
+ for (const candidate of ["index.js", "index.mjs", "index.cjs", "index.ts", "index.json"]) {
134
+ const resolved = resolveModulePath(join(directoryPath, candidate));
135
+ if (resolved)
136
+ return resolved;
137
+ }
138
+ return null;
139
+ }
140
+ function resolveModulePath(candidatePath) {
141
+ if (!existsSync(candidatePath)) {
142
+ for (const extension of [".ts", ".mts", ".tsx", ".js", ".mjs", ".cjs", ".json"]) {
143
+ const withExtension = `${candidatePath}${extension}`;
144
+ if (existsSync(withExtension))
145
+ return resolveModulePath(withExtension);
146
+ }
147
+ return null;
148
+ }
149
+ try {
150
+ const stat = statSync(candidatePath);
151
+ if (stat.isFile())
152
+ return candidatePath;
153
+ if (stat.isDirectory())
154
+ return resolveDirectoryModulePath(candidatePath);
155
+ } catch {
156
+ return null;
157
+ }
158
+ return null;
159
+ }
160
+ function resolvePackageExportFromDir(packageDir, subpath) {
161
+ const packageJsonPath = join(packageDir, "package.json");
162
+ if (existsSync(packageJsonPath)) {
163
+ try {
164
+ const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
165
+ const target = exportTargetFromPackageJson(pkg, subpath);
166
+ if (target) {
167
+ const resolved = resolveModulePath(join(packageDir, target));
168
+ if (resolved)
169
+ return resolved;
170
+ }
171
+ } catch {
172
+ return null;
173
+ }
174
+ }
175
+ if (subpath !== ".") {
176
+ const legacySubpath = subpath.replace(/^\.\//, "");
177
+ for (const candidate of [
178
+ join(packageDir, legacySubpath),
179
+ join(packageDir, `${legacySubpath}.js`),
180
+ join(packageDir, `${legacySubpath}.mjs`),
181
+ join(packageDir, `${legacySubpath}.cjs`),
182
+ join(packageDir, `${legacySubpath}.ts`),
183
+ join(packageDir, `${legacySubpath}.json`)
184
+ ]) {
185
+ const resolved = resolveModulePath(candidate);
186
+ if (resolved)
187
+ return resolved;
188
+ }
189
+ }
190
+ return subpath === "." ? resolveDirectoryModulePath(packageDir) : null;
191
+ }
192
+ var runtimeBundleQueue = Promise.resolve();
193
+ function enqueueRuntimeBundle(operation) {
194
+ const next = runtimeBundleQueue.then(operation, operation);
195
+ runtimeBundleQueue = next.then(() => {
196
+ return;
197
+ }, () => {
198
+ return;
199
+ });
200
+ return next;
201
+ }
202
+ function isWithinDir(candidatePath, rootPath) {
203
+ const rel = relative(resolve(rootPath), resolve(candidatePath));
204
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
205
+ }
206
+ function resolvedFilePath(path, rootPath) {
207
+ if (!path || !isAbsolute(path))
208
+ return null;
209
+ const resolved = resolveModulePath(path);
210
+ if (!resolved)
211
+ return null;
212
+ return rootPath && !isWithinDir(resolved, rootPath) ? null : resolved;
213
+ }
214
+ function resolveProjectPackageImport(specifier, configDir) {
215
+ const parsed = packageNameAndSubpath(specifier);
216
+ if (!parsed)
217
+ return null;
218
+ const nodeModulesDir = join(configDir, "node_modules");
219
+ const directPackageDir = join(nodeModulesDir, parsed.packageName);
220
+ const packageDir = existsSync(join(directPackageDir, "package.json")) ? directPackageDir : resolvePackageDirFromBunStore(parsed.packageName, nodeModulesDir);
221
+ return packageDir ? resolvePackageExportFromDir(packageDir, parsed.subpath) : null;
222
+ }
223
+ async function importConfigViaRuntimeBundleUnserialized(configPath) {
224
+ const bun = globalThis.Bun;
225
+ if (!bun?.build) {
226
+ throw new Error(`Failed to import ${configPath}: bare imports could not be resolved and no Bun.build runtime bundler is available.`);
227
+ }
228
+ const RUNTIME_ONLY_EXTERNAL_PACKAGES = new Set(["mupdf", "fastembed", "onnxruntime-node", "markit-ai"]);
229
+ const configDir = dirname(configPath);
230
+ const UNRESOLVED_NAMESPACE = "rig-config-unresolved";
231
+ const unresolvedLocalPlugin = {
232
+ name: "rig-config-unresolved-local",
233
+ setup(build) {
234
+ build.onResolve({ filter: /.*/ }, (args) => {
235
+ const directFilePath = resolvedFilePath(args.path, configDir);
236
+ if (directFilePath)
237
+ return { path: directFilePath };
238
+ const packageImport = packageNameAndSubpath(args.path);
239
+ if (packageImport && RUNTIME_ONLY_EXTERNAL_PACKAGES.has(packageImport.packageName))
240
+ return { path: args.path, external: true };
241
+ const projectPackagePath = resolveProjectPackageImport(args.path, configDir);
242
+ if (projectPackagePath)
243
+ return packageImport?.packageName.startsWith("@oh-my-pi/") ? { path: projectPackagePath, external: true } : { path: projectPackagePath };
244
+ if (/^(?:node|bun):/.test(args.path) || isBuiltin(args.path))
245
+ return;
246
+ if (packageImport)
247
+ return { path: args.path, external: true };
248
+ const parentCandidates = args.path.startsWith(".") ? [args.importer && isAbsolute(args.importer) ? dirname(args.importer) : null, configDir].filter((value) => Boolean(value)) : [args.importer && isAbsolute(args.importer) ? dirname(args.importer) : configDir];
249
+ for (const parent2 of parentCandidates) {
250
+ const filePath = resolvedFilePath(resolve(parent2, args.path), configDir);
251
+ if (filePath)
252
+ return { path: filePath };
253
+ }
254
+ const parent = parentCandidates[0] ?? configDir;
255
+ try {
256
+ const resolved = bun.resolveSync?.(args.path, parent) ?? resolve(parent, args.path);
257
+ const filePath = resolvedFilePath(resolved, configDir);
258
+ if (filePath)
259
+ return { path: filePath };
260
+ } catch {}
261
+ return { path: args.path, namespace: UNRESOLVED_NAMESPACE };
262
+ });
263
+ build.onLoad({ filter: /.*/, namespace: UNRESOLVED_NAMESPACE }, (args) => ({
264
+ loader: "js",
265
+ contents: `module.exports = {};
266
+ throw new Error(${JSON.stringify(`Failed to bundle ${configPath}: Could not resolve local import "${args.path}". Maybe you need to fix a relative import in rig.config.ts or install project deps.`)});
267
+ `
268
+ }));
269
+ }
270
+ };
271
+ const result = await bun.build({
272
+ entrypoints: [configPath],
273
+ target: "bun",
274
+ external: ["mupdf", "fastembed", "onnxruntime-node", "markit-ai"],
275
+ format: "esm",
276
+ throw: false,
277
+ packages: "bundle",
278
+ plugins: [unresolvedLocalPlugin]
279
+ });
280
+ if (!result.success || !result.outputs[0]) {
281
+ const detail = result.logs.map((log) => String(log)).join(`
282
+ `);
283
+ throw new Error(`Failed to bundle ${configPath}: ${detail || "unknown bundler error"}`);
284
+ }
285
+ const bundleParentDir = join(configDir, ".rig", "tmp");
286
+ mkdirSync(bundleParentDir, { recursive: true });
287
+ const dir = mkdtempSync(join(bundleParentDir, "rig-config-bundle-"));
288
+ try {
289
+ const bundledPath = join(dir, "rig.config.bundled.js");
290
+ await bun.write(bundledPath, await result.outputs[0].text());
291
+ return await import(pathToFileURL(bundledPath).href);
292
+ } finally {
293
+ try {
294
+ rmSync(dir, { recursive: true, force: true });
295
+ } catch {}
296
+ }
297
+ }
10
298
  async function loadConfig(cwd) {
11
299
  for (const name of TS_NAMES) {
12
300
  const p = join(cwd, name);
13
301
  if (existsSync(p)) {
14
- const mod = await import(pathToFileURL(p).href);
302
+ const mod = await enqueueRuntimeBundle(async () => {
303
+ if (runningFromCompiledBinary()) {
304
+ return importConfigViaRuntimeBundleUnserialized(p);
305
+ }
306
+ const source = readFileSync(p, "utf8");
307
+ const importsRigHostPackages = /(?:import\s+[^;]*?from\s*|import\s*\()\s*["']@rig\//.test(source);
308
+ if (importsRigHostPackages) {
309
+ return importConfigViaRuntimeBundleUnserialized(p);
310
+ }
311
+ try {
312
+ return await import(pathToFileURL(p).href);
313
+ } catch (error) {
314
+ if (!isModuleResolutionError(error))
315
+ throw error;
316
+ return importConfigViaRuntimeBundleUnserialized(p);
317
+ }
318
+ });
15
319
  const raw = mod.default ?? mod.config;
16
320
  return decodePreservingRuntime(raw);
17
321
  }
@@ -35,7 +339,7 @@ function decodePreservingRuntime(raw) {
35
339
  }
36
340
  }
37
341
  }
38
- const decoded = Schema.decodeUnknownSync(RigConfig)(raw);
342
+ const decoded = Schema2.decodeUnknownSync(RigConfig2)(applyConfigDefaults(raw));
39
343
  const plugins = decoded.plugins.map((p) => {
40
344
  const runtime = runtimeByName.get(p.name);
41
345
  if (!runtime)
@@ -0,0 +1,65 @@
1
+ import type { BlockerClassifierRegistration, CliCommandRegistration, HookRegistration, PanelRegistration, ProductCapabilityRegistration, RepoSourceRegistration, AgentRoleRegistration, SkillRegistration, Stage, StageMutation, StageRun, TaskFieldExtension, TaskSourceRegistration, ValidatorRegistration } from "@rig/contracts";
2
+ import type { RegisteredValidator, RigPluginWithRuntime, RuntimeBlockerClassifier, RuntimeCliCommand, RuntimeFeatureCapability, RuntimePanelProducer, TaskSourceFactoryEntry } from "./plugin-runtime";
3
+ export interface PluginHost {
4
+ getValidator(id: string): ValidatorRegistration | undefined;
5
+ getHook(id: string): HookRegistration | undefined;
6
+ getSkill(id: string): SkillRegistration | undefined;
7
+ getRepoSource(id: string): RepoSourceRegistration | undefined;
8
+ getAgentRole(id: string): AgentRoleRegistration | undefined;
9
+ getTaskFieldExtension(id: string): TaskFieldExtension | undefined;
10
+ getTaskSource(id: string): TaskSourceRegistration | undefined;
11
+ getCliCommand(id: string): CliCommandRegistration | undefined;
12
+ getCapability(id: string): ProductCapabilityRegistration | undefined;
13
+ getPanel(id: string): PanelRegistration | undefined;
14
+ getBlockerClassifier(id: string): BlockerClassifierRegistration | undefined;
15
+ listValidators(): readonly ValidatorRegistration[];
16
+ listHooks(): readonly HookRegistration[];
17
+ listSkills(): readonly SkillRegistration[];
18
+ listRepoSources(): readonly RepoSourceRegistration[];
19
+ listAgentRoles(): readonly AgentRoleRegistration[];
20
+ listTaskFieldExtensions(): readonly TaskFieldExtension[];
21
+ listTaskSources(): readonly TaskSourceRegistration[];
22
+ listCliCommands(): readonly CliCommandRegistration[];
23
+ listCapabilities(): readonly ProductCapabilityRegistration[];
24
+ listPanels(): readonly PanelRegistration[];
25
+ listBlockerClassifiers(): readonly BlockerClassifierRegistration[];
26
+ listStages(): readonly Stage[];
27
+ /**
28
+ * Stage mutations contributed by all loaded plugins, flattened in load order.
29
+ * The kernel resolves these (+ the default-lifecycle stages) into the run
30
+ * pipeline — this is the wire that makes "every plugin mutates the lifecycle"
31
+ * real (drift pre-merge gate, closure observer, reclassify observer).
32
+ */
33
+ listStageMutations(): readonly StageMutation[];
34
+ /**
35
+ * Executable stage implementations contributed by plugins (`runtime.stages`),
36
+ * merged by stage id. The kernel's stage runner uses these to execute
37
+ * plugin-contributed stages inserted via stageMutations.
38
+ */
39
+ listStageExecutors(): Readonly<Record<string, StageRun>>;
40
+ /**
41
+ * Executable validators contributed by plugins. Only validators whose
42
+ * metadata appears in `listValidators()` and whose plugin shipped a runtime
43
+ * implementation via `definePlugin(meta, { validators: [...] })` appear here.
44
+ * Validators registered metadata-only (no run() implementation) are absent.
45
+ */
46
+ listExecutableValidators(): readonly RegisteredValidator[];
47
+ /**
48
+ * Executable task source factories contributed by plugins. Used by the
49
+ * runtime's `buildTaskSourceRegistry` to instantiate non-standard task
50
+ * source kinds (Linear, Jira, custom) declared by plugins.
51
+ */
52
+ listExecutableTaskSources(): readonly TaskSourceFactoryEntry[];
53
+ listExecutableCapabilities(): readonly RuntimeFeatureCapability[];
54
+ listExecutablePanels(): readonly RuntimePanelProducer[];
55
+ listExecutableBlockerClassifiers(): readonly RuntimeBlockerClassifier[];
56
+ listExecutableCliCommands(): readonly RuntimeCliCommand[];
57
+ resolveExecutableCliCommand(requested: string): RuntimeCliCommand | undefined;
58
+ /**
59
+ * Look up an executable task source factory by its `kind` (the value in
60
+ * `config.taskSource.kind`). Returns undefined when no plugin provides
61
+ * a factory for that kind.
62
+ */
63
+ resolveTaskSourceFactoryByKind(kind: string): TaskSourceFactoryEntry | undefined;
64
+ }
65
+ export declare function createPluginHost(plugins: readonly RigPluginWithRuntime[]): PluginHost;
@@ -58,6 +58,59 @@ function assertRuntimeMatchesMetadata(plugin) {
58
58
  }
59
59
  }
60
60
  }
61
+ const declaredHooks = new Map((plugin.contributes?.hooks ?? []).map((hook) => [hook.id, hook]));
62
+ const runtimeHooks = plugin.__runtime?.hooks;
63
+ for (const hookId of Object.keys(runtimeHooks ?? {})) {
64
+ const metadata = declaredHooks.get(hookId);
65
+ if (!metadata) {
66
+ throw new Error(`plugin "${plugin.name}" typed hook "${hookId}" has no matching metadata entry in contributes.hooks`);
67
+ }
68
+ if (metadata.command) {
69
+ throw new Error(`plugin "${plugin.name}" hook "${hookId}" has both a typed implementation and a command string \u2014 pick one`);
70
+ }
71
+ }
72
+ if (runtimeHooks) {
73
+ for (const hook of declaredHooks.values()) {
74
+ if (!runtimeHooks[hook.id] && !hook.command) {
75
+ throw new Error(`plugin "${plugin.name}" hook metadata "${hook.id}" has no implementation (typed function or command)`);
76
+ }
77
+ }
78
+ }
79
+ const declaredCapabilities = new Map((plugin.contributes?.capabilities ?? []).map((capability) => [capability.id, capability]));
80
+ const runtimeCapabilities = new Map((plugin.__runtime?.featureCapabilities ?? []).map((capability) => [capability.id, capability]));
81
+ for (const capability of runtimeCapabilities.values()) {
82
+ if (!declaredCapabilities.has(capability.id)) {
83
+ throw new Error(`plugin "${plugin.name}" executable capability "${capability.id}" has no matching metadata entry in contributes.capabilities`);
84
+ }
85
+ }
86
+ const declaredPanels = new Map((plugin.contributes?.panels ?? []).map((panel) => [panel.id, panel]));
87
+ const runtimePanels = new Map((plugin.__runtime?.panels ?? []).map((panel) => [panel.id, panel]));
88
+ for (const panel of runtimePanels.values()) {
89
+ const metadata = declaredPanels.get(panel.id);
90
+ if (!metadata) {
91
+ throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" has no matching metadata entry in contributes.panels`);
92
+ }
93
+ if (metadata.slot !== panel.slot) {
94
+ throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" slot "${panel.slot}" does not match metadata slot "${metadata.slot}"`);
95
+ }
96
+ if (metadata.capabilityId !== panel.capabilityId) {
97
+ throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" capabilityId "${panel.capabilityId ?? "(none)"}" does not match metadata capabilityId "${metadata.capabilityId ?? "(none)"}"`);
98
+ }
99
+ }
100
+ const declaredBlockerClassifiers = new Map((plugin.contributes?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
101
+ const runtimeBlockerClassifiers = new Map((plugin.__runtime?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
102
+ for (const classifier of runtimeBlockerClassifiers.values()) {
103
+ if (!declaredBlockerClassifiers.has(classifier.id)) {
104
+ throw new Error(`plugin "${plugin.name}" executable blocker classifier "${classifier.id}" has no matching metadata entry in contributes.blockerClassifiers`);
105
+ }
106
+ }
107
+ const declaredCliCommands = new Map((plugin.contributes?.cliCommands ?? []).map((command) => [command.id, command]));
108
+ const runtimeCliCommands = new Map((plugin.__runtime?.cliCommands ?? []).map((command) => [command.id, command]));
109
+ for (const command of runtimeCliCommands.values()) {
110
+ if (!declaredCliCommands.has(command.id)) {
111
+ throw new Error(`plugin "${plugin.name}" executable cli command "${command.id}" has no matching metadata entry in contributes.cliCommands`);
112
+ }
113
+ }
61
114
  }
62
115
  function createPluginHost(plugins) {
63
116
  assertUniquePluginNames(plugins);
@@ -72,8 +125,18 @@ function createPluginHost(plugins) {
72
125
  const taskFieldExtensions = [];
73
126
  const taskSources = [];
74
127
  const cliCommands = [];
128
+ const capabilities = [];
129
+ const panels = [];
130
+ const blockerClassifiers = [];
131
+ const stages = [];
132
+ const stageMutations = [];
133
+ const stageExecutors = {};
75
134
  const executableValidators = [];
76
135
  const executableTaskSources = [];
136
+ const executableCapabilities = [];
137
+ const executablePanels = [];
138
+ const executableBlockerClassifiers = [];
139
+ const executableCliCommands = [];
77
140
  for (const plugin of plugins) {
78
141
  const c = plugin.contributes;
79
142
  if (!c)
@@ -85,6 +148,18 @@ function createPluginHost(plugins) {
85
148
  if (plugin.__runtime?.taskSources) {
86
149
  executableTaskSources.push(...plugin.__runtime.taskSources.map((item) => ({ item, pluginName })));
87
150
  }
151
+ if (plugin.__runtime?.featureCapabilities) {
152
+ executableCapabilities.push(...plugin.__runtime.featureCapabilities.map((item) => ({ item, pluginName })));
153
+ }
154
+ if (plugin.__runtime?.panels) {
155
+ executablePanels.push(...plugin.__runtime.panels.map((item) => ({ item, pluginName })));
156
+ }
157
+ if (plugin.__runtime?.blockerClassifiers) {
158
+ executableBlockerClassifiers.push(...plugin.__runtime.blockerClassifiers.map((item) => ({ item, pluginName })));
159
+ }
160
+ if (plugin.__runtime?.cliCommands) {
161
+ executableCliCommands.push(...plugin.__runtime.cliCommands.map((item) => ({ item, pluginName })));
162
+ }
88
163
  if (c.validators)
89
164
  validators.push(...c.validators.map((item) => ({ item, pluginName })));
90
165
  if (c.hooks)
@@ -101,9 +176,25 @@ function createPluginHost(plugins) {
101
176
  taskSources.push(...c.taskSources.map((item) => ({ item, pluginName })));
102
177
  if (c.cliCommands)
103
178
  cliCommands.push(...c.cliCommands.map((item) => ({ item, pluginName })));
179
+ if (c.capabilities)
180
+ capabilities.push(...c.capabilities.map((item) => ({ item, pluginName })));
181
+ if (c.panels)
182
+ panels.push(...c.panels.map((item) => ({ item, pluginName })));
183
+ if (c.blockerClassifiers)
184
+ blockerClassifiers.push(...c.blockerClassifiers.map((item) => ({ item, pluginName })));
185
+ if (c.stages)
186
+ stages.push(...c.stages.map((item) => ({ item, pluginName })));
187
+ if (c.stageMutations)
188
+ stageMutations.push(...c.stageMutations.map((item) => ({ item, pluginName })));
189
+ if (plugin.__runtime?.stages)
190
+ Object.assign(stageExecutors, plugin.__runtime.stages);
104
191
  }
105
192
  indexById(executableValidators, "executableValidator");
106
193
  indexById(executableTaskSources, "executableTaskSource");
194
+ indexById(executableCapabilities, "executableCapability");
195
+ indexById(executablePanels, "executablePanel");
196
+ indexById(executableBlockerClassifiers, "executableBlockerClassifier");
197
+ indexById(executableCliCommands, "executableCliCommand");
107
198
  const taskSourceFactoryByKind = new Map;
108
199
  const taskSourceKindRegistrant = new Map;
109
200
  for (const { item, pluginName } of executableTaskSources) {
@@ -121,6 +212,9 @@ function createPluginHost(plugins) {
121
212
  const taskFieldExtMap = indexById(taskFieldExtensions, "taskFieldExtension");
122
213
  const taskSourceMap = indexById(taskSources, "taskSource");
123
214
  const cliCommandMap = indexById(cliCommands, "cliCommand");
215
+ const capabilityMap = indexById(capabilities, "capability");
216
+ const panelMap = indexById(panels, "panel");
217
+ const blockerClassifierMap = indexById(blockerClassifiers, "blockerClassifier");
124
218
  const allValidators = validators.map((c) => c.item);
125
219
  const allHooks = hooks.map((c) => c.item);
126
220
  const allSkills = skills.map((c) => c.item);
@@ -129,8 +223,34 @@ function createPluginHost(plugins) {
129
223
  const allTaskFieldExtensions = taskFieldExtensions.map((c) => c.item);
130
224
  const allTaskSources = taskSources.map((c) => c.item);
131
225
  const allCliCommands = cliCommands.map((c) => c.item);
226
+ const allStageMutations = stageMutations.map((c) => c.item);
227
+ const allStages = stages.map((c) => c.item);
228
+ const allCapabilities = capabilities.map((c) => c.item);
229
+ const allPanels = panels.map((c) => c.item);
230
+ const allBlockerClassifiers = blockerClassifiers.map((c) => c.item);
132
231
  const allExecutableValidators = executableValidators.map((c) => c.item);
133
232
  const allExecutableTaskSources = executableTaskSources.map((c) => c.item);
233
+ const allExecutableCapabilities = executableCapabilities.map((c) => c.item);
234
+ const allExecutablePanels = executablePanels.map((c) => c.item);
235
+ const allExecutableBlockerClassifiers = executableBlockerClassifiers.map((c) => c.item);
236
+ const allExecutableCliCommands = executableCliCommands.map((c) => c.item);
237
+ const executableCliCommandByName = new Map;
238
+ const registerExecutableCliCommandSelector = (selector, contribution) => {
239
+ const existing = executableCliCommandByName.get(selector);
240
+ if (existing && existing.item.id !== contribution.item.id) {
241
+ throw new Error(`duplicate executable CLI selector "${selector}" registered by command "${existing.item.id}" from plugin "${existing.pluginName}" and command "${contribution.item.id}" from plugin "${contribution.pluginName}"`);
242
+ }
243
+ executableCliCommandByName.set(selector, contribution);
244
+ };
245
+ for (const contribution of executableCliCommands) {
246
+ const command = contribution.item;
247
+ const family = command.family ?? command.id;
248
+ registerExecutableCliCommandSelector(command.id, contribution);
249
+ registerExecutableCliCommandSelector(family, contribution);
250
+ for (const alias of command.aliases ?? []) {
251
+ registerExecutableCliCommandSelector(alias, contribution);
252
+ }
253
+ }
134
254
  return {
135
255
  getValidator: (id) => validatorMap.get(id),
136
256
  getHook: (id) => hookMap.get(id),
@@ -140,6 +260,9 @@ function createPluginHost(plugins) {
140
260
  getTaskFieldExtension: (id) => taskFieldExtMap.get(id),
141
261
  getTaskSource: (id) => taskSourceMap.get(id),
142
262
  getCliCommand: (id) => cliCommandMap.get(id),
263
+ getCapability: (id) => capabilityMap.get(id),
264
+ getPanel: (id) => panelMap.get(id),
265
+ getBlockerClassifier: (id) => blockerClassifierMap.get(id),
143
266
  listValidators: () => allValidators,
144
267
  listHooks: () => allHooks,
145
268
  listSkills: () => allSkills,
@@ -148,8 +271,19 @@ function createPluginHost(plugins) {
148
271
  listTaskFieldExtensions: () => allTaskFieldExtensions,
149
272
  listTaskSources: () => allTaskSources,
150
273
  listCliCommands: () => allCliCommands,
274
+ listCapabilities: () => allCapabilities,
275
+ listPanels: () => allPanels,
276
+ listBlockerClassifiers: () => allBlockerClassifiers,
277
+ listStages: () => allStages,
278
+ listStageMutations: () => allStageMutations,
279
+ listStageExecutors: () => stageExecutors,
151
280
  listExecutableValidators: () => allExecutableValidators,
152
281
  listExecutableTaskSources: () => allExecutableTaskSources,
282
+ listExecutableCapabilities: () => allExecutableCapabilities,
283
+ listExecutablePanels: () => allExecutablePanels,
284
+ listExecutableBlockerClassifiers: () => allExecutableBlockerClassifiers,
285
+ listExecutableCliCommands: () => allExecutableCliCommands,
286
+ resolveExecutableCliCommand: (requested) => executableCliCommandByName.get(requested)?.item,
153
287
  resolveTaskSourceFactoryByKind: (kind) => taskSourceFactoryByKind.get(kind)
154
288
  };
155
289
  }