Files
redsen-lean-harness/tests/e2e.test.mjs
T
mozempkandCopilot 383129f571 feat: scaffold redsen-lean-harness v0.1.0
Recovered from crashed session (Node OOM). Repo contains full P0-P6
scaffold: plugin.json/marketplace.json, AGENTS.md, ADRs 0001-0006,
lh CLI (init/index/graph/lane/run/memory/host/report/doctor), 10
.github/agents, 12 CLI skills, instructions, context7 mcp.json, and
unit/e2e test suite.

Fixed: run.mjs read --in-tokens/--out-tokens but tests and CLI docs
use --input-tokens/--output-tokens, so telemetry totals were always 0.
Now accepts both forms.

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

288 lines
12 KiB
JavaScript

import { test, describe, after } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, existsSync, appendFileSync } from 'node:fs';
import { join } from 'node:path';
import { tempRepo, write, commitAll, lh, cleanup } from './helpers.mjs';
after(cleanup);
const PKG = JSON.stringify({
name: 'fixture',
version: '1.0.0',
scripts: { test: "node -e \"console.log('ok')\"" },
}, null, 2);
/** A brownfield repo carrying two planted structural defects. */
function brownfield() {
return tempRepo({
'package.json': PKG,
'src/a.js': 'export function helper(x) { return x + 1; }\nexport function shared() { return 1; }\n',
'src/b.js': "import { helper } from './a.js';\nexport function shared() { return 2; }\nexport function main() { return helper(missingFn()); }\n",
'README.md': '# fixture\n',
});
}
describe('cli contract', () => {
test('--help exits 0 and lists the command surface', () => {
const r = lh(process.cwd(), ['--help']);
assert.equal(r.code, 0);
for (const cmd of ['init', 'index', 'graph', 'lane', 'run', 'memory', 'host', 'report', 'doctor']) {
assert.ok(r.stdout.includes(cmd), `help should mention ${cmd}`);
}
});
test('an unknown command fails rather than silently succeeding', () => {
assert.notEqual(lh(process.cwd(), ['no-such-command']).code, 0);
});
test('host detection always emits a usable strategy', () => {
const r = lh(brownfield(), ['host', '--json']);
assert.equal(r.code, 0);
const host = JSON.parse(r.stdout);
assert.ok(host.host, 'a host must always be named');
assert.ok(host.maxWriteLanes >= 1, 'must allow at least one write lane');
assert.ok(host.readOnlyFanOut >= 1, 'must allow at least one scout');
});
});
describe('init (greenfield + brownfield)', () => {
test('greenfield init creates config and memory shards', () => {
const dir = tempRepo({ 'README.md': '# empty\n' });
assert.equal(lh(dir, ['init', '--yes']).code, 0);
assert.ok(existsSync(join(dir, '.agents/harness.config.json')));
assert.ok(existsSync(join(dir, '.agents/memory/INDEX.md')));
for (const shard of ['failures', 'corrections', 'insights', 'conventions', 'quirks']) {
assert.ok(existsSync(join(dir, `.agents/memory/${shard}.md`)), `missing shard ${shard}`);
}
});
test('brownfield init autodetects the existing verify command', () => {
const dir = brownfield();
const r = lh(dir, ['init', '--yes', '--json']);
assert.equal(r.code, 0);
const out = JSON.parse(r.stdout);
assert.ok(out.verify.some((v) => v.command.includes('test')), 'should detect npm test');
});
test('the Context7 key is never written to disk', () => {
const dir = brownfield();
lh(dir, ['init', '--yes']);
const raw = readFileSync(join(dir, '.agents/harness.config.json'), 'utf8');
assert.ok(raw.includes('CONTEXT7_API_KEY'), 'config should reference the env var by name');
if (process.env.CONTEXT7_API_KEY) {
assert.ok(!raw.includes(process.env.CONTEXT7_API_KEY), 'the key value must never be persisted');
}
});
test('re-running init refuses to clobber an existing config', () => {
const dir = brownfield();
assert.equal(lh(dir, ['init', '--yes']).code, 0);
assert.notEqual(lh(dir, ['init', '--yes']).code, 0, 'second init must refuse');
});
test('doctor passes once the repo is initialised', () => {
const dir = brownfield();
lh(dir, ['init', '--yes']);
lh(dir, ['index']);
assert.equal(lh(dir, ['doctor']).code, 0);
});
test('doctor fails on an uninitialised repo', () => {
assert.notEqual(lh(tempRepo({ 'README.md': '#\n' }), ['doctor']).code, 0);
});
});
describe('index and structural gate', () => {
test('index works offline and reports symbols', () => {
const dir = brownfield();
lh(dir, ['init', '--yes']);
const r = lh(dir, ['index', '--stats']);
assert.equal(r.code, 0);
assert.match(r.stdout, /symbols\s+[1-9]/, 'should discover symbols without network access');
assert.ok(existsSync(join(dir, '.agents/.cache/symbols.json')));
});
test('a second index run hits the cache', () => {
const dir = brownfield();
lh(dir, ['init', '--yes']);
lh(dir, ['index']);
assert.match(lh(dir, ['index', '--stats']).stdout, /cacheHits\s+[1-9]/);
});
test('the gate exits 1 and names both planted defects', () => {
const dir = brownfield();
lh(dir, ['init', '--yes']);
lh(dir, ['index']);
const r = lh(dir, ['graph', '--brief']);
assert.equal(r.code, 1, 'structural violations must exit 1 — the Ralph loop gates on this');
assert.match(r.stdout, /duplicate-definition/);
assert.match(r.stdout, /shared/);
assert.match(r.stdout, /unresolved-call/);
assert.match(r.stdout, /missingFn/);
});
test('the gate exits 0 on a clean repo', () => {
const dir = tempRepo({
'package.json': PKG,
'src/a.js': 'export function helper(x) { return x + 1; }\n',
'src/b.js': "import { helper } from './a.js';\nexport function main() { return helper(1); }\n",
});
lh(dir, ['init', '--yes']);
lh(dir, ['index']);
const r = lh(dir, ['graph', '--brief']);
assert.equal(r.code, 0, `clean repo must pass, got: ${r.stdout}${r.stderr}`);
});
});
describe('parallel write lanes', () => {
function laneRepo() {
const dir = tempRepo({
'package.json': PKG,
'src/a/deep/f.txt': 'one\n',
'src/b/g.txt': 'two\n',
'root.txt': 'base\n',
});
lh(dir, ['init', '--yes']);
return dir;
}
test('disjoint lanes both get a worktree', () => {
const dir = laneRepo();
assert.equal(lh(dir, ['lane', 'create', '--id', 'a', '--title', 'A', '--kind', 'write', '--scope', 'src/a/**']).code, 0);
assert.equal(lh(dir, ['lane', 'create', '--id', 'b', '--title', 'B', '--kind', 'write', '--scope', 'src/b/**']).code, 0);
assert.match(lh(dir, ['lane', 'list']).stdout, /\ba\b[\s\S]*\bb\b/);
});
test('an overlapping write lane is rejected', () => {
const dir = laneRepo();
lh(dir, ['lane', 'create', '--id', 'a', '--title', 'A', '--kind', 'write', '--scope', 'src/a/**']);
// src/a/deep/** is a strict subset of src/a/** — must still be refused.
const r = lh(dir, ['lane', 'create', '--id', 'c', '--title', 'C', '--kind', 'write', '--scope', 'src/a/deep/**']);
assert.notEqual(r.code, 0, 'overlapping write scopes must never both be leased');
assert.match(r.stderr + r.stdout, /overlap|intersect/i);
});
test('out-of-scope writes are detected and exit 1', () => {
const dir = laneRepo();
lh(dir, ['lane', 'create', '--id', 'a', '--title', 'A', '--kind', 'write', '--scope', 'src/a/**']);
const wt = join(dir, '.agents/.cache/worktrees/a');
write(wt, 'src/a/deep/f.txt', 'legit edit\n');
write(wt, 'root.txt', 'OUT OF SCOPE\n');
commitAll(wt, 'one legit, one violation');
const r = lh(dir, ['lane', 'status', 'a']);
assert.equal(r.code, 1, 'scope violation is Ralph exit criterion 5 — must be machine-detectable');
assert.match(r.stdout, /root\.txt/);
assert.match(r.stdout, /OUT/);
});
test('a clean lane merges, then a conflicting lane is blocked not auto-resolved', () => {
const dir = laneRepo();
lh(dir, ['lane', 'create', '--id', 'a', '--title', 'A', '--kind', 'write', '--scope', 'src/a/**']);
lh(dir, ['lane', 'create', '--id', 'b', '--title', 'B', '--kind', 'write', '--scope', 'src/b/**']);
const wtA = join(dir, '.agents/.cache/worktrees/a');
write(wtA, 'root.txt', 'LANE A\n');
commitAll(wtA, 'lane a');
const wtB = join(dir, '.agents/.cache/worktrees/b');
write(wtB, 'root.txt', 'LANE B\n');
commitAll(wtB, 'lane b');
assert.equal(lh(dir, ['lane', 'merge', 'a']).code, 0, 'first merge should be clean');
const conflict = lh(dir, ['lane', 'merge', 'b']);
assert.notEqual(conflict.code, 0, 'a conflict must fail loudly');
assert.match(conflict.stdout + conflict.stderr, /root\.txt/);
assert.match(lh(dir, ['lane', 'list']).stdout, /blocked/);
});
test('merge --abort restores a clean tree', () => {
const dir = laneRepo();
lh(dir, ['lane', 'create', '--id', 'a', '--title', 'A', '--kind', 'write', '--scope', 'src/a/**']);
lh(dir, ['lane', 'create', '--id', 'b', '--title', 'B', '--kind', 'write', '--scope', 'src/b/**']);
for (const id of ['a', 'b']) {
const wt = join(dir, `.agents/.cache/worktrees/${id}`);
write(wt, 'root.txt', `LANE ${id}\n`);
commitAll(wt, `lane ${id}`);
}
lh(dir, ['lane', 'merge', 'a']);
lh(dir, ['lane', 'merge', 'b']);
assert.equal(lh(dir, ['lane', 'merge', 'b', '--abort']).code, 0);
assert.equal(lh(dir, ['lane', 'drop', 'b', '--force']).code, 0);
});
});
describe('memory', () => {
function memRepo() {
const dir = brownfield();
lh(dir, ['init', '--yes']);
return dir;
}
test('put then retrieve by query', () => {
const dir = memRepo();
assert.equal(lh(dir, ['memory', 'put', '--shard', 'failures', '--title', 'flaky suite', '--body', 'Retry once before reporting.']).code, 0);
const r = lh(dir, ['memory', 'get', '--query', 'flaky']);
assert.equal(r.code, 0);
assert.match(r.stdout, /Retry once/);
});
test('writing a secret is refused', () => {
const dir = memRepo();
const r = lh(dir, ['memory', 'put', '--shard', 'insights', '--title', 'leak',
'--body', 'key ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789']);
assert.notEqual(r.code, 0, 'secrets must never reach the memory shards');
const shard = readFileSync(join(dir, '.agents/memory/insights.md'), 'utf8');
assert.ok(!shard.includes('ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 'shard must stay clean');
});
test('INDEX.md stays small — it is the only always-loaded file', () => {
const dir = memRepo();
for (let i = 0; i < 20; i++) {
lh(dir, ['memory', 'put', '--shard', 'insights', '--title', `note ${i}`, '--body', 'x'.repeat(200)]);
}
const index = readFileSync(join(dir, '.agents/memory/INDEX.md'), 'utf8');
assert.ok(index.length < 2000, `INDEX.md must stay lean, was ${index.length} bytes`);
});
});
describe('telemetry', () => {
test('a full run produces events, a board and a report', () => {
const dir = brownfield();
lh(dir, ['init', '--yes']);
const start = lh(dir, ['run', 'start', '--objective', 'test run', '--json']);
assert.equal(start.code, 0);
const runId = JSON.parse(start.stdout).runId;
assert.ok(runId, 'run start must return a runId');
assert.equal(lh(dir, ['run', 'event', '--run', runId, '--type', 'agent.end',
'--agent', 'scout', '--input-tokens', '100', '--output-tokens', '50']).code, 0);
assert.equal(lh(dir, ['run', 'end', '--run', runId]).code, 0);
assert.ok(existsSync(join(dir, `.agents/runs/${runId}/events.ndjson`)));
assert.ok(existsSync(join(dir, `.agents/runs/${runId}/board.md`)));
const report = lh(dir, ['report', runId, '--json']);
assert.equal(report.code, 0);
assert.equal(JSON.parse(report.stdout).totals.totalTokens, 150);
});
test('a malformed NDJSON line does not break reporting', () => {
const dir = brownfield();
lh(dir, ['init', '--yes']);
const runId = JSON.parse(lh(dir, ['run', 'start', '--objective', 'test run', '--json']).stdout).runId;
lh(dir, ['run', 'event', '--run', runId, '--type', 'agent.end', '--input-tokens', '10']);
const events = join(dir, `.agents/runs/${runId}/events.ndjson`);
appendFileSync(events, '{ this is not json\n');
lh(dir, ['run', 'event', '--run', runId, '--type', 'agent.end', '--input-tokens', '5']);
const r = lh(dir, ['report', runId, '--json']);
assert.equal(r.code, 0, 'the parser must tolerate corrupt lines');
assert.equal(JSON.parse(r.stdout).totals.inputTokens, 15, 'valid events must still be counted');
});
});