mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-15 04:35:29 +02:00
241 lines
14 KiB
JavaScript
241 lines
14 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const test = require('node:test');
|
|
const { withFixture } = require('./helpers/context-fixture');
|
|
const store = require('../../scripts/lib/context-profile-store');
|
|
const native = () => require('../../scripts/lib/context-profile-native');
|
|
|
|
function fixture(callback) {
|
|
return withFixture(repoRoot => {
|
|
const parent = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'ecc-native-claude-test-'));
|
|
const options = { stateRoot: path.join(parent, 'managed'), nativeRoot: path.join(parent, 'native'), claudePath: process.execPath };
|
|
try {
|
|
store.applyStore({ repoRoot, stateRoot: options.stateRoot, target: 'claude' });
|
|
return callback(options, repoRoot, parent);
|
|
} finally { fs.rmSync(parent, { recursive: true, force: true }); }
|
|
});
|
|
}
|
|
|
|
function details(pluginDir, overrides) {
|
|
const skills = fs.readdirSync(path.join(pluginDir, 'skills')).sort();
|
|
const counts = { agents: 0, hooks: 0, mcp: 0, lsp: 0, ...overrides.counts };
|
|
return ['ecc-context-carrier', ' Source: ecc-context-carrier@inline', '', 'Component inventory',
|
|
` Skills (${skills.length}) ${skills.join(', ')}`, ` Agents (${counts.agents})`, ` Hooks (${counts.hooks})`,
|
|
` MCP servers (${counts.mcp})`, ` LSP servers (${counts.lsp})`, '', 'Projected token cost',
|
|
` Always-on: ${overrides.alwaysOn || '~1,398 tok'} added to every session`, ''].join('\n');
|
|
}
|
|
|
|
function provider(overrides = {}) {
|
|
const calls = [];
|
|
const execute = (command, args, options) => {
|
|
calls.push({ command, args, options });
|
|
assert.equal(options.killSignal, 'SIGKILL');
|
|
assert.equal(options.shell, false);
|
|
for (const key of ['ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'NODE_OPTIONS', 'HTTP_PROXY', 'AWS_ACCESS_KEY_ID', 'CODEX_HOME']) {
|
|
assert.equal(options.env[key], undefined);
|
|
}
|
|
assert.equal(options.env.CLAUDE_CONFIG_DIR, path.join(options.env.HOME, '.claude'));
|
|
assert.equal(options.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, '1');
|
|
assert.equal(options.env.CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL, '1');
|
|
assert.equal(options.cwd, path.join(path.dirname(options.env.HOME), 'project'));
|
|
if (args[0] === '--version') return { status: 0, stdout: `${overrides.version || '2.1.292 (Claude Code)'}\n` };
|
|
if (args[0] === 'plugin' && args[1] === 'validate') {
|
|
const report = { success: true, manifest: { errors: [], warnings: [] }, contents: [] };
|
|
return { status: 0, stdout: JSON.stringify(overrides.validate ? overrides.validate(report) : report) };
|
|
}
|
|
const pluginDir = args[1];
|
|
if (overrides.tamper) overrides.tamper(pluginDir);
|
|
if (args[3] === 'list') {
|
|
const plugins = [{ id: 'ecc-context-carrier@inline', version: 'unknown', scope: 'session', enabled: true, installPath: pluginDir }];
|
|
return { status: 0, stdout: JSON.stringify(overrides.list ? overrides.list(plugins) : plugins) };
|
|
}
|
|
if (args[3] === 'details') return { status: 0, stdout: details(pluginDir, overrides) };
|
|
return { status: 1, stderr: 'unexpected command' };
|
|
};
|
|
return { execute, calls, ...overrides };
|
|
}
|
|
|
|
test('Claude preview is deterministic and never invokes the provider or creates a home', () => fixture(options => {
|
|
const first = native().previewNativeProfile(options);
|
|
assert.deepEqual(first, native().previewNativeProfile(options));
|
|
assert.equal(first.target, 'claude');
|
|
assert.equal(first.status, 'proposed');
|
|
assert.deepEqual(first.supportedProviderVersions, ['>=2.1.292 <2.2.0']);
|
|
assert.equal(fs.existsSync(options.nativeRoot), false);
|
|
}));
|
|
|
|
test('Claude prepare verifies session-only discovery and records the host token projection', () => fixture(options => {
|
|
const dependency = provider();
|
|
const result = native().prepareNativeProfile(options, dependency);
|
|
assert.equal(result.status, 'ready');
|
|
assert.equal(result.target, 'claude');
|
|
assert.equal(result.providerVersion, '2.1.292');
|
|
assert.equal(result.discovery, 'verified');
|
|
assert.equal(result.codexHome, null);
|
|
assert.equal(result.claudeConfigDir, path.join(result.home, '.claude'));
|
|
assert.equal(result.pluginDir, path.join(path.dirname(result.home), 'plugin'));
|
|
assert.equal(result.claudePath, process.execPath);
|
|
assert.deepEqual(result.nativeObservation, { surface: 'plugin-always-on-listing',
|
|
method: 'claude-plugin-details@2.1.292', tokens: 1398, approximate: true });
|
|
assert.equal(result.selectedIds.length, 3);
|
|
assert.match(fs.readFileSync(path.join(result.claudeConfigDir, 'CLAUDE.md'), 'utf8'), /grants no tools/);
|
|
assert.equal(fs.existsSync(path.join(result.home, '.codex')), false);
|
|
const pluginCalls = dependency.calls.filter(call => call.args.includes('--plugin-dir'));
|
|
assert.deepEqual(pluginCalls.map(call => call.args.slice(2)), [['plugin', 'list', '--json'], ['plugin', 'details', 'ecc-context-carrier']]);
|
|
assert.ok(pluginCalls.every(call => call.args[1] === result.pluginDir));
|
|
assert.equal(native().getNativeProfileStatus(options).status, 'ready');
|
|
}));
|
|
|
|
for (const version of ['2.1.293 (Claude Code)', '2.1.1000 (Claude Code)']) {
|
|
test(`Claude version gate admits later 2.1 patch "${version}" and records it exactly`, () => fixture(options => {
|
|
const result = native().prepareNativeProfile(options, provider({ version }));
|
|
assert.equal(result.providerVersion, version.split(' ')[0]);
|
|
assert.equal(native().getNativeProfileStatus(options).providerVersion, version.split(' ')[0]);
|
|
}));
|
|
}
|
|
|
|
for (const version of ['2.1.291 (Claude Code)', '2.1.292', '2.1.292 (Claude Code) extra', '2.2.0 (Claude Code)',
|
|
'2.1.292-beta (Claude Code)', '3.1.292 (Claude Code)', '02.1.292 (Claude Code)']) {
|
|
test(`Claude version gate rejects "${version}" before plugin calls`, () => fixture(options => {
|
|
const dependency = provider({ version });
|
|
assert.throws(() => native().prepareNativeProfile(options, dependency), /version/i);
|
|
assert.equal(dependency.calls.some(call => call.args.includes('plugin')), false);
|
|
}));
|
|
}
|
|
|
|
const corruptions = {
|
|
'validation error': [{ validate: report => ({ ...report, success: false, manifest: { errors: [{ path: 'skills' }] } }) }, /plugin validation failed/],
|
|
'null validation entry': [{ validate: report => ({ ...report, contents: [null] }) }, /plugin validation failed/],
|
|
'validation entry without errors': [{ validate: report => ({ ...report, contents: [{ type: 'skill' }] }) }, /plugin validation failed/],
|
|
'validation entry with non-array errors': [{ validate: report => ({ ...report, contents: [{ type: 'skill', errors: 'bad' }] }) },
|
|
/plugin validation failed/],
|
|
'validation entry error': [{ validate: report => ({ ...report, contents: [{ type: 'skill', errors: [{ path: 'name' }] }] }) },
|
|
/plugin validation failed/],
|
|
'extra plugin': [{ list: plugins => [...plugins, { id: 'other@market', scope: 'user', enabled: true, installPath: '/elsewhere' }] }, /plugin discovery mismatch/],
|
|
'moved plugin': [{ list: plugins => [{ ...plugins[0], installPath: '/elsewhere' }] }, /plugin discovery mismatch/],
|
|
'disabled plugin': [{ list: plugins => [{ ...plugins[0], enabled: false }] }, /plugin discovery mismatch/],
|
|
'extra agent': [{ counts: { agents: 1 } }, /component discovery mismatch/],
|
|
'extra hook': [{ counts: { hooks: 1 } }, /component discovery mismatch/],
|
|
'extra MCP server': [{ counts: { mcp: 1 } }, /component discovery mismatch/],
|
|
'unparseable token projection': [{ alwaysOn: 'unknown' }, /token projection/],
|
|
'tampered plugin bytes': [{ tamper: pluginDir => fs.appendFileSync(path.join(pluginDir, 'skills/ecc-guide/SKILL.md'), 'tamper') },
|
|
/file set or digest mismatch/],
|
|
};
|
|
for (const [label, [overrides, expected]] of Object.entries(corruptions)) {
|
|
test(`Claude ${label} never commits a ready pointer`, () => fixture(options => {
|
|
assert.throws(() => native().prepareNativeProfile(options, provider(overrides)), expected);
|
|
assert.equal(fs.existsSync(path.join(options.nativeRoot, 'state.json')), false);
|
|
}));
|
|
}
|
|
|
|
test('Claude details skill mismatch never commits a ready pointer', () => fixture(options => {
|
|
const dependency = provider();
|
|
const execute = dependency.execute;
|
|
dependency.execute = (command, args, config) => {
|
|
const result = execute(command, args, config);
|
|
return args[3] === 'details' ? { ...result, stdout: result.stdout.replace(', ecc-guide', '') } : result;
|
|
};
|
|
assert.throws(() => native().prepareNativeProfile(options, dependency), /component discovery mismatch/);
|
|
assert.equal(fs.existsSync(path.join(options.nativeRoot, 'state.json')), false);
|
|
}));
|
|
|
|
test('Claude plugin bytes and added user skills reject static readiness', () => fixture(options => {
|
|
const prepared = native().prepareNativeProfile(options, provider());
|
|
fs.mkdirSync(path.join(prepared.claudeConfigDir, 'skills/extra'), { recursive: true });
|
|
fs.writeFileSync(path.join(prepared.claudeConfigDir, 'skills/extra/SKILL.md'), 'extra');
|
|
assert.throws(() => native().getNativeProfileStatus(options), /changed/);
|
|
fs.rmSync(path.join(prepared.claudeConfigDir, 'skills'), { recursive: true });
|
|
assert.equal(native().getNativeProfileStatus(options).ready, true);
|
|
fs.appendFileSync(path.join(prepared.pluginDir, 'skills/ecc-guide/SKILL.md'), 'tamper');
|
|
assert.throws(() => native().getNativeProfileStatus(options), /changed/);
|
|
}));
|
|
|
|
test('Claude provider runtime state outside discovery controls keeps readiness', () => fixture(options => {
|
|
const prepared = native().prepareNativeProfile(options, provider());
|
|
fs.writeFileSync(path.join(prepared.claudeConfigDir, '.claude.json'), '{"numStartups":1}');
|
|
fs.mkdirSync(path.join(prepared.claudeConfigDir, 'projects/session'), { recursive: true });
|
|
assert.equal(native().getNativeProfileStatus(options).status, 'ready');
|
|
}));
|
|
|
|
test('Claude provider homes are never accepted as the native root', () => fixture(options => {
|
|
const dependency = provider();
|
|
const previous = process.env.CLAUDE_CONFIG_DIR;
|
|
const configured = path.join(path.dirname(options.nativeRoot), 'configured-claude');
|
|
process.env.CLAUDE_CONFIG_DIR = configured;
|
|
try {
|
|
for (const root of [path.join(os.homedir(), '.claude'), configured]) {
|
|
assert.throws(() => native().prepareNativeProfile({ ...options, nativeRoot: root }, dependency), /dedicated/);
|
|
}
|
|
} finally {
|
|
if (previous === undefined) delete process.env.CLAUDE_CONFIG_DIR; else process.env.CLAUDE_CONFIG_DIR = previous;
|
|
}
|
|
assert.equal(dependency.calls.length, 0);
|
|
}));
|
|
|
|
test('a Codex generation becomes stale when the managed store targets Claude', () => fixture((options, repoRoot) => {
|
|
store.applyStore({ repoRoot, stateRoot: options.stateRoot, target: 'codex' });
|
|
const codex = provider();
|
|
codex.execute = (_command, args) => args[0] === '--version' ? { status: 0, stdout: 'codex-cli 0.154.0' } : { status: 0, stdout: '{}' };
|
|
codex.discover = (_command, config) => {
|
|
const base = path.dirname(config.env.HOME);
|
|
const name = JSON.parse(fs.readFileSync(path.join(base, 'marketplace/.agents/plugins/marketplace.json'))).name;
|
|
const cache = path.join(config.env.CODEX_HOME, 'plugins/cache', name, 'ecc-context-carrier/local');
|
|
fs.mkdirSync(path.dirname(cache), { recursive: true });
|
|
fs.cpSync(path.join(base, 'marketplace/carrier'), cache, { recursive: true });
|
|
return { data: [{ cwd: config.cwd, errors: [], skills: fs.readdirSync(path.join(cache, 'skills')).map(skill => ({
|
|
name: `ecc-context-carrier:${skill}`, pluginId: `ecc-context-carrier@${name}`, enabled: true, scope: 'user',
|
|
path: path.join(cache, 'skills', skill, 'SKILL.md') })) }] };
|
|
};
|
|
const first = native().prepareNativeProfile({ ...options, codexPath: process.execPath, claudePath: undefined }, codex);
|
|
assert.equal(first.target, 'codex');
|
|
store.applyStore({ repoRoot, stateRoot: options.stateRoot, target: 'claude' });
|
|
assert.equal(native().getNativeProfileStatus(options).status, 'stale');
|
|
const second = native().prepareNativeProfile(options, provider());
|
|
assert.equal(second.target, 'claude');
|
|
assert.equal(second.revision, first.revision + 1);
|
|
}));
|
|
|
|
test('Claude task launch loads the verified plugin directory in the isolated home', () => fixture(options => {
|
|
const prepared = native().prepareNativeProfile(options, provider());
|
|
const { launchTaskContext } = require('../../scripts/lib/context-profile-launch');
|
|
let called = false;
|
|
launchTaskContext({ task: { sessionId: 's', taskId: 't', revision: 1, phase: 'p', query: 'hello', noWorkflow: true },
|
|
target: 'claude', nativeEnvironment: { home: prepared.home, claudeConfigDir: prepared.claudeConfigDir,
|
|
claudePath: prepared.claudePath, pluginDir: prepared.pluginDir, executableDigest: prepared.executableDigest },
|
|
execute(command, args, config) {
|
|
called = true;
|
|
assert.equal(command, prepared.claudePath);
|
|
assert.deepEqual(args, ['--plugin-dir', prepared.pluginDir, '--print']);
|
|
assert.equal(config.env.CLAUDE_CONFIG_DIR, prepared.claudeConfigDir);
|
|
assert.equal(config.env.CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL, '1');
|
|
assert.equal(config.env.CODEX_HOME, undefined);
|
|
return { status: 0, stdout: 'ok' };
|
|
} });
|
|
assert.equal(called, true);
|
|
}));
|
|
|
|
test('Claude interactive start launches the pinned binary with the verified plugin directory', () => {
|
|
const parent = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'ecc-native-claude-start-'));
|
|
const options = { stateRoot: path.join(parent, 'managed'), nativeRoot: path.join(parent, 'native') };
|
|
try {
|
|
store.applyStore({ stateRoot: options.stateRoot, target: 'claude' });
|
|
const prepared = native().prepareNativeProfile({ ...options, claudePath: process.execPath }, provider());
|
|
const { startInteractiveProfile } = require('../../scripts/lib/context-profile-interactive');
|
|
let call;
|
|
const result = startInteractiveProfile(options,
|
|
{ execute: (command, args, config) => { call = { command, args, config }; return { status: 0 }; } });
|
|
assert.equal(call.command, prepared.claudePath);
|
|
assert.deepEqual(call.args, ['--plugin-dir', prepared.pluginDir]);
|
|
assert.equal(call.config.env.CLAUDE_CONFIG_DIR, prepared.claudeConfigDir);
|
|
assert.equal(call.config.env.CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL, '1');
|
|
assert.equal(call.config.env.CODEX_HOME, undefined);
|
|
assert.equal(call.config.stdio, 'inherit');
|
|
assert.equal(result.status, 'exited');
|
|
assert.match(result.bootstrapDigest, /^[a-f0-9]{64}$/);
|
|
} finally { fs.rmSync(parent, { recursive: true, force: true }); }
|
|
});
|