@h-rig/core 0.0.6-alpha.136 → 0.0.6-alpha.138
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/define-plugin.js +31 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +159 -45
- package/dist/src/plugin-host.d.ts +14 -2
- package/dist/src/plugin-host.js +107 -0
- package/dist/src/plugin-runtime.d.ts +34 -1
- package/dist/src/rig-init-builder.js +12 -9
- package/dist/src/rigSelectors.d.ts +1 -1
- package/dist/src/stageResolve.d.ts +0 -20
- package/dist/src/stageResolve.js +9 -37
- package/package.json +2 -2
|
@@ -60,6 +60,37 @@ function definePlugin(meta, runtime) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
const declaredCapabilities = new Map((validated.contributes?.capabilities ?? []).map((capability) => [capability.id, capability]));
|
|
64
|
+
for (const capability of runtime.featureCapabilities ?? []) {
|
|
65
|
+
if (!declaredCapabilities.has(capability.id)) {
|
|
66
|
+
throw new Error(`definePlugin(${validated.name}): executable capability "${capability.id}" has no matching metadata entry in contributes.capabilities`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const declaredPanels = new Map((validated.contributes?.panels ?? []).map((panel) => [panel.id, panel]));
|
|
70
|
+
for (const panel of runtime.panels ?? []) {
|
|
71
|
+
const metadata = declaredPanels.get(panel.id);
|
|
72
|
+
if (!metadata) {
|
|
73
|
+
throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" has no matching metadata entry in contributes.panels`);
|
|
74
|
+
}
|
|
75
|
+
if (metadata.slot !== panel.slot) {
|
|
76
|
+
throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" slot "${panel.slot}" does not match metadata slot "${metadata.slot}"`);
|
|
77
|
+
}
|
|
78
|
+
if (metadata.capabilityId !== panel.capabilityId) {
|
|
79
|
+
throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" capabilityId "${panel.capabilityId ?? "(none)"}" does not match metadata capabilityId "${metadata.capabilityId ?? "(none)"}"`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const declaredBlockerClassifiers = new Map((validated.contributes?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
|
|
83
|
+
for (const classifier of runtime.blockerClassifiers ?? []) {
|
|
84
|
+
if (!declaredBlockerClassifiers.has(classifier.id)) {
|
|
85
|
+
throw new Error(`definePlugin(${validated.name}): executable blocker classifier "${classifier.id}" has no matching metadata entry in contributes.blockerClassifiers`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const declaredCliCommands = new Map((validated.contributes?.cliCommands ?? []).map((command) => [command.id, command]));
|
|
89
|
+
for (const command of runtime.cliCommands ?? []) {
|
|
90
|
+
if (!declaredCliCommands.has(command.id)) {
|
|
91
|
+
throw new Error(`definePlugin(${validated.name}): executable cli command "${command.id}" has no matching metadata entry in contributes.cliCommands`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
63
94
|
return { ...validated, __runtime: runtime };
|
|
64
95
|
}
|
|
65
96
|
export {
|
package/dist/src/index.d.ts
CHANGED
|
@@ -4,13 +4,13 @@ export { defineConfig } from "./define-config";
|
|
|
4
4
|
export { createPluginHost } from "./plugin-host";
|
|
5
5
|
export type { PluginHost } from "./plugin-host";
|
|
6
6
|
export { buildRigInitConfigSource, type RigInitConfigInput, } from "./rig-init-builder";
|
|
7
|
-
export type { RegisteredValidator, RigPluginRuntime, RigPluginWithRuntime, TaskSourceFactoryContext, TaskSourceFactoryEntry, ValidatorContext, ValidatorResult, } from "./plugin-runtime";
|
|
7
|
+
export type { RegisteredValidator, RigPluginRuntime, RigPluginWithRuntime, TaskSourceFactoryContext, RuntimeCliCommand, RuntimeCliContext, TaskSourceFactoryEntry, ValidatorContext, ValidatorResult, } from "./plugin-runtime";
|
|
8
8
|
export { applyEngineEvent, applyEngineEvents, applyEngineEvent as applyRigEvent, pruneQueueEntries, type ApplyStatus, type EngineEventApplyResult, type EngineEventApplyResult as RigEventApplyResult, } from "./engineReadModelReducer";
|
|
9
9
|
export { pickDefaultWorkspaceId, projectRunStatusForTaskGrouping, projectTaskStatusForGrouping, projectTaskStatusWithSessions, normalizeTaskAssigneeFilter, readTaskAssigneeLogins, selectAdhocRuns, selectAdhocRunsForWorkspace, selectApprovalsForRun, selectApprovalsForWorkspace, selectGraphsForWorkspace, selectPendingApprovals, selectPrimaryWorkspace, selectQueueForWorkspace, selectRun, selectRunsByTask, selectRunsForTask, selectRunsForWorkspace, selectTask, selectTasksByStatus, selectTasksByWorkspace, selectTasksForWorkspace, selectTasksGroupedByStatus, selectTasksAssignedTo, selectTasksAssignedToMe, selectUserInputsForRun, selectUserInputsForWorkspace, selectWorkspace, selectWorkspaces, type RigTaskSessionProjection, type RigTaskStatusGroup, type RigTaskStatusGroupingInput, } from "./rigSelectors";
|
|
10
10
|
export { buildTaskReferenceIndex, computeTaskBlockingDepths, computeTaskDependencyBadges, disjointScope, isTaskTerminalStatus, rankReadyTasks, readTaskBlockingDependencyRefs, readTaskDependencyRefs, readTaskMetadataStringList, readTaskScope, readTaskSourceIssueId, resolveTaskReference, selectNextReadyTaskByPriority, selectRankedReadyTasks, type RankedReadyTask, type RankedReadyTaskOptions, type ReadyTaskSelectionMode, type TaskDependencyBadge, type TaskDependencyBadgeKind, type TaskDependencyBadgeSummary, type TaskDependencyProjection, type TaskScopeInput, } from "./taskGraph";
|
|
11
11
|
export { extractTaskCode, extractTaskGroupKey, stripTaskCode, } from "./taskGraphCodes";
|
|
12
12
|
export { buildTaskGraphLayout, type TaskGraphEdge, type TaskGraphLane, type TaskGraphLayout, type TaskGraphNode, type TaskGraphStage, } from "./taskGraphLayout";
|
|
13
|
-
export { PipelineUnresolvableError, resolveStagePipeline, type DroppedStageAnchor, type
|
|
13
|
+
export { PipelineUnresolvableError, resolveStagePipeline, type DroppedStageAnchor, type ResolvableStage, type ResolvedStagePipeline, type ResolveStagePipelineInput, type StageKind, type StageMutation, type StageMutationOp, type StageResolutionRecordEntry, type StageWrapper, } from "./stageResolve";
|
|
14
14
|
export { rankTasks, scoreTask, type RankedTask, type TaskCriticality, type TaskScoreInput, } from "./taskScore";
|
|
15
15
|
export { buildDependencyGraphModel, type BuildDependencyGraphModelOptions, type DependencyEdge, type DependencyEdgeType, type DependencyGraphModel, type DependencyNode, } from "./dependencyGraph";
|
|
16
16
|
export { rollupByAssignee, rollupByEpic, type AssigneeRollup, type BlockerClass, type EpicRollup, } from "./rollups";
|
package/dist/src/index.js
CHANGED
|
@@ -60,6 +60,37 @@ function definePlugin(meta, runtime) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
const declaredCapabilities = new Map((validated.contributes?.capabilities ?? []).map((capability) => [capability.id, capability]));
|
|
64
|
+
for (const capability of runtime.featureCapabilities ?? []) {
|
|
65
|
+
if (!declaredCapabilities.has(capability.id)) {
|
|
66
|
+
throw new Error(`definePlugin(${validated.name}): executable capability "${capability.id}" has no matching metadata entry in contributes.capabilities`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const declaredPanels = new Map((validated.contributes?.panels ?? []).map((panel) => [panel.id, panel]));
|
|
70
|
+
for (const panel of runtime.panels ?? []) {
|
|
71
|
+
const metadata = declaredPanels.get(panel.id);
|
|
72
|
+
if (!metadata) {
|
|
73
|
+
throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" has no matching metadata entry in contributes.panels`);
|
|
74
|
+
}
|
|
75
|
+
if (metadata.slot !== panel.slot) {
|
|
76
|
+
throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" slot "${panel.slot}" does not match metadata slot "${metadata.slot}"`);
|
|
77
|
+
}
|
|
78
|
+
if (metadata.capabilityId !== panel.capabilityId) {
|
|
79
|
+
throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" capabilityId "${panel.capabilityId ?? "(none)"}" does not match metadata capabilityId "${metadata.capabilityId ?? "(none)"}"`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const declaredBlockerClassifiers = new Map((validated.contributes?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
|
|
83
|
+
for (const classifier of runtime.blockerClassifiers ?? []) {
|
|
84
|
+
if (!declaredBlockerClassifiers.has(classifier.id)) {
|
|
85
|
+
throw new Error(`definePlugin(${validated.name}): executable blocker classifier "${classifier.id}" has no matching metadata entry in contributes.blockerClassifiers`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const declaredCliCommands = new Map((validated.contributes?.cliCommands ?? []).map((command) => [command.id, command]));
|
|
89
|
+
for (const command of runtime.cliCommands ?? []) {
|
|
90
|
+
if (!declaredCliCommands.has(command.id)) {
|
|
91
|
+
throw new Error(`definePlugin(${validated.name}): executable cli command "${command.id}" has no matching metadata entry in contributes.cliCommands`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
63
94
|
return { ...validated, __runtime: runtime };
|
|
64
95
|
}
|
|
65
96
|
// packages/core/src/define-config.ts
|
|
@@ -176,6 +207,41 @@ function assertRuntimeMatchesMetadata(plugin) {
|
|
|
176
207
|
}
|
|
177
208
|
}
|
|
178
209
|
}
|
|
210
|
+
const declaredCapabilities = new Map((plugin.contributes?.capabilities ?? []).map((capability) => [capability.id, capability]));
|
|
211
|
+
const runtimeCapabilities = new Map((plugin.__runtime?.featureCapabilities ?? []).map((capability) => [capability.id, capability]));
|
|
212
|
+
for (const capability of runtimeCapabilities.values()) {
|
|
213
|
+
if (!declaredCapabilities.has(capability.id)) {
|
|
214
|
+
throw new Error(`plugin "${plugin.name}" executable capability "${capability.id}" has no matching metadata entry in contributes.capabilities`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const declaredPanels = new Map((plugin.contributes?.panels ?? []).map((panel) => [panel.id, panel]));
|
|
218
|
+
const runtimePanels = new Map((plugin.__runtime?.panels ?? []).map((panel) => [panel.id, panel]));
|
|
219
|
+
for (const panel of runtimePanels.values()) {
|
|
220
|
+
const metadata = declaredPanels.get(panel.id);
|
|
221
|
+
if (!metadata) {
|
|
222
|
+
throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" has no matching metadata entry in contributes.panels`);
|
|
223
|
+
}
|
|
224
|
+
if (metadata.slot !== panel.slot) {
|
|
225
|
+
throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" slot "${panel.slot}" does not match metadata slot "${metadata.slot}"`);
|
|
226
|
+
}
|
|
227
|
+
if (metadata.capabilityId !== panel.capabilityId) {
|
|
228
|
+
throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" capabilityId "${panel.capabilityId ?? "(none)"}" does not match metadata capabilityId "${metadata.capabilityId ?? "(none)"}"`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const declaredBlockerClassifiers = new Map((plugin.contributes?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
|
|
232
|
+
const runtimeBlockerClassifiers = new Map((plugin.__runtime?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
|
|
233
|
+
for (const classifier of runtimeBlockerClassifiers.values()) {
|
|
234
|
+
if (!declaredBlockerClassifiers.has(classifier.id)) {
|
|
235
|
+
throw new Error(`plugin "${plugin.name}" executable blocker classifier "${classifier.id}" has no matching metadata entry in contributes.blockerClassifiers`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const declaredCliCommands = new Map((plugin.contributes?.cliCommands ?? []).map((command) => [command.id, command]));
|
|
239
|
+
const runtimeCliCommands = new Map((plugin.__runtime?.cliCommands ?? []).map((command) => [command.id, command]));
|
|
240
|
+
for (const command of runtimeCliCommands.values()) {
|
|
241
|
+
if (!declaredCliCommands.has(command.id)) {
|
|
242
|
+
throw new Error(`plugin "${plugin.name}" executable cli command "${command.id}" has no matching metadata entry in contributes.cliCommands`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
179
245
|
}
|
|
180
246
|
function createPluginHost(plugins) {
|
|
181
247
|
assertUniquePluginNames(plugins);
|
|
@@ -190,10 +256,18 @@ function createPluginHost(plugins) {
|
|
|
190
256
|
const taskFieldExtensions = [];
|
|
191
257
|
const taskSources = [];
|
|
192
258
|
const cliCommands = [];
|
|
259
|
+
const capabilities = [];
|
|
260
|
+
const panels = [];
|
|
261
|
+
const blockerClassifiers = [];
|
|
262
|
+
const stages = [];
|
|
193
263
|
const stageMutations = [];
|
|
194
264
|
const stageExecutors = {};
|
|
195
265
|
const executableValidators = [];
|
|
196
266
|
const executableTaskSources = [];
|
|
267
|
+
const executableCapabilities = [];
|
|
268
|
+
const executablePanels = [];
|
|
269
|
+
const executableBlockerClassifiers = [];
|
|
270
|
+
const executableCliCommands = [];
|
|
197
271
|
for (const plugin of plugins) {
|
|
198
272
|
const c = plugin.contributes;
|
|
199
273
|
if (!c)
|
|
@@ -205,6 +279,18 @@ function createPluginHost(plugins) {
|
|
|
205
279
|
if (plugin.__runtime?.taskSources) {
|
|
206
280
|
executableTaskSources.push(...plugin.__runtime.taskSources.map((item) => ({ item, pluginName })));
|
|
207
281
|
}
|
|
282
|
+
if (plugin.__runtime?.featureCapabilities) {
|
|
283
|
+
executableCapabilities.push(...plugin.__runtime.featureCapabilities.map((item) => ({ item, pluginName })));
|
|
284
|
+
}
|
|
285
|
+
if (plugin.__runtime?.panels) {
|
|
286
|
+
executablePanels.push(...plugin.__runtime.panels.map((item) => ({ item, pluginName })));
|
|
287
|
+
}
|
|
288
|
+
if (plugin.__runtime?.blockerClassifiers) {
|
|
289
|
+
executableBlockerClassifiers.push(...plugin.__runtime.blockerClassifiers.map((item) => ({ item, pluginName })));
|
|
290
|
+
}
|
|
291
|
+
if (plugin.__runtime?.cliCommands) {
|
|
292
|
+
executableCliCommands.push(...plugin.__runtime.cliCommands.map((item) => ({ item, pluginName })));
|
|
293
|
+
}
|
|
208
294
|
if (c.validators)
|
|
209
295
|
validators.push(...c.validators.map((item) => ({ item, pluginName })));
|
|
210
296
|
if (c.hooks)
|
|
@@ -221,6 +307,14 @@ function createPluginHost(plugins) {
|
|
|
221
307
|
taskSources.push(...c.taskSources.map((item) => ({ item, pluginName })));
|
|
222
308
|
if (c.cliCommands)
|
|
223
309
|
cliCommands.push(...c.cliCommands.map((item) => ({ item, pluginName })));
|
|
310
|
+
if (c.capabilities)
|
|
311
|
+
capabilities.push(...c.capabilities.map((item) => ({ item, pluginName })));
|
|
312
|
+
if (c.panels)
|
|
313
|
+
panels.push(...c.panels.map((item) => ({ item, pluginName })));
|
|
314
|
+
if (c.blockerClassifiers)
|
|
315
|
+
blockerClassifiers.push(...c.blockerClassifiers.map((item) => ({ item, pluginName })));
|
|
316
|
+
if (c.stages)
|
|
317
|
+
stages.push(...c.stages.map((item) => ({ item, pluginName })));
|
|
224
318
|
if (c.stageMutations)
|
|
225
319
|
stageMutations.push(...c.stageMutations.map((item) => ({ item, pluginName })));
|
|
226
320
|
if (plugin.__runtime?.stages)
|
|
@@ -228,6 +322,10 @@ function createPluginHost(plugins) {
|
|
|
228
322
|
}
|
|
229
323
|
indexById(executableValidators, "executableValidator");
|
|
230
324
|
indexById(executableTaskSources, "executableTaskSource");
|
|
325
|
+
indexById(executableCapabilities, "executableCapability");
|
|
326
|
+
indexById(executablePanels, "executablePanel");
|
|
327
|
+
indexById(executableBlockerClassifiers, "executableBlockerClassifier");
|
|
328
|
+
indexById(executableCliCommands, "executableCliCommand");
|
|
231
329
|
const taskSourceFactoryByKind = new Map;
|
|
232
330
|
const taskSourceKindRegistrant = new Map;
|
|
233
331
|
for (const { item, pluginName } of executableTaskSources) {
|
|
@@ -245,6 +343,9 @@ function createPluginHost(plugins) {
|
|
|
245
343
|
const taskFieldExtMap = indexById(taskFieldExtensions, "taskFieldExtension");
|
|
246
344
|
const taskSourceMap = indexById(taskSources, "taskSource");
|
|
247
345
|
const cliCommandMap = indexById(cliCommands, "cliCommand");
|
|
346
|
+
const capabilityMap = indexById(capabilities, "capability");
|
|
347
|
+
const panelMap = indexById(panels, "panel");
|
|
348
|
+
const blockerClassifierMap = indexById(blockerClassifiers, "blockerClassifier");
|
|
248
349
|
const allValidators = validators.map((c) => c.item);
|
|
249
350
|
const allHooks = hooks.map((c) => c.item);
|
|
250
351
|
const allSkills = skills.map((c) => c.item);
|
|
@@ -254,8 +355,33 @@ function createPluginHost(plugins) {
|
|
|
254
355
|
const allTaskSources = taskSources.map((c) => c.item);
|
|
255
356
|
const allCliCommands = cliCommands.map((c) => c.item);
|
|
256
357
|
const allStageMutations = stageMutations.map((c) => c.item);
|
|
358
|
+
const allStages = stages.map((c) => c.item);
|
|
359
|
+
const allCapabilities = capabilities.map((c) => c.item);
|
|
360
|
+
const allPanels = panels.map((c) => c.item);
|
|
361
|
+
const allBlockerClassifiers = blockerClassifiers.map((c) => c.item);
|
|
257
362
|
const allExecutableValidators = executableValidators.map((c) => c.item);
|
|
258
363
|
const allExecutableTaskSources = executableTaskSources.map((c) => c.item);
|
|
364
|
+
const allExecutableCapabilities = executableCapabilities.map((c) => c.item);
|
|
365
|
+
const allExecutablePanels = executablePanels.map((c) => c.item);
|
|
366
|
+
const allExecutableBlockerClassifiers = executableBlockerClassifiers.map((c) => c.item);
|
|
367
|
+
const allExecutableCliCommands = executableCliCommands.map((c) => c.item);
|
|
368
|
+
const executableCliCommandByName = new Map;
|
|
369
|
+
const registerExecutableCliCommandSelector = (selector, contribution) => {
|
|
370
|
+
const existing = executableCliCommandByName.get(selector);
|
|
371
|
+
if (existing && existing.item.id !== contribution.item.id) {
|
|
372
|
+
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}"`);
|
|
373
|
+
}
|
|
374
|
+
executableCliCommandByName.set(selector, contribution);
|
|
375
|
+
};
|
|
376
|
+
for (const contribution of executableCliCommands) {
|
|
377
|
+
const command = contribution.item;
|
|
378
|
+
const family = command.family ?? command.id;
|
|
379
|
+
registerExecutableCliCommandSelector(command.id, contribution);
|
|
380
|
+
registerExecutableCliCommandSelector(family, contribution);
|
|
381
|
+
for (const alias of command.aliases ?? []) {
|
|
382
|
+
registerExecutableCliCommandSelector(alias, contribution);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
259
385
|
return {
|
|
260
386
|
getValidator: (id) => validatorMap.get(id),
|
|
261
387
|
getHook: (id) => hookMap.get(id),
|
|
@@ -265,6 +391,9 @@ function createPluginHost(plugins) {
|
|
|
265
391
|
getTaskFieldExtension: (id) => taskFieldExtMap.get(id),
|
|
266
392
|
getTaskSource: (id) => taskSourceMap.get(id),
|
|
267
393
|
getCliCommand: (id) => cliCommandMap.get(id),
|
|
394
|
+
getCapability: (id) => capabilityMap.get(id),
|
|
395
|
+
getPanel: (id) => panelMap.get(id),
|
|
396
|
+
getBlockerClassifier: (id) => blockerClassifierMap.get(id),
|
|
268
397
|
listValidators: () => allValidators,
|
|
269
398
|
listHooks: () => allHooks,
|
|
270
399
|
listSkills: () => allSkills,
|
|
@@ -273,32 +402,44 @@ function createPluginHost(plugins) {
|
|
|
273
402
|
listTaskFieldExtensions: () => allTaskFieldExtensions,
|
|
274
403
|
listTaskSources: () => allTaskSources,
|
|
275
404
|
listCliCommands: () => allCliCommands,
|
|
405
|
+
listCapabilities: () => allCapabilities,
|
|
406
|
+
listPanels: () => allPanels,
|
|
407
|
+
listBlockerClassifiers: () => allBlockerClassifiers,
|
|
408
|
+
listStages: () => allStages,
|
|
276
409
|
listStageMutations: () => allStageMutations,
|
|
277
410
|
listStageExecutors: () => stageExecutors,
|
|
278
411
|
listExecutableValidators: () => allExecutableValidators,
|
|
279
412
|
listExecutableTaskSources: () => allExecutableTaskSources,
|
|
413
|
+
listExecutableCapabilities: () => allExecutableCapabilities,
|
|
414
|
+
listExecutablePanels: () => allExecutablePanels,
|
|
415
|
+
listExecutableBlockerClassifiers: () => allExecutableBlockerClassifiers,
|
|
416
|
+
listExecutableCliCommands: () => allExecutableCliCommands,
|
|
417
|
+
resolveExecutableCliCommand: (requested) => executableCliCommandByName.get(requested)?.item,
|
|
280
418
|
resolveTaskSourceFactoryByKind: (kind) => taskSourceFactoryByKind.get(kind)
|
|
281
419
|
};
|
|
282
420
|
}
|
|
283
421
|
// packages/core/src/rig-init-builder.ts
|
|
284
422
|
function buildRigInitConfigSource(input) {
|
|
285
423
|
const lines = [`import { defineConfig } from "@rig/core";`];
|
|
286
|
-
if (input.useStandardPlugin
|
|
287
|
-
lines.push(`import
|
|
288
|
-
|
|
289
|
-
|
|
424
|
+
if (input.useStandardPlugin) {
|
|
425
|
+
lines.push(`import { standardProjectPlugins } from "@rig/standard-plugin/bundle";`);
|
|
426
|
+
if (input.taskSource.kind === "github-issues") {
|
|
427
|
+
lines.push(`import { createStateGitHubCredentialProvider } from "@rig/standard-plugin";`);
|
|
428
|
+
}
|
|
290
429
|
}
|
|
291
430
|
lines.push(``, `export default defineConfig({`);
|
|
292
431
|
const projectRepo = input.projectRepo ?? (input.taskSource.kind === "github-issues" ? `${input.taskSource.owner}/${input.taskSource.repo}` : undefined);
|
|
293
432
|
lines.push(projectRepo ? ` project: { name: ${JSON.stringify(input.projectName)}, repo: ${JSON.stringify(projectRepo)} },` : ` project: { name: ${JSON.stringify(input.projectName)} },`);
|
|
294
433
|
if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
|
|
295
|
-
lines.push(` plugins: [
|
|
296
|
-
lines.push(`
|
|
297
|
-
lines.push(`
|
|
298
|
-
lines.push(`
|
|
434
|
+
lines.push(` plugins: [...standardProjectPlugins({`);
|
|
435
|
+
lines.push(` standard: {`);
|
|
436
|
+
lines.push(` githubCredentialProvider: createStateGitHubCredentialProvider(),`);
|
|
437
|
+
lines.push(` githubWorkspaceId: ${JSON.stringify(`${input.taskSource.owner}/${input.taskSource.repo}`)},`);
|
|
438
|
+
lines.push(` githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user",`);
|
|
439
|
+
lines.push(` },`);
|
|
299
440
|
lines.push(` })],`);
|
|
300
441
|
} else {
|
|
301
|
-
lines.push(` plugins: [${input.useStandardPlugin ? "
|
|
442
|
+
lines.push(` plugins: [${input.useStandardPlugin ? "...standardProjectPlugins()" : ""}],`);
|
|
302
443
|
}
|
|
303
444
|
if (input.taskSource.kind === "github-issues") {
|
|
304
445
|
lines.push(` taskSource: {`);
|
|
@@ -3067,19 +3208,6 @@ class PipelineUnresolvableError extends Error {
|
|
|
3067
3208
|
this.contributors = contributors;
|
|
3068
3209
|
}
|
|
3069
3210
|
}
|
|
3070
|
-
|
|
3071
|
-
class ProtectedStageViolationError extends PipelineUnresolvableError {
|
|
3072
|
-
stageId;
|
|
3073
|
-
pluginId;
|
|
3074
|
-
op;
|
|
3075
|
-
constructor(stageId, pluginId, op) {
|
|
3076
|
-
super(`Protected stage ${stageId} cannot be ${op === "remove" ? "removed" : "replaced"} by ${pluginId} without an explicit grant`, [], [pluginId]);
|
|
3077
|
-
this.name = "ProtectedStageViolationError";
|
|
3078
|
-
this.stageId = stageId;
|
|
3079
|
-
this.pluginId = pluginId;
|
|
3080
|
-
this.op = op;
|
|
3081
|
-
}
|
|
3082
|
-
}
|
|
3083
3211
|
function uniqueSorted(values) {
|
|
3084
3212
|
return Array.from(new Set(values)).sort((left, right) => left.localeCompare(right));
|
|
3085
3213
|
}
|
|
@@ -3124,9 +3252,6 @@ function assertNoDuplicateMutationTargets(mutations, op) {
|
|
|
3124
3252
|
seen.set(id, contributorOf(mutation));
|
|
3125
3253
|
}
|
|
3126
3254
|
}
|
|
3127
|
-
function hasProtectedGrant(grants, stageId, pluginId, op) {
|
|
3128
|
-
return grants.some((grant) => grant.stageId === stageId && grant.pluginId === pluginId && (grant.op === undefined || grant.op === op));
|
|
3129
|
-
}
|
|
3130
3255
|
function mergeAnchors(current, incoming) {
|
|
3131
3256
|
return uniqueSorted([...current, ...incoming ?? []]);
|
|
3132
3257
|
}
|
|
@@ -3257,19 +3382,18 @@ function resolveStagePipeline(input) {
|
|
|
3257
3382
|
const mutations = [...input.mutations ?? []];
|
|
3258
3383
|
assertNoDuplicateMutationTargets(mutations, "insert");
|
|
3259
3384
|
assertNoDuplicateMutationTargets(mutations, "replace");
|
|
3260
|
-
const grants = input.protectedStageGrants ?? [];
|
|
3261
3385
|
const states = new Map;
|
|
3262
3386
|
const removedStates = new Map;
|
|
3263
3387
|
for (const [index, stage] of input.defaultStages.entries()) {
|
|
3264
3388
|
states.set(stage.id, {
|
|
3265
|
-
stage,
|
|
3389
|
+
stage: { ...stage, protected: false },
|
|
3266
3390
|
before: [...stage.before ?? []],
|
|
3267
3391
|
after: [...stage.after ?? []],
|
|
3268
3392
|
baseIndex: index,
|
|
3269
3393
|
contributedBy: "default",
|
|
3270
3394
|
wrappers: [],
|
|
3271
3395
|
droppedAnchors: [],
|
|
3272
|
-
isProtected:
|
|
3396
|
+
isProtected: false
|
|
3273
3397
|
});
|
|
3274
3398
|
}
|
|
3275
3399
|
for (const mutation of mutations.filter((entry) => entry.op === "remove").sort(stableMutationCompare)) {
|
|
@@ -3277,13 +3401,9 @@ function resolveStagePipeline(input) {
|
|
|
3277
3401
|
const contributor = contributorOf(mutation);
|
|
3278
3402
|
if (!state)
|
|
3279
3403
|
continue;
|
|
3280
|
-
if (state.isProtected && !hasProtectedGrant(grants, mutation.id, contributor, "remove")) {
|
|
3281
|
-
throw new ProtectedStageViolationError(mutation.id, contributor, "remove");
|
|
3282
|
-
}
|
|
3283
3404
|
const removedState = {
|
|
3284
3405
|
...state,
|
|
3285
|
-
removedBy: contributor
|
|
3286
|
-
...state.isProtected ? { grantUsedBy: contributor } : {}
|
|
3406
|
+
removedBy: contributor
|
|
3287
3407
|
};
|
|
3288
3408
|
removedStates.set(mutation.id, removedState);
|
|
3289
3409
|
states.delete(mutation.id);
|
|
@@ -3293,10 +3413,7 @@ function resolveStagePipeline(input) {
|
|
|
3293
3413
|
const contributor = contributorOf(mutation);
|
|
3294
3414
|
if (!state)
|
|
3295
3415
|
continue;
|
|
3296
|
-
|
|
3297
|
-
throw new ProtectedStageViolationError(mutation.id, contributor, "replace");
|
|
3298
|
-
}
|
|
3299
|
-
const replacement = { ...mutation.stage, id: mutation.id };
|
|
3416
|
+
const replacement = { ...mutation.stage, id: mutation.id, protected: false };
|
|
3300
3417
|
states.set(mutation.id, {
|
|
3301
3418
|
...state,
|
|
3302
3419
|
stage: replacement,
|
|
@@ -3304,8 +3421,7 @@ function resolveStagePipeline(input) {
|
|
|
3304
3421
|
after: mutation.stage.after ? [...mutation.stage.after] : state.after,
|
|
3305
3422
|
replacedBy: contributor,
|
|
3306
3423
|
contributedBy: state.contributedBy,
|
|
3307
|
-
isProtected:
|
|
3308
|
-
...state.isProtected ? { grantUsedBy: contributor } : {}
|
|
3424
|
+
isProtected: false
|
|
3309
3425
|
});
|
|
3310
3426
|
}
|
|
3311
3427
|
for (const mutation of mutations.filter((entry) => entry.op === "insert").sort(stableMutationCompare)) {
|
|
@@ -3314,14 +3430,14 @@ function resolveStagePipeline(input) {
|
|
|
3314
3430
|
throw new PipelineUnresolvableError(`Inserted stage ${mutation.stage.id} conflicts with an existing stage`, [], [contributor]);
|
|
3315
3431
|
}
|
|
3316
3432
|
states.set(mutation.stage.id, {
|
|
3317
|
-
stage: mutation.stage,
|
|
3433
|
+
stage: { ...mutation.stage, protected: false },
|
|
3318
3434
|
before: [...mutation.stage.before ?? []],
|
|
3319
3435
|
after: [...mutation.stage.after ?? []],
|
|
3320
3436
|
baseIndex: null,
|
|
3321
3437
|
contributedBy: contributor,
|
|
3322
3438
|
wrappers: [],
|
|
3323
3439
|
droppedAnchors: [],
|
|
3324
|
-
isProtected:
|
|
3440
|
+
isProtected: false
|
|
3325
3441
|
});
|
|
3326
3442
|
}
|
|
3327
3443
|
for (const mutation of mutations.filter((entry) => entry.op === "reorder").sort(stableMutationCompare)) {
|
|
@@ -3420,8 +3536,7 @@ function resolveStagePipeline(input) {
|
|
|
3420
3536
|
...state.replacedBy ? { replacedBy: state.replacedBy } : {},
|
|
3421
3537
|
...wrappedBy.length > 0 ? { wrappedBy } : {},
|
|
3422
3538
|
...state.droppedAnchors.length > 0 ? { droppedAnchors: state.droppedAnchors.toSorted((left, right) => left.anchor.localeCompare(right.anchor)) } : {},
|
|
3423
|
-
isProtected: state.isProtected
|
|
3424
|
-
...state.grantUsedBy ? { grantUsedBy: state.grantUsedBy } : {}
|
|
3539
|
+
isProtected: state.isProtected
|
|
3425
3540
|
});
|
|
3426
3541
|
}
|
|
3427
3542
|
record.push(...[...removedStates.entries()].toSorted((left, right) => {
|
|
@@ -3434,8 +3549,7 @@ function resolveStagePipeline(input) {
|
|
|
3434
3549
|
stageId,
|
|
3435
3550
|
contributedBy: state.contributedBy,
|
|
3436
3551
|
...state.removedBy ? { removedBy: state.removedBy } : {},
|
|
3437
|
-
isProtected: state.isProtected
|
|
3438
|
-
...state.grantUsedBy ? { grantUsedBy: state.grantUsedBy } : {}
|
|
3552
|
+
isProtected: state.isProtected
|
|
3439
3553
|
})));
|
|
3440
3554
|
return { stages, order, record, cycles: [] };
|
|
3441
3555
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { RegisteredValidator, RigPluginWithRuntime, TaskSourceFactoryEntry } from "./plugin-runtime";
|
|
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
3
|
export interface PluginHost {
|
|
4
4
|
getValidator(id: string): ValidatorRegistration | undefined;
|
|
5
5
|
getHook(id: string): HookRegistration | undefined;
|
|
@@ -9,6 +9,9 @@ export interface PluginHost {
|
|
|
9
9
|
getTaskFieldExtension(id: string): TaskFieldExtension | undefined;
|
|
10
10
|
getTaskSource(id: string): TaskSourceRegistration | undefined;
|
|
11
11
|
getCliCommand(id: string): CliCommandRegistration | undefined;
|
|
12
|
+
getCapability(id: string): ProductCapabilityRegistration | undefined;
|
|
13
|
+
getPanel(id: string): PanelRegistration | undefined;
|
|
14
|
+
getBlockerClassifier(id: string): BlockerClassifierRegistration | undefined;
|
|
12
15
|
listValidators(): readonly ValidatorRegistration[];
|
|
13
16
|
listHooks(): readonly HookRegistration[];
|
|
14
17
|
listSkills(): readonly SkillRegistration[];
|
|
@@ -17,6 +20,10 @@ export interface PluginHost {
|
|
|
17
20
|
listTaskFieldExtensions(): readonly TaskFieldExtension[];
|
|
18
21
|
listTaskSources(): readonly TaskSourceRegistration[];
|
|
19
22
|
listCliCommands(): readonly CliCommandRegistration[];
|
|
23
|
+
listCapabilities(): readonly ProductCapabilityRegistration[];
|
|
24
|
+
listPanels(): readonly PanelRegistration[];
|
|
25
|
+
listBlockerClassifiers(): readonly BlockerClassifierRegistration[];
|
|
26
|
+
listStages(): readonly Stage[];
|
|
20
27
|
/**
|
|
21
28
|
* Stage mutations contributed by all loaded plugins, flattened in load order.
|
|
22
29
|
* The kernel resolves these (+ the default-lifecycle stages) into the run
|
|
@@ -43,6 +50,11 @@ export interface PluginHost {
|
|
|
43
50
|
* source kinds (Linear, Jira, custom) declared by plugins.
|
|
44
51
|
*/
|
|
45
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;
|
|
46
58
|
/**
|
|
47
59
|
* Look up an executable task source factory by its `kind` (the value in
|
|
48
60
|
* `config.taskSource.kind`). Returns undefined when no plugin provides
|
package/dist/src/plugin-host.js
CHANGED
|
@@ -76,6 +76,41 @@ function assertRuntimeMatchesMetadata(plugin) {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
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
|
+
}
|
|
79
114
|
}
|
|
80
115
|
function createPluginHost(plugins) {
|
|
81
116
|
assertUniquePluginNames(plugins);
|
|
@@ -90,10 +125,18 @@ function createPluginHost(plugins) {
|
|
|
90
125
|
const taskFieldExtensions = [];
|
|
91
126
|
const taskSources = [];
|
|
92
127
|
const cliCommands = [];
|
|
128
|
+
const capabilities = [];
|
|
129
|
+
const panels = [];
|
|
130
|
+
const blockerClassifiers = [];
|
|
131
|
+
const stages = [];
|
|
93
132
|
const stageMutations = [];
|
|
94
133
|
const stageExecutors = {};
|
|
95
134
|
const executableValidators = [];
|
|
96
135
|
const executableTaskSources = [];
|
|
136
|
+
const executableCapabilities = [];
|
|
137
|
+
const executablePanels = [];
|
|
138
|
+
const executableBlockerClassifiers = [];
|
|
139
|
+
const executableCliCommands = [];
|
|
97
140
|
for (const plugin of plugins) {
|
|
98
141
|
const c = plugin.contributes;
|
|
99
142
|
if (!c)
|
|
@@ -105,6 +148,18 @@ function createPluginHost(plugins) {
|
|
|
105
148
|
if (plugin.__runtime?.taskSources) {
|
|
106
149
|
executableTaskSources.push(...plugin.__runtime.taskSources.map((item) => ({ item, pluginName })));
|
|
107
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
|
+
}
|
|
108
163
|
if (c.validators)
|
|
109
164
|
validators.push(...c.validators.map((item) => ({ item, pluginName })));
|
|
110
165
|
if (c.hooks)
|
|
@@ -121,6 +176,14 @@ function createPluginHost(plugins) {
|
|
|
121
176
|
taskSources.push(...c.taskSources.map((item) => ({ item, pluginName })));
|
|
122
177
|
if (c.cliCommands)
|
|
123
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 })));
|
|
124
187
|
if (c.stageMutations)
|
|
125
188
|
stageMutations.push(...c.stageMutations.map((item) => ({ item, pluginName })));
|
|
126
189
|
if (plugin.__runtime?.stages)
|
|
@@ -128,6 +191,10 @@ function createPluginHost(plugins) {
|
|
|
128
191
|
}
|
|
129
192
|
indexById(executableValidators, "executableValidator");
|
|
130
193
|
indexById(executableTaskSources, "executableTaskSource");
|
|
194
|
+
indexById(executableCapabilities, "executableCapability");
|
|
195
|
+
indexById(executablePanels, "executablePanel");
|
|
196
|
+
indexById(executableBlockerClassifiers, "executableBlockerClassifier");
|
|
197
|
+
indexById(executableCliCommands, "executableCliCommand");
|
|
131
198
|
const taskSourceFactoryByKind = new Map;
|
|
132
199
|
const taskSourceKindRegistrant = new Map;
|
|
133
200
|
for (const { item, pluginName } of executableTaskSources) {
|
|
@@ -145,6 +212,9 @@ function createPluginHost(plugins) {
|
|
|
145
212
|
const taskFieldExtMap = indexById(taskFieldExtensions, "taskFieldExtension");
|
|
146
213
|
const taskSourceMap = indexById(taskSources, "taskSource");
|
|
147
214
|
const cliCommandMap = indexById(cliCommands, "cliCommand");
|
|
215
|
+
const capabilityMap = indexById(capabilities, "capability");
|
|
216
|
+
const panelMap = indexById(panels, "panel");
|
|
217
|
+
const blockerClassifierMap = indexById(blockerClassifiers, "blockerClassifier");
|
|
148
218
|
const allValidators = validators.map((c) => c.item);
|
|
149
219
|
const allHooks = hooks.map((c) => c.item);
|
|
150
220
|
const allSkills = skills.map((c) => c.item);
|
|
@@ -154,8 +224,33 @@ function createPluginHost(plugins) {
|
|
|
154
224
|
const allTaskSources = taskSources.map((c) => c.item);
|
|
155
225
|
const allCliCommands = cliCommands.map((c) => c.item);
|
|
156
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);
|
|
157
231
|
const allExecutableValidators = executableValidators.map((c) => c.item);
|
|
158
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
|
+
}
|
|
159
254
|
return {
|
|
160
255
|
getValidator: (id) => validatorMap.get(id),
|
|
161
256
|
getHook: (id) => hookMap.get(id),
|
|
@@ -165,6 +260,9 @@ function createPluginHost(plugins) {
|
|
|
165
260
|
getTaskFieldExtension: (id) => taskFieldExtMap.get(id),
|
|
166
261
|
getTaskSource: (id) => taskSourceMap.get(id),
|
|
167
262
|
getCliCommand: (id) => cliCommandMap.get(id),
|
|
263
|
+
getCapability: (id) => capabilityMap.get(id),
|
|
264
|
+
getPanel: (id) => panelMap.get(id),
|
|
265
|
+
getBlockerClassifier: (id) => blockerClassifierMap.get(id),
|
|
168
266
|
listValidators: () => allValidators,
|
|
169
267
|
listHooks: () => allHooks,
|
|
170
268
|
listSkills: () => allSkills,
|
|
@@ -173,10 +271,19 @@ function createPluginHost(plugins) {
|
|
|
173
271
|
listTaskFieldExtensions: () => allTaskFieldExtensions,
|
|
174
272
|
listTaskSources: () => allTaskSources,
|
|
175
273
|
listCliCommands: () => allCliCommands,
|
|
274
|
+
listCapabilities: () => allCapabilities,
|
|
275
|
+
listPanels: () => allPanels,
|
|
276
|
+
listBlockerClassifiers: () => allBlockerClassifiers,
|
|
277
|
+
listStages: () => allStages,
|
|
176
278
|
listStageMutations: () => allStageMutations,
|
|
177
279
|
listStageExecutors: () => stageExecutors,
|
|
178
280
|
listExecutableValidators: () => allExecutableValidators,
|
|
179
281
|
listExecutableTaskSources: () => allExecutableTaskSources,
|
|
282
|
+
listExecutableCapabilities: () => allExecutableCapabilities,
|
|
283
|
+
listExecutablePanels: () => allExecutablePanels,
|
|
284
|
+
listExecutableBlockerClassifiers: () => allExecutableBlockerClassifiers,
|
|
285
|
+
listExecutableCliCommands: () => allExecutableCliCommands,
|
|
286
|
+
resolveExecutableCliCommand: (requested) => executableCliCommandByName.get(requested)?.item,
|
|
180
287
|
resolveTaskSourceFactoryByKind: (kind) => taskSourceFactoryByKind.get(kind)
|
|
181
288
|
};
|
|
182
289
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HookImplementation, RegisteredTaskSource, RigConfig, RigPlugin, StageRun, TaskSourceConfig, TaskSourceRegistration, ValidatorRegistration } from "@rig/contracts";
|
|
1
|
+
import type { BlockerClassifierRegistration, CliCommandRegistration, HookImplementation, PanelRegistration, ProductCapabilityRegistration, RegisteredTaskSource, RigConfig, RigPlugin, StageRun, TaskSourceConfig, TaskSourceRegistration, ValidatorRegistration } from "@rig/contracts";
|
|
2
2
|
export interface ValidatorResult {
|
|
3
3
|
id: string;
|
|
4
4
|
passed: boolean;
|
|
@@ -44,6 +44,23 @@ export interface TaskSourceFactoryContext {
|
|
|
44
44
|
export interface TaskSourceFactoryEntry extends TaskSourceRegistration {
|
|
45
45
|
factory(config: TaskSourceConfig, context?: TaskSourceFactoryContext): RegisteredTaskSource;
|
|
46
46
|
}
|
|
47
|
+
export interface RuntimeFeatureCapability extends ProductCapabilityRegistration {
|
|
48
|
+
run?: (input: unknown) => Promise<unknown> | unknown;
|
|
49
|
+
}
|
|
50
|
+
export interface RuntimePanelProducer extends PanelRegistration {
|
|
51
|
+
produce?: (context?: unknown) => Promise<unknown> | unknown;
|
|
52
|
+
}
|
|
53
|
+
export interface RuntimeBlockerClassifier extends BlockerClassifierRegistration {
|
|
54
|
+
classify(input: unknown): Promise<unknown> | unknown;
|
|
55
|
+
}
|
|
56
|
+
export interface RuntimeCliContext {
|
|
57
|
+
projectRoot: string;
|
|
58
|
+
outputMode?: string;
|
|
59
|
+
dryRun?: boolean;
|
|
60
|
+
}
|
|
61
|
+
export interface RuntimeCliCommand extends CliCommandRegistration {
|
|
62
|
+
run(context: RuntimeCliContext, args: readonly string[]): Promise<unknown> | unknown;
|
|
63
|
+
}
|
|
47
64
|
export interface RigPluginRuntime {
|
|
48
65
|
validators?: readonly RegisteredValidator[];
|
|
49
66
|
taskSources?: readonly TaskSourceFactoryEntry[];
|
|
@@ -66,6 +83,22 @@ export interface RigPluginRuntime {
|
|
|
66
83
|
* execute plugin-contributed stages (e.g. the docs-drift pre-merge gate).
|
|
67
84
|
*/
|
|
68
85
|
stages?: Record<string, StageRun>;
|
|
86
|
+
/**
|
|
87
|
+
* Executable product-capability registrations. This is deliberately separate
|
|
88
|
+
* from `capabilities`: that map is reserved for the five kernel provider
|
|
89
|
+
* capability tags resolved by `@rig/kernel-seed`.
|
|
90
|
+
*/
|
|
91
|
+
featureCapabilities?: readonly RuntimeFeatureCapability[];
|
|
92
|
+
panels?: readonly RuntimePanelProducer[];
|
|
93
|
+
blockerClassifiers?: readonly RuntimeBlockerClassifier[];
|
|
94
|
+
cliCommands?: readonly RuntimeCliCommand[];
|
|
95
|
+
/**
|
|
96
|
+
* Executable kernel-capability providers keyed by **capability tag**
|
|
97
|
+
* (`transport`, `journal`, …) — the same shape the kernel-seed resolver reads
|
|
98
|
+
* via `plugin.runtime.capabilities[tag]`. Which tags a plugin provides is
|
|
99
|
+
* declared by the top-level `provides: CapabilityTag[]`; this holds the impls.
|
|
100
|
+
*/
|
|
101
|
+
capabilities?: Record<string, unknown>;
|
|
69
102
|
}
|
|
70
103
|
export type RigPluginWithRuntime = RigPlugin & {
|
|
71
104
|
__runtime?: RigPluginRuntime;
|
|
@@ -2,22 +2,25 @@
|
|
|
2
2
|
// packages/core/src/rig-init-builder.ts
|
|
3
3
|
function buildRigInitConfigSource(input) {
|
|
4
4
|
const lines = [`import { defineConfig } from "@rig/core";`];
|
|
5
|
-
if (input.useStandardPlugin
|
|
6
|
-
lines.push(`import
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
if (input.useStandardPlugin) {
|
|
6
|
+
lines.push(`import { standardProjectPlugins } from "@rig/standard-plugin/bundle";`);
|
|
7
|
+
if (input.taskSource.kind === "github-issues") {
|
|
8
|
+
lines.push(`import { createStateGitHubCredentialProvider } from "@rig/standard-plugin";`);
|
|
9
|
+
}
|
|
9
10
|
}
|
|
10
11
|
lines.push(``, `export default defineConfig({`);
|
|
11
12
|
const projectRepo = input.projectRepo ?? (input.taskSource.kind === "github-issues" ? `${input.taskSource.owner}/${input.taskSource.repo}` : undefined);
|
|
12
13
|
lines.push(projectRepo ? ` project: { name: ${JSON.stringify(input.projectName)}, repo: ${JSON.stringify(projectRepo)} },` : ` project: { name: ${JSON.stringify(input.projectName)} },`);
|
|
13
14
|
if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
|
|
14
|
-
lines.push(` plugins: [
|
|
15
|
-
lines.push(`
|
|
16
|
-
lines.push(`
|
|
17
|
-
lines.push(`
|
|
15
|
+
lines.push(` plugins: [...standardProjectPlugins({`);
|
|
16
|
+
lines.push(` standard: {`);
|
|
17
|
+
lines.push(` githubCredentialProvider: createStateGitHubCredentialProvider(),`);
|
|
18
|
+
lines.push(` githubWorkspaceId: ${JSON.stringify(`${input.taskSource.owner}/${input.taskSource.repo}`)},`);
|
|
19
|
+
lines.push(` githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user",`);
|
|
20
|
+
lines.push(` },`);
|
|
18
21
|
lines.push(` })],`);
|
|
19
22
|
} else {
|
|
20
|
-
lines.push(` plugins: [${input.useStandardPlugin ? "
|
|
23
|
+
lines.push(` plugins: [${input.useStandardPlugin ? "...standardProjectPlugins()" : ""}],`);
|
|
21
24
|
}
|
|
22
25
|
if (input.taskSource.kind === "github-issues") {
|
|
23
26
|
lines.push(` taskSource: {`);
|
|
@@ -189,7 +189,7 @@ export declare function selectPolicyDecisionsForTask(snapshot: EngineReadModel |
|
|
|
189
189
|
readonly id: string;
|
|
190
190
|
readonly createdAt: string;
|
|
191
191
|
readonly decision: "allow" | "block" | "warn";
|
|
192
|
-
readonly mode: "
|
|
192
|
+
readonly mode: "observe" | "off" | "enforce";
|
|
193
193
|
readonly runId: string & import("effect/Brand").Brand<"RunId">;
|
|
194
194
|
readonly actionId: (string & import("effect/Brand").Brand<"ActionId">) | null;
|
|
195
195
|
readonly reason: string;
|
|
@@ -43,15 +43,9 @@ export type StageMutation<TStage extends ResolvableStage = ResolvableStage> = Re
|
|
|
43
43
|
after?: readonly string[];
|
|
44
44
|
contributedBy?: string;
|
|
45
45
|
}>;
|
|
46
|
-
export interface ProtectedStageGrant {
|
|
47
|
-
readonly stageId: string;
|
|
48
|
-
readonly pluginId: string;
|
|
49
|
-
readonly op?: "remove" | "replace";
|
|
50
|
-
}
|
|
51
46
|
export interface ResolveStagePipelineInput<TStage extends ResolvableStage = ResolvableStage> {
|
|
52
47
|
readonly defaultStages: readonly TStage[];
|
|
53
48
|
readonly mutations?: readonly StageMutation<TStage>[];
|
|
54
|
-
readonly protectedStageGrants?: readonly ProtectedStageGrant[];
|
|
55
49
|
}
|
|
56
50
|
export interface DroppedStageAnchor {
|
|
57
51
|
readonly stageId: string;
|
|
@@ -68,7 +62,6 @@ export interface StageResolutionRecordEntry {
|
|
|
68
62
|
readonly wrappedBy?: readonly string[];
|
|
69
63
|
readonly droppedAnchors?: readonly DroppedStageAnchor[];
|
|
70
64
|
readonly isProtected: boolean;
|
|
71
|
-
readonly grantUsedBy?: string;
|
|
72
65
|
}
|
|
73
66
|
export interface ResolvedStagePipeline<TStage extends ResolvableStage = ResolvableStage> {
|
|
74
67
|
readonly stages: readonly TStage[];
|
|
@@ -81,17 +74,4 @@ export declare class PipelineUnresolvableError extends Error {
|
|
|
81
74
|
readonly contributors: readonly string[];
|
|
82
75
|
constructor(message: string, cycles: readonly (readonly string[])[], contributors: readonly string[]);
|
|
83
76
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Raised when a plugin attempts to `remove`/`replace` a protected stage without
|
|
86
|
-
* an explicit grant. Subclasses {@link PipelineUnresolvableError} so existing
|
|
87
|
-
* `instanceof PipelineUnresolvableError` checks keep working, but carries the
|
|
88
|
-
* stage/plugin/op so the kernel layer can surface the typed contracts
|
|
89
|
-
* `ProtectedStageViolation` (with its `KernelBootErrorCode`).
|
|
90
|
-
*/
|
|
91
|
-
export declare class ProtectedStageViolationError extends PipelineUnresolvableError {
|
|
92
|
-
readonly stageId: string;
|
|
93
|
-
readonly pluginId: string;
|
|
94
|
-
readonly op: "remove" | "replace";
|
|
95
|
-
constructor(stageId: string, pluginId: string, op: "remove" | "replace");
|
|
96
|
-
}
|
|
97
77
|
export declare function resolveStagePipeline<TStage extends ResolvableStage>(input: ResolveStagePipelineInput<TStage>): ResolvedStagePipeline<TStage>;
|
package/dist/src/stageResolve.js
CHANGED
|
@@ -10,19 +10,6 @@ class PipelineUnresolvableError extends Error {
|
|
|
10
10
|
this.contributors = contributors;
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
class ProtectedStageViolationError extends PipelineUnresolvableError {
|
|
15
|
-
stageId;
|
|
16
|
-
pluginId;
|
|
17
|
-
op;
|
|
18
|
-
constructor(stageId, pluginId, op) {
|
|
19
|
-
super(`Protected stage ${stageId} cannot be ${op === "remove" ? "removed" : "replaced"} by ${pluginId} without an explicit grant`, [], [pluginId]);
|
|
20
|
-
this.name = "ProtectedStageViolationError";
|
|
21
|
-
this.stageId = stageId;
|
|
22
|
-
this.pluginId = pluginId;
|
|
23
|
-
this.op = op;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
13
|
function uniqueSorted(values) {
|
|
27
14
|
return Array.from(new Set(values)).sort((left, right) => left.localeCompare(right));
|
|
28
15
|
}
|
|
@@ -67,9 +54,6 @@ function assertNoDuplicateMutationTargets(mutations, op) {
|
|
|
67
54
|
seen.set(id, contributorOf(mutation));
|
|
68
55
|
}
|
|
69
56
|
}
|
|
70
|
-
function hasProtectedGrant(grants, stageId, pluginId, op) {
|
|
71
|
-
return grants.some((grant) => grant.stageId === stageId && grant.pluginId === pluginId && (grant.op === undefined || grant.op === op));
|
|
72
|
-
}
|
|
73
57
|
function mergeAnchors(current, incoming) {
|
|
74
58
|
return uniqueSorted([...current, ...incoming ?? []]);
|
|
75
59
|
}
|
|
@@ -200,19 +184,18 @@ function resolveStagePipeline(input) {
|
|
|
200
184
|
const mutations = [...input.mutations ?? []];
|
|
201
185
|
assertNoDuplicateMutationTargets(mutations, "insert");
|
|
202
186
|
assertNoDuplicateMutationTargets(mutations, "replace");
|
|
203
|
-
const grants = input.protectedStageGrants ?? [];
|
|
204
187
|
const states = new Map;
|
|
205
188
|
const removedStates = new Map;
|
|
206
189
|
for (const [index, stage] of input.defaultStages.entries()) {
|
|
207
190
|
states.set(stage.id, {
|
|
208
|
-
stage,
|
|
191
|
+
stage: { ...stage, protected: false },
|
|
209
192
|
before: [...stage.before ?? []],
|
|
210
193
|
after: [...stage.after ?? []],
|
|
211
194
|
baseIndex: index,
|
|
212
195
|
contributedBy: "default",
|
|
213
196
|
wrappers: [],
|
|
214
197
|
droppedAnchors: [],
|
|
215
|
-
isProtected:
|
|
198
|
+
isProtected: false
|
|
216
199
|
});
|
|
217
200
|
}
|
|
218
201
|
for (const mutation of mutations.filter((entry) => entry.op === "remove").sort(stableMutationCompare)) {
|
|
@@ -220,13 +203,9 @@ function resolveStagePipeline(input) {
|
|
|
220
203
|
const contributor = contributorOf(mutation);
|
|
221
204
|
if (!state)
|
|
222
205
|
continue;
|
|
223
|
-
if (state.isProtected && !hasProtectedGrant(grants, mutation.id, contributor, "remove")) {
|
|
224
|
-
throw new ProtectedStageViolationError(mutation.id, contributor, "remove");
|
|
225
|
-
}
|
|
226
206
|
const removedState = {
|
|
227
207
|
...state,
|
|
228
|
-
removedBy: contributor
|
|
229
|
-
...state.isProtected ? { grantUsedBy: contributor } : {}
|
|
208
|
+
removedBy: contributor
|
|
230
209
|
};
|
|
231
210
|
removedStates.set(mutation.id, removedState);
|
|
232
211
|
states.delete(mutation.id);
|
|
@@ -236,10 +215,7 @@ function resolveStagePipeline(input) {
|
|
|
236
215
|
const contributor = contributorOf(mutation);
|
|
237
216
|
if (!state)
|
|
238
217
|
continue;
|
|
239
|
-
|
|
240
|
-
throw new ProtectedStageViolationError(mutation.id, contributor, "replace");
|
|
241
|
-
}
|
|
242
|
-
const replacement = { ...mutation.stage, id: mutation.id };
|
|
218
|
+
const replacement = { ...mutation.stage, id: mutation.id, protected: false };
|
|
243
219
|
states.set(mutation.id, {
|
|
244
220
|
...state,
|
|
245
221
|
stage: replacement,
|
|
@@ -247,8 +223,7 @@ function resolveStagePipeline(input) {
|
|
|
247
223
|
after: mutation.stage.after ? [...mutation.stage.after] : state.after,
|
|
248
224
|
replacedBy: contributor,
|
|
249
225
|
contributedBy: state.contributedBy,
|
|
250
|
-
isProtected:
|
|
251
|
-
...state.isProtected ? { grantUsedBy: contributor } : {}
|
|
226
|
+
isProtected: false
|
|
252
227
|
});
|
|
253
228
|
}
|
|
254
229
|
for (const mutation of mutations.filter((entry) => entry.op === "insert").sort(stableMutationCompare)) {
|
|
@@ -257,14 +232,14 @@ function resolveStagePipeline(input) {
|
|
|
257
232
|
throw new PipelineUnresolvableError(`Inserted stage ${mutation.stage.id} conflicts with an existing stage`, [], [contributor]);
|
|
258
233
|
}
|
|
259
234
|
states.set(mutation.stage.id, {
|
|
260
|
-
stage: mutation.stage,
|
|
235
|
+
stage: { ...mutation.stage, protected: false },
|
|
261
236
|
before: [...mutation.stage.before ?? []],
|
|
262
237
|
after: [...mutation.stage.after ?? []],
|
|
263
238
|
baseIndex: null,
|
|
264
239
|
contributedBy: contributor,
|
|
265
240
|
wrappers: [],
|
|
266
241
|
droppedAnchors: [],
|
|
267
|
-
isProtected:
|
|
242
|
+
isProtected: false
|
|
268
243
|
});
|
|
269
244
|
}
|
|
270
245
|
for (const mutation of mutations.filter((entry) => entry.op === "reorder").sort(stableMutationCompare)) {
|
|
@@ -363,8 +338,7 @@ function resolveStagePipeline(input) {
|
|
|
363
338
|
...state.replacedBy ? { replacedBy: state.replacedBy } : {},
|
|
364
339
|
...wrappedBy.length > 0 ? { wrappedBy } : {},
|
|
365
340
|
...state.droppedAnchors.length > 0 ? { droppedAnchors: state.droppedAnchors.toSorted((left, right) => left.anchor.localeCompare(right.anchor)) } : {},
|
|
366
|
-
isProtected: state.isProtected
|
|
367
|
-
...state.grantUsedBy ? { grantUsedBy: state.grantUsedBy } : {}
|
|
341
|
+
isProtected: state.isProtected
|
|
368
342
|
});
|
|
369
343
|
}
|
|
370
344
|
record.push(...[...removedStates.entries()].toSorted((left, right) => {
|
|
@@ -377,13 +351,11 @@ function resolveStagePipeline(input) {
|
|
|
377
351
|
stageId,
|
|
378
352
|
contributedBy: state.contributedBy,
|
|
379
353
|
...state.removedBy ? { removedBy: state.removedBy } : {},
|
|
380
|
-
isProtected: state.isProtected
|
|
381
|
-
...state.grantUsedBy ? { grantUsedBy: state.grantUsedBy } : {}
|
|
354
|
+
isProtected: state.isProtected
|
|
382
355
|
})));
|
|
383
356
|
return { stages, order, record, cycles: [] };
|
|
384
357
|
}
|
|
385
358
|
export {
|
|
386
359
|
resolveStagePipeline,
|
|
387
|
-
ProtectedStageViolationError,
|
|
388
360
|
PipelineUnresolvableError
|
|
389
361
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-rig/core",
|
|
3
|
-
"version": "0.0.6-alpha.
|
|
3
|
+
"version": "0.0.6-alpha.138",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Config and plugin composition library for Rig's OMP extension ecosystem; not a product host/runtime.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"module": "./dist/src/index.js",
|
|
46
46
|
"types": "./dist/src/index.d.ts",
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.
|
|
48
|
+
"@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.138",
|
|
49
49
|
"effect": "https://pkg.pr.new/Effect-TS/effect-smol/effect@8881a9b"
|
|
50
50
|
}
|
|
51
51
|
}
|