@uniqbit/mate-core 0.18.4 → 1.0.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/claude-plugin/hooks/companion-instructions.mjs +9 -0
  2. package/claude-plugin/hooks/hooks.json +12 -2
  3. package/opencode-plugin/src/add-dir.ts +76 -10
  4. package/opencode-plugin/src/companion.ts +1 -8
  5. package/opencode-plugin/src/server.ts +2 -15
  6. package/package.json +10 -2
  7. package/src/brand/art.generated.ts +69 -0
  8. package/src/brand/index.ts +12 -0
  9. package/src/brand/palette.ts +19 -0
  10. package/src/brand/svg.generated.ts +8 -0
  11. package/src/brand/terminal.ts +83 -0
  12. package/src/brand/types.ts +33 -0
  13. package/src/cli/commands/artifact/artifact.ts +8 -0
  14. package/src/cli/commands/artifact/delivery/archive.ts +117 -0
  15. package/src/cli/commands/artifact/delivery/branch-flags.ts +55 -0
  16. package/src/cli/commands/artifact/delivery/environment.ts +104 -0
  17. package/src/cli/commands/artifact/delivery/link.ts +154 -0
  18. package/src/cli/commands/artifact/delivery/output.ts +58 -0
  19. package/src/cli/commands/artifact/finish/command.ts +33 -3
  20. package/src/cli/commands/artifact/finish/engine.ts +47 -3
  21. package/src/cli/commands/artifact/finish/finisher.ts +10 -0
  22. package/src/cli/commands/artifact/finish/openspec.ts +200 -129
  23. package/src/cli/commands/artifact/finish/secret-scan.ts +43 -0
  24. package/src/cli/commands/artifact/pending/command.ts +31 -3
  25. package/src/cli/commands/artifact/pending/discovery.ts +21 -57
  26. package/src/cli/commands/cap/graphify.ts +52 -3
  27. package/src/cli/commands/cap/index-cmd.ts +8 -1
  28. package/src/cli/commands/cap/openspec.ts +13 -25
  29. package/src/cli/commands/companion/open.ts +56 -39
  30. package/src/cli/commands/companion/update.ts +75 -42
  31. package/src/cli/commands/doctor.ts +257 -78
  32. package/src/cli/commands/install.ts +0 -8
  33. package/src/cli/commands/plugin/install.ts +9 -4
  34. package/src/cli/commands/plugin/plugin.ts +3 -2
  35. package/src/cli/commands/report/adapter.ts +11 -2
  36. package/src/cli/commands/report/contract.ts +21 -0
  37. package/src/cli/commands/report/highlight.ts +34 -4
  38. package/src/cli/commands/report/markdown.ts +110 -0
  39. package/src/cli/commands/report/palette.ts +118 -0
  40. package/src/cli/commands/report/renderer.ts +456 -153
  41. package/src/cli/commands/report/types.ts +16 -0
  42. package/src/cli/commands/setup.ts +7 -15
  43. package/src/cli/commands/studio/client-asset.ts +16 -0
  44. package/src/cli/commands/studio/openspec-cli.ts +10 -58
  45. package/src/cli/commands/studio/routes.ts +2 -0
  46. package/src/cli/commands/studio/selection.ts +19 -1
  47. package/src/cli/commands/studio/server.ts +42 -1
  48. package/src/cli/commands/studio/terminal-launch.ts +7 -16
  49. package/src/cli/commands/studio/views/client.ts +6 -616
  50. package/src/cli/commands/studio/views/dashboard/changes.tsx +5 -10
  51. package/src/cli/commands/studio/views/document.tsx +137 -69
  52. package/src/cli/commands/studio/views/markdown.tsx +250 -0
  53. package/src/cli/commands/studio/views/palette.ts +134 -0
  54. package/src/cli/commands/studio/views/reports/index.tsx +2 -7
  55. package/src/cli/commands/studio/views/skills/index.tsx +43 -62
  56. package/src/cli/commands/studio/views/specs/index.tsx +7 -15
  57. package/src/cli/commands/studio/views/studio-client/actions.ts +51 -0
  58. package/src/cli/commands/studio/views/studio-client/companion.ts +13 -0
  59. package/src/cli/commands/studio/views/studio-client/index.ts +32 -0
  60. package/src/cli/commands/studio/views/studio-client/keys.ts +3 -0
  61. package/src/cli/commands/studio/views/studio-client/main.ts +3 -0
  62. package/src/cli/commands/studio/views/studio-client/page.ts +146 -0
  63. package/src/cli/commands/studio/views/studio-client/panels.ts +57 -0
  64. package/src/cli/commands/studio/views/studio-client/reports.ts +122 -0
  65. package/src/cli/commands/studio/views/studio-client/theme.ts +63 -0
  66. package/src/cli/commands/studio/views/studio-client/vault-editor.ts +166 -0
  67. package/src/cli/commands/studio/views/studio-client/vault-pane.ts +202 -0
  68. package/src/cli/commands/studio/views/studio-client/vault-tree.ts +258 -0
  69. package/src/cli/commands/studio/views/studio-client.generated.ts +2 -0
  70. package/src/cli/commands/studio/views/styles.ts +217 -206
  71. package/src/cli/commands/studio/views/vault/state.ts +3 -5
  72. package/src/cli/commands/studio/views/workflow/delivery-path.tsx +370 -0
  73. package/src/cli/commands/studio/views/workflow/graph.ts +220 -0
  74. package/src/cli/commands/studio/views/workflow/index.tsx +2 -3
  75. package/src/cli/commands/studio/views/workflow/steps.ts +228 -287
  76. package/src/cli/commands/wrap.ts +9 -42
  77. package/src/cli/main.ts +4 -1
  78. package/src/cli/parse-flags.ts +25 -7
  79. package/src/cli/setup-selector.tsx +38 -162
  80. package/src/cli/usage.ts +5 -2
  81. package/src/hooks/companion-instructions.ts +190 -0
  82. package/src/hooks/hook-io.ts +63 -0
  83. package/src/hooks/session-banner.ts +21 -29
  84. package/src/hooks/session-guidance.ts +10 -36
  85. package/src/hooks/session-prepare.ts +115 -28
  86. package/src/hooks/validate-artifact-path.ts +82 -24
  87. package/src/index.ts +4 -0
  88. package/src/lib/change-layout.ts +154 -0
  89. package/src/lib/components/wizard-header.tsx +3 -1
  90. package/src/lib/context-mode-package.ts +24 -11
  91. package/src/lib/delivery/archive-change.ts +154 -0
  92. package/src/lib/delivery/branches.ts +108 -0
  93. package/src/lib/delivery/environment.ts +29 -0
  94. package/src/lib/delivery/forge.ts +279 -0
  95. package/src/lib/delivery/overlap.ts +202 -0
  96. package/src/lib/delivery/record-delivery.ts +196 -0
  97. package/src/lib/delivery/record.ts +343 -0
  98. package/src/lib/delivery/refresh.ts +174 -0
  99. package/src/lib/delivery/repository-access.ts +97 -0
  100. package/src/lib/delivery/stages.ts +111 -0
  101. package/src/lib/delivery/token.ts +58 -0
  102. package/src/lib/frontmatter.ts +18 -0
  103. package/src/lib/opencode-plugin-package.ts +5 -5
  104. package/src/lib/openspec-cli.ts +116 -0
  105. package/src/lib/openspec-scopes.ts +86 -0
  106. package/src/lib/orchestrator/adapters/claude-args.ts +50 -0
  107. package/src/lib/orchestrator/adapters/claude.ts +60 -162
  108. package/src/lib/orchestrator/adapters/opencode.ts +9 -24
  109. package/src/lib/orchestrator/config-store.ts +2 -70
  110. package/src/lib/orchestrator/editor.ts +9 -9
  111. package/src/lib/orchestrator/openspec-store.ts +14 -54
  112. package/src/lib/orchestrator/projection-claude-commands.ts +36 -0
  113. package/src/lib/orchestrator/projection-claude-links.ts +159 -0
  114. package/src/lib/orchestrator/projection-claude-skills.ts +19 -190
  115. package/src/lib/orchestrator/projection-entries.ts +45 -15
  116. package/src/lib/orchestrator/projection-runtime-documents.ts +15 -6
  117. package/src/lib/orchestrator/projection-types.ts +3 -2
  118. package/src/lib/orchestrator/refresh-working-claude-links.ts +87 -0
  119. package/src/lib/orchestrator/session-prepare.ts +49 -87
  120. package/src/lib/orchestrator/setup-compatibilities.ts +18 -75
  121. package/src/lib/orchestrator/types.ts +2 -2
  122. package/src/lib/orchestrator/wrap-pass.ts +22 -88
  123. package/src/lib/package-paths.ts +8 -0
  124. package/src/opencode/companion-hooks.ts +64 -5
  125. package/src/opencode/companion-policy.ts +37 -18
  126. package/src/opencode/projected-guidance.ts +4 -1
  127. package/src/opencode/session-prepare.ts +2 -1
  128. package/src/opencode/tui.tsx +4 -2
  129. package/src/runtime/artifact-refusal.ts +63 -0
  130. package/src/runtime/claude-delivery-plan.ts +89 -7
  131. package/src/runtime/commit-attribution-guard.ts +181 -0
  132. package/src/runtime/companion-guidance.ts +15 -3
  133. package/src/runtime/companion-instructions.ts +340 -0
  134. package/src/runtime/error-message.ts +4 -0
  135. package/src/runtime/guidance.ts +0 -17
  136. package/src/runtime/index.ts +5 -0
  137. package/src/runtime/managed-block.ts +117 -0
  138. package/src/runtime/markdown-scan.ts +266 -0
  139. package/src/runtime/policy.ts +16 -15
  140. package/src/runtime/projected-flag.ts +7 -0
  141. package/src/runtime/projected-guidance.ts +1 -0
  142. package/src/runtime/runtime-document-manifest.ts +62 -28
  143. package/src/runtime/secret-guard.ts +148 -0
  144. package/src/runtime/secret-patterns.ts +164 -0
  145. package/src/runtime/secret-scan.ts +90 -0
  146. package/src/runtime/session-kind.ts +13 -0
  147. package/src/runtime/shell-heredoc.ts +81 -0
  148. package/src/runtime/wrap-state.ts +36 -6
  149. package/src/runtime/write-atomic.ts +64 -0
  150. package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +8 -6
  151. package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +3 -5
  152. package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +7 -9
  153. package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
  154. package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +10 -12
  155. package/src/templates/mate-skills/THIRD-PARTY-NOTICES.md +40 -0
  156. package/src/templates/mate-skills/agents/mate-apply-change/SKILL.md +76 -0
  157. package/src/templates/mate-skills/agents/mate-apply-change/references/openspec-apply-change.md +198 -0
  158. package/src/templates/mate-skills/agents/mate-archive-change/SKILL.md +59 -0
  159. package/src/templates/mate-skills/{claude/mate-openspec-backfill → agents/mate-backfill-spec}/SKILL.md +4 -4
  160. package/src/templates/mate-skills/agents/mate-clarify-intent/SKILL.md +95 -0
  161. package/src/templates/mate-skills/agents/mate-explore-change/SKILL.md +50 -0
  162. package/src/templates/mate-skills/agents/mate-explore-change/references/openspec-explore.md +348 -0
  163. package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +3 -3
  164. package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +5 -5
  165. package/src/templates/mate-skills/agents/mate-propose-change/SKILL.md +103 -0
  166. package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-propose.md +167 -0
  167. package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-update-change.md +101 -0
  168. package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/SKILL.md +91 -23
  169. package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
  170. package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +74 -16
  171. package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +74 -68
  172. package/src/templates/mate-skills/claude/mate-apply-change/SKILL.md +76 -0
  173. package/src/templates/mate-skills/claude/mate-apply-change/references/openspec-apply-change.md +198 -0
  174. package/src/templates/mate-skills/claude/mate-archive-change/SKILL.md +59 -0
  175. package/src/templates/mate-skills/{agents/mate-openspec-backfill → claude/mate-backfill-spec}/SKILL.md +4 -4
  176. package/src/templates/mate-skills/claude/mate-clarify-intent/SKILL.md +95 -0
  177. package/src/templates/mate-skills/claude/mate-explore-change/SKILL.md +50 -0
  178. package/src/templates/mate-skills/claude/mate-explore-change/references/openspec-explore.md +348 -0
  179. package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +3 -3
  180. package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +5 -5
  181. package/src/templates/mate-skills/claude/mate-propose-change/SKILL.md +103 -0
  182. package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-propose.md +167 -0
  183. package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-update-change.md +101 -0
  184. package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/SKILL.md +91 -23
  185. package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
  186. package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +74 -16
  187. package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +74 -68
  188. package/src/templates/mate-skills/openspec-references.ts +140 -0
  189. package/src/templates/root/TEMPLATE_AGENTS.md +2 -2
  190. package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3727 -320
  191. package/src/tools/setup/capabilities/context-mode.ts +25 -5
  192. package/src/tools/setup/capabilities/graphify-gate.ts +31 -0
  193. package/src/tools/setup/capabilities/graphify-secrets.ts +255 -0
  194. package/src/tools/setup/capabilities/graphify.ts +12 -0
  195. package/src/tools/setup/capabilities/openspec.ts +273 -270
  196. package/src/tools/setup/capabilities/tokensave.ts +36 -3
  197. package/src/tools/setup/context-services.ts +9 -17
  198. package/src/tools/setup/dynamic-plugins/audiences.ts +7 -39
  199. package/src/tools/setup/dynamic-plugins/declarations.ts +1 -1
  200. package/src/tools/setup/dynamic-plugins/hydrate.ts +4 -7
  201. package/src/tools/setup/dynamic-plugins/install.ts +24 -28
  202. package/src/tools/setup/engine.ts +93 -148
  203. package/src/tools/setup/managed-output.ts +51 -0
  204. package/src/tools/setup/mate.ts +86 -11
  205. package/src/tools/setup/plugin.ts +37 -17
  206. package/src/tools/setup/plugins/gitignore.ts +1 -7
  207. package/src/tools/setup/plugins/guidance.ts +7 -4
  208. package/src/tools/setup/providers/agent-file-sections.ts +4 -2
  209. package/src/tools/setup/providers/claude-attribution.ts +65 -0
  210. package/src/tools/setup/providers/claude-plugin-folders.ts +625 -0
  211. package/src/tools/setup/providers/claude-plugin-hooks.ts +39 -19
  212. package/src/tools/setup/providers/claude.ts +285 -34
  213. package/src/tools/setup/providers/opencode.ts +180 -11
  214. package/src/tools/setup/registry.ts +2 -24
  215. package/src/tools/setup/surface-target.ts +4 -13
  216. package/src/tools/setup/utils.ts +14 -4
  217. package/src/tools/setup.ts +131 -18
  218. package/wrappers/bin/graphify +52 -4
  219. package/wrappers/bin/openspec +33 -9
  220. package/wrappers/lib/graphify-gate.mjs +8 -0
  221. package/opencode-plugin/src/audience-layer.ts +0 -91
  222. package/src/cli/commands/studio/views/workflow/transcript.tsx +0 -190
  223. package/src/runtime/audience-layer.ts +0 -84
  224. package/src/runtime/opencode-audience-layer.ts +0 -176
  225. package/src/tools/setup/audience-isolation.fixture.ts +0 -325
  226. package/src/tools/setup/audience-layer.ts +0 -207
  227. package/src/tools/setup/audience-registrations.ts +0 -52
  228. package/src/tools/setup/contribution-ledger.ts +0 -207
  229. package/src/tools/setup/isolation-diagnostics.ts +0 -139
  230. package/src/tools/setup/runtime-documents.ts +0 -291
  231. package/src/tools/setup/upgrade-sweep.ts +0 -153
@@ -2,28 +2,29 @@
2
2
  import { getActiveDistribution } from "../../distribution";
3
3
  import { FRAMEWORK_NAME } from "../../framework";
4
4
  import type { FrameworkConfig } from "../../lib/orchestrator/types";
5
- import type { RenderedRuntimeDocument } from "../../runtime/runtime-document-manifest";
5
+ import { getClaudePluginRoot } from "../../lib/package-paths";
6
+ import type { RenderedRuntimeDocuments } from "../../runtime/runtime-document-manifest";
6
7
  import { collectHostingProviders, ContextServiceMediator } from "./context-services";
7
- import {
8
- ledgerFromInputs,
9
- ledgerTeardownInput,
10
- readContributionLedger,
11
- writeContributionLedger,
12
- type ContributionLedger,
13
- } from "./contribution-ledger";
14
8
  import type {
15
9
  CapabilityContributionInput,
16
10
  CapabilityPlugin,
17
11
  Plugin,
18
12
  PluginRegistration,
13
+ RuntimeContributionsByRuntime,
19
14
  SetupContext,
20
15
  } from "./plugin";
21
- import { reconcileClaudeContributions } from "./providers/claude";
22
- import { reconcileOpenCodeContributions } from "./providers/opencode";
23
- import { mergeCompanionOpenCodeConfig, renderDocumentsForTarget } from "./runtime-documents";
16
+ import { reconcileClaudeContributions, renderClaudeWorkingDocuments } from "./providers/claude";
17
+ import {
18
+ inspectClaudePluginFolders,
19
+ publishClaudePluginFolders,
20
+ type ClaudePluginFolders,
21
+ } from "./providers/claude-plugin-folders";
22
+ import {
23
+ reconcileOpenCodeContributions,
24
+ renderOpenCodeWorkingDocuments,
25
+ } from "./providers/opencode";
24
26
  import { normalizeRegistration, type NormalizedRegistration } from "./registry";
25
- import { surfaceRoot } from "./surface-target";
26
- import { removeForeignGuidanceBlocks, sweepAudienceDeclarations } from "./upgrade-sweep";
27
+ import { projectionRepoRoot } from "./surface-target";
27
28
 
28
29
  export interface SetupInstallationPlanAction {
29
30
  phase: Plugin["kind"];
@@ -42,25 +43,18 @@ export interface SetupInstallationOutcome {
42
43
  executedActions: SetupInstallationPlanAction[];
43
44
  skippedActions: SetupInstallationPlanAction[];
44
45
  warnings: string[];
45
- }
46
-
47
- /**
48
- * The registrations a pass that writes a persisted surface may reconcile:
49
- * base only. An audience registration takes part in no such pass — neither
50
- * its lifecycle hooks nor its declared contributions — whatever audience is
51
- * active; its contributions reach sessions only through the launch layer.
52
- */
53
- export function persistedRegistrations(
54
- registrations: PluginRegistration[],
55
- ): NormalizedRegistration[] {
56
- return registrations.map(normalizeRegistration).filter((entry) => entry.audience === null);
46
+ /**
47
+ * Enabled Capabilities' Claude plugin folders, published outside a hub (a hub
48
+ * has no wrap for plain sessions to load them through); never printed here.
49
+ */
50
+ claudePluginFolders: ClaudePluginFolders;
57
51
  }
58
52
 
59
53
  export function buildSetupInstallationPlan(
60
54
  config: FrameworkConfig,
61
55
  registrations: PluginRegistration[],
62
56
  ): SetupInstallationPlan {
63
- const entries = persistedRegistrations(registrations);
57
+ const entries = registrations.map(normalizeRegistration);
64
58
  // Required plugins are always treated as enabled: saved selections cannot
65
59
  // deselect them and deselection never produces a teardown action.
66
60
  const isEntryEnabled = (entry: NormalizedRegistration) =>
@@ -130,7 +124,7 @@ export async function executeSetupInstallationPlan(
130
124
  registrations: PluginRegistration[],
131
125
  plan: SetupInstallationPlan,
132
126
  ): Promise<SetupInstallationOutcome> {
133
- const plugins = persistedRegistrations(registrations).map((entry) => entry.plugin);
127
+ const plugins = registrations.map((entry) => normalizeRegistration(entry).plugin);
134
128
  const pluginById = new Map(plugins.map((plugin) => [plugin.id, plugin]));
135
129
  const executedActions: SetupInstallationPlanAction[] = [];
136
130
  const skippedActions: SetupInstallationPlanAction[] = [];
@@ -197,22 +191,19 @@ export async function executeSetupInstallationPlan(
197
191
 
198
192
  await mediator.finalize();
199
193
 
200
- const reconcileWarnings = await reconcileCapabilityContributions(
201
- ctx,
202
- plugins,
203
- plan,
204
- registrations.map(normalizeRegistration),
205
- );
206
- for (const warning of reconcileWarnings) {
207
- warnings.push(warning);
208
- process.stderr.write(`${warning}\n`);
209
- }
194
+ const inputsByRuntime = await reconcileCapabilityContributions(ctx, plugins, plan);
195
+ const claudeInputs = inputsByRuntime.get("claude") ?? [];
196
+ const folderOptions = { bundledPluginRoot: getClaudePluginRoot() };
197
+ const claudePluginFolders =
198
+ ctx.scope === "hub"
199
+ ? await inspectClaudePluginFolders(claudeInputs, folderOptions)
200
+ : await publishClaudePluginFolders(claudeInputs, folderOptions);
210
201
 
211
- return { executedActions, skippedActions, warnings };
202
+ return { executedActions, skippedActions, warnings, claudePluginFolders };
212
203
  }
213
204
 
214
205
  // The Runtime Surface of each active Agent Runtime, by runtime id.
215
- export const RUNTIME_SURFACE_RECONCILERS: Record<
206
+ const RUNTIME_SURFACE_RECONCILERS: Record<
216
207
  string,
217
208
  (ctx: SetupContext, inputs: CapabilityContributionInput[]) => Promise<void>
218
209
  > = {
@@ -220,6 +211,18 @@ export const RUNTIME_SURFACE_RECONCILERS: Record<
220
211
  opencode: reconcileOpenCodeContributions,
221
212
  };
222
213
 
214
+ /**
215
+ * The working-target render of each Runtime Surface, by runtime id, in the
216
+ * order its documents are rendered.
217
+ */
218
+ const RUNTIME_SURFACE_WORKING_RENDERERS: Record<
219
+ string,
220
+ (ctx: SetupContext, inputs: CapabilityContributionInput[]) => Promise<RenderedRuntimeDocuments>
221
+ > = {
222
+ claude: renderClaudeWorkingDocuments,
223
+ opencode: renderOpenCodeWorkingDocuments,
224
+ };
225
+
223
226
  /**
224
227
  * The declared Capability contributions of one pass, keyed by runtime. Shared
225
228
  * by both targets: what a companion pass writes is what a working pass renders.
@@ -228,6 +231,7 @@ async function collectContributionInputs(
228
231
  ctx: SetupContext,
229
232
  plugins: Plugin[],
230
233
  plan: SetupInstallationPlan,
234
+ onContributionError?: ContributionErrorHandler,
231
235
  ): Promise<Map<string, CapabilityContributionInput[]>> {
232
236
  // The plan already resolved enablement (policy, saved selection, package
233
237
  // manager requirements); mirror it instead of recomputing.
@@ -243,7 +247,14 @@ async function collectContributionInputs(
243
247
  const capability = plugin as CapabilityPlugin;
244
248
  if (!capability.getRuntimeContributions) continue;
245
249
  const enabled = enabledByPluginId.get(capability.id) ?? false;
246
- const byRuntime = await capability.getRuntimeContributions(ctx);
250
+ let byRuntime: RuntimeContributionsByRuntime;
251
+ try {
252
+ byRuntime = await capability.getRuntimeContributions(ctx);
253
+ } catch (error) {
254
+ if (!onContributionError) throw error;
255
+ onContributionError(capability.id, error);
256
+ continue;
257
+ }
247
258
  for (const [runtimeId, contributions] of Object.entries(byRuntime)) {
248
259
  if (!contributions) continue;
249
260
  // Inactive runtimes still reconcile — with everything disabled — so a
@@ -267,143 +278,77 @@ async function collectContributionInputs(
267
278
  * Reconcile declared Capability contributions through every active runtime's
268
279
  * Runtime Surface (spec: plugin-engine). Runs after all plugin phases, so
269
280
  * providers are always done first. Disabled capabilities participate with
270
- * `enabled: false` and drive teardown of their managed entries.
281
+ * `enabled: false` and drive teardown of their managed entries. Returns the
282
+ * inputs it reconciled, so the pass derives launch-only data from the same ones.
271
283
  */
272
284
  async function reconcileCapabilityContributions(
273
285
  ctx: SetupContext,
274
286
  plugins: Plugin[],
275
287
  plan: SetupInstallationPlan,
276
- registrations: NormalizedRegistration[],
277
- ): Promise<string[]> {
288
+ ): Promise<Map<string, CapabilityContributionInput[]>> {
278
289
  const inputsByRuntime = await collectContributionInputs(ctx, plugins, plan);
279
- const ledgerRoot = surfaceRoot(ctx);
280
- const ledger = await readContributionLedger(ledgerRoot);
281
- const warnings: string[] = [];
282
- let unswept = ledger?.unswept ?? [];
283
- if (!ledger || unswept.length > 0) {
284
- if (!ledger) {
285
- await removeForeignGuidanceBlocks(ledgerRoot, new Set(plugins.map((plugin) => plugin.id)));
286
- }
287
- const sweep = await sweepAudienceDeclarations(ctx, {
288
- registrations,
289
- ...(ledger ? { packages: new Set(unswept) } : {}),
290
- });
291
- prependTeardown(inputsByRuntime, sweep.teardown);
292
- unswept = sweep.unswept;
293
- warnings.push(...sweep.warnings);
294
- }
295
- prependLedgerTeardown(inputsByRuntime, ledger);
296
290
  for (const [runtimeId, inputs] of inputsByRuntime) {
297
291
  const reconcile = RUNTIME_SURFACE_RECONCILERS[runtimeId];
298
292
  if (!reconcile || inputs.length === 0) continue;
299
293
  await reconcile(ctx, inputs);
300
294
  }
301
- await writeContributionLedger(ledgerRoot, ledgerFromInputs(inputsByRuntime, unswept));
302
- return warnings;
303
- }
304
-
305
- /**
306
- * Disabled inputs placed first, skipping any owner that is an enabled input
307
- * of the pass on that runtime: removal runs before enabled inputs apply.
308
- */
309
- function prependTeardown(
310
- inputsByRuntime: Map<string, CapabilityContributionInput[]>,
311
- teardownByRuntime: ReadonlyMap<string, readonly CapabilityContributionInput[]>,
312
- ): void {
313
- for (const [runtimeId, teardown] of teardownByRuntime) {
314
- const inputs = inputsByRuntime.get(runtimeId) ?? [];
315
- const enabled = new Set(inputs.filter((input) => input.enabled).map((input) => input.pluginId));
316
- const kept = teardown.filter((input) => !enabled.has(input.pluginId));
317
- if (kept.length > 0) inputsByRuntime.set(runtimeId, [...kept, ...inputs]);
318
- }
295
+ return inputsByRuntime;
319
296
  }
320
297
 
321
- /**
322
- * Every recorded owner that is not an enabled input of this pass, as a
323
- * disabled input placed first: its entries are removed before enabled inputs
324
- * apply, so an identity another plugin still declares is re-applied after.
325
- * Entries the ledger does not record are never named, so they survive.
326
- */
327
- function prependLedgerTeardown(
328
- inputsByRuntime: Map<string, CapabilityContributionInput[]>,
329
- ledger: ContributionLedger | null,
330
- ): void {
331
- prependTeardown(
332
- inputsByRuntime,
333
- new Map(
334
- Object.entries(ledger?.runtimes ?? {}).map(([runtimeId, owners]) => [
335
- runtimeId,
336
- Object.entries(owners).map(([owner, identities]) => ledgerTeardownInput(owner, identities)),
337
- ]),
338
- ),
339
- );
340
- }
298
+ /** Receives one Capability's rejected contributions; that Capability then contributes nothing. */
299
+ export type ContributionErrorHandler = (capabilityId: string, error: unknown) => void;
341
300
 
342
301
  /**
343
- * The declared contributions of one audience's plugins, as enabled inputs per
344
- * active runtime. No plugin phase and no lifecycle hook runs: an audience
345
- * plugin contributes declaratively only. OpenCode plugin references are
346
- * dropped — the Mate plugin has no way to load another plugin.
302
+ * One pass's declared Capability contributions by runtime, collected without a
303
+ * reconcile. A rejecting Capability fails the collection unless
304
+ * `onContributionError` takes its error; only diagnostics should pass one.
347
305
  */
348
- export async function collectAudienceInputs(
306
+ export async function collectRuntimeContributionInputs(
349
307
  ctx: SetupContext,
350
308
  registrations: PluginRegistration[],
351
- audience: string,
309
+ plan: SetupInstallationPlan,
310
+ onContributionError?: ContributionErrorHandler,
352
311
  ): Promise<Map<string, CapabilityContributionInput[]>> {
353
- const inputsByRuntime = new Map<string, CapabilityContributionInput[]>();
354
- for (const entry of registrations.map(normalizeRegistration)) {
355
- if (entry.audience !== audience || entry.plugin.kind !== "capability") continue;
356
- const capability = entry.plugin as CapabilityPlugin;
357
- if (!capability.getRuntimeContributions) continue;
358
- if (entry.policy !== "required" && !capability.isEnabled(ctx.config)) continue;
359
- const byRuntime = await capability.getRuntimeContributions(ctx);
360
- for (const [runtimeId, contributions] of Object.entries(byRuntime)) {
361
- if (!contributions || !ctx.activeProviders.includes(runtimeId)) continue;
362
- const { pluginReferences: _dropped, ...declarative } = contributions;
363
- const inputs = inputsByRuntime.get(runtimeId) ?? [];
364
- inputs.push({
365
- pluginId: capability.id,
366
- enabled: true,
367
- capabilityEnabled: true,
368
- contributions: declarative,
369
- });
370
- inputsByRuntime.set(runtimeId, inputs);
371
- }
372
- }
373
- return inputsByRuntime;
374
- }
375
-
376
- /** What a render returns: its warnings are data for the caller, never printed here. */
377
- export interface RenderedRuntimeDocuments {
378
- documents: RenderedRuntimeDocument[];
379
- warnings: string[];
312
+ const plugins = registrations.map((entry) => normalizeRegistration(entry).plugin);
313
+ return collectContributionInputs(ctx, plugins, plan, onContributionError);
380
314
  }
381
315
 
382
316
  /**
383
317
  * The same declarations, rendered rather than written: a working-target pass
384
318
  * produces the documents and the Projection Root places them, so no Runtime
385
- * Surface reaches inside a Working Repository.
319
+ * Surface reaches inside a Working Repository. Refuses before anything renders
320
+ * when the context has no Working Repository: every document is addressed by it.
321
+ * Its warnings are data for the caller, never printed here.
386
322
  */
387
323
  export async function renderRuntimeDocuments(
388
324
  ctx: SetupContext,
389
325
  registrations: PluginRegistration[],
390
326
  plan: SetupInstallationPlan,
391
- wrapped?: { audience: string; registrations: PluginRegistration[] },
392
327
  ): Promise<RenderedRuntimeDocuments> {
393
- const plugins = persistedRegistrations(registrations).map((entry) => entry.plugin);
394
- const inputsByRuntime = await collectContributionInputs(ctx, plugins, plan);
395
- /** A checkout wrapped for an audience carries it beside base; no other audience ever. */
396
- if (wrapped) {
397
- const audienceInputs = await collectAudienceInputs(
398
- ctx,
399
- wrapped.registrations,
400
- wrapped.audience,
401
- );
402
- for (const [runtimeId, inputs] of audienceInputs) {
403
- inputsByRuntime.set(runtimeId, [...(inputsByRuntime.get(runtimeId) ?? []), ...inputs]);
404
- }
328
+ projectionRepoRoot(ctx);
329
+ const inputsByRuntime = await collectRuntimeContributionInputs(ctx, registrations, plan);
330
+ const rendered: RenderedRuntimeDocuments = { documents: [], warnings: [] };
331
+ for (const [runtimeId, render] of Object.entries(RUNTIME_SURFACE_WORKING_RENDERERS)) {
332
+ if (!ctx.activeProviders.includes(runtimeId)) continue;
333
+ const { documents, warnings } = await render(ctx, inputsByRuntime.get(runtimeId) ?? []);
334
+ rendered.documents.push(...documents);
335
+ rendered.warnings.push(...warnings);
405
336
  }
406
- const documents = renderDocumentsForTarget(ctx, inputsByRuntime);
407
- if (ctx.target !== "working") return { documents, warnings: [] };
408
- return mergeCompanionOpenCodeConfig(ctx.companionPath, documents);
337
+ return rendered;
338
+ }
339
+
340
+ /**
341
+ * Publishes the enabled Claude plugin folders for a wrap pass, before it
342
+ * renders: writes Mate's marketplace files and removes those of disabled
343
+ * Capabilities. Runs no plugin phase.
344
+ */
345
+ export async function publishRuntimeClaudePluginMarketplaces(
346
+ ctx: SetupContext,
347
+ registrations: PluginRegistration[],
348
+ plan: SetupInstallationPlan,
349
+ ): Promise<ClaudePluginFolders> {
350
+ const inputsByRuntime = await collectRuntimeContributionInputs(ctx, registrations, plan);
351
+ return publishClaudePluginFolders(inputsByRuntime.get("claude") ?? [], {
352
+ bundledPluginRoot: getClaudePluginRoot(),
353
+ });
409
354
  }
@@ -0,0 +1,51 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import type {
5
+ ManagedOutputEntry,
6
+ ManagedOutputOwner,
7
+ ManagedOutputRecorder,
8
+ ManagedOutputReport,
9
+ } from "./plugin";
10
+
11
+ export interface ManagedOutputCollector {
12
+ recorder: ManagedOutputRecorder;
13
+ /** Entries inside the companion, deduplicated, as companion-relative POSIX paths. */
14
+ report(): ManagedOutputReport;
15
+ }
16
+
17
+ export function createManagedOutputCollector(companionPath: string): ManagedOutputCollector {
18
+ const entries: ManagedOutputEntry[] = [];
19
+ return {
20
+ recorder: { record: (entry) => entries.push(entry) },
21
+ report() {
22
+ const seen = new Set<string>();
23
+ const report: ManagedOutputEntry[] = [];
24
+ for (const entry of entries) {
25
+ const relative = path.relative(companionPath, entry.path);
26
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) continue;
27
+ const posix = relative.split(path.sep).join("/");
28
+ const key = `${entry.change}\0${entry.owner}\0${posix}`;
29
+ if (seen.has(key)) continue;
30
+ seen.add(key);
31
+ report.push({ ...entry, path: posix });
32
+ }
33
+ return report;
34
+ },
35
+ };
36
+ }
37
+
38
+ /** Removes `target` and records it, only when it existed: a no-op removal claims nothing. */
39
+ export async function removeRecorded(
40
+ target: string,
41
+ owner: ManagedOutputOwner,
42
+ recorder?: ManagedOutputRecorder,
43
+ ): Promise<void> {
44
+ const existed = await fs.lstat(target).then(
45
+ () => true,
46
+ () => false,
47
+ );
48
+ if (!existed) return;
49
+ await fs.rm(target, { recursive: true, force: true });
50
+ recorder?.record({ path: target, change: "removed", owner });
51
+ }
@@ -1,13 +1,20 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
 
4
+ import { removeRecorded } from "./managed-output";
5
+ import type { ManagedOutputRecorder } from "./plugin";
4
6
  import { pruneEmptyAncestors } from "./utils";
7
+ import { readFrontmatter } from "../../lib/frontmatter";
5
8
 
6
- export const MATE_ARTIFACT_SKILLS = ["mate-artifact-publish"] as const;
7
9
  export const MATE_SKILLS = [
8
- "mate-artifact-publish",
10
+ "mate-clarify-intent",
11
+ "mate-explore-change",
12
+ "mate-propose-change",
13
+ "mate-apply-change",
14
+ "mate-archive-change",
15
+ "mate-publish-artifact",
16
+ "mate-backfill-spec",
9
17
  "mate-create-report",
10
- "mate-openspec-backfill",
11
18
  "mate-interview-me",
12
19
  "mate-grill-me",
13
20
  "mate-grilling",
@@ -17,8 +24,11 @@ export const MATE_SKILLS = [
17
24
  "mate-simplify-code",
18
25
  ] as const;
19
26
  const LEGACY_MATE_SKILLS = ["mate-artifact-finish", "mate-openspec-artifact-finish"] as const;
20
- /** Every Mate-owned skill name a skill root may hold, current or retired. */
21
- export const MANAGED_MATE_SKILL_NAMES = [...MATE_SKILLS, ...LEGACY_MATE_SKILLS] as const;
27
+ /**
28
+ * Renamed without aliases: removed only when Mate wrote them (`metadata.author: mate`),
29
+ * so a user-authored skill that happens to share a retired name is left alone.
30
+ */
31
+ const RETIRED_MATE_SKILLS = ["mate-artifact-publish", "mate-openspec-backfill"] as const;
22
32
 
23
33
  const MATE_SKILLS_SOURCE = path.join(import.meta.dirname, "../../templates/mate-skills");
24
34
 
@@ -83,13 +93,41 @@ A \`diff\` section carries a unified diff patch as produced by \`git diff\`. The
83
93
 
84
94
  A report carrying a mermaid payload loads the pinned, integrity-checked diagram runtime from a CDN; a report without one does not. Offline, each diagram stays readable as its source. Otherwise the page stays a single self-contained file.
85
95
 
96
+ ## Presentation Fields
97
+
98
+ These fields are optional. A document without them stays valid and renders with the defaults.
99
+
100
+ - \`tone\` on a status item: \`ok\`, \`warn\`, \`error\`, \`info\`, or \`neutral\`. It sets the marker's colour and shape. When absent, the tone is inferred from well-known status words such as \`passed\`, \`stale\`, or \`failed\`, and any other text is \`neutral\`.
101
+
102
+ \`\`\`json
103
+ { "label": "Build", "status": "flaky", "tone": "warn" }
104
+ \`\`\`
105
+
106
+ - \`format\` on a \`text\` section: \`plain\` (default, shown verbatim with its line breaks) or \`markdown\`.
107
+
108
+ \`\`\`json
109
+ { "id": "notes", "title": "Notes", "type": "text", "format": "markdown", "content": "- **Renderer** owns layout\\n- See \`highlight.ts\`" }
110
+ \`\`\`
111
+
112
+ - \`caption\` on a \`diagram\` or \`diff\` section: a non-empty Markdown note shown beneath the visual, in the same section and without a heading of its own.
113
+
114
+ \`\`\`json
115
+ { "id": "changes", "title": "Changes", "type": "diff", "patch": "...", "caption": "Moves validation into \`contract.ts\`." }
116
+ \`\`\`
117
+
118
+ Markdown supports paragraphs, \`#\` headings, one-level \`-\`, \`*\`, or \`1.\` lists, \`**strong**\`, \`*emphasis*\`, \`\` \`code\` \`\`, fenced code blocks with syntax highlighting, and \`[text](https://…)\` links. Raw HTML is shown as text, links other than \`http\` or \`https\` are shown as plain text, and any other syntax stays literal.
119
+
120
+ The page follows the reader's colour scheme: Mate brand colours on white in light mode, and on a dark background in dark mode, with highlighted code and mermaid diagrams following the same scheme. It always prints as dark text on white.
121
+
122
+ To mark mermaid nodes, apply the theme classes \`added\`, \`changed\`, and \`removed\` with \`class\` or \`:::\`; they follow the colour scheme. Lines that fix a colour with \`classDef\`, \`style\`, or \`linkStyle\` are dropped from the dark copy, because a fill chosen for a white page hides the labels there.
123
+
86
124
  ## Invocation
87
125
 
88
126
  1. Serialize the complete document to a temporary file or pipe it to stdin.
89
127
  2. Run \`mate report --input <file-or->\`.
90
128
  3. Add \`--json\` when the caller needs normalized JSON instead of browser delivery.
91
129
 
92
- The CLI chooses the delivery surface; deliver only through \`mate report\`, never a hand-written file, server, or browser launch. Inside a Studio terminal it hosts the report in Studio and prints a full link only when Studio has a public address, otherwise it tells the operator to open Studio → Reports; relay that output. Elsewhere, or when Studio is unreachable, it writes self-contained HTML to a unique OS temporary directory and opens it in the default browser. The report includes a visible "Print / Save as PDF" control that calls the browser's native print dialog. If HTML delivery fails, the CLI warns on stderr and emits the complete report document as JSON on stdout.
130
+ The CLI chooses the delivery surface; deliver only through \`mate report\`, never a hand-written file, server, or browser launch. Inside a Studio terminal it hosts the report in Studio and prints a full link only when Studio has a public address, otherwise it tells the operator to open Studio → Reports; relay that output. Elsewhere, or when Studio is unreachable, it writes self-contained HTML to a unique OS temporary directory and opens it in the default browser. The report header carries an icon-only print control, named "Print / Save as PDF", that calls the browser's native print dialog. If HTML delivery fails, the CLI warns on stderr and emits the complete report document as JSON on stdout.
93
131
 
94
132
  The built-in \`mate report\` path collects Mate usage data and adapts it to the same contract and renderer.
95
133
  `;
@@ -133,10 +171,41 @@ async function resolveMateSkillSource(skill: string, tool: string): Promise<stri
133
171
  throw new Error(`Mate skill source not found: ${skill}`);
134
172
  }
135
173
 
136
- export async function applyMateSkills(skillsDir: string, tool: string): Promise<void> {
174
+ /** The `metadata.author` a skill directory's SKILL.md declares, if any. */
175
+ export async function readSkillAuthor(skillDir: string): Promise<unknown> {
176
+ let source: string;
177
+ try {
178
+ source = await fs.readFile(path.join(skillDir, "SKILL.md"), "utf8");
179
+ } catch {
180
+ return undefined;
181
+ }
182
+ const metadata = readFrontmatter(source)?.metadata as { author?: unknown } | null | undefined;
183
+ return metadata?.author;
184
+ }
185
+
186
+ async function isMateAuthored(skillDir: string): Promise<boolean> {
187
+ return (await readSkillAuthor(skillDir)) === "mate";
188
+ }
189
+
190
+ export async function removeRetiredMateSkills(
191
+ skillsDir: string,
192
+ recorder?: ManagedOutputRecorder,
193
+ ): Promise<void> {
194
+ for (const skill of RETIRED_MATE_SKILLS) {
195
+ const dir = path.join(skillsDir, skill);
196
+ if (await isMateAuthored(dir)) await removeRecorded(dir, "mate", recorder);
197
+ }
198
+ }
199
+
200
+ export async function applyMateSkills(
201
+ skillsDir: string,
202
+ tool: string,
203
+ recorder?: ManagedOutputRecorder,
204
+ ): Promise<void> {
137
205
  for (const skill of LEGACY_MATE_SKILLS) {
138
- await fs.rm(path.join(skillsDir, skill), { recursive: true, force: true });
206
+ await removeRecorded(path.join(skillsDir, skill), "mate", recorder);
139
207
  }
208
+ await removeRetiredMateSkills(skillsDir, recorder);
140
209
  for (const skill of MATE_SKILLS) {
141
210
  const destination = path.join(skillsDir, skill);
142
211
  if (skill === "mate-create-report") {
@@ -145,13 +214,19 @@ export async function applyMateSkills(skillsDir: string, tool: string): Promise<
145
214
  } else {
146
215
  await deployMateSkillDir(await resolveMateSkillSource(skill, tool), destination);
147
216
  }
217
+ recorder?.record({ path: destination, change: "written", owner: "mate" });
148
218
  }
149
219
  }
150
220
 
151
- export async function teardownMateSkills(skillsDir: string, companionPath: string): Promise<void> {
152
- for (const skill of MANAGED_MATE_SKILL_NAMES) {
221
+ export async function teardownMateSkills(
222
+ skillsDir: string,
223
+ companionPath: string,
224
+ recorder?: ManagedOutputRecorder,
225
+ ): Promise<void> {
226
+ await removeRetiredMateSkills(skillsDir, recorder);
227
+ for (const skill of [...MATE_SKILLS, ...LEGACY_MATE_SKILLS]) {
153
228
  try {
154
- await fs.rm(path.join(skillsDir, skill), { recursive: true, force: true });
229
+ await removeRecorded(path.join(skillsDir, skill), "mate", recorder);
155
230
  } catch {
156
231
  /* not present */
157
232
  }
@@ -1,4 +1,5 @@
1
1
  import type { FrameworkConfig, LinkedRepository } from "../../lib/orchestrator/types";
2
+ import type { SessionKind } from "../../runtime/session-kind";
2
3
  import type { InstallRequirement, InstallRequirementContext } from "./install-contract";
3
4
  import type { ClaudeHookGroup } from "./providers/claude-format";
4
5
 
@@ -7,10 +8,9 @@ export type SetupScope = "companion" | "hub";
7
8
  /**
8
9
  * Where a reconcile pass lands its documents. `companion` writes the material's
9
10
  * own repository; `working` renders the documents an Agent Runtime discovers by
10
- * its own upward walk and leaves the placing to the Projection Root;
11
- * `audience-layer` writes one audience's launch layer, outside every repository.
11
+ * its own upward walk and leaves the placing to the Projection Root.
12
12
  */
13
- export type ProjectionTarget = "companion" | "working" | "audience-layer";
13
+ export type ProjectionTarget = "companion" | "working";
14
14
 
15
15
  export interface SetupContext {
16
16
  companionPath: string;
@@ -23,14 +23,11 @@ export interface SetupContext {
23
23
  repoPath?: string;
24
24
  /** Defaults to `companion`, so a caller that names no target is unchanged. */
25
25
  target?: ProjectionTarget;
26
- /** The audience an `audience-layer` pass renders; required by that target only. */
27
- audience?: string;
28
26
  /**
29
- * Where an `audience-layer` pass writes: the staging directory a render is
30
- * built in before it is renamed into place. The audience's layer directory
31
- * when absent.
27
+ * Asks the OpenSpec capability to regenerate its CLI output in `sync` mode.
28
+ * Only `mate companion update` sets it; launches leave committed output alone.
32
29
  */
33
- layerRoot?: string;
30
+ regenerateOpenSpec?: boolean;
34
31
  /**
35
32
  * Provider-mediated context services. Always populated when the engine
36
33
  * executes a plugin; optional so hand-built contexts (tests, direct calls)
@@ -39,13 +36,34 @@ export interface SetupContext {
39
36
  mcp?: McpService;
40
37
  instructions?: InstructionsService;
41
38
  templates?: TemplatesService;
39
+ /** Collects the managed output a sync pass touched; absent outside one. */
40
+ managedOutput?: ManagedOutputRecorder;
42
41
  }
43
42
 
43
+ export type ManagedOutputOwner = "mate" | "openspec";
44
+
45
+ /** One path a pass wrote or removed, with the side that owns it. */
46
+ export interface ManagedOutputEntry {
47
+ /** Absolute when recorded; companion-relative POSIX in a {@link ManagedOutputReport}. */
48
+ path: string;
49
+ change: "written" | "removed";
50
+ owner: ManagedOutputOwner;
51
+ }
52
+
53
+ export interface ManagedOutputRecorder {
54
+ record(entry: ManagedOutputEntry): void;
55
+ }
56
+
57
+ /** What a sync pass reports it touched, as companion-relative POSIX paths. */
58
+ export type ManagedOutputReport = readonly ManagedOutputEntry[];
59
+
44
60
  export interface LaunchPreflightContext {
45
61
  companionPath: string;
46
62
  config: FrameworkConfig;
47
63
  repository?: LinkedRepository;
48
64
  providerId: string;
65
+ /** The session the prepare answers for, so a check can skip what that session does not load. */
66
+ sessionKind: SessionKind;
49
67
  }
50
68
 
51
69
  /** Provider-agnostic description of an MCP server to register. */
@@ -131,15 +149,9 @@ export type PluginPolicy = "required" | "default" | "optional";
131
149
 
132
150
  /**
133
151
  * A plugin registration entry. Bare plugins derive their policy from
134
- * `defaultSelected` (`true` → "default", `false` → "optional"). `audience` is
135
- * the source of a declared plugin: `null` (or absent) for compiled-in plugins
136
- * and top-level `plugins` declarations, the audience name for a declaration
137
- * under `audiences.<name>.plugins`. `packageName` names the declared package a
138
- * dynamic plugin was loaded from.
152
+ * `defaultSelected` (`true` → "default", `false` → "optional").
139
153
  */
140
- export type PluginRegistration =
141
- | Plugin
142
- | { plugin: Plugin; policy: PluginPolicy; audience?: string | null; packageName?: string };
154
+ export type PluginRegistration = Plugin | { plugin: Plugin; policy: PluginPolicy };
143
155
 
144
156
  /**
145
157
  * A hook group a Capability contributes to a runtime's settings. `marker` is
@@ -203,10 +215,17 @@ export interface AgentDefinitionContribution {
203
215
  content: string;
204
216
  }
205
217
 
218
+ /** A Claude Code plugin folder, declared under the `claude` key. Launch-only: never written to a file. */
219
+ export interface PluginDirectoryContribution {
220
+ /** Absolute path of the folder holding `.claude-plugin/plugin.json`. */
221
+ path: string;
222
+ }
223
+
206
224
  /**
207
225
  * Declarative Agent Runtime contributions of one Capability for one runtime.
208
226
  * The runtime's Runtime Surface reconciles these symmetrically: applied while
209
227
  * the Capability is enabled, removed when it is not, idempotent across runs.
228
+ * `pluginDirectories` is the exception: gathered for the launch, written nowhere.
210
229
  */
211
230
  export interface RuntimeContributions {
212
231
  mcpServers?: McpServerDescriptor[];
@@ -216,6 +235,7 @@ export interface RuntimeContributions {
216
235
  skillTrees?: SkillTreeContribution[];
217
236
  pluginReferences?: PluginReferenceContribution[];
218
237
  agentDefinitions?: AgentDefinitionContribution[];
238
+ pluginDirectories?: PluginDirectoryContribution[];
219
239
  }
220
240
 
221
241
  /**