@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.
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.js +136 -0
- package/dist/src/define-config.d.ts +18 -0
- package/dist/src/define-config.js +20 -2
- package/dist/src/define-plugin.d.ts +9 -0
- package/dist/src/define-plugin.js +48 -0
- package/dist/src/dependencyGraph.d.ts +43 -0
- package/dist/src/dependencyGraph.js +703 -0
- package/dist/src/engineReadModelReducer.d.ts +12 -0
- package/dist/src/engineReadModelReducer.js +17 -13
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.js +1319 -43
- package/dist/src/load-config.d.ts +2 -0
- package/dist/src/load-config.js +308 -4
- package/dist/src/plugin-host.d.ts +65 -0
- package/dist/src/plugin-host.js +134 -0
- package/dist/src/plugin-runtime.d.ts +105 -0
- package/dist/src/rig-init-builder.d.ts +30 -0
- package/dist/src/rig-init-builder.js +15 -11
- package/dist/src/rigSelectors.d.ts +220 -0
- package/dist/src/rigSelectors.js +125 -4
- package/dist/src/rollups.d.ts +6 -0
- package/dist/src/rollups.js +377 -0
- package/dist/src/stageResolve.d.ts +77 -0
- package/dist/src/stageResolve.js +361 -0
- package/dist/src/taskGraph.d.ts +64 -0
- package/dist/src/taskGraph.js +321 -8
- package/dist/src/taskGraphCodes.d.ts +3 -0
- package/dist/src/taskGraphLayout.d.ts +61 -0
- package/dist/src/taskGraphLayout.js +27 -4
- package/dist/src/taskScore.d.ts +17 -0
- package/dist/src/taskScore.js +49 -0
- package/package.json +28 -10
package/dist/src/load-config.js
CHANGED
|
@@ -1,17 +1,321 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/core/src/load-config.ts
|
|
3
|
-
import { existsSync, readFileSync } from "fs";
|
|
4
|
-
import {
|
|
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
|
|
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 =
|
|
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;
|
package/dist/src/plugin-host.js
CHANGED
|
@@ -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
|
}
|