Files
msd-core/tests/cline-dispatch-degradation.test.cjs

102 lines
4.8 KiB
JavaScript

'use strict';
/**
* cline dispatch DEGRADATION — ADR-1239 / #2090.
*
* Proves cline's dispatch STAYS degraded/flat and is NEVER silently upgraded to
* the programmatic-cli profile baseline's full nested/background dispatch.
* Cline's own docs (docs/features/subagents.mdx) restrict subagents to a single
* level, read-only toolkit, no nested spawning — so claiming full dispatch would
* misrepresent a documented host restriction and violate the fail-closed
* negotiation contract. This is the cline counterpart to cursor's dispatch
* UPGRADE (#2089), asserting the OPPOSITE invariant: cline flattens.
*
* Cite:
* https://github.com/cline/cline/blob/main/docs/features/subagents.mdx
* — "subagents are restricted from editing files, using the browser,
* accessing MCP servers, or creating nested subagents."
* https://github.com/cline/cline/blob/main/docs/features/subagents.mdx
* — "They are explicitly prohibited from ... spawning other subagents."
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const {
shouldFlattenDispatch,
degradationFor,
} = require('../gsd-core/bin/lib/host-integration.cjs');
const CLN_CAP = JSON.parse(
fs.readFileSync(path.join(__dirname, '..', 'capabilities', 'cline', 'capability.json'), 'utf8'),
);
const CLN_DISPATCH = CLN_CAP.runtime.hostIntegration.dispatch;
// -- cline dispatch axes: documented restrictions ----------------------------
test('cline dispatch declares namedDispatch but NOT nested (single-level only)', () => {
assert.equal(CLN_DISPATCH.namedDispatch, true,
'cite agents-squad example — start_subagent(preset:..., task:...) named dispatch');
assert.equal(CLN_DISPATCH.nested, false,
'cite subagents.mdx — subagents cannot create nested subagents');
});
test('cline dispatch respects maxDepth: 1 (the documented hard limit)', () => {
assert.equal(CLN_DISPATCH.maxDepth, 1,
'cite subagents.mdx — "explicitly prohibited from ... spawning other subagents"');
});
test('cline subagentToolkit is read-only (no write/browser/mcp for subagents)', () => {
assert.equal(CLN_DISPATCH.subagentToolkit, 'read-only',
'cite subagents.mdx — "strictly limited to read-only operations"');
});
test('cline backgroundDispatch is false (background commands run, but no nested-dispatch)', () => {
assert.equal(CLN_DISPATCH.background, true,
'cite subagents.mdx — "Commands executed by subagents run in the background"');
assert.equal(CLN_DISPATCH.backgroundDispatch, false,
'cite subagents.mdx — cannot spawn nested subagents from a background context');
});
// -- shouldFlattenDispatch: cline MUST flatten (degraded) --------------------
test('shouldFlattenDispatch(cline) is true — waves run inline (the #853 rule)', () => {
assert.equal(shouldFlattenDispatch(CLN_DISPATCH), true,
'cline has backgroundDispatch:false → GSD must force-flatten (run inline)');
});
test('a hypothetical full-upgrade (backgroundDispatch:true) would NOT flatten — proving the discriminator', () => {
const hypothetical = { ...CLN_DISPATCH, backgroundDispatch: true, nested: true, maxDepth: 2, subagentToolkit: 'full' };
assert.equal(shouldFlattenDispatch(hypothetical), false,
'only background:true AND backgroundDispatch:true escapes flattening — cline lacks both');
});
// -- degradationFor: cline dispatch is 'degraded' / flat ---------------------
test('degradationFor("dispatch", cline) is degraded with the flat-dispatch fallback', () => {
const result = degradationFor('dispatch', CLN_CAP.runtime.hostIntegration);
assert.equal(result.level, 'degraded',
'maxDepth:1 (flat) is a degraded dispatch surface, never full');
assert.equal(result.fallback, 'flat dispatch — waves run inline');
assert.notEqual(result.level, 'full',
'cline dispatch must NEVER be classified as full — that would misrepresent the host');
});
test('cline dispatch is never silently upgraded to the programmatic-cli baseline', () => {
// The full baseline requires nested + maxDepth>=2 + subagentToolkit 'full'.
// Cline violates ALL three (nested:false, maxDepth:1, read-only) — so it must
// stay degraded regardless of any negotiation defaults.
const result = degradationFor('dispatch', CLN_CAP.runtime.hostIntegration);
assert.notEqual(result.level, 'full');
});
// -- boundary: maxDepth 1 vs 0 vs -1 ----------------------------------------
test('maxDepth 1 is flat (NOT absent, NOT unbounded)', () => {
assert.ok(CLN_DISPATCH.maxDepth > 0, 'maxDepth must be positive (not absent/single-agent)');
assert.notEqual(CLN_DISPATCH.maxDepth, -1, 'cline is NOT unbounded — depth-1 is the documented limit');
assert.notEqual(CLN_DISPATCH.maxDepth, 0, 'maxDepth:0 would mean no named dispatch — cline HAS named dispatch');
});