@h-rig/core 0.0.6-alpha.135 → 0.0.6-alpha.137

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.
@@ -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 {
@@ -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 ProtectedStageGrant, type ResolvableStage, type ResolvedStagePipeline, type ResolveStagePipelineInput, type StageKind, type StageMutation, type StageMutationOp, type StageResolutionRecordEntry, type StageWrapper, } from "./stageResolve";
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,8 +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 = [];
263
+ const stageMutations = [];
264
+ const stageExecutors = {};
193
265
  const executableValidators = [];
194
266
  const executableTaskSources = [];
267
+ const executableCapabilities = [];
268
+ const executablePanels = [];
269
+ const executableBlockerClassifiers = [];
270
+ const executableCliCommands = [];
195
271
  for (const plugin of plugins) {
196
272
  const c = plugin.contributes;
197
273
  if (!c)
@@ -203,6 +279,18 @@ function createPluginHost(plugins) {
203
279
  if (plugin.__runtime?.taskSources) {
204
280
  executableTaskSources.push(...plugin.__runtime.taskSources.map((item) => ({ item, pluginName })));
205
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
+ }
206
294
  if (c.validators)
207
295
  validators.push(...c.validators.map((item) => ({ item, pluginName })));
208
296
  if (c.hooks)
@@ -219,9 +307,25 @@ function createPluginHost(plugins) {
219
307
  taskSources.push(...c.taskSources.map((item) => ({ item, pluginName })));
220
308
  if (c.cliCommands)
221
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 })));
318
+ if (c.stageMutations)
319
+ stageMutations.push(...c.stageMutations.map((item) => ({ item, pluginName })));
320
+ if (plugin.__runtime?.stages)
321
+ Object.assign(stageExecutors, plugin.__runtime.stages);
222
322
  }
223
323
  indexById(executableValidators, "executableValidator");
224
324
  indexById(executableTaskSources, "executableTaskSource");
325
+ indexById(executableCapabilities, "executableCapability");
326
+ indexById(executablePanels, "executablePanel");
327
+ indexById(executableBlockerClassifiers, "executableBlockerClassifier");
328
+ indexById(executableCliCommands, "executableCliCommand");
225
329
  const taskSourceFactoryByKind = new Map;
226
330
  const taskSourceKindRegistrant = new Map;
227
331
  for (const { item, pluginName } of executableTaskSources) {
@@ -239,6 +343,9 @@ function createPluginHost(plugins) {
239
343
  const taskFieldExtMap = indexById(taskFieldExtensions, "taskFieldExtension");
240
344
  const taskSourceMap = indexById(taskSources, "taskSource");
241
345
  const cliCommandMap = indexById(cliCommands, "cliCommand");
346
+ const capabilityMap = indexById(capabilities, "capability");
347
+ const panelMap = indexById(panels, "panel");
348
+ const blockerClassifierMap = indexById(blockerClassifiers, "blockerClassifier");
242
349
  const allValidators = validators.map((c) => c.item);
243
350
  const allHooks = hooks.map((c) => c.item);
244
351
  const allSkills = skills.map((c) => c.item);
@@ -247,8 +354,34 @@ function createPluginHost(plugins) {
247
354
  const allTaskFieldExtensions = taskFieldExtensions.map((c) => c.item);
248
355
  const allTaskSources = taskSources.map((c) => c.item);
249
356
  const allCliCommands = cliCommands.map((c) => c.item);
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);
250
362
  const allExecutableValidators = executableValidators.map((c) => c.item);
251
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
+ }
252
385
  return {
253
386
  getValidator: (id) => validatorMap.get(id),
254
387
  getHook: (id) => hookMap.get(id),
@@ -258,6 +391,9 @@ function createPluginHost(plugins) {
258
391
  getTaskFieldExtension: (id) => taskFieldExtMap.get(id),
259
392
  getTaskSource: (id) => taskSourceMap.get(id),
260
393
  getCliCommand: (id) => cliCommandMap.get(id),
394
+ getCapability: (id) => capabilityMap.get(id),
395
+ getPanel: (id) => panelMap.get(id),
396
+ getBlockerClassifier: (id) => blockerClassifierMap.get(id),
261
397
  listValidators: () => allValidators,
262
398
  listHooks: () => allHooks,
263
399
  listSkills: () => allSkills,
@@ -266,30 +402,44 @@ function createPluginHost(plugins) {
266
402
  listTaskFieldExtensions: () => allTaskFieldExtensions,
267
403
  listTaskSources: () => allTaskSources,
268
404
  listCliCommands: () => allCliCommands,
405
+ listCapabilities: () => allCapabilities,
406
+ listPanels: () => allPanels,
407
+ listBlockerClassifiers: () => allBlockerClassifiers,
408
+ listStages: () => allStages,
409
+ listStageMutations: () => allStageMutations,
410
+ listStageExecutors: () => stageExecutors,
269
411
  listExecutableValidators: () => allExecutableValidators,
270
412
  listExecutableTaskSources: () => allExecutableTaskSources,
413
+ listExecutableCapabilities: () => allExecutableCapabilities,
414
+ listExecutablePanels: () => allExecutablePanels,
415
+ listExecutableBlockerClassifiers: () => allExecutableBlockerClassifiers,
416
+ listExecutableCliCommands: () => allExecutableCliCommands,
417
+ resolveExecutableCliCommand: (requested) => executableCliCommandByName.get(requested)?.item,
271
418
  resolveTaskSourceFactoryByKind: (kind) => taskSourceFactoryByKind.get(kind)
272
419
  };
273
420
  }
274
421
  // packages/core/src/rig-init-builder.ts
275
422
  function buildRigInitConfigSource(input) {
276
423
  const lines = [`import { defineConfig } from "@rig/core";`];
277
- if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
278
- lines.push(`import standard, { createStateGitHubCredentialProvider } from "@rig/standard-plugin";`);
279
- } else if (input.useStandardPlugin) {
280
- lines.push(`import standard from "@rig/standard-plugin";`);
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
+ }
281
429
  }
282
430
  lines.push(``, `export default defineConfig({`);
283
431
  const projectRepo = input.projectRepo ?? (input.taskSource.kind === "github-issues" ? `${input.taskSource.owner}/${input.taskSource.repo}` : undefined);
284
432
  lines.push(projectRepo ? ` project: { name: ${JSON.stringify(input.projectName)}, repo: ${JSON.stringify(projectRepo)} },` : ` project: { name: ${JSON.stringify(input.projectName)} },`);
285
433
  if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
286
- lines.push(` plugins: [standard({`);
287
- lines.push(` githubCredentialProvider: createStateGitHubCredentialProvider(),`);
288
- lines.push(` githubWorkspaceId: ${JSON.stringify(`${input.taskSource.owner}/${input.taskSource.repo}`)},`);
289
- lines.push(` githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user",`);
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(` },`);
290
440
  lines.push(` })],`);
291
441
  } else {
292
- lines.push(` plugins: [${input.useStandardPlugin ? "standard()" : ""}],`);
442
+ lines.push(` plugins: [${input.useStandardPlugin ? "...standardProjectPlugins()" : ""}],`);
293
443
  }
294
444
  if (input.taskSource.kind === "github-issues") {
295
445
  lines.push(` taskSource: {`);
@@ -3102,9 +3252,6 @@ function assertNoDuplicateMutationTargets(mutations, op) {
3102
3252
  seen.set(id, contributorOf(mutation));
3103
3253
  }
3104
3254
  }
3105
- function hasProtectedGrant(grants, stageId, pluginId, op) {
3106
- return grants.some((grant) => grant.stageId === stageId && grant.pluginId === pluginId && (grant.op === undefined || grant.op === op));
3107
- }
3108
3255
  function mergeAnchors(current, incoming) {
3109
3256
  return uniqueSorted([...current, ...incoming ?? []]);
3110
3257
  }
@@ -3235,19 +3382,18 @@ function resolveStagePipeline(input) {
3235
3382
  const mutations = [...input.mutations ?? []];
3236
3383
  assertNoDuplicateMutationTargets(mutations, "insert");
3237
3384
  assertNoDuplicateMutationTargets(mutations, "replace");
3238
- const grants = input.protectedStageGrants ?? [];
3239
3385
  const states = new Map;
3240
3386
  const removedStates = new Map;
3241
3387
  for (const [index, stage] of input.defaultStages.entries()) {
3242
3388
  states.set(stage.id, {
3243
- stage,
3389
+ stage: { ...stage, protected: false },
3244
3390
  before: [...stage.before ?? []],
3245
3391
  after: [...stage.after ?? []],
3246
3392
  baseIndex: index,
3247
3393
  contributedBy: "default",
3248
3394
  wrappers: [],
3249
3395
  droppedAnchors: [],
3250
- isProtected: stage.protected === true
3396
+ isProtected: false
3251
3397
  });
3252
3398
  }
3253
3399
  for (const mutation of mutations.filter((entry) => entry.op === "remove").sort(stableMutationCompare)) {
@@ -3255,13 +3401,9 @@ function resolveStagePipeline(input) {
3255
3401
  const contributor = contributorOf(mutation);
3256
3402
  if (!state)
3257
3403
  continue;
3258
- if (state.isProtected && !hasProtectedGrant(grants, mutation.id, contributor, "remove")) {
3259
- throw new PipelineUnresolvableError(`Protected stage ${mutation.id} cannot be removed by ${contributor} without an explicit grant`, [], [contributor]);
3260
- }
3261
3404
  const removedState = {
3262
3405
  ...state,
3263
- removedBy: contributor,
3264
- ...state.isProtected ? { grantUsedBy: contributor } : {}
3406
+ removedBy: contributor
3265
3407
  };
3266
3408
  removedStates.set(mutation.id, removedState);
3267
3409
  states.delete(mutation.id);
@@ -3271,10 +3413,7 @@ function resolveStagePipeline(input) {
3271
3413
  const contributor = contributorOf(mutation);
3272
3414
  if (!state)
3273
3415
  continue;
3274
- if (state.isProtected && !hasProtectedGrant(grants, mutation.id, contributor, "replace")) {
3275
- throw new PipelineUnresolvableError(`Protected stage ${mutation.id} cannot be replaced by ${contributor} without an explicit grant`, [], [contributor]);
3276
- }
3277
- const replacement = { ...mutation.stage, id: mutation.id };
3416
+ const replacement = { ...mutation.stage, id: mutation.id, protected: false };
3278
3417
  states.set(mutation.id, {
3279
3418
  ...state,
3280
3419
  stage: replacement,
@@ -3282,8 +3421,7 @@ function resolveStagePipeline(input) {
3282
3421
  after: mutation.stage.after ? [...mutation.stage.after] : state.after,
3283
3422
  replacedBy: contributor,
3284
3423
  contributedBy: state.contributedBy,
3285
- isProtected: mutation.stage.protected ?? state.isProtected,
3286
- ...state.isProtected ? { grantUsedBy: contributor } : {}
3424
+ isProtected: false
3287
3425
  });
3288
3426
  }
3289
3427
  for (const mutation of mutations.filter((entry) => entry.op === "insert").sort(stableMutationCompare)) {
@@ -3292,14 +3430,14 @@ function resolveStagePipeline(input) {
3292
3430
  throw new PipelineUnresolvableError(`Inserted stage ${mutation.stage.id} conflicts with an existing stage`, [], [contributor]);
3293
3431
  }
3294
3432
  states.set(mutation.stage.id, {
3295
- stage: mutation.stage,
3433
+ stage: { ...mutation.stage, protected: false },
3296
3434
  before: [...mutation.stage.before ?? []],
3297
3435
  after: [...mutation.stage.after ?? []],
3298
3436
  baseIndex: null,
3299
3437
  contributedBy: contributor,
3300
3438
  wrappers: [],
3301
3439
  droppedAnchors: [],
3302
- isProtected: mutation.stage.protected === true
3440
+ isProtected: false
3303
3441
  });
3304
3442
  }
3305
3443
  for (const mutation of mutations.filter((entry) => entry.op === "reorder").sort(stableMutationCompare)) {
@@ -3398,8 +3536,7 @@ function resolveStagePipeline(input) {
3398
3536
  ...state.replacedBy ? { replacedBy: state.replacedBy } : {},
3399
3537
  ...wrappedBy.length > 0 ? { wrappedBy } : {},
3400
3538
  ...state.droppedAnchors.length > 0 ? { droppedAnchors: state.droppedAnchors.toSorted((left, right) => left.anchor.localeCompare(right.anchor)) } : {},
3401
- isProtected: state.isProtected,
3402
- ...state.grantUsedBy ? { grantUsedBy: state.grantUsedBy } : {}
3539
+ isProtected: state.isProtected
3403
3540
  });
3404
3541
  }
3405
3542
  record.push(...[...removedStates.entries()].toSorted((left, right) => {
@@ -3412,8 +3549,7 @@ function resolveStagePipeline(input) {
3412
3549
  stageId,
3413
3550
  contributedBy: state.contributedBy,
3414
3551
  ...state.removedBy ? { removedBy: state.removedBy } : {},
3415
- isProtected: state.isProtected,
3416
- ...state.grantUsedBy ? { grantUsedBy: state.grantUsedBy } : {}
3552
+ isProtected: state.isProtected
3417
3553
  })));
3418
3554
  return { stages, order, record, cycles: [] };
3419
3555
  }
@@ -1,5 +1,5 @@
1
- import type { ValidatorRegistration, HookRegistration, SkillRegistration, RepoSourceRegistration, AgentRoleRegistration, TaskFieldExtension, TaskSourceRegistration, CliCommandRegistration } from "@rig/contracts";
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,23 @@ 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[];
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>>;
20
40
  /**
21
41
  * Executable validators contributed by plugins. Only validators whose
22
42
  * metadata appears in `listValidators()` and whose plugin shipped a runtime
@@ -30,6 +50,11 @@ export interface PluginHost {
30
50
  * source kinds (Linear, Jira, custom) declared by plugins.
31
51
  */
32
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;
33
58
  /**
34
59
  * Look up an executable task source factory by its `kind` (the value in
35
60
  * `config.taskSource.kind`). Returns undefined when no plugin provides
@@ -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,8 +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 = [];
132
+ const stageMutations = [];
133
+ const stageExecutors = {};
93
134
  const executableValidators = [];
94
135
  const executableTaskSources = [];
136
+ const executableCapabilities = [];
137
+ const executablePanels = [];
138
+ const executableBlockerClassifiers = [];
139
+ const executableCliCommands = [];
95
140
  for (const plugin of plugins) {
96
141
  const c = plugin.contributes;
97
142
  if (!c)
@@ -103,6 +148,18 @@ function createPluginHost(plugins) {
103
148
  if (plugin.__runtime?.taskSources) {
104
149
  executableTaskSources.push(...plugin.__runtime.taskSources.map((item) => ({ item, pluginName })));
105
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
+ }
106
163
  if (c.validators)
107
164
  validators.push(...c.validators.map((item) => ({ item, pluginName })));
108
165
  if (c.hooks)
@@ -119,9 +176,25 @@ function createPluginHost(plugins) {
119
176
  taskSources.push(...c.taskSources.map((item) => ({ item, pluginName })));
120
177
  if (c.cliCommands)
121
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);
122
191
  }
123
192
  indexById(executableValidators, "executableValidator");
124
193
  indexById(executableTaskSources, "executableTaskSource");
194
+ indexById(executableCapabilities, "executableCapability");
195
+ indexById(executablePanels, "executablePanel");
196
+ indexById(executableBlockerClassifiers, "executableBlockerClassifier");
197
+ indexById(executableCliCommands, "executableCliCommand");
125
198
  const taskSourceFactoryByKind = new Map;
126
199
  const taskSourceKindRegistrant = new Map;
127
200
  for (const { item, pluginName } of executableTaskSources) {
@@ -139,6 +212,9 @@ function createPluginHost(plugins) {
139
212
  const taskFieldExtMap = indexById(taskFieldExtensions, "taskFieldExtension");
140
213
  const taskSourceMap = indexById(taskSources, "taskSource");
141
214
  const cliCommandMap = indexById(cliCommands, "cliCommand");
215
+ const capabilityMap = indexById(capabilities, "capability");
216
+ const panelMap = indexById(panels, "panel");
217
+ const blockerClassifierMap = indexById(blockerClassifiers, "blockerClassifier");
142
218
  const allValidators = validators.map((c) => c.item);
143
219
  const allHooks = hooks.map((c) => c.item);
144
220
  const allSkills = skills.map((c) => c.item);
@@ -147,8 +223,34 @@ function createPluginHost(plugins) {
147
223
  const allTaskFieldExtensions = taskFieldExtensions.map((c) => c.item);
148
224
  const allTaskSources = taskSources.map((c) => c.item);
149
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);
150
231
  const allExecutableValidators = executableValidators.map((c) => c.item);
151
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
+ }
152
254
  return {
153
255
  getValidator: (id) => validatorMap.get(id),
154
256
  getHook: (id) => hookMap.get(id),
@@ -158,6 +260,9 @@ function createPluginHost(plugins) {
158
260
  getTaskFieldExtension: (id) => taskFieldExtMap.get(id),
159
261
  getTaskSource: (id) => taskSourceMap.get(id),
160
262
  getCliCommand: (id) => cliCommandMap.get(id),
263
+ getCapability: (id) => capabilityMap.get(id),
264
+ getPanel: (id) => panelMap.get(id),
265
+ getBlockerClassifier: (id) => blockerClassifierMap.get(id),
161
266
  listValidators: () => allValidators,
162
267
  listHooks: () => allHooks,
163
268
  listSkills: () => allSkills,
@@ -166,8 +271,19 @@ function createPluginHost(plugins) {
166
271
  listTaskFieldExtensions: () => allTaskFieldExtensions,
167
272
  listTaskSources: () => allTaskSources,
168
273
  listCliCommands: () => allCliCommands,
274
+ listCapabilities: () => allCapabilities,
275
+ listPanels: () => allPanels,
276
+ listBlockerClassifiers: () => allBlockerClassifiers,
277
+ listStages: () => allStages,
278
+ listStageMutations: () => allStageMutations,
279
+ listStageExecutors: () => stageExecutors,
169
280
  listExecutableValidators: () => allExecutableValidators,
170
281
  listExecutableTaskSources: () => allExecutableTaskSources,
282
+ listExecutableCapabilities: () => allExecutableCapabilities,
283
+ listExecutablePanels: () => allExecutablePanels,
284
+ listExecutableBlockerClassifiers: () => allExecutableBlockerClassifiers,
285
+ listExecutableCliCommands: () => allExecutableCliCommands,
286
+ resolveExecutableCliCommand: (requested) => executableCliCommandByName.get(requested)?.item,
171
287
  resolveTaskSourceFactoryByKind: (kind) => taskSourceFactoryByKind.get(kind)
172
288
  };
173
289
  }
@@ -1,4 +1,4 @@
1
- import type { HookImplementation, RegisteredTaskSource, RigConfig, RigPlugin, 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[];
@@ -58,6 +75,30 @@ export interface RigPluginRuntime {
58
75
  * `@rig/runtime/control-plane/hook-runner`).
59
76
  */
60
77
  hooks?: Record<string, HookImplementation>;
78
+ /**
79
+ * Executable stage implementations keyed by stage id — the runtime counterpart
80
+ * to `contributes.stageMutations` (which carries only the ordering descriptor).
81
+ * When a plugin inserts/replaces a stage, its `run(ctx)` lives here. The plugin
82
+ * host flattens these (`listStageExecutors()`) so the kernel's stage runner can
83
+ * execute plugin-contributed stages (e.g. the docs-drift pre-merge gate).
84
+ */
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>;
61
102
  }
62
103
  export type RigPluginWithRuntime = RigPlugin & {
63
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 && input.taskSource.kind === "github-issues") {
6
- lines.push(`import standard, { createStateGitHubCredentialProvider } from "@rig/standard-plugin";`);
7
- } else if (input.useStandardPlugin) {
8
- lines.push(`import standard from "@rig/standard-plugin";`);
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: [standard({`);
15
- lines.push(` githubCredentialProvider: createStateGitHubCredentialProvider(),`);
16
- lines.push(` githubWorkspaceId: ${JSON.stringify(`${input.taskSource.owner}/${input.taskSource.repo}`)},`);
17
- lines.push(` githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user",`);
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 ? "standard()" : ""}],`);
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: "off" | "observe" | "enforce";
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[];
@@ -54,9 +54,6 @@ function assertNoDuplicateMutationTargets(mutations, op) {
54
54
  seen.set(id, contributorOf(mutation));
55
55
  }
56
56
  }
57
- function hasProtectedGrant(grants, stageId, pluginId, op) {
58
- return grants.some((grant) => grant.stageId === stageId && grant.pluginId === pluginId && (grant.op === undefined || grant.op === op));
59
- }
60
57
  function mergeAnchors(current, incoming) {
61
58
  return uniqueSorted([...current, ...incoming ?? []]);
62
59
  }
@@ -187,19 +184,18 @@ function resolveStagePipeline(input) {
187
184
  const mutations = [...input.mutations ?? []];
188
185
  assertNoDuplicateMutationTargets(mutations, "insert");
189
186
  assertNoDuplicateMutationTargets(mutations, "replace");
190
- const grants = input.protectedStageGrants ?? [];
191
187
  const states = new Map;
192
188
  const removedStates = new Map;
193
189
  for (const [index, stage] of input.defaultStages.entries()) {
194
190
  states.set(stage.id, {
195
- stage,
191
+ stage: { ...stage, protected: false },
196
192
  before: [...stage.before ?? []],
197
193
  after: [...stage.after ?? []],
198
194
  baseIndex: index,
199
195
  contributedBy: "default",
200
196
  wrappers: [],
201
197
  droppedAnchors: [],
202
- isProtected: stage.protected === true
198
+ isProtected: false
203
199
  });
204
200
  }
205
201
  for (const mutation of mutations.filter((entry) => entry.op === "remove").sort(stableMutationCompare)) {
@@ -207,13 +203,9 @@ function resolveStagePipeline(input) {
207
203
  const contributor = contributorOf(mutation);
208
204
  if (!state)
209
205
  continue;
210
- if (state.isProtected && !hasProtectedGrant(grants, mutation.id, contributor, "remove")) {
211
- throw new PipelineUnresolvableError(`Protected stage ${mutation.id} cannot be removed by ${contributor} without an explicit grant`, [], [contributor]);
212
- }
213
206
  const removedState = {
214
207
  ...state,
215
- removedBy: contributor,
216
- ...state.isProtected ? { grantUsedBy: contributor } : {}
208
+ removedBy: contributor
217
209
  };
218
210
  removedStates.set(mutation.id, removedState);
219
211
  states.delete(mutation.id);
@@ -223,10 +215,7 @@ function resolveStagePipeline(input) {
223
215
  const contributor = contributorOf(mutation);
224
216
  if (!state)
225
217
  continue;
226
- if (state.isProtected && !hasProtectedGrant(grants, mutation.id, contributor, "replace")) {
227
- throw new PipelineUnresolvableError(`Protected stage ${mutation.id} cannot be replaced by ${contributor} without an explicit grant`, [], [contributor]);
228
- }
229
- const replacement = { ...mutation.stage, id: mutation.id };
218
+ const replacement = { ...mutation.stage, id: mutation.id, protected: false };
230
219
  states.set(mutation.id, {
231
220
  ...state,
232
221
  stage: replacement,
@@ -234,8 +223,7 @@ function resolveStagePipeline(input) {
234
223
  after: mutation.stage.after ? [...mutation.stage.after] : state.after,
235
224
  replacedBy: contributor,
236
225
  contributedBy: state.contributedBy,
237
- isProtected: mutation.stage.protected ?? state.isProtected,
238
- ...state.isProtected ? { grantUsedBy: contributor } : {}
226
+ isProtected: false
239
227
  });
240
228
  }
241
229
  for (const mutation of mutations.filter((entry) => entry.op === "insert").sort(stableMutationCompare)) {
@@ -244,14 +232,14 @@ function resolveStagePipeline(input) {
244
232
  throw new PipelineUnresolvableError(`Inserted stage ${mutation.stage.id} conflicts with an existing stage`, [], [contributor]);
245
233
  }
246
234
  states.set(mutation.stage.id, {
247
- stage: mutation.stage,
235
+ stage: { ...mutation.stage, protected: false },
248
236
  before: [...mutation.stage.before ?? []],
249
237
  after: [...mutation.stage.after ?? []],
250
238
  baseIndex: null,
251
239
  contributedBy: contributor,
252
240
  wrappers: [],
253
241
  droppedAnchors: [],
254
- isProtected: mutation.stage.protected === true
242
+ isProtected: false
255
243
  });
256
244
  }
257
245
  for (const mutation of mutations.filter((entry) => entry.op === "reorder").sort(stableMutationCompare)) {
@@ -350,8 +338,7 @@ function resolveStagePipeline(input) {
350
338
  ...state.replacedBy ? { replacedBy: state.replacedBy } : {},
351
339
  ...wrappedBy.length > 0 ? { wrappedBy } : {},
352
340
  ...state.droppedAnchors.length > 0 ? { droppedAnchors: state.droppedAnchors.toSorted((left, right) => left.anchor.localeCompare(right.anchor)) } : {},
353
- isProtected: state.isProtected,
354
- ...state.grantUsedBy ? { grantUsedBy: state.grantUsedBy } : {}
341
+ isProtected: state.isProtected
355
342
  });
356
343
  }
357
344
  record.push(...[...removedStates.entries()].toSorted((left, right) => {
@@ -364,8 +351,7 @@ function resolveStagePipeline(input) {
364
351
  stageId,
365
352
  contributedBy: state.contributedBy,
366
353
  ...state.removedBy ? { removedBy: state.removedBy } : {},
367
- isProtected: state.isProtected,
368
- ...state.grantUsedBy ? { grantUsedBy: state.grantUsedBy } : {}
354
+ isProtected: state.isProtected
369
355
  })));
370
356
  return { stages, order, record, cycles: [] };
371
357
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/core",
3
- "version": "0.0.6-alpha.135",
3
+ "version": "0.0.6-alpha.137",
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.135",
48
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.137",
49
49
  "effect": "https://pkg.pr.new/Effect-TS/effect-smol/effect@8881a9b"
50
50
  }
51
51
  }