@uniqbit/mate-core 0.17.0-canary.2 → 0.17.0-canary.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniqbit/mate-core",
3
- "version": "0.17.0-canary.2",
3
+ "version": "0.17.0-canary.5",
4
4
  "description": "Core framework and plugin APIs for Mate.",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The Node.js version an `engines.node` range may be enforced against, or null
3
+ * where there is none to enforce.
4
+ *
5
+ * Under Bun, `process.versions.node` is a compatibility claim rather than an
6
+ * installed Node release: it trails upstream on Bun's own schedule, and Bun
7
+ * ignores `engines` when loading a package anyway. Comparing a real range such
8
+ * as `^24.15.0` against that claim refuses graphs the runtime loads perfectly
9
+ * well, so there is nothing meaningful to compare and the check is skipped.
10
+ */
11
+ export function enforceableNodeVersion(
12
+ versions: NodeJS.ProcessVersions = process.versions,
13
+ ): string | null {
14
+ return versions.bun === undefined ? versions.node : null;
15
+ }
@@ -10,6 +10,7 @@ import {
10
10
  CONTEXT_MODE_PACKAGE_NAME,
11
11
  CONTEXT_MODE_VERSION,
12
12
  } from "./context-mode-package";
13
+ import { enforceableNodeVersion } from "./node-engine-version";
13
14
  import { getLocalWorkspaceDir } from "./preinstalled-plugins";
14
15
 
15
16
  /**
@@ -41,7 +42,8 @@ const DECLARED_NODE_REQUIREMENTS: Record<string, string> = {
41
42
  export interface PrebuiltWorkspaceTarget {
42
43
  platform: string;
43
44
  arch: string;
44
- nodeVersion: string;
45
+ /** Null where no installed Node release backs the runtime, which leaves `engines.node` unenforced. */
46
+ nodeVersion: string | null;
45
47
  }
46
48
 
47
49
  export interface PrebuiltWorkspaceValidation {
@@ -53,7 +55,7 @@ export interface PrebuiltWorkspaceValidation {
53
55
  }
54
56
 
55
57
  export function currentTarget(): PrebuiltWorkspaceTarget {
56
- return { platform: process.platform, arch: process.arch, nodeVersion: process.versions.node };
58
+ return { platform: process.platform, arch: process.arch, nodeVersion: enforceableNodeVersion() };
57
59
  }
58
60
 
59
61
  interface InstalledManifest {
@@ -182,7 +184,11 @@ export async function validatePrebuiltWorkspace(
182
184
  }
183
185
 
184
186
  const declaredRequirement = DECLARED_NODE_REQUIREMENTS[name];
185
- if (declaredRequirement && !semver.satisfies(target.nodeVersion, declaredRequirement)) {
187
+ if (
188
+ declaredRequirement &&
189
+ target.nodeVersion !== null &&
190
+ !semver.satisfies(target.nodeVersion, declaredRequirement)
191
+ ) {
186
192
  failures.push(
187
193
  `${name}: requires Node.js ${declaredRequirement}; the target runs ${target.nodeVersion}.`,
188
194
  );
@@ -215,7 +221,11 @@ export async function validatePrebuiltWorkspace(
215
221
  `${name}: built for ${installed.cpu.join(", ")}; the target architecture is ${target.arch}.`,
216
222
  );
217
223
  }
218
- if (installed.engines?.node && !semver.satisfies(target.nodeVersion, installed.engines.node)) {
224
+ if (
225
+ installed.engines?.node &&
226
+ target.nodeVersion !== null &&
227
+ !semver.satisfies(target.nodeVersion, installed.engines.node)
228
+ ) {
219
229
  failures.push(
220
230
  `${name}: requires Node.js ${installed.engines.node}; the target runs ${target.nodeVersion}.`,
221
231
  );
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import semver from "semver";
5
5
 
6
6
  import { FRAMEWORK_NAME } from "../framework";
7
+ import { enforceableNodeVersion } from "./node-engine-version";
7
8
 
8
9
  /**
9
10
  * A plugin reference bound to installed files is an absolute path to the
@@ -49,7 +50,7 @@ export async function resolvePreinstalledPluginReference(
49
50
  companionPath: string,
50
51
  packageName: string,
51
52
  expectedVersion: string,
52
- nodeVersion: string = process.versions.node,
53
+ nodeVersion: string | null = enforceableNodeVersion(),
53
54
  ): Promise<string | null> {
54
55
  const workspace = getLocalWorkspaceDir(companionPath);
55
56
  const supplied = await fs.access(path.join(workspace, PREBUILT_BUNDLE_MARKER)).then(
@@ -73,7 +74,11 @@ export async function resolvePreinstalledPluginReference(
73
74
  `${packageName}: the installed copy at ${dir} is ${manifest.version ?? "an unknown version"}; the capability declares ${expectedVersion}.`,
74
75
  );
75
76
  }
76
- if (manifest.engines?.node && !semver.satisfies(nodeVersion, manifest.engines.node)) {
77
+ if (
78
+ manifest.engines?.node &&
79
+ nodeVersion !== null &&
80
+ !semver.satisfies(nodeVersion, manifest.engines.node)
81
+ ) {
77
82
  throw new PreinstalledPluginMismatchError(
78
83
  `${packageName}: the installed copy at ${dir} requires Node.js ${manifest.engines.node}; this runtime is ${nodeVersion}.`,
79
84
  );
@@ -241,7 +241,7 @@ exports[`runtime surface golden fixtures setup graphify × opencode 1`] = `
241
241
  "reserved": 15000,
242
242
  },
243
243
  "plugin": [
244
- "@uniqbit/mate-opencode-plugin@0.16.0",
244
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
245
245
  ],
246
246
  "references": {
247
247
  "mate": "..",
@@ -277,7 +277,7 @@ Write chunks to $GRAPHIFY_OUT and read $GRAPHIFY_OUT later.
277
277
  "opencodeTui": {
278
278
  "$schema": "https://opencode.ai/tui.json",
279
279
  "plugin": [
280
- "@uniqbit/mate-opencode-plugin@0.16.0",
280
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
281
281
  ],
282
282
  },
283
283
  }
@@ -367,7 +367,7 @@ exports[`runtime surface golden fixtures setup graphify × opencode 3`] = `
367
367
  ],
368
368
  "kind": "list",
369
369
  "values": [
370
- "@uniqbit/mate-opencode-plugin@0.16.0",
370
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
371
371
  ],
372
372
  },
373
373
  {
@@ -460,7 +460,7 @@ Write chunks to $GRAPHIFY_OUT and read $GRAPHIFY_OUT later.
460
460
  "reserved": 15000,
461
461
  },
462
462
  "plugin": [
463
- "@uniqbit/mate-opencode-plugin@0.16.0",
463
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
464
464
  ],
465
465
  "references": {
466
466
  "mate": "..",
@@ -496,7 +496,7 @@ Write chunks to $GRAPHIFY_OUT and read $GRAPHIFY_OUT later.
496
496
  "opencodeTui": {
497
497
  "$schema": "https://opencode.ai/tui.json",
498
498
  "plugin": [
499
- "@uniqbit/mate-opencode-plugin@0.16.0",
499
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
500
500
  ],
501
501
  },
502
502
  }
@@ -717,7 +717,7 @@ exports[`runtime surface golden fixtures setup graphify × claude+opencode 3`] =
717
717
  ],
718
718
  "kind": "list",
719
719
  "values": [
720
- "@uniqbit/mate-opencode-plugin@0.16.0",
720
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
721
721
  ],
722
722
  },
723
723
  {
@@ -1019,7 +1019,7 @@ exports[`runtime surface golden fixtures setup tokensave × opencode 1`] = `
1019
1019
  },
1020
1020
  },
1021
1021
  "plugin": [
1022
- "@uniqbit/mate-opencode-plugin@0.16.0",
1022
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
1023
1023
  ],
1024
1024
  "references": {
1025
1025
  "mate": "..",
@@ -1034,7 +1034,7 @@ exports[`runtime surface golden fixtures setup tokensave × opencode 1`] = `
1034
1034
  "opencodeTui": {
1035
1035
  "$schema": "https://opencode.ai/tui.json",
1036
1036
  "plugin": [
1037
- "@uniqbit/mate-opencode-plugin@0.16.0",
1037
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
1038
1038
  ],
1039
1039
  },
1040
1040
  }
@@ -1121,7 +1121,7 @@ exports[`runtime surface golden fixtures setup tokensave × opencode 3`] = `
1121
1121
  ],
1122
1122
  "kind": "list",
1123
1123
  "values": [
1124
- "@uniqbit/mate-opencode-plugin@0.16.0",
1124
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
1125
1125
  ],
1126
1126
  },
1127
1127
  {
@@ -1257,7 +1257,7 @@ exports[`runtime surface golden fixtures setup tokensave × claude+opencode 1`]
1257
1257
  },
1258
1258
  },
1259
1259
  "plugin": [
1260
- "@uniqbit/mate-opencode-plugin@0.16.0",
1260
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
1261
1261
  ],
1262
1262
  "references": {
1263
1263
  "mate": "..",
@@ -1272,7 +1272,7 @@ exports[`runtime surface golden fixtures setup tokensave × claude+opencode 1`]
1272
1272
  "opencodeTui": {
1273
1273
  "$schema": "https://opencode.ai/tui.json",
1274
1274
  "plugin": [
1275
- "@uniqbit/mate-opencode-plugin@0.16.0",
1275
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
1276
1276
  ],
1277
1277
  },
1278
1278
  }
@@ -1505,7 +1505,7 @@ exports[`runtime surface golden fixtures setup tokensave × claude+opencode 3`]
1505
1505
  ],
1506
1506
  "kind": "list",
1507
1507
  "values": [
1508
- "@uniqbit/mate-opencode-plugin@0.16.0",
1508
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
1509
1509
  ],
1510
1510
  },
1511
1511
  {
@@ -1747,7 +1747,7 @@ Canonical Mate policy and any more specific tool-routing instructions remain aut
1747
1747
  "reserved": 15000,
1748
1748
  },
1749
1749
  "plugin": [
1750
- "@uniqbit/mate-opencode-plugin@0.16.0",
1750
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
1751
1751
  "context-mode@1.0.169",
1752
1752
  ],
1753
1753
  "references": {
@@ -1763,7 +1763,7 @@ Canonical Mate policy and any more specific tool-routing instructions remain aut
1763
1763
  "opencodeTui": {
1764
1764
  "$schema": "https://opencode.ai/tui.json",
1765
1765
  "plugin": [
1766
- "@uniqbit/mate-opencode-plugin@0.16.0",
1766
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
1767
1767
  "context-mode@1.0.169",
1768
1768
  ],
1769
1769
  },
@@ -1843,7 +1843,7 @@ exports[`runtime surface golden fixtures setup context-mode × opencode 3`] = `
1843
1843
  ],
1844
1844
  "kind": "list",
1845
1845
  "values": [
1846
- "@uniqbit/mate-opencode-plugin@0.16.0",
1846
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
1847
1847
  ],
1848
1848
  },
1849
1849
  {
@@ -1913,7 +1913,7 @@ Canonical Mate policy and any more specific tool-routing instructions remain aut
1913
1913
  "reserved": 15000,
1914
1914
  },
1915
1915
  "plugin": [
1916
- "@uniqbit/mate-opencode-plugin@0.16.0",
1916
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
1917
1917
  "context-mode@1.0.169",
1918
1918
  ],
1919
1919
  "references": {
@@ -1929,7 +1929,7 @@ Canonical Mate policy and any more specific tool-routing instructions remain aut
1929
1929
  "opencodeTui": {
1930
1930
  "$schema": "https://opencode.ai/tui.json",
1931
1931
  "plugin": [
1932
- "@uniqbit/mate-opencode-plugin@0.16.0",
1932
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
1933
1933
  "context-mode@1.0.169",
1934
1934
  ],
1935
1935
  },
@@ -2129,7 +2129,7 @@ exports[`runtime surface golden fixtures setup context-mode × claude+opencode 3
2129
2129
  ],
2130
2130
  "kind": "list",
2131
2131
  "values": [
2132
- "@uniqbit/mate-opencode-plugin@0.16.0",
2132
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
2133
2133
  ],
2134
2134
  },
2135
2135
  {
@@ -2377,7 +2377,7 @@ exports[`runtime surface golden fixtures setup react-doctor × opencode 1`] = `
2377
2377
  "reserved": 15000,
2378
2378
  },
2379
2379
  "plugin": [
2380
- "@uniqbit/mate-opencode-plugin@0.16.0",
2380
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
2381
2381
  ],
2382
2382
  "references": {
2383
2383
  "mate": "..",
@@ -2395,7 +2395,7 @@ exports[`runtime surface golden fixtures setup react-doctor × opencode 1`] = `
2395
2395
  "opencodeTui": {
2396
2396
  "$schema": "https://opencode.ai/tui.json",
2397
2397
  "plugin": [
2398
- "@uniqbit/mate-opencode-plugin@0.16.0",
2398
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
2399
2399
  ],
2400
2400
  },
2401
2401
  }
@@ -2474,7 +2474,7 @@ exports[`runtime surface golden fixtures setup react-doctor × opencode 3`] = `
2474
2474
  ],
2475
2475
  "kind": "list",
2476
2476
  "values": [
2477
- "@uniqbit/mate-opencode-plugin@0.16.0",
2477
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
2478
2478
  ],
2479
2479
  },
2480
2480
  {
@@ -2567,7 +2567,7 @@ exports[`runtime surface golden fixtures setup react-doctor × claude+opencode 1
2567
2567
  "reserved": 15000,
2568
2568
  },
2569
2569
  "plugin": [
2570
- "@uniqbit/mate-opencode-plugin@0.16.0",
2570
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
2571
2571
  ],
2572
2572
  "references": {
2573
2573
  "mate": "..",
@@ -2585,7 +2585,7 @@ exports[`runtime surface golden fixtures setup react-doctor × claude+opencode 1
2585
2585
  "opencodeTui": {
2586
2586
  "$schema": "https://opencode.ai/tui.json",
2587
2587
  "plugin": [
2588
- "@uniqbit/mate-opencode-plugin@0.16.0",
2588
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
2589
2589
  ],
2590
2590
  },
2591
2591
  }
@@ -2783,7 +2783,7 @@ exports[`runtime surface golden fixtures setup react-doctor × claude+opencode 3
2783
2783
  ],
2784
2784
  "kind": "list",
2785
2785
  "values": [
2786
- "@uniqbit/mate-opencode-plugin@0.16.0",
2786
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
2787
2787
  ],
2788
2788
  },
2789
2789
  {
@@ -3047,7 +3047,7 @@ exports[`runtime surface golden fixtures setup openspec × opencode 1`] = `
3047
3047
  "reserved": 15000,
3048
3048
  },
3049
3049
  "plugin": [
3050
- "@uniqbit/mate-opencode-plugin@0.16.0",
3050
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
3051
3051
  ],
3052
3052
  "references": {
3053
3053
  "mate": "..",
@@ -3080,7 +3080,7 @@ exports[`runtime surface golden fixtures setup openspec × opencode 1`] = `
3080
3080
  "opencodeTui": {
3081
3081
  "$schema": "https://opencode.ai/tui.json",
3082
3082
  "plugin": [
3083
- "@uniqbit/mate-opencode-plugin@0.16.0",
3083
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
3084
3084
  ],
3085
3085
  },
3086
3086
  }
@@ -3161,7 +3161,7 @@ exports[`runtime surface golden fixtures setup openspec × opencode 3`] = `
3161
3161
  ],
3162
3162
  "kind": "list",
3163
3163
  "values": [
3164
- "@uniqbit/mate-opencode-plugin@0.16.0",
3164
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
3165
3165
  ],
3166
3166
  },
3167
3167
  {
@@ -3255,7 +3255,7 @@ exports[`runtime surface golden fixtures setup openspec × claude+opencode 1`] =
3255
3255
  "reserved": 15000,
3256
3256
  },
3257
3257
  "plugin": [
3258
- "@uniqbit/mate-opencode-plugin@0.16.0",
3258
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
3259
3259
  ],
3260
3260
  "references": {
3261
3261
  "mate": "..",
@@ -3288,7 +3288,7 @@ exports[`runtime surface golden fixtures setup openspec × claude+opencode 1`] =
3288
3288
  "opencodeTui": {
3289
3289
  "$schema": "https://opencode.ai/tui.json",
3290
3290
  "plugin": [
3291
- "@uniqbit/mate-opencode-plugin@0.16.0",
3291
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
3292
3292
  ],
3293
3293
  },
3294
3294
  }
@@ -3503,7 +3503,7 @@ exports[`runtime surface golden fixtures setup openspec × claude+opencode 3`] =
3503
3503
  ],
3504
3504
  "kind": "list",
3505
3505
  "values": [
3506
- "@uniqbit/mate-opencode-plugin@0.16.0",
3506
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
3507
3507
  ],
3508
3508
  },
3509
3509
  {
@@ -3723,7 +3723,7 @@ Write chunks to $GRAPHIFY_OUT and read $GRAPHIFY_OUT later.
3723
3723
  },
3724
3724
  },
3725
3725
  "plugin": [
3726
- "@uniqbit/mate-opencode-plugin@0.16.0",
3726
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
3727
3727
  "context-mode@1.0.169",
3728
3728
  ],
3729
3729
  "references": {
@@ -3779,7 +3779,7 @@ Write chunks to $GRAPHIFY_OUT and read $GRAPHIFY_OUT later.
3779
3779
  "opencodeTui": {
3780
3780
  "$schema": "https://opencode.ai/tui.json",
3781
3781
  "plugin": [
3782
- "@uniqbit/mate-opencode-plugin@0.16.0",
3782
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
3783
3783
  "context-mode@1.0.169",
3784
3784
  ],
3785
3785
  },
@@ -4050,7 +4050,7 @@ exports[`runtime surface golden fixtures setup all capabilities × claude+openco
4050
4050
  ],
4051
4051
  "kind": "list",
4052
4052
  "values": [
4053
- "@uniqbit/mate-opencode-plugin@0.16.0",
4053
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
4054
4054
  ],
4055
4055
  },
4056
4056
  {
@@ -4126,7 +4126,7 @@ exports[`runtime surface golden fixtures teardown graphify × claude+opencode 1`
4126
4126
  "reserved": 15000,
4127
4127
  },
4128
4128
  "plugin": [
4129
- "@uniqbit/mate-opencode-plugin@0.16.0",
4129
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
4130
4130
  ],
4131
4131
  "references": {
4132
4132
  "mate": "..",
@@ -4141,7 +4141,7 @@ exports[`runtime surface golden fixtures teardown graphify × claude+opencode 1`
4141
4141
  "opencodeTui": {
4142
4142
  "$schema": "https://opencode.ai/tui.json",
4143
4143
  "plugin": [
4144
- "@uniqbit/mate-opencode-plugin@0.16.0",
4144
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
4145
4145
  ],
4146
4146
  },
4147
4147
  }
@@ -4188,7 +4188,7 @@ exports[`runtime surface golden fixtures teardown tokensave × claude+opencode 1
4188
4188
  "reserved": 15000,
4189
4189
  },
4190
4190
  "plugin": [
4191
- "@uniqbit/mate-opencode-plugin@0.16.0",
4191
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
4192
4192
  ],
4193
4193
  "references": {
4194
4194
  "mate": "..",
@@ -4203,7 +4203,7 @@ exports[`runtime surface golden fixtures teardown tokensave × claude+opencode 1
4203
4203
  "opencodeTui": {
4204
4204
  "$schema": "https://opencode.ai/tui.json",
4205
4205
  "plugin": [
4206
- "@uniqbit/mate-opencode-plugin@0.16.0",
4206
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
4207
4207
  ],
4208
4208
  },
4209
4209
  }
@@ -4251,7 +4251,7 @@ exports[`runtime surface golden fixtures teardown context-mode × claude+opencod
4251
4251
  "reserved": 15000,
4252
4252
  },
4253
4253
  "plugin": [
4254
- "@uniqbit/mate-opencode-plugin@0.16.0",
4254
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
4255
4255
  ],
4256
4256
  "references": {
4257
4257
  "mate": "..",
@@ -4266,7 +4266,7 @@ exports[`runtime surface golden fixtures teardown context-mode × claude+opencod
4266
4266
  "opencodeTui": {
4267
4267
  "$schema": "https://opencode.ai/tui.json",
4268
4268
  "plugin": [
4269
- "@uniqbit/mate-opencode-plugin@0.16.0",
4269
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
4270
4270
  ],
4271
4271
  },
4272
4272
  }
@@ -4313,7 +4313,7 @@ exports[`runtime surface golden fixtures teardown react-doctor × claude+opencod
4313
4313
  "reserved": 15000,
4314
4314
  },
4315
4315
  "plugin": [
4316
- "@uniqbit/mate-opencode-plugin@0.16.0",
4316
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
4317
4317
  ],
4318
4318
  "references": {
4319
4319
  "mate": "..",
@@ -4328,7 +4328,7 @@ exports[`runtime surface golden fixtures teardown react-doctor × claude+opencod
4328
4328
  "opencodeTui": {
4329
4329
  "$schema": "https://opencode.ai/tui.json",
4330
4330
  "plugin": [
4331
- "@uniqbit/mate-opencode-plugin@0.16.0",
4331
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
4332
4332
  ],
4333
4333
  },
4334
4334
  }
@@ -4375,7 +4375,7 @@ exports[`runtime surface golden fixtures teardown openspec × claude+opencode 1`
4375
4375
  "reserved": 15000,
4376
4376
  },
4377
4377
  "plugin": [
4378
- "@uniqbit/mate-opencode-plugin@0.16.0",
4378
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
4379
4379
  ],
4380
4380
  "references": {
4381
4381
  "mate": "..",
@@ -4390,7 +4390,7 @@ exports[`runtime surface golden fixtures teardown openspec × claude+opencode 1`
4390
4390
  "opencodeTui": {
4391
4391
  "$schema": "https://opencode.ai/tui.json",
4392
4392
  "plugin": [
4393
- "@uniqbit/mate-opencode-plugin@0.16.0",
4393
+ "@uniqbit/mate-opencode-plugin@0.17.0-canary.2",
4394
4394
  ],
4395
4395
  },
4396
4396
  }
@@ -11,6 +11,7 @@ import {
11
11
  validateContextModePackage,
12
12
  } from "../../../lib/context-mode-package";
13
13
  import { warmOpenCodePackageCache } from "../../../lib/opencode-plugin-package";
14
+ import { isPreinstalledPluginPath } from "../../../lib/preinstalled-plugins";
14
15
  import type {
15
16
  CapabilityPlugin,
16
17
  LaunchPreflightContext,
@@ -70,17 +71,60 @@ async function assertNoMcpConflict(ctx: SetupContext, provider: "claude" | "open
70
71
  }
71
72
  }
72
73
 
74
+ /**
75
+ * The version behind a reference bound to an installed copy.
76
+ *
77
+ * A published reference carries its version in the string; a bound one carries
78
+ * it in the package it points at, so staleness has to be read off disk.
79
+ */
80
+ async function installedVersionAt(packageRoot: string): Promise<string | undefined> {
81
+ try {
82
+ const manifest = JSON.parse(
83
+ await fs.readFile(path.join(packageRoot, "package.json"), "utf8"),
84
+ ) as { version?: string };
85
+ return manifest.version;
86
+ } catch {
87
+ return undefined;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Both spellings of a current reference are accepted, because the Runtime
93
+ * Surface writes either one: the published spec on an ordinary workstation, and
94
+ * an absolute path into the machine-local workspace where the distribution
95
+ * supplied an installed copy. Accepting only the first would refuse the launch
96
+ * on exactly the deployments preinstalled binding exists for.
97
+ *
98
+ * Staleness is still caught in both cases — a published reference by its
99
+ * version, a bound one by the version of the copy it points at — so this is not
100
+ * the looser check it might look like.
101
+ */
73
102
  async function validateOpenCodeReferences(ctx: LaunchPreflightContext): Promise<string[]> {
74
103
  const expected = getContextModePackageReference();
75
104
  const diagnostics: string[] = [];
76
105
  for (const name of ["opencode.json", "tui.json"]) {
77
106
  const configPath = path.join(ctx.companionPath, ".opencode", name);
78
107
  const { config } = await readOpenCodeConfig(configPath);
79
- if (!getOpenCodePluginReferences(config).includes(expected)) {
108
+ const references = getOpenCodePluginReferences(config);
109
+ if (references.includes(expected)) continue;
110
+
111
+ const bound = references.find(
112
+ (reference): reference is string =>
113
+ typeof reference === "string" &&
114
+ isPreinstalledPluginPath(reference, CONTEXT_MODE_PACKAGE_NAME),
115
+ );
116
+ if (bound !== undefined) {
117
+ const version = await installedVersionAt(bound);
118
+ if (version === CONTEXT_MODE_VERSION) continue;
80
119
  diagnostics.push(
81
- `Missing or stale context-mode package reference in ${configPath}; expected ${expected}.`,
120
+ `Stale context-mode package reference in ${configPath}; it points at ${bound}, which is ${version ?? "not installed"} rather than ${CONTEXT_MODE_VERSION}.`,
82
121
  );
122
+ continue;
83
123
  }
124
+
125
+ diagnostics.push(
126
+ `Missing or stale context-mode package reference in ${configPath}; expected ${expected}.`,
127
+ );
84
128
  }
85
129
  return diagnostics;
86
130
  }
@@ -6,7 +6,7 @@ import { promisify } from "node:util";
6
6
  import { getOpenSpecSchemaSelection } from "../../../lib/orchestrator/setup-compatibilities";
7
7
  import { getWrapperBinPath } from "../../../lib/package-paths";
8
8
  import { fetchPublicPackageVersion } from "../../../lib/public-npm";
9
- import { isNewer } from "../../../lib/update-checker";
9
+ import { isNewer, isPinnedDeployment } from "../../../lib/update-checker";
10
10
  import { FRAMEWORK_NAME } from "../../../framework";
11
11
  import { confirm } from "../../../cli/confirm";
12
12
  import type { CapabilityPlugin, RuntimeContributionsByRuntime, SetupContext } from "../plugin";
@@ -63,6 +63,7 @@ export interface OpenSpecPluginDeps {
63
63
  wrapperBinPath?: () => string;
64
64
  getInstalledVersion?: (pathValue: string) => Promise<string | undefined>;
65
65
  fetchLatestVersion?: () => Promise<string | undefined>;
66
+ isPinnedDeployment?: () => boolean;
66
67
  }
67
68
 
68
69
  const OPEN_SPEC_SUPPORTED_TOOLS = Object.keys(OPENSPEC_TOOL_DIRS) as OpenSpecTool[];
@@ -170,10 +171,17 @@ async function updateOpenSpecIfOutdated(
170
171
  deps: Required<
171
172
  Pick<
172
173
  OpenSpecPluginDeps,
173
- "installCommand" | "pathValue" | "getInstalledVersion" | "fetchLatestVersion"
174
+ | "installCommand"
175
+ | "pathValue"
176
+ | "getInstalledVersion"
177
+ | "fetchLatestVersion"
178
+ | "isPinnedDeployment"
174
179
  >
175
180
  >,
176
181
  ): Promise<void> {
182
+ /** Pinned artifacts change tool versions by replacement, never reconciliation. */
183
+ if (deps.isPinnedDeployment()) return;
184
+
177
185
  const installed = await deps.getInstalledVersion(deps.pathValue());
178
186
  if (!installed) return;
179
187
 
@@ -204,6 +212,7 @@ async function ensureOpenSpecInstalled(
204
212
  | "pathValue"
205
213
  | "getInstalledVersion"
206
214
  | "fetchLatestVersion"
215
+ | "isPinnedDeployment"
207
216
  >
208
217
  >,
209
218
  ): Promise<boolean> {
@@ -326,6 +335,7 @@ export function createOpenspecPlugin(deps: OpenSpecPluginDeps = {}): CapabilityP
326
335
  wrapperBinPath: deps.wrapperBinPath ?? getWrapperBinPath,
327
336
  getInstalledVersion: deps.getInstalledVersion ?? defaultGetInstalledVersion,
328
337
  fetchLatestVersion: deps.fetchLatestVersion ?? defaultFetchLatestVersion,
338
+ isPinnedDeployment: deps.isPinnedDeployment ?? (() => isPinnedDeployment()),
329
339
  };
330
340
 
331
341
  return {