Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
132 lines
6.3 KiB
JavaScript
132 lines
6.3 KiB
JavaScript
// docs-guard-exempt: cursor.com/docs/... is an external URL citation in an assert message, not a repo path.
|
|
// allow-test-rule: structural-regression-guard — AC2 requires asserting no `runtime === 'cursor'` string-equality branch remains in bin/install.js/src — the descriptor-migration contract is a property of the source text, so a source-grep is the only faithful check (#2089)
|
|
'use strict';
|
|
|
|
/**
|
|
* cursor imperative reference host — ADR-1239 Phase D / #2089 (EoS/cursor).
|
|
*
|
|
* Proves cursor is driven through the PUBLIC Host-Integration Interface (the
|
|
* imperative adapter), that its negotiated axes classify + negotiate correctly,
|
|
* that negotiation fails CLOSED on a corrupted descriptor, that the Context7-
|
|
* verified dispatch UPGRADE (named/background nested subagents) changes
|
|
* `shouldFlattenDispatch`, and that the migration retired the hardcoded
|
|
* `runtime === 'cursor'` / `isCursor` branches (folded into descriptor-driven
|
|
* `runtime.hostBehaviors`).
|
|
*/
|
|
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const { createImperativeAdapter } = require('../msd-core/bin/lib/adapter-imperative.cjs');
|
|
const {
|
|
profileOf,
|
|
negotiateHostCapabilities,
|
|
shouldFlattenDispatch,
|
|
PROFILE_BASELINES,
|
|
UNDOCUMENTED,
|
|
} = require('../msd-core/bin/lib/host-integration.cjs');
|
|
|
|
const CUR_CAP = JSON.parse(
|
|
fs.readFileSync(path.join(__dirname, '..', 'capabilities', 'cursor', 'capability.json'), 'utf8'),
|
|
);
|
|
const CUR_AXES = CUR_CAP.runtime.hostIntegration;
|
|
|
|
// -- AC2: driven through the public interface (imperative adapter) -----------
|
|
|
|
test('createImperativeAdapter classifies cursor as imperative + composes the registry', () => {
|
|
const adapter = createImperativeAdapter({ runtime: 'cursor' });
|
|
assert.equal(adapter.kind, 'imperative');
|
|
assert.equal(adapter.runtime, 'cursor');
|
|
assert.ok(adapter.registry && typeof adapter.registry === 'object');
|
|
assert.equal(typeof adapter.install, 'function');
|
|
assert.equal(typeof adapter.uninstall, 'function');
|
|
});
|
|
|
|
test('cursor axes classify as the programmatic-cli reference profile', () => {
|
|
assert.equal(profileOf(CUR_AXES), 'programmatic-cli');
|
|
});
|
|
|
|
// -- AC3: all axes populated + validated -------------------------------------
|
|
|
|
test('cursor descriptor declares all 8 axes + 6 dispatch sub-axes (no undocumented)', () => {
|
|
assert.equal(CUR_AXES.embeddingMode, 'imperative');
|
|
assert.equal(CUR_AXES.commandSurface, 'slash-file');
|
|
assert.equal(CUR_AXES.modelMode, 'passive');
|
|
assert.equal(CUR_AXES.hookBus, 'host');
|
|
assert.equal(CUR_AXES.stateIO, 'filesystem');
|
|
assert.equal(CUR_AXES.transport, 'mcp');
|
|
assert.equal(CUR_AXES.runtime, 'node');
|
|
const d = CUR_AXES.dispatch;
|
|
assert.equal(d.namedDispatch, true);
|
|
assert.equal(d.nested, true);
|
|
assert.equal(d.maxDepth, 2);
|
|
assert.equal(d.background, true);
|
|
assert.equal(d.subagentToolkit, 'full');
|
|
assert.equal(d.backgroundDispatch, true);
|
|
});
|
|
|
|
// -- AC4b: the Context7-verified dispatch UPGRADE (named/background nested) ---
|
|
|
|
test('cursor descriptor declares background dispatch true/true + nested + maxDepth 2', () => {
|
|
assert.equal(CUR_AXES.dispatch.background, true);
|
|
assert.equal(CUR_AXES.dispatch.backgroundDispatch, true);
|
|
assert.equal(CUR_AXES.dispatch.nested, true);
|
|
assert.equal(CUR_AXES.dispatch.maxDepth, 2, 'cite https://cursor.com/docs/sdk/typescript');
|
|
});
|
|
|
|
test('dispatch UPGRADE changes shouldFlattenDispatch: false now (may background), true for pre-upgrade axes', () => {
|
|
assert.equal(shouldFlattenDispatch(CUR_AXES.dispatch), false,
|
|
'with background:true+backgroundDispatch:true, MSD must NOT force-flatten cursor dispatch');
|
|
const preUpgrade = { ...CUR_AXES.dispatch, background: false, backgroundDispatch: 'undocumented' };
|
|
assert.equal(shouldFlattenDispatch(preUpgrade), true,
|
|
'pre-upgrade (background:false) cursor was force-flattened — this is the behavioral change #2089 lands');
|
|
});
|
|
|
|
// -- AC5: negotiation fails CLOSED on a corrupted descriptor ------------------
|
|
|
|
test('negotiateHostCapabilities never throws for cursor, even fully corrupted', () => {
|
|
assert.doesNotThrow(() => negotiateHostCapabilities({}));
|
|
assert.doesNotThrow(() => negotiateHostCapabilities({ ...CUR_AXES, embeddingMode: UNDOCUMENTED }));
|
|
assert.doesNotThrow(() => negotiateHostCapabilities({ ...CUR_AXES, embeddingMode: 'future-unknown' }));
|
|
});
|
|
|
|
test('a partial/empty cursor descriptor degrades to the safe floor, not the programmatic-cli baseline', () => {
|
|
const result = negotiateHostCapabilities({});
|
|
assert.equal(result.effective.embeddingMode, 'declarative', 'omitted embeddingMode degrades closed');
|
|
assert.equal(result.effective.hookBus, 'none');
|
|
assert.notDeepEqual(result.effective, PROFILE_BASELINES['programmatic-cli']);
|
|
assert.ok(result.warnings.length > 0);
|
|
});
|
|
|
|
// -- AC2: the hardcoded branches are retired ---------------------------------
|
|
|
|
test('cursor descriptor declares runtime.hostBehaviors (the folded-in behaviors)', () => {
|
|
const hb = CUR_CAP.runtime.hostBehaviors;
|
|
assert.ok(hb && typeof hb === 'object');
|
|
assert.equal(hb.reapplyCommand, 'msd-update --reapply (mention the skill name)');
|
|
assert.equal(hb.frontmatterDialect, 'cursor');
|
|
assert.equal(hb.hooksJsonSurface, true);
|
|
assert.equal(hb.skipSharedHooksInstall, true);
|
|
assert.deepEqual(hb.retiredArtifacts, [
|
|
{ destSubpath: 'commands', prefix: 'msd-', suffix: '.md' },
|
|
]);
|
|
assert.equal(hb.reportCommandsDir, undefined,
|
|
'Cursor no longer installs a parallel commands/ surface (#2644)');
|
|
assert.ok(Array.isArray(hb.managedHookEvents) && hb.managedHookEvents.length >= 6,
|
|
'managedHookEvents must list at least 6 events (AC4a)');
|
|
});
|
|
|
|
test('no `runtime === "cursor"` string-equality branch remains in the install source (AC2)', () => {
|
|
const strip = (src) => src
|
|
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
.replace(/\/\/[^\r\n]*/g, '')
|
|
.replace(/`[^`]*`/g, '');
|
|
for (const rel of ['bin/install.js', 'src/install-engine.cts', 'src/runtime-artifact-conversion.cts', 'src/runtime-hooks-surface.cts']) {
|
|
const src = fs.readFileSync(path.join(__dirname, '..', rel), 'utf8');
|
|
const offenders = strip(src).match(/runtime\s*[!=]==\s*'cursor'/g) || [];
|
|
assert.deepEqual(offenders, [], `AC2: no hardcoded runtime==='cursor' branch may remain in ${rel}; found: ${offenders.join(', ')}`);
|
|
}
|
|
});
|