Files
redsen-lean-harness/tests/onboard.test.mjs
T
mozempkandCopilot da20a6eaff feat: onboarding script, full lh flag reference, expanded README
- scripts/onboard.mjs: bootstraps a target repo onto the harness — copies
  the behaviour layer (.github/agents, skills, instructions, prompts,
  mcp.json, copilot-instructions.md, AGENTS.md), npm links the `lh` CLI,
  runs `lh init` + `lh doctor`, prints next steps. Idempotent: identical
  files are skipped, differing files require --force, re-running `lh init`
  on an initialized repo warns instead of failing. Supports --dry-run and
  --no-npm-link for CI/sandboxed use.
- tests/onboard.test.mjs: 5 new e2e tests (dry-run, full run, idempotent
  rerun, missing target dir, conflict + --force).
- README: new "Onboarding a new project" section, full lh flag reference
  for every subcommand (previously only one-line summaries), Development
  section mentions the onboarding script.

Live-tested against ~/Sources/ralph-runtime (a real, not-yet-git-tracked
project): git init, .github/ copied, `npm link` succeeded, `lh init`
ran, `lh doctor` correctly flagged its one real gap (no verify command
configured) rather than a false pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-09-09 22:58:46 +02:00

80 lines
3.1 KiB
JavaScript

import { test, describe, after } from 'node:test';
import assert from 'node:assert/strict';
import { existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { execFileSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { tempRepo, cleanup } from './helpers.mjs';
after(cleanup);
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
const ONBOARD = join(ROOT, 'scripts', 'onboard.mjs');
/** Run the onboarding script directly. Never throws — returns {code, stdout, stderr}. */
function onboard(args) {
try {
const stdout = execFileSync(process.execPath, [ONBOARD, ...args], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
});
return { code: 0, stdout, stderr: '' };
} catch (e) {
return { code: e.status ?? 1, stdout: e.stdout ?? '', stderr: e.stderr ?? '' };
}
}
describe('onboard script', () => {
test('--dry-run writes nothing', () => {
const dir = tempRepo();
const r = onboard([dir, '--dry-run']);
assert.equal(r.code, 0);
assert.ok(!existsSync(join(dir, '.github', 'agents')), 'dry run must not create files');
assert.ok(!existsSync(join(dir, '.agents')), 'dry run must not run lh init');
assert.match(r.stdout, /would write/);
});
test('--yes --no-npm-link copies the behaviour layer and runs lh init + doctor', () => {
const dir = tempRepo({
'package.json': JSON.stringify({ name: 'fixture', version: '1.0.0', scripts: { test: 'true' } }, null, 2),
});
const r = onboard([dir, '--yes', '--no-npm-link']);
assert.equal(r.code, 0, r.stdout + r.stderr);
for (const rel of ['.github/agents/conductor.agent.md', '.github/skills/design/SKILL.md',
'.github/mcp.json', 'AGENTS.md', '.agents/harness.config.json']) {
assert.ok(existsSync(join(dir, rel)), `expected ${rel} to exist`);
}
assert.match(r.stdout, /doctor: all required checks passed/);
});
test('rerunning with --yes is idempotent (no forced overwrite, no init failure)', () => {
const dir = tempRepo({
'package.json': JSON.stringify({ name: 'fixture', version: '1.0.0', scripts: { test: 'true' } }, null, 2),
});
assert.equal(onboard([dir, '--yes', '--no-npm-link']).code, 0);
const second = onboard([dir, '--yes', '--no-npm-link']);
assert.equal(second.code, 0, second.stdout + second.stderr);
assert.match(second.stdout, /already up to date/);
assert.match(second.stdout, /already initialized/);
});
test('refuses a target directory that does not exist', () => {
const r = onboard(['/nonexistent/path/for/sure', '--yes']);
assert.notEqual(r.code, 0);
});
test('a conflicting existing file is skipped unless --force', () => {
const dir = tempRepo({ 'AGENTS.md': 'custom project notes, do not clobber\n' });
const r = onboard([dir, '--yes', '--no-npm-link']);
assert.equal(r.code, 0);
assert.match(r.stdout, /skipped/);
const kept = existsSync(join(dir, 'AGENTS.md'));
assert.ok(kept, 'AGENTS.md must still exist');
const forced = onboard([dir, '--yes', '--no-npm-link', '--force']);
assert.equal(forced.code, 0);
assert.match(forced.stdout, /written/);
});
});