mirror of
https://github.com/affaan-m/ECC.git
synced 2026-08-17 21:15:40 +02:00
* chore(continuous-learning-v2): standardize shell shebangs to env bash Three scripts under skills/continuous-learning-v2/ used the hardcoded `#!/bin/bash` shebang while the other four already used the portable `#!/usr/bin/env bash`: - hooks/observe.sh (runs on every hook invocation) - scripts/detect-project.sh - agents/start-observer.sh The hardcoded interpreter path fails to execute on systems where bash is not installed at /bin/bash (NixOS, some Homebrew layouts, FreeBSD). Standardize all three to `#!/usr/bin/env bash`, matching the repo-wide majority convention, and add a regression test that asserts shebang uniformity for every shell script in this skill so the inconsistency cannot reappear. Fixes #2303 * test(continuous-learning-v2): harden shebang test runner Address review feedback on the shebang-consistency regression test: - firstLine() now splits on /\r?\n/ so a script checked out with CRLF line endings does not leave a trailing carriage return that would break the shebang comparison on Windows. - The test() helper now surfaces the full error (stack trace, not just the message) on failure and writes pass/fail lines via process.stdout/stderr so diagnostics are preserved. * test(continuous-learning-v2): skip hidden dirs in shebang scan The recursive shell-script scan now skips hidden directories (e.g. the observer's runtime `.observer-tmp`). This keeps the shebang-consistency check deterministic: only committed skill scripts are examined, and an untracked local artifact left over from an observer run can no longer cause a false failure.
102 lines
3.3 KiB
JavaScript
102 lines
3.3 KiB
JavaScript
/**
|
|
* Tests for shebang consistency across continuous-learning-v2 shell scripts
|
|
*
|
|
* Every `*.sh` script under skills/continuous-learning-v2/ must use the
|
|
* portable `#!/usr/bin/env bash` shebang rather than the hardcoded
|
|
* `#!/bin/bash`. The hardcoded interpreter path fails on systems where bash
|
|
* is not installed at /bin/bash (NixOS, some Homebrew layouts, FreeBSD), and
|
|
* observe.sh runs on every hook invocation. This guards against the
|
|
* inconsistency (#2303) reappearing.
|
|
*
|
|
* Run with: node tests/hooks/continuous-learning-shebang-consistency.test.js
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
function test(name, fn) {
|
|
try {
|
|
fn();
|
|
process.stdout.write(` ✓ ${name}\n`);
|
|
passed++;
|
|
} catch (err) {
|
|
process.stderr.write(` ✗ ${name}\n`);
|
|
process.stderr.write(` ${err && err.stack ? err.stack : String(err)}\n`);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
const repoRoot = path.resolve(__dirname, '..', '..');
|
|
const skillDir = path.join(repoRoot, 'skills', 'continuous-learning-v2');
|
|
const PORTABLE_SHEBANG = '#!/usr/bin/env bash';
|
|
const HARDCODED_SHEBANG = '#!/bin/bash';
|
|
|
|
function collectShellScripts(dir, acc = []) {
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
const fullPath = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
// Skip hidden/runtime directories (e.g. the observer's `.observer-tmp`)
|
|
// so an untracked local artifact cannot trigger a false failure; only
|
|
// committed skill scripts are checked.
|
|
if (entry.name.startsWith('.')) {
|
|
continue;
|
|
}
|
|
collectShellScripts(fullPath, acc);
|
|
} else if (entry.isFile() && entry.name.endsWith('.sh')) {
|
|
acc.push(fullPath);
|
|
}
|
|
}
|
|
return acc;
|
|
}
|
|
|
|
function firstLine(filePath) {
|
|
return fs.readFileSync(filePath, 'utf8').split(/\r?\n/, 1)[0];
|
|
}
|
|
|
|
console.log('\n=== continuous-learning-v2 shebang consistency ===\n');
|
|
|
|
const scripts = collectShellScripts(skillDir);
|
|
|
|
test('skill directory contains shell scripts to check', () => {
|
|
assert.ok(scripts.length > 0, `expected at least one .sh under ${skillDir}`);
|
|
});
|
|
|
|
for (const scriptPath of scripts) {
|
|
const rel = path.relative(repoRoot, scriptPath).split(path.sep).join('/');
|
|
test(`${rel} uses portable '#!/usr/bin/env bash'`, () => {
|
|
assert.strictEqual(
|
|
firstLine(scriptPath),
|
|
PORTABLE_SHEBANG,
|
|
`${rel} should start with '${PORTABLE_SHEBANG}'`
|
|
);
|
|
});
|
|
}
|
|
|
|
test('no continuous-learning-v2 script uses hardcoded #!/bin/bash', () => {
|
|
const offenders = scripts
|
|
.filter(scriptPath => firstLine(scriptPath) === HARDCODED_SHEBANG)
|
|
.map(scriptPath => path.relative(repoRoot, scriptPath).split(path.sep).join('/'));
|
|
assert.strictEqual(
|
|
offenders.length,
|
|
0,
|
|
`hardcoded #!/bin/bash found in: ${offenders.join(', ')}`
|
|
);
|
|
});
|
|
|
|
// ──────────────────────────────────────────────────────
|
|
// Summary
|
|
// ──────────────────────────────────────────────────────
|
|
|
|
console.log('\n=== Test Results ===');
|
|
console.log(`Passed: ${passed}`);
|
|
console.log(`Failed: ${failed}`);
|
|
console.log(`Total: ${passed + failed}\n`);
|
|
|
|
process.exit(failed > 0 ? 1 : 0);
|