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.
174 lines
6.6 KiB
JavaScript
174 lines
6.6 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* claude-orchestration-command-router.test.cjs — end-to-end tests for the
|
|
* `msd-tools claude-orchestration` command surface (#1143).
|
|
*
|
|
* Exercises the full dispatch path: msd-tools → dispatchCapabilityCommand →
|
|
* routeClaudeOrchestrationCommand → the pure detect/emitter functions.
|
|
*
|
|
* NOTE: runMsdTools() returns { success, output, exitCode, error } — it does
|
|
* NOT throw on a non-zero exit (helpers.cjs). Tests assert `.success` and parse
|
|
* `.output`, and check `.error` on the failure path.
|
|
*/
|
|
|
|
const { describe, test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const { runMsdTools, createTempProject, cleanup } = require('./helpers.cjs');
|
|
|
|
// ─── Fixtures ─────────────────────────────────────────────────────────────────
|
|
|
|
const WAVES_MANIFEST = {
|
|
waves: [
|
|
{
|
|
id: 'w1',
|
|
plans: [
|
|
{ id: 'p1', brief: 'Implement the foo module', files_modified: ['src/foo.cts'] },
|
|
{ id: 'p2', brief: 'Wire the bar seam', files_modified: ['src/bar.cts'] },
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
function writeManifest(tmpDir) {
|
|
const manifestPath = path.join(tmpDir, 'waves.json');
|
|
fs.writeFileSync(manifestPath, JSON.stringify(WAVES_MANIFEST), 'utf8');
|
|
return manifestPath;
|
|
}
|
|
|
|
/** Run the command and assert it succeeded, returning the parsed JSON output. */
|
|
function runAndParse(args, cwd) {
|
|
const res = runMsdTools(args, cwd);
|
|
assert.strictEqual(res.success, true, 'command should succeed; stderr: ' + (res.error || ''));
|
|
assert.ok(res.output.length > 0, 'command should emit output');
|
|
return JSON.parse(res.output);
|
|
}
|
|
|
|
// ─── emit-workflow ────────────────────────────────────────────────────────────
|
|
|
|
describe('claude-orchestration emit-workflow (CLI)', () => {
|
|
test('emits a Workflow script with parallel barriers, msd-executor + worktree, and resumeFromRunId', () => {
|
|
const tmp = createTempProject('claw-emit-');
|
|
try {
|
|
const manifestPath = writeManifest(tmp);
|
|
const parsed = runAndParse([
|
|
'claude-orchestration', 'emit-workflow',
|
|
'--waves', manifestPath,
|
|
'--run-id', 'run-cli-1143',
|
|
'--phase-dir', '.planning/phases/01-foo',
|
|
], tmp);
|
|
assert.ok(typeof parsed.script === 'string' && parsed.script.length > 0);
|
|
assert.ok(parsed.script.includes('parallel('), 'parallel() barrier emitted');
|
|
assert.ok(parsed.script.includes('msd-executor'), 'msd-executor agentType');
|
|
assert.ok(parsed.script.includes('worktree'), 'worktree isolation');
|
|
// #2590: never CALLED — it is a Workflow tool input, not a script function.
|
|
assert.ok(!/^\s*resumeFromRunId\s*\(/m.test(parsed.script), 'must not CALL resumeFromRunId');
|
|
assert.ok(parsed.script.includes('run-cli-1143'), 'carries the run id');
|
|
assert.strictEqual(parsed.summary.resumeRunId, 'run-cli-1143');
|
|
assert.strictEqual(parsed.summary.waves, 1);
|
|
assert.strictEqual(parsed.summary.plans, 2);
|
|
} finally {
|
|
cleanup(tmp);
|
|
}
|
|
});
|
|
|
|
test('budget flag threads a shared token pool into the script', () => {
|
|
const tmp = createTempProject('claw-budget-');
|
|
try {
|
|
const manifestPath = writeManifest(tmp);
|
|
const parsed = runAndParse([
|
|
'claude-orchestration', 'emit-workflow',
|
|
'--waves', manifestPath,
|
|
'--run-id', 'r',
|
|
'--budget', '750000',
|
|
], tmp);
|
|
// #2590: `budget` is read-only; budget(750000) threw "budget is not a
|
|
// function". The intended pool is recorded, not called.
|
|
assert.ok(!/^\s*budget\s*\(/m.test(parsed.script), 'must not CALL budget()');
|
|
assert.ok(parsed.script.includes('750000'));
|
|
assert.strictEqual(parsed.summary.budgetTokens, 750000);
|
|
} finally {
|
|
cleanup(tmp);
|
|
}
|
|
});
|
|
|
|
test('missing --waves -> non-zero exit with a diagnostic', () => {
|
|
const tmp = createTempProject('claw-noargs-');
|
|
try {
|
|
const res = runMsdTools([
|
|
'claude-orchestration', 'emit-workflow', '--run-id', 'r',
|
|
], tmp);
|
|
assert.strictEqual(res.success, false, 'missing --waves must fail');
|
|
assert.ok(res.exitCode !== 0, 'non-zero exit');
|
|
assert.match(res.error || '', /--waves/);
|
|
} finally {
|
|
cleanup(tmp);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ─── detect-backend ───────────────────────────────────────────────────────────
|
|
|
|
describe('claude-orchestration detect-backend (CLI)', () => {
|
|
test('default config (capability off) -> inline, even on Claude', () => {
|
|
const tmp = createTempProject('claw-detect-');
|
|
try {
|
|
const parsed = runAndParse([
|
|
'claude-orchestration', 'detect-backend',
|
|
'--runtime', 'claude',
|
|
'--agent-sdk-version', '1.0.0',
|
|
], tmp);
|
|
assert.strictEqual(parsed.backend, 'inline');
|
|
assert.strictEqual(parsed.available, false);
|
|
assert.match(parsed.reason, /disabled/);
|
|
} finally {
|
|
cleanup(tmp);
|
|
}
|
|
});
|
|
|
|
test('enabled + claude + capable + new-enough SDK -> workflow', () => {
|
|
const tmp = createTempProject('claw-detect-on-');
|
|
try {
|
|
fs.mkdirSync(path.join(tmp, '.planning'), { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(tmp, '.planning', 'config.json'),
|
|
JSON.stringify({ claude_orchestration: { enabled: true, execution_backend: 'auto' } }),
|
|
'utf8',
|
|
);
|
|
const parsed = runAndParse([
|
|
'claude-orchestration', 'detect-backend',
|
|
'--runtime', 'claude',
|
|
'--agent-sdk-version', '1.2.0',
|
|
], tmp);
|
|
assert.strictEqual(parsed.backend, 'workflow');
|
|
assert.strictEqual(parsed.available, true);
|
|
} finally {
|
|
cleanup(tmp);
|
|
}
|
|
});
|
|
|
|
test('non-Claude runtime -> inline (criterion 6)', () => {
|
|
const tmp = createTempProject('claw-detect-nonclaude-');
|
|
try {
|
|
fs.mkdirSync(path.join(tmp, '.planning'), { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(tmp, '.planning', 'config.json'),
|
|
JSON.stringify({ claude_orchestration: { enabled: true } }),
|
|
'utf8',
|
|
);
|
|
const parsed = runAndParse([
|
|
'claude-orchestration', 'detect-backend',
|
|
'--runtime', 'codex',
|
|
'--agent-sdk-version', '1.0.0',
|
|
], tmp);
|
|
assert.strictEqual(parsed.backend, 'inline');
|
|
assert.match(parsed.reason, /claude/i);
|
|
} finally {
|
|
cleanup(tmp);
|
|
}
|
|
});
|
|
});
|