@uniqbit/mate-core 0.17.0-canary.8 → 0.17.1

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 (103) hide show
  1. package/package.json +11 -5
  2. package/src/cli/commands/artifact/finish/index.ts +0 -9
  3. package/src/cli/commands/artifact/finish/openspec.ts +2 -2
  4. package/src/cli/commands/artifact/finish/registry.ts +1 -1
  5. package/src/cli/commands/artifact/pending/discovery.ts +1 -1
  6. package/src/cli/commands/artifact/pending/index.ts +0 -16
  7. package/src/cli/commands/cap/graphify.ts +1 -1
  8. package/src/cli/commands/cap/index-cmd.ts +1 -1
  9. package/src/cli/commands/cap/tokensave.ts +4 -3
  10. package/src/cli/commands/companion/companion.ts +4 -0
  11. package/src/cli/commands/companion/hub.ts +2 -1
  12. package/src/cli/commands/companion/list.ts +2 -1
  13. package/src/cli/commands/companion/prepare.ts +2 -2
  14. package/src/cli/commands/companion/update.ts +334 -0
  15. package/src/cli/commands/doctor.ts +2 -1
  16. package/src/cli/commands/install.ts +59 -5
  17. package/src/cli/commands/launch/shared.ts +2 -1
  18. package/src/cli/commands/plugin/plugin.ts +4 -0
  19. package/src/cli/commands/plugin/verify.ts +25 -0
  20. package/src/cli/commands/report/highlight.ts +1 -4
  21. package/src/cli/commands/report/renderer.ts +0 -2
  22. package/src/cli/commands/studio/access.ts +201 -0
  23. package/src/cli/commands/studio/index.ts +207 -26
  24. package/src/cli/commands/studio/openspec-cli.ts +1 -1
  25. package/src/cli/commands/studio/selection.ts +127 -11
  26. package/src/cli/commands/studio/server.ts +355 -64
  27. package/src/cli/commands/studio/terminal-assets.ts +50 -0
  28. package/src/cli/commands/studio/terminal-launch.ts +71 -0
  29. package/src/cli/commands/studio/terminal.ts +593 -0
  30. package/src/cli/commands/studio/vault.ts +320 -75
  31. package/src/cli/commands/studio/views/client.ts +90 -9
  32. package/src/cli/commands/studio/views/companion-picker.tsx +9 -5
  33. package/src/cli/commands/studio/views/companion-selector.tsx +11 -7
  34. package/src/cli/commands/studio/views/document.tsx +411 -107
  35. package/src/cli/commands/studio/views/model.ts +14 -1
  36. package/src/cli/commands/studio/views/selection-fields.tsx +14 -0
  37. package/src/cli/commands/studio/views/styles.ts +106 -13
  38. package/src/cli/commands/studio/views/terminal.tsx +426 -0
  39. package/src/cli/commands/studio/views/vault/state.ts +42 -0
  40. package/src/cli/commands/studio/views/workflow/steps.ts +10 -3
  41. package/src/cli/commands/update.ts +11 -8
  42. package/src/cli/companion-link-wizard.tsx +1 -1
  43. package/src/cli/companion-selector.tsx +1 -1
  44. package/src/cli/launch-selector.tsx +1 -1
  45. package/src/cli/main.ts +2 -1
  46. package/src/cli/usage.ts +7 -3
  47. package/src/cli/write-json-stdout.ts +2 -2
  48. package/src/lib/components/spinner.tsx +2 -2
  49. package/src/lib/components/startup-progress.tsx +1 -1
  50. package/src/lib/context-mode-package.ts +1 -1
  51. package/src/lib/opencode-plugin-package.ts +3 -0
  52. package/src/lib/orchestrator/adapters/opencode.ts +53 -13
  53. package/src/lib/orchestrator/companion-hub.ts +3 -3
  54. package/src/lib/orchestrator/companion-registry-store.ts +1 -1
  55. package/src/lib/orchestrator/config-store.ts +2 -2
  56. package/src/lib/orchestrator/editor.ts +4 -12
  57. package/src/lib/orchestrator/engine-guard.ts +9 -1
  58. package/src/lib/orchestrator/projection-entries.ts +1 -1
  59. package/src/lib/orchestrator/projection-record.ts +1 -1
  60. package/src/lib/orchestrator/projection-types.ts +0 -4
  61. package/src/lib/orchestrator/repo-local-registry.ts +0 -1
  62. package/src/lib/orchestrator/root-context.ts +1 -1
  63. package/src/lib/orchestrator/setup-compatibilities.ts +1 -1
  64. package/src/lib/orchestrator/setup-preflight.ts +1 -1
  65. package/src/lib/orchestrator/types.ts +1 -1
  66. package/src/lib/orchestrator/working-repo-projection.ts +1 -1
  67. package/src/lib/orchestrator/workspace-inventory.ts +2 -2
  68. package/src/lib/package-paths.ts +1 -11
  69. package/src/lib/package-root.ts +43 -0
  70. package/src/lib/prebuilt-workspace.ts +1 -1
  71. package/src/lib/public-npm.ts +1 -1
  72. package/src/opencode/companion-hooks.ts +131 -153
  73. package/src/opencode/index.ts +12 -6
  74. package/src/opencode/tui.tsx +46 -49
  75. package/src/playbooks/companion-guidance.ts +1 -28
  76. package/src/runtime/companion-sync.ts +0 -3
  77. package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +2 -1
  78. package/src/templates/providers/opencode/opencode.json +6 -4
  79. package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +500 -408
  80. package/src/tools/setup/capabilities/context-mode.ts +45 -25
  81. package/src/tools/setup/capabilities/graphify-shared.ts +3 -3
  82. package/src/tools/setup/capabilities/graphify.ts +2 -2
  83. package/src/tools/setup/capabilities/openspec.ts +2 -7
  84. package/src/tools/setup/capabilities/react-doctor.ts +1 -1
  85. package/src/tools/setup/capabilities/rtk.ts +2 -2
  86. package/src/tools/setup/capabilities/tokensave.ts +98 -12
  87. package/src/tools/setup/context-services.ts +3 -6
  88. package/src/tools/setup/dynamic-plugins/declarations.ts +47 -0
  89. package/src/tools/setup/dynamic-plugins/frozen.ts +215 -0
  90. package/src/tools/setup/dynamic-plugins/hydrate.ts +1 -40
  91. package/src/tools/setup/dynamic-plugins/loader.ts +16 -0
  92. package/src/tools/setup/dynamic-plugins/policy.ts +55 -0
  93. package/src/tools/setup/dynamic-plugins/staging.ts +53 -0
  94. package/src/tools/setup/dynamic-plugins/verify.ts +70 -0
  95. package/src/tools/setup/mate.ts +1 -1
  96. package/src/tools/setup/providers/claude-format.ts +1 -1
  97. package/src/tools/setup/providers/claude.ts +1 -1
  98. package/src/tools/setup/providers/opencode-format.ts +220 -25
  99. package/src/tools/setup/providers/opencode.ts +63 -21
  100. package/src/tools/setup/runtime-documents.ts +9 -11
  101. package/src/tools/setup.ts +0 -4
  102. package/src/cli/commands/studio/views/pairings.tsx +0 -38
  103. package/src/templates/providers/opencode/tui.json +0 -3
@@ -25,6 +25,7 @@ import { mergeDir, pruneEmptyAncestors } from "../utils";
25
25
  import {
26
26
  getOpenCodePluginReferences,
27
27
  isRecord,
28
+ normalizeOpenCodeConfig,
28
29
  readOpenCodeConfig,
29
30
  setOpenCodePluginReferences,
30
31
  toOpenCodeMcpEntry,
@@ -34,6 +35,8 @@ import {
34
35
  } from "./opencode-format";
35
36
  import { getSetupProvidersRoot, getSetupRootTemplates } from "./utils";
36
37
 
38
+ const COMPANION_GITIGNORE_ENTRIES = [".opencode/service.json"];
39
+
37
40
  // Copied Mate plugin source files from earlier releases. Plugin code is
38
41
  // package-owned now; these exact filenames are removed during sync and
39
42
  // teardown while every other companion-local plugin is preserved.
@@ -111,6 +114,11 @@ function isLegacyMatePluginConfigEntry(entry: unknown): boolean {
111
114
  return typeof entry === "string" && LEGACY_PLUGIN_CONFIG_ENTRY_PATTERN.test(entry);
112
115
  }
113
116
 
117
+ /** Current package references and legacy copied-file references alike. */
118
+ function isMatePluginEntry(entry: unknown): boolean {
119
+ return isMateOpenCodePluginReference(entry) || isLegacyMatePluginConfigEntry(entry);
120
+ }
121
+
114
122
  /**
115
123
  * Replace any Mate plugin entry (stale package pins and legacy copied-file
116
124
  * references) with the current pinned package reference while preserving
@@ -118,16 +126,16 @@ function isLegacyMatePluginConfigEntry(entry: unknown): boolean {
118
126
  */
119
127
  function ensureMatePluginReference(config: OpenCodeConfig, pluginReference: string): void {
120
128
  const preserved = getOpenCodePluginReferences(config).filter(
121
- (entry) => !isMateOpenCodePluginReference(entry) && !isLegacyMatePluginConfigEntry(entry),
129
+ (entry) => !isMatePluginEntry(entry),
122
130
  );
123
131
  setOpenCodePluginReferences(config, [...preserved, pluginReference]);
124
132
  }
125
133
 
126
134
  function stripMatePluginReference(config: OpenCodeConfig): void {
127
- if (!Array.isArray(config.plugin)) return;
135
+ if (!Array.isArray(config.plugin) && !Array.isArray(config.plugins)) return;
128
136
 
129
137
  const preserved = getOpenCodePluginReferences(config).filter(
130
- (entry) => !isMateOpenCodePluginReference(entry) && !isLegacyMatePluginConfigEntry(entry),
138
+ (entry) => !isMatePluginEntry(entry),
131
139
  );
132
140
  setOpenCodePluginReferences(config, preserved);
133
141
  }
@@ -137,11 +145,15 @@ async function syncOpenCodeConfigFile(
137
145
  destPath: string,
138
146
  pluginReference: string,
139
147
  ): Promise<void> {
140
- const defaults = JSON.parse(await fs.readFile(srcPath, "utf8")) as Record<string, unknown>;
148
+ const defaults = normalizeOpenCodeConfig(
149
+ JSON.parse(await fs.readFile(srcPath, "utf8")) as Record<string, unknown>,
150
+ );
141
151
 
142
152
  let existing: Record<string, unknown> = {};
143
153
  try {
144
- existing = JSON.parse(await fs.readFile(destPath, "utf8")) as Record<string, unknown>;
154
+ existing = normalizeOpenCodeConfig(
155
+ JSON.parse(await fs.readFile(destPath, "utf8")) as Record<string, unknown>,
156
+ );
145
157
  } catch (error) {
146
158
  const code = (error as NodeJS.ErrnoException).code;
147
159
  if (code !== "ENOENT") {
@@ -154,6 +166,38 @@ async function syncOpenCodeConfigFile(
154
166
  await writeOpenCodeConfig(destPath, existing);
155
167
  }
156
168
 
169
+ /**
170
+ * V2 loads TUI components exposed by server plugins declared in opencode.json;
171
+ * project-local tui.json is no longer a config destination. Strip only Mate's
172
+ * old duplicate reference and leave unrelated legacy contents for OpenCode's
173
+ * own TUI-config migration.
174
+ */
175
+ async function removeMatePluginFromLegacyTuiConfig(configDir: string): Promise<void> {
176
+ const configPath = path.join(configDir, "tui.json");
177
+ let config: Record<string, unknown>;
178
+ try {
179
+ const parsed = JSON.parse(await fs.readFile(configPath, "utf8")) as unknown;
180
+ if (!isRecord(parsed)) return;
181
+ config = parsed;
182
+ } catch {
183
+ return;
184
+ }
185
+
186
+ for (const key of ["plugin", "plugins"]) {
187
+ if (!Array.isArray(config[key])) continue;
188
+ const kept = (config[key] as unknown[]).filter((entry) => !isMatePluginEntry(entry));
189
+ if (kept.length > 0) config[key] = kept;
190
+ else delete config[key];
191
+ }
192
+ if (config.$schema === "https://opencode.ai/tui.json") delete config.$schema;
193
+
194
+ if (Object.keys(config).length === 0) {
195
+ await fs.unlink(configPath).catch(() => {});
196
+ return;
197
+ }
198
+ await writeOpenCodeConfig(configPath, config);
199
+ }
200
+
157
201
  function normalizeForComparison(value: unknown): unknown {
158
202
  if (Array.isArray(value)) {
159
203
  return value.map(normalizeForComparison);
@@ -284,11 +328,7 @@ async function syncOpenCodeRuntimeFiles(
284
328
  path.join(dest, "opencode.json"),
285
329
  pluginReference,
286
330
  );
287
- await syncOpenCodeConfigFile(
288
- path.join(src, "tui.json"),
289
- path.join(dest, "tui.json"),
290
- pluginReference,
291
- );
331
+ await removeMatePluginFromLegacyTuiConfig(dest);
292
332
  await removeLegacyRuntimeFiles(dest);
293
333
  await removeLegacyTuiDependencies(dest);
294
334
 
@@ -331,7 +371,7 @@ async function teardownOpenCode(companionPath: string, activeProviders: string[]
331
371
  path.join(src, "opencode.json"),
332
372
  path.join(dest, "opencode.json"),
333
373
  );
334
- await teardownOpenCodeConfigFile(path.join(src, "tui.json"), path.join(dest, "tui.json"));
374
+ await removeMatePluginFromLegacyTuiConfig(dest);
335
375
  await removeLegacyRuntimeFiles(dest);
336
376
  await removeLegacyTuiDependencies(dest);
337
377
  await pruneEmptyAncestors(path.join(dest, "plugins"), companionPath);
@@ -405,10 +445,10 @@ export async function removeOpenCodeForeignPluginReferences(
405
445
  }
406
446
  try {
407
447
  const parsed = JSON.parse(raw) as Record<string, unknown>;
408
- if (Array.isArray(parsed.plugin)) {
448
+ if (Array.isArray(parsed.plugin) || Array.isArray(parsed.plugins)) {
409
449
  setOpenCodePluginReferences(
410
450
  parsed,
411
- parsed.plugin.filter((entry) => !isForeign(entry)),
451
+ getOpenCodePluginReferences(parsed).filter((entry) => !isForeign(entry)),
412
452
  );
413
453
  }
414
454
  if (Object.keys(parsed).length === 0) {
@@ -428,7 +468,7 @@ export async function removeOpenCodeForeignPluginReferences(
428
468
  // skill trees.
429
469
  // ---------------------------------------------------------------------------
430
470
 
431
- const OPENCODE_CONTRIBUTION_CONFIG_FILES = ["opencode.json", "tui.json"];
471
+ const OPENCODE_CONTRIBUTION_CONFIG_FILES = ["opencode.json"];
432
472
 
433
473
  export async function reconcileOpenCodeContributions(
434
474
  ctx: SetupContext,
@@ -517,7 +557,7 @@ async function reconcileOpenCodeMcpContributions(
517
557
  }
518
558
  }
519
559
 
520
- /** The Mate-managed `mcp` map of one pass, as a value. */
560
+ /** The Mate-managed V2 `mcp.servers` map of one pass, as a value. */
521
561
  export function renderManagedOpenCodeMcpServers(
522
562
  contributions: CapabilityContributionInput[] = [],
523
563
  ): Record<string, unknown> {
@@ -532,14 +572,17 @@ export function renderManagedOpenCodeMcpServers(
532
572
  }
533
573
 
534
574
  /**
535
- * The Mate-managed `permission.external_directory` map, as a value. Rendered
536
- * once for both destinations a session can reach it through — the projected
575
+ * The Mate-managed V2 `external_directory` rules. Rendered once for both
576
+ * destinations a session can reach them through — the projected
537
577
  * document and the launch environment — so the two cannot drift.
538
578
  */
539
579
  export function renderCompanionExternalDirectoryPermissions(
540
580
  companionPath: string,
541
- ): Record<string, string> {
542
- return { [companionPath]: "allow", [`${companionPath}/**`]: "allow" };
581
+ ): Array<{ action: string; resource: string; effect: string }> {
582
+ return [
583
+ { action: "external_directory", resource: companionPath, effect: "allow" },
584
+ { action: "external_directory", resource: `${companionPath}/**`, effect: "allow" },
585
+ ];
543
586
  }
544
587
 
545
588
  /**
@@ -575,6 +618,7 @@ export function createOpenCodePlugin(): ProviderPlugin {
575
618
  description: "Install the OpenCode plugin workspace.",
576
619
  defaultSelected: false,
577
620
  isEnabled: (config) => (config.allowedAgents ?? []).includes("opencode"),
621
+ gitignoreEntries: () => COMPANION_GITIGNORE_ENTRIES,
578
622
  hosting: {
579
623
  mcp: {
580
624
  async register(ctx: SetupContext, descriptor) {
@@ -610,5 +654,3 @@ export function createOpenCodePlugin(): ProviderPlugin {
610
654
  },
611
655
  };
612
656
  }
613
-
614
- export { OPENCODE_PLUGIN_PACKAGE_NAME };
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  CLAUDE_LOCAL_CONFIG_DOCUMENT,
3
- CLAUDE_MCP_DOCUMENT,
4
3
  CLAUDE_SETTINGS_DOCUMENT,
5
4
  OPENCODE_CONFIG_DOCUMENT,
6
5
  } from "../../lib/orchestrator/projection-runtime-documents";
@@ -32,12 +31,7 @@ import { projectionRepoRoot } from "./surface-target";
32
31
  * differs from the companion target.
33
32
  */
34
33
 
35
- export {
36
- CLAUDE_LOCAL_CONFIG_DOCUMENT,
37
- CLAUDE_MCP_DOCUMENT,
38
- CLAUDE_SETTINGS_DOCUMENT,
39
- OPENCODE_CONFIG_DOCUMENT,
40
- };
34
+ export { CLAUDE_LOCAL_CONFIG_DOCUMENT };
41
35
 
42
36
  function nonEmpty(region: ManagedRegion): boolean {
43
37
  if (region.kind === "list") return region.values.length > 0;
@@ -157,15 +151,19 @@ export function renderDocumentsForTarget(
157
151
  documents.push(
158
152
  ...document(OPENCODE_CONFIG_DOCUMENT, [
159
153
  {
160
- at: ["plugin"],
154
+ at: ["plugins"],
161
155
  kind: "list",
162
156
  values: [getOpenCodePluginPackageReference()],
163
157
  },
164
- { at: ["mcp"], kind: "map", entries: renderManagedOpenCodeMcpServers(inputs) },
165
158
  {
166
- at: ["permission", "external_directory"],
159
+ at: ["mcp", "servers"],
167
160
  kind: "map",
168
- entries: renderCompanionExternalDirectoryPermissions(ctx.companionPath),
161
+ entries: renderManagedOpenCodeMcpServers(inputs),
162
+ },
163
+ {
164
+ at: ["permissions"],
165
+ kind: "list",
166
+ values: renderCompanionExternalDirectoryPermissions(ctx.companionPath),
169
167
  },
170
168
  ]),
171
169
  );
@@ -5,7 +5,6 @@ import { FRAMEWORK_NAME } from "../framework";
5
5
  import { ConfigStore, mergeWithDefaults } from "../lib/orchestrator/config-store";
6
6
  import { GlobalConfigStore } from "../lib/orchestrator/global-config-store";
7
7
  import {
8
- BUILTIN_SETUP_COMPATIBILITIES,
9
8
  dedupeCapabilities,
10
9
  getSetupSelectionsFromConfig,
11
10
  } from "../lib/orchestrator/setup-compatibilities";
@@ -194,6 +193,3 @@ export function createUvPluginForTest(deps: PackageManagerSetupDeps) {
194
193
  }
195
194
 
196
195
  export default setup;
197
-
198
- // Re-export BUILTIN_SETUP_COMPATIBILITIES passthrough for setup-selector UI compatibility.
199
- export { BUILTIN_SETUP_COMPATIBILITIES };
@@ -1,38 +0,0 @@
1
- /** @jsxImportSource hono/jsx */
2
-
3
- import type { StudioInventoryCompanion } from "../inventory";
4
-
5
- interface PairingsProps {
6
- companion: StudioInventoryCompanion | null;
7
- }
8
-
9
- /**
10
- * Context, never a selection: everything Studio serves is companion-scoped, so
11
- * a Working Repository is listed and never chosen.
12
- */
13
- export function Pairings({ companion }: PairingsProps) {
14
- const pairings = companion?.pairings ?? [];
15
-
16
- return (
17
- <div className="sidebar-pairings">
18
- <span className="sidebar-label">Working repositories</span>
19
- {pairings.length === 0 ? (
20
- <p className="empty">no linked working repository</p>
21
- ) : (
22
- <ul className="plain pairings">
23
- {pairings.map((pairing) => (
24
- <li key={pairing.repositoryPath} data-repository={pairing.repositoryPath}>
25
- <span className="mono">{pairing.repositoryId}</span>
26
- {pairing.ambiguous ? <span className="chip">also paired elsewhere</span> : null}
27
- {pairing.health === "ready" ? null : (
28
- <span className="chip chip-invalid">{pairing.health}</span>
29
- )}
30
- <br />
31
- <span className="muted">{pairing.repositoryPath}</span>
32
- </li>
33
- ))}
34
- </ul>
35
- )}
36
- </div>
37
- );
38
- }
@@ -1,3 +0,0 @@
1
- {
2
- "$schema": "https://opencode.ai/tui.json"
3
- }