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>
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
export const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
export const CLI = join(ROOT, 'src', 'cli.mjs');
|
||||
|
||||
const created = [];
|
||||
|
||||
export function tempRepo(files = {}) {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'lh-test-'));
|
||||
created.push(dir);
|
||||
git(dir, ['init', '-q', '-b', 'main']);
|
||||
git(dir, ['config', 'user.email', 'test@example.com']);
|
||||
git(dir, ['config', 'user.name', 'Test']);
|
||||
git(dir, ['config', 'commit.gpgsign', 'false']);
|
||||
for (const [rel, body] of Object.entries(files)) write(dir, rel, body);
|
||||
if (Object.keys(files).length) commitAll(dir, 'init');
|
||||
return dir;
|
||||
}
|
||||
|
||||
export function write(dir, rel, body) {
|
||||
const full = join(dir, rel);
|
||||
mkdirSync(dirname(full), { recursive: true });
|
||||
writeFileSync(full, body);
|
||||
return full;
|
||||
}
|
||||
|
||||
export function git(dir, args) {
|
||||
return execFileSync('git', args, { cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
||||
}
|
||||
|
||||
export function commitAll(dir, msg) {
|
||||
git(dir, ['add', '-A']);
|
||||
git(dir, ['commit', '-qm', msg]);
|
||||
}
|
||||
|
||||
/** Run the lh CLI. Never throws — returns {code, stdout, stderr}. */
|
||||
export function lh(dir, args) {
|
||||
try {
|
||||
const stdout = execFileSync(process.execPath, [CLI, ...args], {
|
||||
cwd: dir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
return { code: 0, stdout, stderr: '' };
|
||||
} catch (e) {
|
||||
return { code: e.status ?? 1, stdout: e.stdout ?? '', stderr: e.stderr ?? '' };
|
||||
}
|
||||
}
|
||||
|
||||
export function cleanup() {
|
||||
while (created.length) {
|
||||
try { rmSync(created.pop(), { recursive: true, force: true }); } catch {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import { test, describe, after } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { defaultConfig, validateConfig, modelForRole, MEMORY_SHARDS, RALPH_EXIT_CRITERIA } from '../src/lib/config.mjs';
|
||||
import { globsIntersect } from '../src/lib/lane.mjs';
|
||||
import { scanSecrets } from '../src/lib/memory.mjs';
|
||||
import { summarize } from '../src/lib/telemetry.mjs';
|
||||
import { globMatch } from '../src/lib/repomap.mjs';
|
||||
import { cleanup } from './helpers.mjs';
|
||||
|
||||
after(cleanup);
|
||||
|
||||
describe('config', () => {
|
||||
test('default config is valid', () => {
|
||||
assert.deepEqual(validateConfig(defaultConfig()), []);
|
||||
});
|
||||
|
||||
test('rejects a bad isolation backend', () => {
|
||||
const c = defaultConfig();
|
||||
c.isolation.backend = 'kubernetes';
|
||||
assert.ok(validateConfig(c).some((p) => p.includes('isolation.backend')));
|
||||
});
|
||||
|
||||
test('rejects an unknown ralph exit criterion', () => {
|
||||
const c = defaultConfig();
|
||||
c.ralph.exitCriteria = ['vibes'];
|
||||
assert.ok(validateConfig(c).some((p) => p.includes('vibes')));
|
||||
});
|
||||
|
||||
test('rejects a non-positive memory budget', () => {
|
||||
const c = defaultConfig();
|
||||
c.memory.tokenBudget = 0;
|
||||
assert.ok(validateConfig(c).some((p) => p.includes('memory.tokenBudget')));
|
||||
});
|
||||
|
||||
test('cheap roles resolve to the cheap tier model', () => {
|
||||
const c = defaultConfig();
|
||||
const cheap = modelForRole(c, 'scout');
|
||||
assert.equal(cheap, modelForRole(c, 'verifier'));
|
||||
assert.notEqual(cheap, modelForRole(c, 'conductor'));
|
||||
});
|
||||
|
||||
test('a role override is honoured', () => {
|
||||
const c = defaultConfig();
|
||||
c.models.roles.scout = 'strong';
|
||||
assert.equal(modelForRole(c, 'scout'), modelForRole(c, 'conductor'));
|
||||
});
|
||||
|
||||
test('exposes the five memory shards plus seed', () => {
|
||||
for (const s of ['failures', 'corrections', 'insights', 'conventions', 'quirks']) {
|
||||
assert.ok(MEMORY_SHARDS.includes(s), `missing shard ${s}`);
|
||||
}
|
||||
assert.ok(RALPH_EXIT_CRITERIA.length >= 5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('lane scope safety', () => {
|
||||
// The load-bearing invariant: two write lanes must never share a file.
|
||||
const overlapping = [
|
||||
['src/**', 'src/a/deep/**'],
|
||||
['src/a/**', 'src/a/**'],
|
||||
['**', 'anything/at/all.js'],
|
||||
['src/a.js', 'src/a.js'],
|
||||
['src/*.js', 'src/a.js'],
|
||||
['lib/**/*.ts', 'lib/deep/nested/x.ts'],
|
||||
];
|
||||
for (const [a, b] of overlapping) {
|
||||
test(`rejects overlap: ${a} vs ${b}`, () => {
|
||||
assert.equal(globsIntersect(a, b), true, `${a} should intersect ${b}`);
|
||||
assert.equal(globsIntersect(b, a), true, 'intersection must be symmetric');
|
||||
});
|
||||
}
|
||||
|
||||
const disjoint = [
|
||||
['src/a/**', 'src/b/**'],
|
||||
['src/a.js', 'src/b.js'],
|
||||
['docs/**', 'src/**'],
|
||||
['lib/**/*.ts', 'lib/x.js'],
|
||||
];
|
||||
for (const [a, b] of disjoint) {
|
||||
test(`allows disjoint: ${a} vs ${b}`, () => {
|
||||
assert.equal(globsIntersect(a, b), false, `${a} should not intersect ${b}`);
|
||||
assert.equal(globsIntersect(b, a), false, 'disjointness must be symmetric');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('glob matching', () => {
|
||||
test('** crosses directory boundaries', () => {
|
||||
assert.equal(globMatch('src/**', 'src/a/b/c.js'), true);
|
||||
});
|
||||
test('* does not cross directory boundaries', () => {
|
||||
assert.equal(globMatch('src/*.js', 'src/a/b.js'), false);
|
||||
});
|
||||
test('non-matching prefix fails', () => {
|
||||
assert.equal(globMatch('src/**', 'docs/a.js'), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('secret scanning', () => {
|
||||
const secrets = [
|
||||
['github token', 'token ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'],
|
||||
['openai key', 'sk-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrst'],
|
||||
['private key', '-----BEGIN RSA PRIVATE KEY-----'],
|
||||
['aws key', 'AKIAIOSFODNN7EXAMPLE'],
|
||||
];
|
||||
for (const [name, text] of secrets) {
|
||||
test(`detects a ${name}`, () => {
|
||||
const hits = scanSecrets(text);
|
||||
assert.ok(hits.length > 0, `expected a hit for ${name}`);
|
||||
});
|
||||
}
|
||||
|
||||
test('does not fire on ordinary prose', () => {
|
||||
assert.deepEqual(scanSecrets('The build failed because the test timed out after 30s.'), []);
|
||||
});
|
||||
|
||||
test('never echoes the raw secret back', () => {
|
||||
const raw = 'ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
for (const hit of scanSecrets(`token ${raw}`)) {
|
||||
assert.ok(!JSON.stringify(hit).includes(raw), 'secret must be redacted in the finding');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('telemetry summarize', () => {
|
||||
test('sums token usage across events', () => {
|
||||
const s = summarize([
|
||||
{ ts: '2026-01-01T00:00:00.000Z', type: 'run.start' },
|
||||
{ ts: '2026-01-01T00:00:10.000Z', type: 'agent.end', 'gen_ai.usage.input_tokens': 100, 'gen_ai.usage.output_tokens': 50 },
|
||||
{ ts: '2026-01-01T00:01:00.000Z', type: 'agent.end', 'gen_ai.usage.input_tokens': 200, 'gen_ai.usage.output_tokens': 25 },
|
||||
]);
|
||||
assert.equal(s.totals.inputTokens, 300);
|
||||
assert.equal(s.totals.outputTokens, 75);
|
||||
assert.equal(s.totals.totalTokens, 375);
|
||||
assert.equal(s.totals.wallMs, 60_000);
|
||||
});
|
||||
|
||||
test('tolerates empty input', () => {
|
||||
const s = summarize([]);
|
||||
assert.equal(s.totals.totalTokens, 0);
|
||||
assert.equal(s.totals.wallMs, 0);
|
||||
});
|
||||
|
||||
test('ignores malformed timestamps rather than producing NaN', () => {
|
||||
const s = summarize([
|
||||
{ ts: 'not-a-date', type: 'run.start' },
|
||||
{ ts: '2026-01-01T00:00:00.000Z', type: 'agent.end', 'gen_ai.usage.input_tokens': 10 },
|
||||
]);
|
||||
assert.ok(Number.isFinite(s.totals.wallMs));
|
||||
assert.equal(s.totals.inputTokens, 10);
|
||||
});
|
||||
|
||||
test('ignores non-numeric token fields', () => {
|
||||
const s = summarize([{ ts: '2026-01-01T00:00:00.000Z', 'gen_ai.usage.input_tokens': 'lots' }]);
|
||||
assert.equal(s.totals.inputTokens, 0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user