Files
msd-core/tests/getdirname-registry-derivation.test.cjs
Tom Boucher a0fafedfa0 feat(#2103): drive VS Code through the Embeddable Orchestration System (ADR-1239)
VS Code is a net-new EoS runtime that — unlike every prior migration — is NOT
CLI-installed (Marketplace/VSIX extension). It has zero runtime==='vscode'
branches in bin/install.js and stays that way (regression-guarded); it is driven
entirely through the negotiated imperative Host-Integration adapter.

Registry + validator (the hard part):
- capabilities/vscode/capability.json (role:runtime): full hostIntegration block
  (imperative / palette / active vscode.lm model / engine hook bus /
  sandboxed-storage / mcp transport / sandboxed-web runtime; dispatch nested,
  maxDepth 5 per VS Code's documented subagent depth).
- capability-validator.cjs extended so a role:runtime capability can legitimately
  declare "extension-distributed, no config directory": new configHome.kind:'none'
  + installSurface:'none' (+ GATE-A pairing + the parity maps), with localConfigDir
  and configHome.name made conditional on kind!=='none'. All 18 runtimes still
  validate; getDirName returns a distinct sentinel (not '.claude') for a no-config
  runtime.
- The add-a-registry-runtime tax: NON_INSTALLABLE_RUNTIMES exemption in the
  runtime-flags drift guard, vscode added to global-config-home SPECIAL_CASED,
  EXPECTED_PROFILES.vscode='ide', and the config-adapter/derivation/pin-count
  guards updated. No golden-install fixture, model-catalog, or CONFIGURATION rows
  (vscode never enters allRuntimes).

Dispatch + extension surface:
- Fixed vscode/extension.js's createHub()-no-args bug (every dispatch was
  UnknownCommand, masked by a vacuous reachability test) — now reuses the shared
  dispatchGsdCommand subprocess-shim (Node/desktop); the reachability test is
  tightened to assert real dispatch.
- Promoted the #1933 host binding to a shipped vscode/host-binding.js; activate()
  now composes the model/hookBus/stateIO seams through it. Corrected the model
  seam to VS Code's real API (vscode.lm.selectChatModels() -> model.sendRequest();
  vscode.lm.sendRequest does not exist) so the binding actually composes on real
  desktop VS Code instead of throwing.
- New vscode/browser.js Web Extension entry with ZERO Node APIs (the engine's
  config/capability loading is Node-bound, so the web entry registers the surface
  and directs full dispatch to the native MCP server — honestly documented).
- UPGRADE 1: GSD skills as native Language Model Tools (contributes.languageModelTools
  + vscode.lm.registerTool), invoke() dispatching through the hub.
- UPGRADE 2: native subagent dispatch wired onto #runSubagent /
  chat.subagents.allowInvocationsFromSubagents (fail-soft on API availability,
  maxDepth 5 enforced).
- vscode/package.json: browser entry, engines.vscode ^1.105, chatParticipants +
  languageModelTools contributions; fixed a stale activationPoints->activationEvents
  manifest key. Added "vscode" to the package files array.

Docs (## vscode matrix section) + changeset (Added).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 23:24:42 -04:00

97 lines
4.7 KiB
JavaScript

'use strict';
/**
* Property test: getDirName is a pure projection of each runtime descriptor's
* `localConfigDir`. Because 1.7.0 (ADR-1016 / ADR-1239) makes runtimes
* pluggable data descriptors, this test asserts the DERIVATION CONTRACT —
* `getDirName(id) === registry.runtimes[id].runtime.localConfigDir` — for
* EVERY runtime currently in the registry, rather than pinning a frozen
* per-runtime golden snapshot that would have to be hand-edited every time a
* runtime is added or removed. Adding a new runtime descriptor requires zero
* changes here; if the derivation breaks for any runtime, this fails loudly.
*
* Also covers:
* - the fail-closed fallback (`getDirName('unknown')` / `getDirName('')` → '.claude');
* - a structural cross-check that every descriptor's localConfigDir is a
* non-empty dot-dir string.
*
* #2103: vscode's `configHome.kind === 'none'` (no file-projected config
* directory at all) is the first descriptor with `localConfigDir: null` — it
* is carved out of the "non-empty dot-dir string" invariant below and
* asserted against getDirName's documented NO_LOCAL_CONFIG_DIR_SENTINEL
* instead (mirrors the carve-out in
* tests/non-claude-runtimes-registry-derivation.test.cjs).
*
* ADR-1239 Phase B (#1679). Behavioral tests only: assert on returned values.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const runtimeNamePolicy = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
const { getDirName, NO_LOCAL_CONFIG_DIR_SENTINEL } = runtimeNamePolicy;
const RUNTIME_IDS = Object.keys(registry.runtimes);
// Runtimes whose configHome.kind === 'none' have NO file-projected config
// directory at all (localConfigDir is legitimately null) — carved out of the
// "non-empty dot-dir string" invariant, the same way dot-home-nested's
// .parent is a conditional carve-out in the validator.
const NO_LOCAL_CONFIG_DIR_RUNTIMES = new Set(
RUNTIME_IDS.filter((id) => {
const desc = registry.runtimes[id] && registry.runtimes[id].runtime;
return !!(desc && desc.configHome && desc.configHome.kind === 'none');
}),
);
test('getDirName(id) projects each descriptor runtime.localConfigDir (derivation contract, count-agnostic)', () => {
assert.ok(RUNTIME_IDS.length > 0, 'registry must contain at least one runtime');
for (const id of RUNTIME_IDS) {
const desc = registry.runtimes[id] && registry.runtimes[id].runtime;
if (NO_LOCAL_CONFIG_DIR_RUNTIMES.has(id)) {
// #2103: no file-projected config dir — getDirName must return the
// documented sentinel, not the '.claude' default and not null.
assert.strictEqual(desc.localConfigDir, null,
`${id}: configHome.kind === 'none' runtimes must declare localConfigDir: null`);
assert.strictEqual(
getDirName(id),
NO_LOCAL_CONFIG_DIR_SENTINEL,
`getDirName('${id}') must equal the documented no-local-config-dir sentinel`);
continue;
}
const expected = desc && desc.localConfigDir;
assert.ok(typeof expected === 'string' && expected.length > 0,
`registry.runtimes['${id}'].runtime.localConfigDir must be a non-empty string`);
assert.strictEqual(
getDirName(id),
expected,
`getDirName('${id}') must equal the descriptor localConfigDir '${expected}'`);
}
});
test('getDirName fallback: unknown / empty runtime returns ".claude" (fail-closed)', () => {
assert.strictEqual(getDirName('unknown'), '.claude');
assert.strictEqual(getDirName(''), '.claude');
assert.strictEqual(getDirName('__nonexistent_runtime__'), '.claude');
});
test('registry cross-check: every runtimes[id].runtime.localConfigDir is a non-empty dot-dir string, except configHome.kind==="none" runtimes (localConfigDir: null)', () => {
for (const [id, entry] of Object.entries(registry.runtimes)) {
if (!entry || typeof entry !== 'object') continue;
const runtimeBlock = entry.runtime;
if (!runtimeBlock || typeof runtimeBlock !== 'object') continue;
if (NO_LOCAL_CONFIG_DIR_RUNTIMES.has(id)) {
assert.strictEqual(runtimeBlock.localConfigDir, null,
`registry.runtimes['${id}'].runtime.localConfigDir must be null (configHome.kind === 'none')`);
continue;
}
const dir = runtimeBlock.localConfigDir;
assert.strictEqual(typeof dir, 'string',
`registry.runtimes['${id}'].runtime.localConfigDir must be a string (got: ${typeof dir})`);
assert.ok(dir.length > 0,
`registry.runtimes['${id}'].runtime.localConfigDir must be non-empty`);
assert.ok(dir.startsWith('.'),
`registry.runtimes['${id}'].runtime.localConfigDir must start with '.' (got: ${JSON.stringify(dir)})`);
}
});