Merge remote-tracking branch 'origin/main' into fix/stocktake-live-inventory

This commit is contained in:
Yann Roberto
2026-10-01 01:59:10 +08:00
732 changed files with 89237 additions and 2028 deletions
+53 -1
View File
@@ -404,6 +404,7 @@ function runHermeticPythonPrePush({
? process.env.PATH
: `${toBashPath(pathBin)}${path.delimiter}${process.env.PATH}`,
HOME: process.env.HOME ?? '',
ECC_PREPUSH_RUN_CHECKS: '1',
ECC_SKIP_GIT_HOOKS: '0',
ECC_SKIP_PREPUSH: '0',
MSYS_NO_PATHCONV: '1',
@@ -916,7 +917,13 @@ if (
const merged = fs.readFileSync(configPath, 'utf8');
const parsed = TOML.parse(merged);
assert.strictEqual(parsed.mcp_servers['chrome-devtools'].command, 'npx');
assert.deepStrictEqual(parsed.mcp_servers['chrome-devtools'].args, ['chrome-devtools-mcp@latest']);
assert.deepStrictEqual(parsed.mcp_servers['chrome-devtools'].args, ['chrome-devtools-mcp@1.10.1']);
const rootMcp = JSON.parse(fs.readFileSync(path.join(repoRoot, '.mcp.json'), 'utf8'));
const rootPackages = rootMcp.mcpServers['chrome-devtools'].args.filter(arg =>
arg.startsWith('chrome-devtools-mcp@'));
assert.deepStrictEqual(rootPackages, parsed.mcp_servers['chrome-devtools'].args,
'root MCP and generated Codex TOML must use the same connector pin');
assert.deepStrictEqual(rootPackages, ['chrome-devtools-mcp@1.10.1']);
assert.strictEqual(parsed.mcp_servers['chrome-devtools'].startup_timeout_sec, 30);
// No retired server may be (re-)emitted — exa's url form broke Codex (#2224).
assert.strictEqual(parsed.mcp_servers.exa, undefined);
@@ -936,6 +943,51 @@ if (
passed++;
else failed++;
if (
test('merge-mcp-config preserves an existing latest entry until explicit recommendation refresh', () => {
const tempDir = createTempDir('mcp-merge-explicit-refresh-');
const configPath = path.join(tempDir, 'config.toml');
const original = [
'# User-maintained configuration',
'[mcp_servers.chrome-devtools]',
'command = "npx"',
'args = ["chrome-devtools-mcp@latest", "--custom-browser-argument"]',
'startup_timeout_sec = 75',
'[mcp_servers.chrome-devtools.env]',
'CUSTOM_BROWSER_SETTING = "preserve-until-explicit-refresh"',
'',
'[mcp_servers.user_tool]',
'command = "custom-launcher"',
'args = ["--user-setting"]',
'',
].join('\n');
try {
fs.writeFileSync(configPath, original);
const preserved = runNode(mergeMcpConfigScript, [configPath], deterministicPackageEnv);
assert.strictEqual(preserved.status, 0, `${preserved.stdout}\n${preserved.stderr}`);
assert.match(preserved.stderr, /chrome-devtools differs from ECC recommendation/);
assert.match(preserved.stderr, /--update-mcp to refresh/);
assert.strictEqual(fs.readFileSync(configPath, 'utf8'), original,
'normal sync must preserve existing latest and customized settings byte-for-byte');
const refreshed = runNode(mergeMcpConfigScript, [configPath, '--update-mcp'], deterministicPackageEnv);
assert.strictEqual(refreshed.status, 0, `${refreshed.stdout}\n${refreshed.stderr}`);
assert.match(refreshed.stdout, /\[update\] mcp_servers\.chrome-devtools/);
const updated = TOML.parse(fs.readFileSync(configPath, 'utf8'));
assert.deepStrictEqual(updated.mcp_servers['chrome-devtools'], {
command: 'npx', args: ['chrome-devtools-mcp@1.10.1'], startup_timeout_sec: 30,
}, 'explicit refresh replaces the whole recommended section, including custom subsettings');
assert.deepStrictEqual(updated.mcp_servers.user_tool, TOML.parse(original).mcp_servers.user_tool,
'unrelated user-managed server settings remain untouched');
} finally {
cleanup(tempDir);
}
})
)
passed++;
else failed++;
if (
test('merge-mcp-config repairs the invalid exa url entry from earlier ECC versions (#2224)', () => {
const tempDir = createTempDir('mcp-merge-exa-repair-');
+4 -3
View File
@@ -604,9 +604,10 @@ async function runTests() {
if (
await test('CLI browser opener handles spawn errors', async () => {
const source = fs.readFileSync(SCRIPT, 'utf8');
assert.match(source, /child\.on\('error'/);
assert.match(source, /child\.unref\(\)/);
const helper = fs.readFileSync(path.join(path.dirname(SCRIPT), 'lib/platform-launch.js'), 'utf8');
assert.match(source, /require\('\.\/lib\/platform-launch'\)/);
assert.match(helper, /child\.on\('error'/);
assert.match(helper, /child\.unref\(\)/);
})
)
passed++;
@@ -0,0 +1,18 @@
'use strict';
const assert = require('node:assert/strict');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const test = require('node:test');
const command = path.resolve(__dirname, '../../scripts/dev/generate-skill-triggers.js');
test('trigger generation rejects batch sizes that cannot advance', () => {
for (const value of ['0', '-1', 'NaN', '1.5', '9007199254740992']) {
const result = spawnSync(process.execPath, [command, '--batch', value, '--dry-run'], {
encoding: 'utf8', timeout: 5000,
});
assert.equal(result.status, 1, `${value}: ${result.stderr}`);
assert.match(result.stderr, /--batch must be a positive integer/);
}
});
+25 -1
View File
@@ -1104,7 +1104,7 @@ function runTests() {
assert.strictEqual(fs.readFileSync(scriptsPackagePath, 'utf8'), userScriptsPackage);
const state = readJson(path.join(claudeRoot, 'ecc', 'install-state.json'));
const boundaryPaths = [hooksPackagePath, libPackagePath];
const boundaryPaths = [hooksPackagePath, libPackagePath].map(file => fs.realpathSync(file));
const packageBoundaryOperations = state.operations.filter(operation => (
boundaryPaths.includes(operation.destinationPath)
));
@@ -1191,6 +1191,7 @@ function runTests() {
applyInstallPlan({
targetRoot: path.join(tempDir, 'installed'),
adapter: { id: 'test-install', target: 'test-install' },
installStatePath,
statePreview: {
schemaVersion: 'ecc.install.v1',
@@ -1487,6 +1488,29 @@ function runTests() {
}
})) passed++; else failed++;
for (const explicitConfig of [true, false]) {
if (test(`installs from a UTF-8 BOM config (${explicitConfig ? '--config' : 'auto-detected'})`, () => {
const homeDir = createTempDir('install-apply-bom-home-');
const projectDir = createTempDir('install-apply-bom-project-');
const configPath = path.join(projectDir, 'ecc-install.json');
const content = '\uFEFF{\r\n "version": 1,\r\n "target": "cursor",\r\n "modules": ["rules-core"]\r\n}\r\n';
try {
fs.writeFileSync(configPath, content, 'utf8');
const args = explicitConfig ? ['--config', configPath] : [];
const result = run(args, { cwd: projectDir, homeDir });
assert.strictEqual(result.code, 0, result.stderr);
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'common-coding-style.mdc')));
const state = readJson(path.join(projectDir, '.cursor', 'ecc-install-state.json'));
assert.deepStrictEqual(state.request.modules, ['rules-core']);
assert.strictEqual(fs.readFileSync(configPath, 'utf8'), content);
} finally {
cleanup(homeDir);
cleanup(projectDir);
}
})) passed++; else failed++;
}
if (test('preserves legacy language installs when a project config is present', () => {
const homeDir = createTempDir('install-apply-home-');
const projectDir = createTempDir('install-apply-project-');
+469 -57
View File
@@ -1,14 +1,19 @@
/**
* Tests for install.sh wrapper delegation
* Tests for install.sh wrapper delegation and owned child lifecycle.
* --lifecycle-only runs inert Node fixtures without invoking an installer/shell.
*/
const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { execFileSync } = require('child_process');
const { spawn } = require('child_process');
const SCRIPT = path.join(__dirname, '..', '..', 'install.sh');
const CHILD_LIMITS = Object.freeze({ timeout: 10000, termGrace: 100, closeGrace: 500, maxBytes: 1024 * 1024 });
const FIXTURE_LIMITS = Object.freeze({ ...CHILD_LIMITS, maxBytes: 65536 });
const STALL_LIMITS = Object.freeze({ ...FIXTURE_LIMITS, timeout: 500 });
const CHILD_PATH = [path.dirname(process.execPath), '/opt/homebrew/bin', '/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin'].join(path.delimiter);
function createTempDir(prefix) {
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
@@ -18,45 +23,393 @@ function cleanup(dirPath) {
fs.rmSync(dirPath, { recursive: true, force: true });
}
function run(args = [], options = {}) {
const env = {
...process.env,
HOME: options.homeDir || process.env.HOME,
...(options.env || {}),
};
try {
const stdout = execFileSync('bash', [options.scriptPath || SCRIPT, ...args], {
cwd: options.cwd,
env,
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 10000,
});
return { code: 0, stdout, stderr: '' };
} catch (error) {
return {
code: error.status || 1,
stdout: error.stdout || '',
stderr: error.stderr || '',
};
function finishCleanup(dirs, failed, primary, remove = cleanup) {
const errors = [];
for (const dir of dirs) {
try { remove(dir); } catch (error) { errors.push(error); }
}
if (failed) {
if (errors.length) console.error(`Fixture cleanup also failed (${errors.length} errors)`);
throw primary; // Includes falsy thrown values; cleanup cannot replace them.
}
if (errors.length) throw new AggregateError(errors, 'Fixture cleanup failed');
}
function test(name, fn) {
async function withTempDirs(prefixes, fn) {
const dirs = [];
let failed = false;
let primary;
let value;
try {
fn();
for (const prefix of prefixes) dirs.push(createTempDir(prefix));
value = await fn(...dirs);
} catch (error) {
failed = true;
primary = error;
}
finishCleanup(dirs, failed, primary);
return value;
}
function privateEnvironment(root, homeDir, overrides = {}) {
const home = homeDir || path.join(root, 'home');
const temp = path.join(root, 'tmp');
fs.mkdirSync(home, { recursive: true });
fs.mkdirSync(temp, { recursive: true });
return {
PATH: CHILD_PATH, HOME: home, USERPROFILE: home,
TMPDIR: temp, TMP: temp, TEMP: temp, LANG: 'C', LC_ALL: 'C',
...overrides,
};
}
// Each invocation owns a new POSIX group. The deadline covers headers/output
// through 'close', even when a dead leader's descendant retains a pipe.
function runChild(binary, args, options, limits = CHILD_LIMITS) {
return new Promise(resolve => {
const started = Date.now();
let child;
let status = null;
let signal = null;
let spawnError = null;
let exited = false;
let closed = false;
let settled = false;
let terminating = false;
let hardKillSent = false;
let timedOut = false;
let outputLimit = false;
let forcedPipeClose = false;
let bytes = 0;
const stdout = [];
const stderr = [];
const signalErrors = [];
const timers = [];
const detached = process.platform !== 'win32';
const later = (fn, ms) => { const timer = setTimeout(fn, ms); timers.push(timer); return timer; };
const finish = () => {
if (settled) return;
settled = true;
for (const timer of timers) clearTimeout(timer);
const failed = terminating || spawnError || signal || forcedPipeClose || !closed;
const code = failed ? (status || 1) : status;
const result = {
code, status, signal, errorCode: spawnError && spawnError.code,
errorMessage: spawnError && spawnError.message,
timedOut, outputLimit, forcedPipeClose, closed, reaped: exited,
pid: child && child.pid, signalErrors, elapsedMs: Date.now() - started,
stdout: Buffer.concat(stdout).toString('utf8'), stderr: Buffer.concat(stderr).toString('utf8'),
};
result.diagnostic = JSON.stringify({
status, signal, errorCode: result.errorCode, timedOut, outputLimit,
forcedPipeClose, closed, reaped: exited, signalErrors,
});
resolve(result);
};
const sendSignal = name => {
if (!child || !Number.isInteger(child.pid) || child.pid <= 0) return;
try {
if (detached) process.kill(-child.pid, name);
else child.kill(name);
} catch (error) {
if (error.code !== 'ESRCH') signalErrors.push({ signal: name, code: error.code });
}
};
const terminate = () => {
if (terminating || settled) return;
terminating = true;
sendSignal('SIGTERM');
later(() => {
// Do not cancel escalation on leader exit or early pipe closure.
sendSignal('SIGKILL');
hardKillSent = true;
if (closed) return finish();
later(() => {
forcedPipeClose = true;
for (const stream of [child.stdin, child.stdout, child.stderr]) stream.destroy();
// An OS that cannot reap after SIGKILL is a reported failure, never a
// successful flush. Do not leave its ChildProcess handle blocking us.
child.unref();
finish();
}, limits.closeGrace);
}, limits.termGrace);
};
const capture = target => data => {
const remaining = limits.maxBytes - bytes;
const part = data.subarray(0, Math.max(0, remaining));
if (part.length) target.push(part);
bytes += part.length;
if (part.length !== data.length) {
outputLimit = true;
terminate();
}
};
try {
child = spawn(binary, args, { ...options, shell: false, detached, stdio: ['pipe', 'pipe', 'pipe'] });
} catch (error) {
spawnError = error;
finish();
return;
}
child.stdout.on('data', capture(stdout));
child.stderr.on('data', capture(stderr));
for (const stream of [child.stdin, child.stdout, child.stderr]) {
stream.on('error', error => {
if (stream === child.stdin && error.code === 'EPIPE') return;
spawnError = spawnError || error;
terminate();
});
}
child.once('error', error => { spawnError = error; terminate(); });
child.once('exit', (code, exitSignal) => { status = code; signal = exitSignal; exited = true; });
child.once('close', (code, exitSignal) => {
status = code;
signal = exitSignal;
closed = true;
if (!terminating || hardKillSent) finish();
});
later(() => { timedOut = true; terminate(); }, limits.timeout);
child.stdin.end();
});
}
function classifyProbe(result) {
if (result.errorCode === 'ENOENT' && !result.timedOut && !result.signal && !result.forcedPipeClose) return 'missing';
if (result.timedOut || result.outputLimit || result.forcedPipeClose || result.errorCode || result.signal || !result.closed) {
throw new Error(`Shell capability probe infrastructure failure: ${result.diagnostic}`);
}
if (result.code === 0) return 'supported';
if (result.code === 127 && /\[\[: (?:not found|command not found)/.test(result.stderr)) return 'unsupported';
throw new Error(`Unexpected shell capability probe failure: ${result.diagnostic}; ${result.stderr}`);
}
async function findPosixOnlyShell(runner = runChild) {
return withTempDirs(['install-sh-probe-'], async root => {
const env = privateEnvironment(root);
const probeScript = path.join(root, 'probe.sh');
fs.writeFileSync(probeScript, "eval '[[ 1 == 1 ]]'\n", { flag: 'wx', mode: 0o600 });
for (const candidate of ['dash', 'sh']) {
console.log(` START shell capability probe: ${candidate}`);
const result = await runner(candidate, [probeScript], { env });
const capability = classifyProbe(result);
console.log(` END shell capability probe: ${candidate}: ${capability}`);
if (capability === 'unsupported') return candidate;
}
return null;
});
}
async function run(args = [], options = {}) {
if (!options.scriptPath) {
// Never let a real-source test enter install.sh's network bootstrap path.
assert.ok(fs.statSync(path.join(path.dirname(SCRIPT), 'node_modules')).isDirectory(),
'Installer source tests require already installed repository dependencies; no bootstrap is allowed');
}
return withTempDirs(['install-sh-env-'], async root => {
const result = await runChild(options.shell || 'bash', [options.scriptPath || SCRIPT, ...args], {
cwd: options.cwd,
env: privateEnvironment(root, options.homeDir, options.env),
});
if (result.code !== 0) result.stderr += `\nChild failure: ${result.diagnostic}`;
return result;
});
}
async function test(name, fn) {
console.log(` START ${name}`);
try {
await fn();
console.log(` \u2713 ${name}`);
return true;
} catch (error) {
console.log(` \u2717 ${name}`);
console.log(` Error: ${error.message}`);
console.log(` Error: ${error && error.message ? error.message : String(error)}`);
return false;
}
}
function runTests() {
async function inertChild(runner, source, args = [], limits = FIXTURE_LIMITS) {
return withTempDirs(['install-sh-child-'], async root => {
const script = path.join(root, 'child.js');
fs.writeFileSync(script, source);
return runner(process.execPath, [script, ...args], { cwd: root, env: privateEnvironment(root) }, limits);
});
}
async function assertGone(pid) {
assert.ok(Number.isInteger(pid) && pid > 0, 'fixture must identify its owned process');
for (let attempt = 0; attempt < 25; attempt++) {
try { process.kill(pid, 0); } catch (error) {
if (error.code === 'ESRCH') return;
throw error;
}
await new Promise(resolve => setTimeout(resolve, 20));
}
assert.fail(`owned fixture process ${pid} survived cleanup`);
}
function lifecycleCases(runner = runChild, probe = classifyProbe, finish = finishCleanup, findProbe = findPosixOnlyShell) {
return [
['inert child preserves stdout, stderr, arguments and stdin EOF', async () => {
const result = await inertChild(runner,
"process.stdout.write('out:' + process.argv[2]); process.stderr.write('warning'); process.stdin.resume(); process.stdin.on('end', () => process.stdout.write(':EOF'));",
['literal argument']);
assert.strictEqual(result.code, 0, result.diagnostic);
assert.strictEqual(result.stdout, 'out:literal argument:EOF');
assert.strictEqual(result.stderr, 'warning');
assert.strictEqual(result.closed, true);
assert.strictEqual(result.reaped, true);
}],
['nonzero inert child preserves exact status and partial output', async () => {
const result = await inertChild(runner, "process.stdout.write('partial'); process.stderr.write('failure'); process.exitCode = 7;");
assert.strictEqual(result.code, 7);
assert.strictEqual(result.status, 7);
assert.strictEqual(result.stdout, 'partial');
assert.strictEqual(result.stderr, 'failure');
}],
['missing executable remains unavailable rather than a supported capability', async () => {
await withTempDirs(['install-sh-missing-'], async root => {
const result = await runner(path.join(root, 'absent-binary'), [], { env: privateEnvironment(root) }, FIXTURE_LIMITS);
assert.strictEqual(result.errorCode, 'ENOENT');
assert.strictEqual(probe(result), 'missing');
assert.notStrictEqual(result.code, 0);
});
}],
['signal termination remains an infrastructure failure', async () => {
const result = await inertChild(runner, "process.kill(process.pid, 'SIGTERM');");
assert.strictEqual(result.signal, 'SIGTERM');
assert.notStrictEqual(result.code, 0);
assert.throws(() => probe(result), /infrastructure failure/);
}],
['ignored SIGTERM is escalated and the owned leader is reaped', async () => {
const result = await inertChild(runner,
"process.on('SIGTERM', () => {}); console.log(process.pid); setInterval(() => {}, 1000);", [], STALL_LIMITS);
assert.strictEqual(result.timedOut, true);
assert.strictEqual(result.signal, 'SIGKILL');
assert.strictEqual(result.reaped, true);
assert.strictEqual(result.forcedPipeClose, false);
assert.notStrictEqual(result.code, 0);
assert.ok(result.elapsedMs < 2000, result.diagnostic);
await assertGone(Number(result.stdout.trim()));
}],
['leader exit cannot hide an owned descendant retaining captured pipes', async () => {
const descendant = "process.on('SIGTERM', () => {}); process.send('ready'); process.disconnect(); setInterval(() => {}, 1000);";
const source = "const { spawn } = require('child_process'); const child = spawn(process.execPath, ['-e', " + JSON.stringify(descendant) + "], { stdio: ['ignore', 1, 2, 'ipc'] }); child.once('message', () => { console.log(child.pid); process.exit(0); });";
const result = await inertChild(runner, source, [], STALL_LIMITS);
assert.strictEqual(result.status, 0, 'the leader exits successfully before the deadline');
assert.strictEqual(result.timedOut, true);
assert.notStrictEqual(result.code, 0);
assert.strictEqual(result.closed, true);
assert.strictEqual(result.forcedPipeClose, false);
assert.ok(result.elapsedMs < 2000, result.diagnostic);
await assertGone(Number(result.stdout.trim()));
}],
['captured output is capped and overflow fails', async () => {
const result = await inertChild(runner, "process.stdout.write('x'.repeat(131072));");
assert.strictEqual(result.outputLimit, true);
assert.notStrictEqual(result.code, 0);
assert.ok(Buffer.byteLength(result.stdout) + Buffer.byteLength(result.stderr) <= FIXTURE_LIMITS.maxBytes);
}],
['a stalled capability probe fails instead of selecting or skipping a shell', async () => {
const result = await inertChild(runner, "console.log('probe started'); setInterval(() => {}, 1000);", [], STALL_LIMITS);
assert.strictEqual(result.timedOut, true);
assert.ok(result.stdout.includes('probe started'));
assert.throws(() => probe(result), /infrastructure failure/);
}],
['probe classification accepts only known capability outcomes', () => {
const complete = { code: 0, closed: true, stderr: '', diagnostic: 'synthetic probe' };
assert.strictEqual(probe(complete), 'supported');
assert.strictEqual(probe({ ...complete, code: 127, stderr: 'dash: 1: eval: [[: not found\n' }), 'unsupported');
assert.strictEqual(probe({ ...complete, code: 127, stderr: 'sh: [[: command not found\n' }), 'unsupported');
for (const result of [
{ ...complete, code: 2, stderr: 'syntax error' },
{ ...complete, code: 127, stderr: 'other command not found' },
{ ...complete, errorCode: 'EACCES' },
{ ...complete, signal: 'SIGKILL' },
{ ...complete, timedOut: true },
{ ...complete, outputLimit: true },
{ ...complete, forcedPipeClose: true },
{ ...complete, closed: false },
]) assert.throws(() => probe(result), /failure/);
}],
['cleanup attempts every owned root and preserves falsy primary errors', () => {
for (const primary of [undefined, null, false, 0, '']) {
const attempts = [];
let threw = false;
try {
finish(['first', 'second'], true, primary, dir => {
attempts.push(dir);
if (dir === 'first') throw new Error('synthetic cleanup failure');
});
} catch (error) {
threw = true;
assert.strictEqual(error, primary);
}
assert.strictEqual(threw, true);
assert.deepStrictEqual(attempts, ['first', 'second']);
}
}],
['cleanup-only failures remain failures after all roots are attempted', () => {
const attempts = [];
assert.throws(() => finish(['first', 'second'], false, undefined, dir => {
attempts.push(dir);
throw new Error('synthetic cleanup failure');
}), error => error instanceof AggregateError && error.errors.length === 2);
assert.deepStrictEqual(attempts, ['first', 'second']);
}],
['shell capability probe passes a private source file as one literal argument', async () => {
const observed = [];
const result = await findProbe(async (binary, args, options) => {
assert.strictEqual(args.length, 1, 'probe source must be a file argument');
assert.ok(path.isAbsolute(args[0]));
assert.strictEqual(fs.readFileSync(args[0], 'utf8'), "eval '[[ 1 == 1 ]]'\n");
assert.strictEqual(fs.statSync(args[0]).mode & 0o777, 0o600);
assert.ok(options.env.HOME.startsWith(path.dirname(args[0]) + path.sep));
observed.push({ binary, source: args[0] });
return binary === 'dash'
? { code: 1, errorCode: 'ENOENT', closed: true, stderr: '' }
: { code: 127, closed: true, stderr: 'sh: [[: not found\n', diagnostic: 'fixed probe' };
});
assert.strictEqual(result, 'sh');
assert.deepStrictEqual(observed.map(item => item.binary), ['dash', 'sh']);
assert.strictEqual(observed[0].source, observed[1].source);
assert.strictEqual(fs.existsSync(path.dirname(observed[0].source)), false);
}],
['shell capability infrastructure failure stops probing and removes its source', async () => {
const observed = [];
await assert.rejects(findProbe(async (binary, args) => {
assert.strictEqual(args.length, 1, 'probe source must be a file argument');
assert.strictEqual(fs.readFileSync(args[0], 'utf8'), "eval '[[ 1 == 1 ]]'\n");
observed.push({ binary, source: args[0] });
return { code: 1, timedOut: true, closed: true, diagnostic: 'fixed timed-out probe' };
}), /infrastructure failure/);
assert.strictEqual(observed.length, 1, 'do not select another shell after infrastructure failure');
assert.strictEqual(observed[0].binary, 'dash');
assert.strictEqual(fs.existsSync(path.dirname(observed[0].source)), false);
}],
['child environment contains only private roots and explicit runtime settings', async () => {
await withTempDirs(['install-sh-env-control-'], async root => {
const env = privateEnvironment(root);
assert.deepStrictEqual(Object.keys(env).sort(), ['HOME', 'LANG', 'LC_ALL', 'PATH', 'TEMP', 'TMP', 'TMPDIR', 'USERPROFILE'].sort());
for (const key of ['HOME', 'USERPROFILE', 'TEMP', 'TMP', 'TMPDIR']) assert.ok(env[key].startsWith(root + path.sep));
assert.strictEqual(env.PATH, CHILD_PATH);
});
}],
];
}
async function runLifecycleTests(setExit = true) {
let passed = 0;
let failed = 0;
for (const [name, fn] of lifecycleCases()) {
if (await test(name, fn)) passed++; else failed++;
}
if (setExit) console.log(`Lifecycle results: Passed: ${passed}, Failed: ${failed}`);
if (setExit) process.exitCode = failed ? 1 : 0;
return { passed, failed };
}
async function runTests() {
console.log('\n=== Testing install.sh ===\n');
let passed = 0;
@@ -65,15 +418,16 @@ function runTests() {
if (process.platform === 'win32') {
console.log(' - skipped on Windows; install.ps1 covers the native wrapper path');
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exit(0);
process.exitCode = 0;
return;
}
if (test('delegates to the Node installer and preserves dry-run output', () => {
const homeDir = createTempDir('install-sh-home-');
const projectDir = createTempDir('install-sh-project-');
if (process.argv.includes('--lifecycle-only')) return runLifecycleTests();
try {
const result = run(['--target', 'cursor', '--dry-run', 'typescript'], {
if (await test('delegates to the Node installer and preserves dry-run output', async () => {
await withTempDirs(['install-sh-home-', 'install-sh-project-'], async (homeDir, projectDir) => {
const result = await run(['--target', 'cursor', '--dry-run', 'typescript'], {
cwd: projectDir,
homeDir,
});
@@ -81,21 +435,16 @@ function runTests() {
assert.strictEqual(result.code, 0, result.stderr);
assert.ok(result.stdout.includes('Dry-run install plan'));
assert.ok(!fs.existsSync(path.join(projectDir, '.cursor', 'hooks.json')));
} finally {
cleanup(homeDir);
cleanup(projectDir);
}
});
})) passed++; else failed++;
if (test('absolute wrapper bootstraps a fresh source while preserving the target project cwd', () => {
const sourceDir = createTempDir('install-sh-source-');
const projectDir = createTempDir('install-sh-target-');
const binDir = path.join(sourceDir, 'test-bin');
const scriptsDir = path.join(sourceDir, 'scripts');
const npmCwdPath = path.join(sourceDir, 'npm-cwd.txt');
const fixtureScript = path.join(sourceDir, 'install.sh');
if (await test('absolute wrapper bootstraps a fresh source while preserving the target project cwd', async () => {
await withTempDirs(['install-sh-source-', 'install-sh-target-'], async (sourceDir, projectDir) => {
const binDir = path.join(sourceDir, 'test-bin');
const scriptsDir = path.join(sourceDir, 'scripts');
const npmCwdPath = path.join(sourceDir, 'npm-cwd.txt');
const fixtureScript = path.join(sourceDir, 'install.sh');
try {
fs.mkdirSync(binDir, { recursive: true });
fs.mkdirSync(scriptsDir, { recursive: true });
fs.copyFileSync(SCRIPT, fixtureScript);
@@ -109,12 +458,12 @@ function runTests() {
'console.log(JSON.stringify({ cwd: process.cwd(), args: process.argv.slice(2) }));\n'
);
const result = run(['--target', 'antigravity', '--dry-run', 'typescript'], {
const result = await run(['--target', 'antigravity', '--dry-run', 'typescript'], {
cwd: projectDir,
scriptPath: fixtureScript,
env: {
ECC_TEST_NPM_CWD: npmCwdPath,
PATH: `${binDir}${path.delimiter}${process.env.PATH}`,
PATH: `${binDir}${path.delimiter}${CHILD_PATH}`,
},
});
@@ -124,14 +473,69 @@ function runTests() {
assert.deepStrictEqual(payload.args, ['--target', 'antigravity', '--dry-run', 'typescript']);
assert.strictEqual(fs.readFileSync(npmCwdPath, 'utf8').trim(), sourceDir);
assert.ok(fs.existsSync(path.join(sourceDir, 'node_modules')));
} finally {
cleanup(sourceDir);
cleanup(projectDir);
}
});
})) passed++; else failed++;
if (test('exposes the corrected Claude target help text', () => {
const result = run(['--help']);
if (await test('delegates to the Node installer when invoked via a POSIX sh wrapper', async () => {
await withTempDirs(['install-sh-posix-source-', 'install-sh-posix-target-'], async (sourceDir, projectDir) => {
const scriptsDir = path.join(sourceDir, 'scripts');
const fixtureScript = path.join(sourceDir, 'install.sh');
fs.mkdirSync(scriptsDir, { recursive: true });
fs.mkdirSync(path.join(sourceDir, 'node_modules'), { recursive: true });
fs.copyFileSync(SCRIPT, fixtureScript);
fs.writeFileSync(
path.join(scriptsDir, 'install-apply.js'),
'console.log(JSON.stringify({ cwd: process.cwd(), args: process.argv.slice(2) }));\n'
);
const result = await run(['--target', 'antigravity', '--dry-run', 'typescript'], {
cwd: projectDir,
scriptPath: fixtureScript,
shell: 'sh',
});
assert.strictEqual(result.code, 0, result.stderr);
const payload = JSON.parse(result.stdout.trim().split('\n').at(-1));
assert.strictEqual(payload.cwd, fs.realpathSync(projectDir));
assert.deepStrictEqual(payload.args, ['--target', 'antigravity', '--dry-run', 'typescript']);
});
})) passed++; else failed++;
const posixOnlyShell = await findPosixOnlyShell();
if (!posixOnlyShell) {
console.log(
' - skipped: re-execs into bash under sh even when BASH_VERSION is spoofed in the environment ' +
'(no shell without `[[` support was found on this system)'
);
} else if (await test('re-execs into bash under sh even when BASH_VERSION is spoofed in the environment', async () => {
await withTempDirs(['install-sh-spoof-source-', 'install-sh-spoof-target-'], async (sourceDir, projectDir) => {
const scriptsDir = path.join(sourceDir, 'scripts');
const fixtureScript = path.join(sourceDir, 'install.sh');
fs.mkdirSync(scriptsDir, { recursive: true });
fs.mkdirSync(path.join(sourceDir, 'node_modules'), { recursive: true });
fs.copyFileSync(SCRIPT, fixtureScript);
fs.writeFileSync(
path.join(scriptsDir, 'install-apply.js'),
'console.log(JSON.stringify({ cwd: process.cwd(), args: process.argv.slice(2) }));\n'
);
const result = await run(['--target', 'antigravity', '--dry-run', 'typescript'], {
cwd: projectDir,
scriptPath: fixtureScript,
shell: posixOnlyShell,
env: { BASH_VERSION: '9.9.9(1)-spoofed' },
});
assert.strictEqual(result.code, 0, result.stderr);
const payload = JSON.parse(result.stdout.trim().split('\n').at(-1));
assert.deepStrictEqual(payload.args, ['--target', 'antigravity', '--dry-run', 'typescript']);
});
})) passed++; else failed++;
if (await test('exposes the corrected Claude target help text', async () => {
const result = await run(['--help']);
assert.strictEqual(result.code, 0, result.stderr);
assert.ok(
result.stdout.includes('claude (default) - Install ECC into ~/.claude/'),
@@ -139,8 +543,16 @@ function runTests() {
);
})) passed++; else failed++;
const lifecycle = await runLifecycleTests(false);
passed += lifecycle.passed;
failed += lifecycle.failed;
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);
process.exitCode = failed > 0 ? 1 : 0;
}
runTests();
if (require.main === module) {
process.exitCode = 1; // A never-settling Promise must not silently exit successfully.
runTests().catch(error => console.error(error && error.stack ? error.stack : String(error)));
}
module.exports = { runChild, classifyProbe, finishCleanup, lifecycleCases, runLifecycleTests };
+211 -1
View File
@@ -114,6 +114,211 @@ function extractNamedTable(content, ariaLabel) {
return match[1];
}
const README_SPONSOR_LABELS = {
"en": {
"active": "Partners and sponsors",
"community": "Community sponsors:",
"past": "Past sponsors:",
"inactive": "inactive"
},
"uk": {
"active": "\u041f\u0430\u0440\u0442\u043d\u0435\u0440\u0438 \u0442\u0430 \u0441\u043f\u043e\u043d\u0441\u043e\u0440\u0438",
"community": "\u0421\u043f\u043e\u043d\u0441\u043e\u0440\u0438 \u0441\u043f\u0456\u043b\u044c\u043d\u043e\u0442\u0438:",
"past": "\u041c\u0438\u043d\u0443\u043b\u0456 \u0441\u043f\u043e\u043d\u0441\u043e\u0440\u0438:",
"inactive": "\u043d\u0435\u0430\u043a\u0442\u0438\u0432\u043d\u0438\u0439"
}
};
const CURRENT_SPONSOR_TIERS = [
'Strategic Sponsors', 'Business Sponsors', 'Team Sponsors',
'Pro Sponsors', 'Builder Sponsors', 'Supporters',
];
const SPANISH_SPONSOR_TIERS = [
'Patrocinadores Empresariales', 'Patrocinadores Business', 'Patrocinadores Team',
'Patrocinadores Pro', 'Patrocinadores Builder', 'Supporters',
];
const FORMER_SPONSOR_PATTERN = /Atlas Cloud|atlascloud|Mike Morgan|mikejmorgan-ai/i;
const ATLAS_STATUS_NOTE = '> Sponsor status: Atlas Cloud is a past sponsor. This status change leaves the documented integration unchanged.';
const ATLAS_GUIDE_HEADING = '# Atlas Cloud \u2014 LLM Provider Guide';
const MIKE_LINK = '[Mike Morgan](https://github.com/mikejmorgan-ai)';
function requiredRegion(content, opening, closing) {
assert.strictEqual(content.split(opening).length - 1, 1, `Expected one opening: ${opening}`);
const start = content.indexOf(opening);
const end = content.indexOf(closing, start + opening.length);
assert.ok(end > start, `Missing or reversed closing: ${closing}`);
const body = content.slice(start + opening.length, end);
assert.doesNotMatch(body, /<p\b|<sub\b/, 'Sponsor region must close before another region starts');
return { body, start, end: end + closing.length };
}
function assertPastSponsorEntries(content, inactive, includeAtlas) {
for (const identity of ['Mike Morgan', 'https://github.com/mikejmorgan-ai']) {
assert.strictEqual(content.split(identity).length - 1, 1, `Expected one past ${identity}`);
}
assert.ok(content.includes(`Mike Morgan (${inactive})`) || content.includes(`${MIKE_LINK} (${inactive})`), 'Past Mike entry must be explicitly inactive');
if (includeAtlas) {
assert.strictEqual((content.match(/Atlas Cloud/g) || []).length, 1, 'Expected one past Atlas Cloud');
assert.strictEqual(content.split('https://www.atlascloud.ai/').length - 1, 1, 'Expected one past Atlas link');
}
}
function assertReadmeSponsorStatus(content, locale) {
const labels = README_SPONSOR_LABELS[locale];
const active = requiredRegion(content, `<p align="center" aria-label="${labels.active}">`, '</p>');
const community = requiredRegion(content, `<sub><strong>${labels.community}</strong>`, '</sub>');
const past = requiredRegion(content, `<sub><strong>${labels.past}</strong>`, '</sub>');
assert.ok(active.end < community.start && active.end < past.start, 'Current logo region must precede sponsor rows');
assert.doesNotMatch(active.body, FORMER_SPONSOR_PATTERN);
assert.doesNotMatch(community.body, FORMER_SPONSOR_PATTERN);
for (const current of ['CodeRabbit', 'Greptile', 'Moonshot', 'compute.itomarkets.com', 'SerpApi']) {
assert.ok(active.body.includes(current), `Missing current sponsor ${current}`);
}
for (const current of ['jasonwu513', '1anter', 'massimotodaro', 'meadmccabe']) {
assert.ok(community.body.includes(`https://github.com/${current}`), `Missing community sponsor ${current}`);
}
assertPastSponsorEntries(past.body, labels.inactive, true);
}
function assertSponsorRoster(content, spanish = false) {
const headings = [...content.matchAll(/^## (.+)$/gm)];
const sections = headings.map((heading, index) => ({
title: heading[1],
body: content.slice(heading.index + heading[0].length, headings[index + 1]?.index ?? content.length),
}));
const pastTitle = spanish ? 'Patrocinadores anteriores' : 'Past Sponsors';
const past = sections.filter(section => section.title === pastTitle);
assert.strictEqual(past.length, 1, 'Expected exactly one past sponsor section');
const tiers = spanish ? SPANISH_SPONSOR_TIERS : CURRENT_SPONSOR_TIERS;
for (const tier of tiers) {
const matches = sections.filter(section => section.title === tier || section.title.startsWith(`${tier} \u2014 `));
assert.strictEqual(matches.length, 1, `Expected one current tier ${tier}`);
assert.doesNotMatch(matches[0].body, FORMER_SPONSOR_PATTERN, `Former sponsor remains in ${tier}`);
}
assertPastSponsorEntries(past[0].body, spanish ? 'inactivo' : 'inactive', !spanish);
if (spanish) assert.doesNotMatch(content, /Atlas Cloud|atlascloud/i);
else {
const business = sections.find(section => section.title === 'Business Sponsors').body;
for (const current of ['CodeRabbit', 'Greptile', 'Moonshot', 'compute.itomarkets.com', 'SerpApi']) {
assert.ok(business.includes(current), `Missing current Business sponsor ${current}`);
}
}
}
function assertAtlasStatusPlacement(content) {
assert.ok(content.startsWith(`${ATLAS_GUIDE_HEADING}\n\n${ATLAS_STATUS_NOTE}\n\n`), 'Status note must immediately follow H1 and one blank line');
}
function readmeSponsorFixture(locale) {
const labels = README_SPONSOR_LABELS[locale];
const active = `<p align="center" aria-label="${labels.active}">CodeRabbit Greptile Moonshot compute.itomarkets.com SerpApi</p>`;
const past = `<sub><strong>${labels.past}</strong> <a href="https://www.atlascloud.ai/">Atlas Cloud</a> <a href="https://github.com/mikejmorgan-ai">Mike Morgan (${labels.inactive})</a></sub>`;
const community = `<sub><strong>${labels.community}</strong> ${['jasonwu513', '1anter', 'massimotodaro', 'meadmccabe'].map(name => `<a href="https://github.com/${name}">${name}</a>`).join(' ')}</sub>`;
return [active, past, community].join('\n\n');
}
function sponsorRosterFixture(spanish = false) {
const tiers = spanish ? SPANISH_SPONSOR_TIERS : CURRENT_SPONSOR_TIERS;
const sections = tiers.map(tier => `## ${tier}\n\n${tier === 'Business Sponsors' ? 'CodeRabbit Greptile Moonshot compute.itomarkets.com SerpApi' : 'Current entries'}\n`);
const past = spanish
? `## Patrocinadores anteriores\n\n${MIKE_LINK} (inactivo) | 2026\n`
: `## Past Sponsors\n\n[Atlas Cloud](https://www.atlascloud.ai/) | 2026\n${MIKE_LINK} (inactive) | 2026\n`;
sections.splice(2, 0, past); // Current Team and later tiers occur after Past.
return sections.join('\n');
}
function sponsorDocsTests() {
const tests = [
['English README keeps former sponsors out of both current regions', () => assertReadmeSponsorStatus(read('README.md'), 'en')],
['Ukrainian README keeps former sponsors out of both current regions', () => assertReadmeSponsorStatus(read('docs/uk-UA/README.md'), 'uk')],
['canonical roster separates past entries from every current tier', () => assertSponsorRoster(read('SPONSORS.md'))],
['Spanish roster moves only inactive Mike out of current tiers', () => assertSponsorRoster(read('docs/es/SPONSORS.md'), true)],
['Atlas status note immediately follows the guide heading', () => assertAtlasStatusPlacement(read('docs/ATLAS-CLOUD-GUIDE.md'))],
];
for (const locale of ['en', 'uk']) {
const fixture = readmeSponsorFixture(locale);
const labels = README_SPONSOR_LABELS[locale];
const start = `<p align="center" aria-label="${labels.active}">`;
const community = `<sub><strong>${labels.community}</strong>`;
const past = `<sub><strong>${labels.past}</strong>`;
tests.push([`${locale} fixture allows past before or after community`, () => {
assertReadmeSponsorStatus(fixture, locale);
const [active, pastRow, communityRow] = fixture.split('\n\n');
assertReadmeSponsorStatus([active, communityRow, pastRow].join('\n\n'), locale);
}]);
const mutations = [
['missing active marker', fixture.replace(start, '<p>')],
['missing closing marker', fixture.replace('</p>', '')],
['reversed closing marker', `</p>${fixture.replace('</p>', '')}`],
['duplicate active marker', `${fixture}\n${start}Extra</p>`],
['duplicate community marker', `${fixture}\n${community}Extra</sub>`],
['duplicate past marker', `${fixture}\n${past}Extra</sub>`],
['missing community marker', fixture.replace(community, '<sub>')],
['missing past marker', fixture.replace(past, '<sub>')],
['Atlas in current logos', fixture.replace(start, `${start}Atlas Cloud `)],
['Mike in current logos', fixture.replace(start, `${start}mikejmorgan-ai `)],
['Mike in current community', fixture.replace(community, `${community}Mike Morgan `)],
['Atlas in current community', fixture.replace(community, `${community}atlascloud `)],
['missing past Mike', fixture.replace('Mike Morgan', 'Former sponsor')],
['missing past status', fixture.replace(` (${labels.inactive})`, '')],
['missing past Atlas', fixture.replace('Atlas Cloud', 'Former organization')],
['past before active closes', fixture.replace('</p>', '').replace(past, `${past}</p>`)],
];
for (const [name, mutated] of mutations) {
tests.push([`${locale} sponsor checker rejects ${name}`, () => {
assertReadmeSponsorStatus(fixture, locale);
assert.notStrictEqual(mutated, fixture);
assert.throws(() => assertReadmeSponsorStatus(mutated, locale));
}]);
}
}
for (const spanish of [false, true]) {
const locale = spanish ? 'es' : 'en';
const fixture = sponsorRosterFixture(spanish);
const tiers = spanish ? SPANISH_SPONSOR_TIERS : CURRENT_SPONSOR_TIERS;
const past = spanish ? 'Patrocinadores anteriores' : 'Past Sponsors';
tests.push([`${locale} roster fixture accepts current tiers on both sides of Past`, () => assertSponsorRoster(fixture, spanish)]);
for (const tier of tiers) {
for (const identity of ['Atlas Cloud', 'mikejmorgan-ai']) {
tests.push([`${locale} roster rejects ${identity} in ${tier}`, () => {
assertSponsorRoster(fixture, spanish);
const mutated = fixture.replace(`## ${tier}\n`, `## ${tier}\n${identity}\n`);
assert.notStrictEqual(mutated, fixture);
assert.throws(() => assertSponsorRoster(mutated, spanish));
}]);
}
}
for (const [name, mutated] of [
['missing past section', fixture.replace(`## ${past}`, '## Archive')],
['duplicate past section', `${fixture}\n## ${past}\n`],
['missing current tier', fixture.replace(`## ${tiers[2]}`, '## Other')],
['duplicate current tier', `${fixture}\n## ${tiers[2]}\n`],
['missing past Mike', fixture.replace('Mike Morgan', 'Former sponsor')],
['missing inactive status', fixture.replace(spanish ? '(inactivo)' : '(inactive)', '')],
]) {
tests.push([`${locale} roster checker rejects ${name}`, () => {
assertSponsorRoster(fixture, spanish);
assert.notStrictEqual(mutated, fixture);
assert.throws(() => assertSponsorRoster(mutated, spanish));
}]);
}
}
const guide = `${ATLAS_GUIDE_HEADING}\n\n${ATLAS_STATUS_NOTE}\n\nProvider documentation.\n`;
tests.push(['guide fixture accepts the immediate undated status note', () => assertAtlasStatusPlacement(guide)]);
for (const [name, mutated] of [
['content between heading and note', guide.replace(`\n\n${ATLAS_STATUS_NOTE}`, `\n\nUnrelated content.\n\n${ATLAS_STATUS_NOTE}`)],
['note after provider description', `${ATLAS_GUIDE_HEADING}\n\nProvider documentation.\n\n${ATLAS_STATUS_NOTE}\n\n`],
['missing heading', guide.replace(ATLAS_GUIDE_HEADING, '')],
['unsupported dated note', guide.replace('Sponsor status:', 'Sponsor status (2026-09-10):')],
]) {
tests.push([`guide checker rejects ${name}`, () => {
assertAtlasStatusPlacement(guide);
assert.throws(() => assertAtlasStatusPlacement(mutated));
}]);
}
return tests;
}
function main() {
console.log('\n=== Testing Ito compute-sponsor surface ===\n');
@@ -459,7 +664,12 @@ function main() {
}],
];
for (const [name, fn] of tests) {
const docsOnly = process.argv.includes('--sponsor-docs-only');
const docsTests = sponsorDocsTests();
const selectedTests = docsOnly ? docsTests : [...tests, ...docsTests];
if (docsOnly) console.log(`Sponsor docs only: ${docsTests.length} pure cases; ${tests.length} unrelated callbacks omitted.`);
for (const [name, fn] of selectedTests) {
if (runTest(name, fn)) {
passed += 1;
} else {
+27
View File
@@ -51,6 +51,7 @@ function buildExpectedPublishPaths(repoRoot) {
"scripts/ci/scan-supply-chain-iocs.js",
"scripts/ci/supply-chain-advisory-sources.js",
"scripts/consult.js",
"scripts/profile.js",
"scripts/control-pane.js",
"scripts/dashboard-web.js",
"scripts/discussion-audit.js",
@@ -108,6 +109,9 @@ function buildExpectedPublishPaths(repoRoot) {
"docs/COMMAND-AGENT-MAP.md",
"docs/ROADMAP.md",
"docs/design/ecc-memory-vault.md",
"docs/design/context-profiles.md",
"docs/design/context-carriers.md",
"docs/design/context-profile-delivery.md",
"assets/images/sponsors",
]
const exclusionPaths = [
@@ -185,6 +189,29 @@ function main() {
"scripts/ci/scan-supply-chain-iocs.js",
"scripts/ci/supply-chain-advisory-sources.js",
"scripts/consult.js",
"scripts/profile.js",
"scripts/lib/context-profiles.js",
"scripts/lib/context-pack-registry.js",
"scripts/lib/context-profile-support.js",
"scripts/lib/context-carriers.js",
"scripts/lib/context-selection.js",
"scripts/lib/context-profile-commands.js",
"scripts/lib/context-profile-launch.js",
"scripts/lib/context-profile-proposal.js",
"scripts/lib/context-profile-native.js",
"scripts/lib/context-profile-native-discovery.js",
"scripts/lib/context-profile-native-executable.js",
"scripts/lib/context-profile-store.js",
"scripts/lib/context-profile-store-fs.js",
"schemas/context-profile.schema.json",
"schemas/context-pack-registry.schema.json",
"schemas/context-carrier.schema.json",
"manifests/context-profiles/lean@1.json",
"manifests/context-profiles/full@1.json",
"manifests/context-packs/skill-registry@1.json",
"docs/design/context-profiles.md",
"docs/design/context-carriers.md",
"docs/design/context-profile-delivery.md",
"scripts/control-pane.js",
"scripts/feedback.js",
"scripts/ito.js",
File diff suppressed because it is too large Load Diff
+114
View File
@@ -0,0 +1,114 @@
'use strict';
const assert = require('node:assert/strict');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { spawnSync } = require('child_process');
const test = require('node:test');
const ROOT = path.resolve(__dirname, '../..');
function withReadOnlyCli(fn) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-carrier-cli-'));
const user = path.join(root, 'user');
const workspace = path.join(root, 'workspace');
fs.mkdirSync(user);
fs.mkdirSync(workspace);
fs.writeFileSync(path.join(user, 'settings.json'), '{"existing":true}\n');
try {
fn(args => spawnSync(process.execPath, [path.join(ROOT, 'scripts/ecc.js'), 'profile', ...args], {
cwd: workspace, encoding: 'utf8', timeout: 30000, maxBuffer: 4 * 1024 * 1024,
env: {
PATH: process.env.PATH, SystemRoot: process.env.SystemRoot,
HOME: user, USERPROFILE: user, CODEX_HOME: path.join(user, '.codex'),
CLAUDE_CONFIG_DIR: path.join(user, '.claude'),
XDG_CONFIG_HOME: path.join(user, 'config'), XDG_STATE_HOME: path.join(user, 'state'),
...(process.env.NODE_V8_COVERAGE ? { NODE_V8_COVERAGE: process.env.NODE_V8_COVERAGE } : {}),
},
}));
} finally {
try {
assert.deepEqual(fs.readdirSync(root).sort(), ['user', 'workspace']);
assert.deepEqual(fs.readdirSync(user), ['settings.json']);
assert.equal(fs.readFileSync(path.join(user, 'settings.json'), 'utf8'), '{"existing":true}\n');
assert.deepEqual(fs.readdirSync(workspace), []);
} finally { fs.rmSync(root, { recursive: true, force: true }); }
}
}
function payload(result) {
assert.equal(result.status, 0, result.stderr || result.stdout);
const value = JSON.parse(result.stdout);
assert.equal(value.status, 'warning');
assert.equal(value.activation, 'unobserved');
assert.equal(value.carrier.active, false);
assert.equal(value.carrier.nativeSupport, 'unobserved');
return value;
}
test('profile help exposes carrier planning without a write command', () => withReadOnlyCli(run => {
const result = run(['--help']);
assert.equal(result.status, 0);
assert.match(result.stdout, /ecc profile carrier/);
assert.match(result.stdout, /read-only/i);
}));
test('default carrier preview is a deterministic Lean Codex proposal', () => withReadOnlyCli(run => {
const first = payload(run(['carrier', '--json']));
assert.deepEqual(payload(run(['carrier', '--json'])), first);
assert.equal(first.carrier.target, 'codex');
assert.equal(first.carrier.profileId, 'lean@1');
assert.equal(first.carrier.selectionMode, 'auto');
assert.equal(first.carrier.selectedIds.length, 3);
assert.equal(first.carrier.status, 'planned');
assert.equal(first.artifacts[0].digest, first.carrier.carrierDigest);
assert.ok(!JSON.stringify(first).includes(ROOT));
}));
test('Full carrier keeps explicit exclusions and manual intent', () => withReadOnlyCli(run => {
const { carrier } = payload(run(['carrier', 'full@1', '--selection', 'manual',
'--exclude', 'skill:python-patterns', '--json']));
assert.equal(carrier.selectionMode, 'manual');
assert.ok(carrier.excludedIds.includes('skill:python-patterns'));
assert.ok(carrier.files.every(file => file.skillId !== 'skill:python-patterns'));
}));
test('every implemented layout remains explicitly native-unobserved', () => withReadOnlyCli(run => {
for (const target of ['claude', 'codex', 'cursor', 'opencode', 'pi']) {
const { carrier } = payload(run(['carrier', '--target', target, '--json']));
assert.equal(carrier.target, target);
assert.equal(carrier.status, 'planned');
assert.ok(carrier.files.length > 0);
}
}));
test('recognized unsupported target returns inventory without generated files', () => withReadOnlyCli(run => {
const { carrier } = payload(run(['carrier', '--target', 'kimi', '--json']));
assert.equal(carrier.status, 'unsupported');
assert.deepEqual(carrier.files, []);
assert.equal(carrier.selectedIds.length, 3);
}));
test('carrier text and dry-run output preserve read-only and unobserved boundaries', () => withReadOnlyCli(run => {
const result = run(['carrier', '--dry-run']);
assert.equal(result.status, 0, result.stderr);
assert.match(result.stdout, /carrier/i);
assert.match(result.stdout, /unobserved/i);
assert.match(result.stdout, /planned|proposed/i);
const expected = payload(run(['carrier', 'lean@1', '--target', 'codex', '--json']));
for (const args of [
['--dry-run', 'carrier', 'lean@1', '--target', 'codex', '--json'],
['carrier', 'lean@1', '--target', '--dry-run', 'codex', '--json'],
]) assert.deepEqual(payload(run(args)), expected);
}));
test('carrier rejects unknown targets, write destinations, and hook flags', () => withReadOnlyCli(run => {
for (const args of [['--target', 'unknown'], ['--output', 'user'], ['--hooks', 'strict']]) {
const result = run(['carrier', ...args, '--json']);
assert.equal(result.status, 1);
const error = JSON.parse(result.stdout);
assert.equal(error.status, 'error');
assert.doesNotMatch(error.summary, /Cannot find module|Require stack/);
}
}));
+60
View File
@@ -0,0 +1,60 @@
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const test = require('node:test');
const CLI = path.resolve(__dirname, '../../scripts/ecc.js');
const task = { sessionId: 'stdin-session', taskId: 'stdin-task', revision: 1,
phase: 'implement', explicitIds: ['skill:python-patterns'] };
function invoke(args, input) {
return spawnSync(process.execPath, [CLI, 'profile', ...args, '--json'], {
input, encoding: 'utf8', timeout: 30000, maxBuffer: 1024 * 1024 });
}
test('resolve accepts bounded task JSON on stdin without creating task files', () => {
const result = invoke(['resolve', '--task-input', '-', '--load'], JSON.stringify(task));
assert.equal(result.status, 0, result.stdout);
assert.deepEqual(JSON.parse(result.stdout).selection.loadedIds, ['skill:python-patterns']);
});
test('stdin task JSON rejects overflow, malformed UTF-8, NUL, and invalid JSON', () => {
for (const [input, message] of [[Buffer.alloc(65537, 32), /65536/], [Buffer.from([0xff]), /UTF-8/],
['\0', /UTF-8/], ['{', /JSON/]]) {
const result = invoke(['resolve', '--task-input', '-'], input);
assert.equal(result.status, 1);
assert.match(JSON.parse(result.stdout).summary, message);
}
});
test('malformed task JSON never echoes private input through the CLI envelope', () => {
const secret = 'PRIVATE_TASK_SENTINEL';
const result = invoke(['resolve', '--task-input', '-'], `{"task":"${secret}"`);
assert.equal(result.status, 1);
assert.match(JSON.parse(result.stdout).summary, /valid JSON/);
assert.doesNotMatch(result.stdout + result.stderr, new RegExp(secret));
});
test('start requires both roots, rejects authority flags, and dry-run never prepares a native home', () => {
const parent = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'ecc-start-cli-'));
try {
const stateRoot = path.join(parent, 'state');
const nativeRoot = path.join(parent, 'native');
for (const [args, message] of [[[], /requires --state-root/],
[['--state-root', stateRoot], /requires --native-root/],
[['--state-root', stateRoot, '--native-root', nativeRoot, '--dangerously-bypass-approvals-and-sandbox'], /Unknown argument/]]) {
const result = invoke(['start', ...args]);
assert.equal(result.status, 1);
assert.match(JSON.parse(result.stdout).summary, message);
}
assert.equal(invoke(['set', 'lean', '--state-root', stateRoot]).status, 0);
const jsonStart = invoke(['start', '--state-root', stateRoot, '--native-root', nativeRoot]);
assert.equal(jsonStart.status, 1);
assert.match(JSON.parse(jsonStart.stdout).summary, /--json requires --dry-run/);
const result = invoke(['start', '--state-root', stateRoot, '--native-root', nativeRoot, '--dry-run']);
assert.equal(result.status, 0, result.stdout);
assert.equal(JSON.parse(result.stdout).interactive.status, 'proposed');
assert.equal(fs.existsSync(nativeRoot), false);
} finally { fs.rmSync(parent, { recursive: true, force: true }); }
});
+211
View File
@@ -0,0 +1,211 @@
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const test = require('node:test');
const { withFixture } = require('../lib/helpers/context-fixture');
const CLI = path.resolve(__dirname, '../../scripts/ecc.js');
const PROFILE_CLI = path.resolve(__dirname, '../../scripts/profile.js');
function cliFixture(run) {
const root = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'ecc-selection-cli-'));
const stateRoot = path.join(root, 'managed');
const input = path.join(root, 'task.json');
const setTask = values => fs.writeFileSync(input, JSON.stringify({ sessionId: 'test', taskId: 'test',
revision: 1, phase: 'implement', query: 'Explain Python lists', ...values }));
setTask({ proposedIds: ['skill:python-patterns'] });
const invoke = (args, { preload, env = {} } = {}) => {
const entry = preload ? ['--require', preload, PROFILE_CLI] : [CLI, 'profile'];
const child = spawnSync(process.execPath, [...entry, ...args, '--json'], {
cwd: root, encoding: 'utf8', timeout: 30000, maxBuffer: 1024 * 1024,
env: { PATH: process.env.PATH, SystemRoot: process.env.SystemRoot,
NODE_V8_COVERAGE: process.env.NODE_V8_COVERAGE, ...env },
});
assert.ok(child.stdout, child.stderr || child.error?.message);
return { code: child.status, response: JSON.parse(child.stdout) };
};
try { return run({ root, stateRoot, input, setTask, invoke }); }
finally { fs.rmSync(root, { recursive: true, force: true }); }
}
function providerFixture(root) {
const preload = path.join(root, 'provider-preload.cjs');
const sentinel = path.join(root, 'provider-executed');
fs.writeFileSync(preload, `
const cp = require('node:child_process');
const fs = require('node:fs');
const original = cp.spawnSync;
cp.spawnSync = (command, ...args) => {
if (command !== 'codex' && command !== 'claude') return original(command, ...args);
fs.writeFileSync(process.env.ECC_TEST_PROVIDER_SENTINEL, 'executed');
return { status: Number(process.env.ECC_TEST_PROVIDER_STATUS || 0),
stdout: 'fixture output', stderr: 'fixture provider failure' };
};
`);
return { preload, sentinel, env: { ECC_TEST_PROVIDER_SENTINEL: sentinel } };
}
test('CLI resolves and loads explicit task context with JSON output', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-selection-cli-'));
try {
const input = path.join(root, 'task.json');
fs.writeFileSync(input, JSON.stringify({ sessionId: 'test', taskId: 'test', revision: 1, phase: 'implement',
explicitIds: ['skill:python-patterns'] }));
const args = ['profile', 'resolve', '--task-input', input, '--load', '--json'];
const run = extra => spawnSync(process.execPath, [CLI, ...extra, ...args], {
cwd: root, encoding: 'utf8', timeout: 30000, maxBuffer: 1024 * 1024,
env: { PATH: process.env.PATH, SystemRoot: process.env.SystemRoot,
NODE_V8_COVERAGE: process.env.NODE_V8_COVERAGE },
});
const result = run([]);
assert.equal(result.status, 0, result.stderr || result.stdout);
assert.deepEqual(JSON.parse(result.stdout).selection.loadedIds, ['skill:python-patterns']);
const preview = run(['--dry-run']);
assert.equal(preview.status, 0, preview.stderr || preview.stdout);
assert.deepEqual(JSON.parse(preview.stdout).selection.loadedIds, []);
assert.deepEqual(fs.readdirSync(root), ['task.json']);
} finally { fs.rmSync(root, { recursive: true, force: true }); }
});
test('resolver rejects unknown flags before reading task input', () => {
const result = spawnSync(process.execPath, [CLI, 'profile', 'resolve', '--task-input', 'missing', '--hooks', 'full', '--json'], { encoding: 'utf8' });
assert.equal(result.status, 1);
assert.match(JSON.parse(result.stdout).summary, /Unknown argument/);
});
test('saved mode and exclusions govern resolution and survive mode changes', () => cliFixture(({ stateRoot, input, setTask, invoke }) => {
const setup = invoke(['set', 'lean', '--state-root', stateRoot, '--selection', 'suggest',
'--include', 'skill:python-patterns', '--exclude', 'skill:python-testing']);
assert.equal(setup.code, 0, setup.response.summary);
const resolveArgs = ['resolve', '--state-root', stateRoot, '--task-input', input, '--load'];
const suggestion = invoke(resolveArgs);
assert.equal(suggestion.code, 0, suggestion.response.summary);
assert.equal(suggestion.response.selection.selectionMode, 'suggest');
assert.deepEqual(suggestion.response.selection.selectedIds, ['skill:python-patterns']);
assert.deepEqual(suggestion.response.selection.loadedIds, []);
const preview = invoke(['mode', 'manual', '--state-root', stateRoot, '--dry-run']);
assert.equal(preview.code, 0, preview.response.summary);
assert.equal(preview.response.store.status, 'proposed');
assert.equal(invoke(['status', '--state-root', stateRoot]).response.store.selectionMode, 'suggest');
const changed = invoke(['mode', 'manual', '--state-root', stateRoot, '--expected-revision', '1']);
assert.equal(changed.code, 0, changed.response.summary);
assert.deepEqual(changed.response.store.include, ['skill:python-patterns']);
assert.deepEqual(changed.response.store.exclude, ['skill:python-testing']);
const manual = invoke(resolveArgs);
assert.equal(manual.code, 0, manual.response.summary);
assert.deepEqual(manual.response.selection.selectedIds, []);
assert.equal(manual.response.selection.selectionMode, 'manual');
setTask({ explicitIds: ['skill:python-testing'] });
const excluded = invoke(resolveArgs);
assert.equal(excluded.code, 1);
assert.match(excluded.response.summary, /excluded/);
setTask({ proposedIds: ['skill:python-patterns'] });
assert.equal(invoke(['mode', 'auto', '--state-root', stateRoot]).code, 0);
const automatic = invoke(resolveArgs);
assert.equal(automatic.code, 0, automatic.response.summary);
assert.deepEqual(automatic.response.selection.loadedIds, ['skill:python-patterns']);
}));
test('stored resolution and launch reject every configuration override before reading input', () => cliFixture(({ stateRoot, invoke }) => {
for (const command of ['resolve', 'run']) {
for (const override of [['full'], ['--target', 'claude'], ['--selection', 'manual'],
['--include', 'skill:python-patterns'], ['--exclude', 'skill:python-testing']]) {
const result = invoke([command, '--state-root', stateRoot, '--task-input', 'missing', ...override]);
assert.equal(result.code, 1);
assert.match(result.response.summary, /cannot override/);
}
}
}));
test('launch dry runs never load bodies or execute a provider', () => cliFixture(({ root, input, invoke }) => {
const provider = providerFixture(root);
for (const dry of [{ args: ['--dry-run'], env: {} }, { args: [], env: { ECC_DRY_RUN: '1' } }]) {
const result = invoke(['run', '--task-input', input, ...dry.args],
{ preload: provider.preload, env: { ...provider.env, ...dry.env } });
assert.equal(result.code, 0, result.response.summary);
assert.equal(result.response.launch.status, 'proposed');
assert.deepEqual(result.response.launch.selection.loadedIds, []);
assert.equal(fs.existsSync(provider.sentinel), false);
}
}));
test('provider exit failures produce a failed CLI result and preserve the native exit code', () => cliFixture(({ root, input, invoke }) => {
const provider = providerFixture(root);
const result = invoke(['run', '--task-input', input], { preload: provider.preload,
env: { ...provider.env, ECC_TEST_PROVIDER_STATUS: '23' } });
assert.equal(fs.readFileSync(provider.sentinel, 'utf8'), 'executed');
assert.equal(result.code, 1);
assert.equal(result.response.status, 'error');
assert.equal(result.response.launch.status, 'failed');
assert.equal(result.response.launch.exitCode, 23);
assert.equal(result.response.launch.taskSuccess, 'unverified');
assert.match(result.response.launch.error, /fixture provider failure/);
}));
test('unsupported targets and stale selection digests fail before provider execution', () => cliFixture(({ root, input, invoke }) => {
const provider = providerFixture(root);
for (const args of [['--target', 'pi'], ['--expected-digest', '0'.repeat(64)]]) {
const result = invoke(['run', '--task-input', input, ...args], provider);
assert.equal(result.code, 1);
assert.match(result.response.summary, /Unsupported|stale/);
assert.equal(fs.existsSync(provider.sentinel), false);
}
}));
test('unconfigured and source-stale stores cannot resolve task context', () => cliFixture(({ stateRoot, input, invoke }) => {
const args = ['resolve', '--state-root', stateRoot, '--task-input', input, '--load'];
const absent = invoke(args);
assert.equal(absent.code, 1);
assert.match(absent.response.summary, /Configure or recover/);
withFixture(repoRoot => require('../../scripts/lib/context-profile-store').applyStore({ repoRoot, stateRoot }));
const stale = invoke(args);
assert.equal(stale.code, 1);
assert.match(stale.response.summary, /source is stale/);
}));
test('malformed operation flags and stale write preconditions fail without creating a store', () => cliFixture(({ stateRoot, invoke }) => {
for (const [args, message] of [
[['mode', 'unknown', '--state-root', stateRoot], /Choose mode/],
[['set', 'lean', '--state-root'], /Missing value/],
[['set', 'lean', '--state-root', stateRoot, '--state-root', stateRoot], /Duplicate argument/],
[['set', 'lean', '--state-root', stateRoot, '--task-input', 'missing'], /unavailable/],
[['set', 'lean', '--state-root', stateRoot, '--expected-revision', '01'], /nonnegative integer/],
[['set', 'lean', '--state-root', stateRoot, '--expected-revision', '1'], /revision changed/],
[['set', 'lean', '--state-root', stateRoot, '--expected-digest', 'bad'], /Invalid expected/],
[['set', 'lean', '--state-root', stateRoot, '--expected-digest', '0'.repeat(64)], /digest changed/],
[['run', '--task-input', 'missing', '--load'], /Unknown argument/],
]) {
const result = invoke(args);
assert.equal(result.code, 1);
assert.match(result.response.summary, message);
assert.equal(fs.existsSync(stateRoot), false);
}
}));
test('native command routing rejects missing roots and unsupported flags before provider or filesystem work', () => cliFixture(({ root, stateRoot, invoke }) => {
const nativeRoot = path.join(root, 'native');
for (const command of ['prepare-native', 'native-status', 'native-rollback', 'native-recover']) {
for (const [args, pattern] of [
[[], /requires --state-root/],
[['--state-root', stateRoot], /requires --native-root/],
[['--state-root', stateRoot, '--native-root', nativeRoot, '--target', 'codex'], /unavailable/],
[['--state-root', stateRoot, '--native-root', nativeRoot, '--expected-revision', '1e2'], /nonnegative integer/],
]) {
const result = invoke([command, ...args]);
assert.equal(result.code, 1);
assert.match(result.response.summary, pattern);
}
}
const orphan = invoke(['run', '--native-root', nativeRoot, '--task-input', 'missing']);
assert.equal(orphan.code, 1);
assert.match(orphan.response.summary, /--native-root requires --state-root/);
const invalidResolve = invoke(['resolve', '--state-root', stateRoot, '--native-root', nativeRoot, '--task-input', 'missing']);
assert.equal(invalidResolve.code, 1);
assert.match(invalidResolve.response.summary, /--native-root is unavailable/);
assert.equal(fs.existsSync(stateRoot), false);
assert.equal(fs.existsSync(nativeRoot), false);
}));
+206
View File
@@ -0,0 +1,206 @@
/** Read-only context profile journeys, exercised through the shipped CLI. */
'use strict';
const assert = require('assert');
const crypto = require('crypto');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { spawnSync } = require('child_process');
const ROOT = path.resolve(__dirname, '../..');
const CLI = path.join(ROOT, 'scripts/ecc.js');
const PROFILE = path.join(ROOT, 'scripts/profile.js');
function snapshot(directory) {
return fs.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))
.flatMap(entry => {
const file = path.join(directory, entry.name);
if (entry.isSymbolicLink()) return [`${entry.name}:link:${fs.readlinkSync(file)}`];
return entry.isDirectory()
? snapshot(file).map(item => `${entry.name}/${item}`)
: [`${entry.name}:${crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex')}`];
});
}
function withFixture(fn) {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-profile-cli-'));
let before;
try {
const userDirectory = path.join(fixture, 'user');
const workspace = path.join(fixture, 'workspace');
fs.mkdirSync(userDirectory);
fs.mkdirSync(workspace);
fs.writeFileSync(path.join(userDirectory, 'settings.json'), '{"keep":"user preference"}\n');
fs.writeFileSync(path.join(workspace, 'owned.txt'), 'existing user work\n');
before = snapshot(fixture);
const run = (args, direct = false) => spawnSync(process.execPath,
[direct ? PROFILE : CLI, ...(direct ? [] : ['profile']), ...args], {
cwd: workspace, encoding: 'utf8', timeout: 30_000, maxBuffer: 4 * 1024 * 1024,
env: {
PATH: process.env.PATH, SystemRoot: process.env.SystemRoot,
HOME: userDirectory, USERPROFILE: userDirectory,
XDG_CONFIG_HOME: path.join(userDirectory, 'config'),
XDG_STATE_HOME: path.join(userDirectory, 'state'),
CLAUDE_CONFIG_DIR: path.join(userDirectory, '.claude'),
CODEX_HOME: path.join(userDirectory, '.codex'),
...(process.env.NODE_V8_COVERAGE ? { NODE_V8_COVERAGE: process.env.NODE_V8_COVERAGE } : {}),
},
});
fn(run);
} finally {
try {
if (before) assert.deepStrictEqual(snapshot(fixture), before,
'inspection must preserve user and workspace files, including failure paths');
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
}
}
function success(result) {
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
const payload = JSON.parse(result.stdout);
assert.ok(['success', 'warning'].includes(payload.status));
assert.strictEqual(typeof payload.summary, 'string');
assert.ok(Array.isArray(payload.next_actions));
assert.ok(Array.isArray(payload.artifacts));
return payload;
}
const tests = [
['the dispatcher exposes read-only profile help', () => withFixture(run => {
const result = run(['--help']);
assert.strictEqual(result.status, 0, result.stderr);
assert.match(result.stdout, /read-only/i);
for (const command of ['show', 'preview', 'explain']) assert.ok(result.stdout.includes(command));
})],
['show lists versioned definitions without claiming an active installation', () => withFixture(run => {
const result = success(run(['show', '--json']));
assert.deepStrictEqual(result.profiles.map(profile => profile.id).sort(), ['full@1', 'lean@1']);
assert.strictEqual(result.activation, 'unobserved');
})],
['show reads one profile definition through the direct packaged entrypoint', () => withFixture(run => {
const result = success(run(['show', 'lean@1', '--json'], true));
assert.strictEqual(result.profile.id, 'lean@1');
})],
['Lean preview is deterministic and reports no observed activation', () => withFixture(run => {
const args = ['preview', 'lean@1', '--target', 'codex', '--selection', 'auto', '--json'];
const first = run(args);
const payload = success(first);
assert.deepStrictEqual(JSON.parse(run(args).stdout), payload);
assert.strictEqual(payload.activation, 'unobserved');
assert.ok(payload.plan);
assert.ok(!first.stdout.includes(ROOT), 'portable output must omit local checkout path');
})],
['Full remains inspectable with explicit manual selection', () => withFixture(run => {
assert.ok(success(run(['preview', 'full@1', '--target', 'claude', '--selection', 'manual', '--json'])).plan);
})],
['explicit includes and exclusions remain inspection only', () => withFixture(run => {
success(run(['preview', '--target', 'codex', '--include', 'skill:security-review',
'--exclude', 'skill:python-patterns', '--json']));
})],
['exact-ID explanation includes an entry and never invokes the skill', () => withFixture(run => {
const payload = success(run(['explain', 'skill:security-review', '--target', 'codex', '--json']));
assert.strictEqual(payload.entry.id, 'skill:security-review');
assert.strictEqual(payload.activation, 'unobserved');
})],
['text output identifies estimates and unobserved runtime state', () => withFixture(run => {
const result = run(['preview', '--target', 'codex']);
assert.strictEqual(result.status, 0, result.stderr);
assert.match(result.stdout, /estimate/i);
assert.match(result.stdout, /unobserved/i);
})],
['text error output renders terminal controls inert', () => withFixture(run => {
const control = String.fromCharCode(27);
const result = run(['explain', `skill:unknown${control}]52;c;example${String.fromCharCode(7)}`]);
assert.strictEqual(result.status, 1);
assert.ok(!result.stderr.includes(control), 'terminal escape must not reach the text output');
assert.match(result.stderr, /\\u001b/);
})],
['global dry-run remains compatible with profile inspection', () => withFixture(run => {
success(run(['preview', '--target', 'codex', '--dry-run', '--json']));
})],
['global dry-run is ignored at every argument position without changing parsed controls', () => {
const { parseArgs } = require('../../scripts/profile');
for (const args of [
['show', 'lean@1', '--json'],
['preview', 'lean@1', '--target', 'codex', '--selection', 'auto',
'--include', 'skill:security-review', '--exclude', 'skill:python-patterns', '--json'],
['explain', 'skill:ecc-guide', '--target', 'codex', '--json'],
]) {
const expected = parseArgs(args);
for (let index = 0; index <= args.length; index++) {
const invocation = [...args.slice(0, index), '--dry-run', ...args.slice(index)];
const before = [...invocation];
assert.deepStrictEqual(parseArgs(invocation), expected, invocation.join(' '));
assert.deepStrictEqual(invocation, before, 'parsing must preserve caller arguments');
}
assert.deepStrictEqual(parseArgs(['--dry-run', ...args, '--dry-run']), expected);
}
}],
['package includes the direct profile entrypoint and public schemas', () => {
const { files } = require('../../package.json');
assert.ok(files.includes('scripts/profile.js'));
assert.ok(files.includes('schemas/'));
assert.ok(files.includes('manifests/'));
}],
];
for (const args of [
['show', 'lean@1', '--json'],
['preview', 'lean@1', '--target', 'codex', '--selection', 'auto', '--json'],
['explain', 'skill:ecc-guide', '--target', 'codex', '--json'],
]) {
tests.push([`leading global dry-run preserves ${args[0]} through both CLI entrypoints`, () => withFixture(run => {
const expected = success(run(args));
for (const direct of [false, true]) {
const observed = success(run(['--dry-run', ...args], direct));
assert.deepStrictEqual(observed, expected);
assert.strictEqual(observed.activation, 'unobserved');
}
})]);
}
for (const args of [
['use', 'lean@1'],
['--dry-run', 'use', 'lean@1'],
['show', 'unknown@1'],
['preview', '--target', 'unknown-host'],
['preview', '--selection', 'eager'],
['preview', '--target'],
['preview', '--target', '--json'],
['preview', '--target', 'codex', '--target', 'claude'],
['preview', '--include', 'skill:missing-workflow'],
['preview', '--include', '../../outside'],
['preview', '--hooks', 'strict'],
['--dry-run', 'preview', '--hooks', 'strict'],
['show', '--include', 'skill:security-review'],
['explain'],
['explain', 'skill:missing-workflow'],
['explain', 'skill:ecc-guide', 'extra'],
]) {
tests.push([`rejects unsupported or malformed input: ${args.join(' ')}`, () => withFixture(run => {
const result = run([...args, '--json']);
assert.notStrictEqual(result.status, 0);
const payload = JSON.parse(result.stdout);
assert.strictEqual(payload.status, 'error');
assert.doesNotMatch(payload.summary, /Cannot find module|Require stack/,
'validation must fail for the request, not a missing implementation');
assert.ok(payload.next_actions.length > 0);
assert.strictEqual(payload.activation, 'unobserved');
})]);
}
function main() {
let passed = 0;
for (const [name, test] of tests) {
try { test(); passed++; console.log(` PASS ${name}`); }
catch (error) { console.error(` FAIL ${name}: ${error.message}`); }
}
console.log(`\nPassed: ${passed}\nFailed: ${tests.length - passed}`);
process.exitCode = passed === tests.length ? 0 : 1;
}
if (require.main === module) main();
module.exports = { main };
+11 -1
View File
@@ -134,12 +134,22 @@ function runTests() {
'release.sh should detect metadata that already declares the requested version'
);
assert.ok(
source.includes('echo " git tag \\"v$VERSION\\""') &&
source.includes('echo " git tag -s \\"v$VERSION\\" -m \\"Release v$VERSION\\""') &&
source.includes('echo " git push origin \\"v$VERSION\\""'),
'same-version guidance should point maintainers to the tag-driven publish path'
);
})) passed++; else failed++;
if (test('release signs an annotated tag and stops before push if signing fails', () => {
assert.match(source, /^set -euo pipefail$/m);
const tagCommand = 'git tag -s "v$VERSION" -m "Release v$VERSION"';
const tagIndex = source.indexOf('\n' + tagCommand + '\n');
const pushIndex = source.indexOf('\ngit push origin main "v$VERSION"');
assert.ok(tagIndex > source.indexOf('git commit -m'), 'sign after the release commit');
assert.ok(pushIndex > tagIndex, 'push only after successful signing');
assert.doesNotMatch(source.slice(tagIndex, pushIndex), /\|\||set \+e/);
})) passed++; else failed++;
if (test('release workflows mark prerelease tags as GitHub prereleases', () => {
assert.ok(
releaseWorkflowSource.includes('prerelease: ${{ contains(github.ref_name, \'-\') }}'),
+43 -12
View File
@@ -78,6 +78,35 @@ function runSetup(fixture, args, options = {}) {
function quoteShellArgument(value) {
return `'${String(value).replace(/'/g, `'\\''`)}'`;
}
// Answer only after the PTY displays a prompt. Fixed-delay pipes can deliver
// blank defaults and EOF before the wizard creates its readline interface.
function driveInteractiveTerminal() {
const { spawn } = require('child_process');
const { pseudoTerminalCommand, answers } = JSON.parse(process.argv[1]);
// Node pipes are sockets on macOS; script requires a real pipe for stdin.
const child = spawn('sh', ['-c', `cat | ${pseudoTerminalCommand}`], { stdio: ['pipe', 'pipe', 'pipe'] });
let pending = '';
let answerIndex = 0;
child.stdout.on('data', chunk => {
process.stdout.write(chunk);
pending += chunk.toString('utf8');
const prompt = /Choose(?: \[\d+\])?: |\[y\/N\] /.exec(pending);
if (!prompt) return;
pending = pending.slice(prompt.index + prompt[0].length);
if (answerIndex >= answers.length) { child.stdin.end(); return; }
const answer = answers[answerIndex++];
child.stdin.write(answer === '\u0004' ? answer : `${answer}\n`);
if (answerIndex === answers.length) child.stdin.end();
});
child.stderr.on('data', chunk => process.stderr.write(chunk));
child.stdin.on('error', error => {
if (error.code !== 'EPIPE') { process.stderr.write(error.message); process.exitCode = 1; }
});
child.on('error', error => { process.stderr.write(error.message); process.exitCode = 1; });
child.on('close', code => { process.exitCode = code ?? 1; });
}
function runInteractiveEccSetup(fixture, options = {}) {
if (process.platform === 'win32') {
return null;
@@ -85,12 +114,18 @@ function runInteractiveEccSetup(fixture, options = {}) {
const args = options.args || ['--dry-run'];
const answers = options.answers || ['3', '3'];
const command = [
const setupCommand = [
process.execPath,
eccScript,
'setup',
...args,
];
const command = options.delayedStartup
? [process.execPath, '-e', `setTimeout(() => {
const result = require('child_process').spawnSync(process.argv[1], process.argv.slice(2), { stdio: 'inherit' });
process.exitCode = result.status ?? 1;
}, 1250);`, ...setupCommand]
: setupCommand;
const scriptArgs = process.platform === 'darwin'
? ['-q', '-e', '/dev/null', ...command]
: [
@@ -100,16 +135,11 @@ function runInteractiveEccSetup(fixture, options = {}) {
command.map(quoteShellArgument).join(' '),
'/dev/null',
];
const pseudoTerminalCommand = ['script', ...scriptArgs]
.map(quoteShellArgument)
.join(' ');
const answerCommands = answers
.map(answer => `sleep 0.5; printf '%s\\n' ${quoteShellArgument(answer)}`)
.join('; ');
return spawnSync('sh', [
'-c',
`(${answerCommands}; sleep 0.1) | ${pseudoTerminalCommand}`,
const pseudoTerminalCommand = ['script', ...scriptArgs].map(quoteShellArgument).join(' ');
return spawnSync(process.execPath, [
'-e',
`(${driveInteractiveTerminal.toString()})();`,
JSON.stringify({ pseudoTerminalCommand, answers }),
], {
cwd: fixture.projectRoot,
env: {
@@ -386,8 +416,8 @@ test('setup automatically migrates an existing install to the selected scope and
const calls = readCalls(fixture);
assert.ok(calls.some(argv => (
argv.join(' ') === 'plugin install ecc@ecc --scope user'
+ ' --config hooks_enabled=true --config hook_profile=minimal'
)));
assert.ok(calls.every(argv => !argv.includes('--config')));
assert.ok(calls.some(argv => (
argv.join(' ') === 'plugin uninstall ecc@ecc --scope local --keep-data'
)));
@@ -914,6 +944,7 @@ test('interactive defaults preserve an existing install scope and hook preferenc
const result = runInteractiveEccSetup(fixture, {
args: ['--dry-run'],
answers: ['', ''],
delayedStartup: true,
});
assert.ifError(result.error);
assert.strictEqual(result.status, 0, `${result.stdout}\n${result.stderr}`);