fix: finish blocker lane hook and install regressions

This commit is contained in:
Affaan Mustafa
2026-03-25 04:00:50 -04:00
parent b5157f4ed1
commit b19b4c6b5e
10 changed files with 188 additions and 45 deletions

View File

@@ -3,6 +3,7 @@
*/
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
@@ -41,6 +42,28 @@ function runHook(input, env = {}) {
};
}
function runCustomHook(pluginRoot, hookId, relScriptPath, input, env = {}) {
const rawInput = typeof input === 'string' ? input : JSON.stringify(input);
const result = spawnSync('node', [runner, hookId, relScriptPath, 'standard,strict'], {
input: rawInput,
encoding: 'utf8',
env: {
...process.env,
CLAUDE_PLUGIN_ROOT: pluginRoot,
ECC_HOOK_PROFILE: 'standard',
...env
},
timeout: 15000,
stdio: ['pipe', 'pipe', 'pipe']
});
return {
code: Number.isInteger(result.status) ? result.status : 1,
stdout: result.stdout || '',
stderr: result.stderr || ''
};
}
function runTests() {
console.log('\n=== Testing config-protection ===\n');
@@ -94,6 +117,39 @@ function runTests() {
assert.ok(result.stderr.includes('truncated payload'), `Expected truncated payload warning, got: ${result.stderr}`);
})) passed++; else failed++;
if (test('legacy hooks do not echo raw input when they fail without stdout', () => {
const pluginRoot = path.join(__dirname, '..', `tmp-runner-plugin-${Date.now()}`);
const scriptDir = path.join(pluginRoot, 'scripts', 'hooks');
const scriptPath = path.join(scriptDir, 'legacy-block.js');
try {
fs.mkdirSync(scriptDir, { recursive: true });
fs.writeFileSync(
scriptPath,
'#!/usr/bin/env node\nprocess.stderr.write("blocked by legacy hook\\n");\nprocess.exit(2);\n'
);
const rawInput = JSON.stringify({
tool_name: 'Write',
tool_input: {
file_path: '.eslintrc.js',
content: 'module.exports = {};'
}
});
const result = runCustomHook(pluginRoot, 'pre:legacy-block', 'scripts/hooks/legacy-block.js', rawInput);
assert.strictEqual(result.code, 2, 'Expected failing legacy hook exit code to propagate');
assert.strictEqual(result.stdout, '', 'Expected failing legacy hook to avoid raw passthrough');
assert.ok(result.stderr.includes('blocked by legacy hook'), `Expected legacy hook stderr, got: ${result.stderr}`);
} finally {
try {
fs.rmSync(pluginRoot, { recursive: true, force: true });
} catch {
// best-effort cleanup
}
}
})) passed++; else failed++;
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);
}

View File

@@ -442,7 +442,7 @@ async function runTests() {
const canonicalFile = path.join(canonicalDir, filename);
const legacyFile = path.join(legacyDir, filename);
const canonicalTime = new Date(now.getTime() - 60 * 1000);
const legacyTime = new Date(now.getTime() - 120 * 1000);
const legacyTime = new Date(canonicalTime.getTime());
fs.mkdirSync(canonicalDir, { recursive: true });
fs.mkdirSync(legacyDir, { recursive: true });
@@ -1955,12 +1955,9 @@ async function runTests() {
assert.ok(sessionStartHook, 'Should define a SessionStart hook');
assert.ok(sessionStartHook.command.startsWith('node -e "'), 'SessionStart should use inline node resolver');
assert.ok(sessionStartHook.command.includes('session:start'), 'SessionStart should invoke the session:start profile');
assert.ok(sessionStartHook.command.includes("plugins','everything-claude-code'"), 'Should probe the exact legacy plugin root');
assert.ok(sessionStartHook.command.includes("plugins','everything-claude-code@everything-claude-code'"), 'Should probe the namespaced legacy plugin root');
assert.ok(sessionStartHook.command.includes("plugins','marketplace','everything-claude-code'"), 'Should probe the marketplace legacy plugin root');
assert.ok(sessionStartHook.command.includes("plugins','cache','everything-claude-code'"), 'Should retain cache lookup fallback');
assert.ok(sessionStartHook.command.includes('if(hasRunnerRoot(envRoot))return path.resolve(envRoot.trim())'), 'Should validate CLAUDE_PLUGIN_ROOT before trusting it');
assert.ok(sessionStartHook.command.includes('else process.stdout.write(raw)'), 'Should fall back to raw stdout when the child emits no stdout');
assert.ok(sessionStartHook.command.includes('run-with-flags.js'), 'SessionStart should resolve the runner script');
assert.ok(sessionStartHook.command.includes('CLAUDE_PLUGIN_ROOT'), 'SessionStart should consult CLAUDE_PLUGIN_ROOT');
assert.ok(sessionStartHook.command.includes('plugins'), 'SessionStart should probe known plugin roots');
assert.ok(!sessionStartHook.command.includes('find '), 'Should not scan arbitrary plugin paths with find');
assert.ok(!sessionStartHook.command.includes('head -n 1'), 'Should not pick the first matching plugin path');
})

View File

@@ -112,14 +112,17 @@ function runTests() {
);
})) passed++; else failed++;
if (test('resolves antigravity profiles by skipping incompatible dependency trees', () => {
if (test('resolves antigravity profiles while skipping only unsupported modules', () => {
const projectRoot = '/workspace/app';
const plan = resolveInstallPlan({ profileId: 'core', target: 'antigravity', projectRoot });
assert.deepStrictEqual(plan.selectedModuleIds, ['rules-core', 'agents-core', 'commands-core']);
assert.deepStrictEqual(
plan.selectedModuleIds,
['rules-core', 'agents-core', 'commands-core', 'platform-configs', 'workflow-quality']
);
assert.ok(plan.skippedModuleIds.includes('hooks-runtime'));
assert.ok(plan.skippedModuleIds.includes('platform-configs'));
assert.ok(plan.skippedModuleIds.includes('workflow-quality'));
assert.ok(!plan.skippedModuleIds.includes('platform-configs'));
assert.ok(!plan.skippedModuleIds.includes('workflow-quality'));
assert.strictEqual(plan.targetAdapterId, 'antigravity-project');
assert.strictEqual(plan.targetRoot, path.join(projectRoot, '.agent'));
})) passed++; else failed++;

View File

@@ -146,7 +146,7 @@ function runTests() {
assert.strictEqual(utils.sanitizeSessionId('my-project_123'), 'my-project_123');
})) passed++; else failed++;
if (test('sanitizeSessionId avoids Windows reserved device names', () => {
if (test('sanitizeSessionId appends hash suffix for all Windows reserved device names', () => {
for (const reservedName of ['CON', 'prn', 'Aux', 'nul', 'COM1', 'lpt9']) {
const sanitized = utils.sanitizeSessionId(reservedName);
assert.ok(sanitized, `Expected sanitized output for ${reservedName}`);
@@ -193,7 +193,7 @@ function runTests() {
}
})) passed++; else failed++;
if (test('sanitizeSessionId avoids Windows reserved device names', () => {
if (test('sanitizeSessionId preserves readable prefixes for Windows reserved device names', () => {
const con = utils.sanitizeSessionId('CON');
const aux = utils.sanitizeSessionId('aux');
assert.ok(con.startsWith('CON-'), `Expected CON to get a suffix, got: ${con}`);

View File

@@ -68,9 +68,9 @@ if (
else failed++;
if (
test('install-global-git-hooks.sh handles quoted hook paths without shell injection', () => {
test('install-global-git-hooks.sh handles shell-sensitive hook paths without shell injection', () => {
const homeDir = createTempDir('codex-hooks-home-');
const weirdHooksDir = path.join(homeDir, 'git-hooks "quoted"');
const weirdHooksDir = path.join(homeDir, "git-hooks 'quoted' & spaced");
try {
const result = runBash(installScript, [], {

View File

@@ -261,7 +261,7 @@ function runTests() {
}
})) passed++; else failed++;
if (test('installs antigravity manifest profiles while skipping incompatible modules', () => {
if (test('installs antigravity manifest profiles while skipping only unsupported modules', () => {
const homeDir = createTempDir('install-apply-home-');
const projectDir = createTempDir('install-apply-project-');
@@ -272,14 +272,18 @@ function runTests() {
assert.ok(fs.existsSync(path.join(projectDir, '.agent', 'rules', 'common-coding-style.md')));
assert.ok(fs.existsSync(path.join(projectDir, '.agent', 'skills', 'architect.md')));
assert.ok(fs.existsSync(path.join(projectDir, '.agent', 'workflows', 'plan.md')));
assert.ok(!fs.existsSync(path.join(projectDir, '.agent', 'skills', 'tdd-workflow', 'SKILL.md')));
assert.ok(fs.existsSync(path.join(projectDir, '.agent', 'skills', 'tdd-workflow', 'SKILL.md')));
const state = readJson(path.join(projectDir, '.agent', 'ecc-install-state.json'));
assert.strictEqual(state.request.profile, 'core');
assert.strictEqual(state.request.legacyMode, false);
assert.deepStrictEqual(state.resolution.selectedModules, ['rules-core', 'agents-core', 'commands-core']);
assert.ok(state.resolution.skippedModules.includes('workflow-quality'));
assert.ok(state.resolution.skippedModules.includes('platform-configs'));
assert.deepStrictEqual(
state.resolution.selectedModules,
['rules-core', 'agents-core', 'commands-core', 'platform-configs', 'workflow-quality']
);
assert.ok(state.resolution.skippedModules.includes('hooks-runtime'));
assert.ok(!state.resolution.skippedModules.includes('workflow-quality'));
assert.ok(!state.resolution.skippedModules.includes('platform-configs'));
} finally {
cleanup(homeDir);
cleanup(projectDir);

View File

@@ -9,8 +9,32 @@ const path = require('path');
const scriptPath = path.join(__dirname, '..', '..', 'scripts', 'sync-ecc-to-codex.sh');
const source = fs.readFileSync(scriptPath, 'utf8');
const normalizedSource = source.replace(/\r\n/g, '\n');
const runOrEchoMatch = normalizedSource.match(/^run_or_echo\(\)\s*\{[\s\S]*?^}/m);
const runOrEchoSource = runOrEchoMatch ? runOrEchoMatch[0] : '';
const runOrEchoSource = (() => {
const start = normalizedSource.indexOf('run_or_echo() {');
if (start < 0) {
return '';
}
let depth = 0;
let bodyStart = normalizedSource.indexOf('{', start);
if (bodyStart < 0) {
return '';
}
for (let i = bodyStart; i < normalizedSource.length; i++) {
const char = normalizedSource[i];
if (char === '{') {
depth += 1;
} else if (char === '}') {
depth -= 1;
if (depth === 0) {
return normalizedSource.slice(start, i + 1);
}
}
}
return '';
})();
function test(name, fn) {
try {