// tests/lib/stats-landing.test.mjs // Veikart steg 1, økt 2's countable form: after a real /trekbrief → /trekplan run // there must be (1) a `brief-approved` record with the slug, (2) a trekbrief-stats // record with `intent_approved: true`, and (3) a trekplan-stats record with the // same slug — all in the directory scripts/yardstick.mjs reads. // // The Bash tool's env carries no CLAUDE_PLUGIN_DATA. Phase 7 of /trekbrief and // Phase 12 of /trekplan used to say "if ${CLAUDE_PLUGIN_DATA} is not set, skip // silently" — the same silent skip that left brief-approved at 0 records. These // tests execute the command lines the prose spells, with CLAUDE_PLUGIN_DATA // EMPTY, and require the records to land in the yardstick's directory. // // The trekbrief record also carries the coord-queue order the run is working on // (order_id, order_claimed_at, order_reason). The WRITER adds those from the // queue, so these tests run the line inside a temp git repo against a temp coord // dir - and every spawn drops an inherited CLAUDE_COORD_DIR, because the real // queue can hold a claimed order while the suite runs. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { spawnSync } from 'node:child_process'; import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const HERE = dirname(fileURLToPath(import.meta.url)); const ROOT = join(HERE, '..', '..'); const read = (rel) => readFileSync(join(ROOT, rel), 'utf8'); const DATA_REL = join('.claude', 'plugins', 'data', 'voyage-ktg-plugin-marketplace'); /** The text of `## ` up to the next `## ` heading. */ function section(rel, heading) { const text = read(rel); const start = text.indexOf(`\n## ${heading}`); assert.ok(start >= 0, `${rel} must have "## ${heading}"`); const next = text.indexOf('\n## ', start + 1); return next < 0 ? text.slice(start) : text.slice(start, next); } /** The one line in `sec` that runs stats-append.mjs for `kind`. */ function appendLine(sec, kind, rel) { const lines = sec.split('\n').filter((l) => /^\s*node \$\{CLAUDE_PLUGIN_ROOT\}\/lib\/stats\/stats-append\.mjs\b/.test(l) && l.includes(` ${kind}`)); assert.equal(lines.length, 1, `${rel} must carry exactly one \`stats-append.mjs ${kind}\` line; found ${lines.length}`); return lines[0].trim().replace(/\s*<<-?'?JSON'?\s*$/, ''); } /** process.env without CLAUDE_COORD_DIR: a test never reads the real coord queue. */ function baseEnv() { const env = { ...process.env }; delete env.CLAUDE_COORD_DIR; return env; } /** Run a prose command line with a record on stdin, CLAUDE_PLUGIN_DATA empty, HOME = home. */ function runLine(line, record, home, { cwd, env: extra = {} } = {}) { const cmd = line.split('${CLAUDE_PLUGIN_ROOT}').join(ROOT); return spawnSync('bash', ['-c', cmd], { encoding: 'utf8', cwd, input: JSON.stringify(record), env: { ...baseEnv(), CLAUDE_PLUGIN_DATA: '', HOME: home, ...extra }, }); } /** A git repo whose directory is named `name` (its mailbox), under a fresh temp parent. */ function gitRepo(name) { const parent = mkdtempSync(join(tmpdir(), 'stats-landing-git-')); const dir = join(parent, name); mkdirSync(dir); assert.equal(spawnSync('git', ['init', '-q'], { cwd: dir }).status, 0); return { parent, dir }; } /** A coord dir holding claimed orders: `{ mailbox: [id, ...] }`, each with a .claim sidecar. */ function coordWith(claims) { const coord = mkdtempSync(join(tmpdir(), 'stats-landing-coord-')); for (const [mailbox, ids] of Object.entries(claims)) { const dir = join(coord, mailbox, 'orders', 'claimed'); mkdirSync(dir, { recursive: true }); for (const id of ids) { writeFileSync(join(dir, `${id}.md`), `---\nfrom: .claude\nto: ${mailbox}\norder-id: ${id}\ndate: 2026-10-04T10:00:00Z\n---\n\n# ORDER ${id}\n`); writeFileSync(join(dir, `${id}.claim`), 'claimed-at: 2026-10-04T17:10:51Z\nclaimed-by-pid: 1\n'); } } return coord; } function records(file) { return readFileSync(file, 'utf8').trim().split('\n').filter(Boolean).map((l) => JSON.parse(l)); } const TB = 'commands/trekbrief.md'; const TP = 'commands/trekplan.md'; test('M6: /trekbrief Phase 7 writes through stats-append (code), not "skip silently" (prose)', () => { const sec = section(TB, 'Phase 7'); appendLine(sec, 'trekbrief', TB); assert.doesNotMatch(sec, /skip silently/i, 'a silent skip is how the record never landed'); assert.match(sec, /"intent_approved"/); assert.match(sec, /"slug"/); }); test('M6: /trekplan Phase 12 writes through stats-append (code), not "skip silently" (prose)', () => { const sec = section(TP, 'Phase 12'); appendLine(sec, 'trekplan', TP); assert.doesNotMatch(sec, /skip (tracking )?silently/i); assert.match(sec, /"slug"/); }); test('M6: with CLAUDE_PLUGIN_DATA empty the trekbrief record (intent_approved) lands where the yardstick reads', () => { const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); try { const line = appendLine(section(TB, 'Phase 7'), 'trekbrief', TB); const r = runLine(line, { ts: '2026-09-23T10:00:00Z', slug: 'dark-mode-toggle', intent_approved: true }, home); assert.equal(r.status, 0, `stats-append failed: ${r.stdout} ${r.stderr}`); const file = join(home, DATA_REL, 'trekbrief-stats.jsonl'); assert.ok(existsSync(file), `record must land in ${file}`); const recs = records(file); assert.equal(recs.length, 1); assert.equal(recs[0].intent_approved, true); assert.equal(recs[0].slug, 'dark-mode-toggle'); } finally { rmSync(home, { recursive: true, force: true }); } }); test('M6: a failed write is reported, never silent (exit 1 + reason on stdout)', () => { const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); try { // the data dir path is occupied by a FILE, so the directory cannot be created mkdirSync(join(home, '.claude', 'plugins', 'data'), { recursive: true }); writeFileSync(join(home, DATA_REL), 'not a directory'); const line = appendLine(section(TB, 'Phase 7'), 'trekbrief', TB); const r = runLine(line, { ts: '2026-09-23T10:00:00Z', slug: 'x', intent_approved: false }, home); assert.equal(r.status, 1); const out = JSON.parse(r.stdout); assert.equal(out.written, false); assert.ok(out.reason && out.reason.length > 0, r.stdout); } finally { rmSync(home, { recursive: true, force: true }); } }); test('M6: stats-append refuses a record that is not a JSON object, and an unknown kind', () => { const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); try { const cli = join(ROOT, 'lib', 'stats', 'stats-append.mjs'); const env = { ...baseEnv(), CLAUDE_PLUGIN_DATA: '', HOME: home }; const bad = spawnSync('node', [cli, 'trekbrief'], { encoding: 'utf8', input: 'not json', env }); assert.equal(bad.status, 1, bad.stdout + bad.stderr); assert.equal(JSON.parse(bad.stdout).written, false); const unknown = spawnSync('node', [cli, 'no-such-command'], { encoding: 'utf8', input: '{}', env }); assert.equal(unknown.status, 1); assert.ok(!existsSync(join(home, DATA_REL, 'no-such-command-stats.jsonl'))); } finally { rmSync(home, { recursive: true, force: true }); } }); test('M6: økt 2\'s countable form lands end to end with CLAUDE_PLUGIN_DATA empty (stamp → trekbrief → trekplan)', async () => { const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); const proj = mkdtempSync(join(tmpdir(), 'stats-landing-proj-')); try { writeFileSync(join(proj, 'brief.md'), `--- type: trekbrief brief_version: "2.1" task: "Add a dark-mode toggle to a recipe app" slug: dark-mode-toggle --- ## Intent Readers cook in dim kitchens and the bright page glares. ## Goal A toggle in settings, defaulting to the system theme. `); // (1) the Phase 4h stamp line, as trekbrief.md spells it const stampLine = read(TB).split('\n').find((l) => /^\s*node \$\{CLAUDE_PLUGIN_ROOT\}\/lib\/validators\/intent-approval\.mjs\b.*--stamp/.test(l)); assert.ok(stampLine, 'trekbrief.md must carry the --stamp line'); let cmd = stampLine.trim().split('${CLAUDE_PLUGIN_ROOT}').join(ROOT); cmd = cmd.split('{BRIEF_PATH}').join(join(proj, 'brief.md')).split('{PROJECT_DIR}').join(proj); const s = spawnSync('bash', ['-c', cmd], { encoding: 'utf8', env: { ...baseEnv(), CLAUDE_PLUGIN_DATA: '', HOME: home } }); assert.equal(s.status, 0, s.stdout + s.stderr); // (2) and (3): the stats lines assert.equal(runLine(appendLine(section(TB, 'Phase 7'), 'trekbrief', TB), { ts: '2026-09-23T10:00:00Z', slug: 'dark-mode-toggle', intent_approved: true }, home).status, 0); assert.equal(runLine(appendLine(section(TP, 'Phase 12'), 'trekplan', TP), { ts: '2026-09-23T10:05:00Z', slug: 'dark-mode-toggle', outcome: 'save' }, home).status, 0); const { loadStats } = await import(join(ROOT, 'scripts', 'yardstick.mjs')); const stats = loadStats(join(home, DATA_REL)); const all = stats.files.flatMap((f) => f.records.map((r) => ({ file: f.name, r }))); const approved = all.filter(({ r }) => r.event === 'brief-approved' && r.payload?.slug === 'dark-mode-toggle'); const brief = all.filter(({ file, r }) => file === 'trekbrief-stats.jsonl' && r.intent_approved === true && r.slug === 'dark-mode-toggle'); const plan = all.filter(({ file, r }) => file === 'trekplan-stats.jsonl' && r.slug === 'dark-mode-toggle'); assert.equal(approved.length, 1, 'brief-approved with slug'); assert.equal(brief.length, 1, 'trekbrief record with intent_approved: true'); assert.equal(plan.length, 1, 'trekplan record with the same slug'); } finally { rmSync(home, { recursive: true, force: true }); rmSync(proj, { recursive: true, force: true }); } }); test('M6: stats-append and intent-approval resolve the same data dir as the yardstick', async () => { const { resolveStatsDir } = await import(join(ROOT, 'lib', 'stats', 'stats-append.mjs')); const { resolveApprovalDataDir } = await import(join(ROOT, 'lib', 'validators', 'intent-approval.mjs')); const env = { HOME: '/home/someone', CLAUDE_PLUGIN_DATA: '' }; assert.equal(resolveStatsDir(env), join('/home/someone', DATA_REL)); assert.equal(resolveApprovalDataDir(env), resolveStatsDir(env)); assert.equal(resolveStatsDir({ HOME: '/h', CLAUDE_PLUGIN_DATA: '/x/y' }), '/x/y'); }); // --- the order the run is working on (written by the writer, never the model) ------- const ORDER_KEYS = ['order_id', 'order_claimed_at', 'order_reason']; /** Run Phase 7's line in `repo` against `coord` (or none) and return the one landed record. */ function landTrekbrief(record, { repo, coord, home }) { const line = appendLine(section(TB, 'Phase 7'), 'trekbrief', TB); const env = coord ? { CLAUDE_COORD_DIR: coord } : {}; const r = runLine(line, record, home, { cwd: repo, env }); assert.equal(r.status, 0, `stats-append failed: ${r.stdout} ${r.stderr}`); const recs = records(join(home, DATA_REL, 'trekbrief-stats.jsonl')); assert.equal(recs.length, 1); return recs[0]; } test('order: one claimed order in the repo\'s mailbox lands in the trekbrief record, with its claim time', () => { const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); const repo = gitRepo('mbx'); const coord = coordWith({ mbx: ['o-1'] }); try { const rec = landTrekbrief({ ts: '2026-10-04T18:00:00Z', slug: 'dark-mode-toggle', intent_approved: true }, { repo: repo.dir, coord, home }); assert.equal(rec.order_id, 'o-1'); assert.equal(rec.order_claimed_at, '2026-10-04T17:10:51Z'); assert.equal(rec.order_reason, null); assert.equal(rec.slug, 'dark-mode-toggle', 'the record keeps its own slug'); } finally { rmSync(home, { recursive: true, force: true }); rmSync(repo.parent, { recursive: true, force: true }); rmSync(coord, { recursive: true, force: true }); } }); test('order: the record\'s own project_dir finds the mailbox when the shell stands outside the repo', () => { const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); const repo = gitRepo('mbx'); const coord = coordWith({ mbx: ['o-1'] }); try { mkdirSync(join(repo.dir, '.claude', 'projects', 'x'), { recursive: true }); const rec = landTrekbrief({ ts: '2026-10-04T18:00:00Z', slug: 's', project_dir: 'mbx/.claude/projects/x' }, { repo: repo.parent, coord, home }); assert.equal(rec.order_id, 'o-1'); assert.equal(rec.order_reason, null); } finally { rmSync(home, { recursive: true, force: true }); rmSync(repo.parent, { recursive: true, force: true }); rmSync(coord, { recursive: true, force: true }); } }); test('order: zero claimed orders - the three keys are PRESENT, order_id null, reason none-claimed', () => { const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); const repo = gitRepo('mbx'); const coord = coordWith({ other: ['o-9'] }); try { const rec = landTrekbrief({ ts: '2026-10-04T18:00:00Z', slug: 's' }, { repo: repo.dir, coord, home }); for (const k of ORDER_KEYS) assert.ok(k in rec, `${k} must be present (absent means: written before the recorder existed)`); assert.equal(rec.order_id, null); assert.equal(rec.order_reason, 'none-claimed'); } finally { rmSync(home, { recursive: true, force: true }); rmSync(repo.parent, { recursive: true, force: true }); rmSync(coord, { recursive: true, force: true }); } }); test('order: two claimed orders - order_id null, reason several-claimed:2, never a guess', () => { const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); const repo = gitRepo('mbx'); const coord = coordWith({ mbx: ['o-1', 'o-2'] }); try { const rec = landTrekbrief({ ts: '2026-10-04T18:00:00Z', slug: 's' }, { repo: repo.dir, coord, home }); assert.equal(rec.order_id, null); assert.equal(rec.order_reason, 'several-claimed:2'); } finally { rmSync(home, { recursive: true, force: true }); rmSync(repo.parent, { recursive: true, force: true }); rmSync(coord, { recursive: true, force: true }); } }); test('order: order_* keys the model put in the record are replaced by the writer\'s', () => { const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); const repo = gitRepo('mbx'); const coord = coordWith({ mbx: ['o-1'] }); try { const rec = landTrekbrief({ ts: '2026-10-04T18:00:00Z', slug: 's', order_id: 'forged', order_extra: 1 }, { repo: repo.dir, coord, home }); assert.equal(rec.order_id, 'o-1'); assert.ok(!('order_extra' in rec), 'only the writer\'s three order keys survive'); } finally { rmSync(home, { recursive: true, force: true }); rmSync(repo.parent, { recursive: true, force: true }); rmSync(coord, { recursive: true, force: true }); } }); test('order: a trekplan record gains no order keys - only /trekbrief records the order', () => { const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); const repo = gitRepo('mbx'); const coord = coordWith({ mbx: ['o-1'] }); try { const r = runLine(appendLine(section(TP, 'Phase 12'), 'trekplan', TP), { ts: '2026-10-04T18:00:00Z', slug: 's' }, home, { cwd: repo.dir, env: { CLAUDE_COORD_DIR: coord } }); assert.equal(r.status, 0, r.stdout + r.stderr); const [rec] = records(join(home, DATA_REL, 'trekplan-stats.jsonl')); for (const k of ORDER_KEYS) assert.ok(!(k in rec), `${k} must not be on a trekplan record`); } finally { rmSync(home, { recursive: true, force: true }); rmSync(repo.parent, { recursive: true, force: true }); rmSync(coord, { recursive: true, force: true }); } }); test('order: with CLAUDE_COORD_DIR unset and HOME a temp dir the real queue is never read - order_id null', () => { const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); const repo = gitRepo('voyage'); try { const rec = landTrekbrief({ ts: '2026-10-04T18:00:00Z', slug: 's' }, { repo: repo.dir, home }); assert.ok('order_id' in rec); assert.equal(rec.order_id, null); assert.equal(rec.order_reason, 'coord-missing'); } finally { rmSync(home, { recursive: true, force: true }); rmSync(repo.parent, { recursive: true, force: true }); } }); test('CLI: stats-append writes when it is run through a symlink', () => { const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); const links = mkdtempSync(join(tmpdir(), 'stats-landing-link-')); try { const link = join(links, 'stats-append.mjs'); symlinkSync(join(ROOT, 'lib', 'stats', 'stats-append.mjs'), link); const r = spawnSync('node', [link, 'trekplan'], { encoding: 'utf8', input: '{"slug":"s"}', env: { ...baseEnv(), CLAUDE_PLUGIN_DATA: '', HOME: home } }); assert.equal(r.status, 0, r.stdout + r.stderr); assert.ok(existsSync(join(home, DATA_REL, 'trekplan-stats.jsonl')), `no record landed; stdout: "${r.stdout}"`); } finally { rmSync(home, { recursive: true, force: true }); rmSync(links, { recursive: true, force: true }); } }); test('CLI: stats-append writes when its path contains a space', () => { const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); const spaced = mkdtempSync(join(tmpdir(), 'stats landing space-')); try { cpSync(join(ROOT, 'lib'), join(spaced, 'with space', 'lib'), { recursive: true }); const cli = join(spaced, 'with space', 'lib', 'stats', 'stats-append.mjs'); const r = spawnSync('node', [cli, 'trekplan'], { encoding: 'utf8', input: '{"slug":"s"}', env: { ...baseEnv(), CLAUDE_PLUGIN_DATA: '', HOME: home } }); assert.equal(r.status, 0, r.stdout + r.stderr); assert.ok(existsSync(join(home, DATA_REL, 'trekplan-stats.jsonl')), `no record landed; stdout: "${r.stdout}"`); } finally { rmSync(home, { recursive: true, force: true }); rmSync(spaced, { recursive: true, force: true }); } }); // ---- /trekexecute: every telemetry write lands through a writer ---------------- // Phase 8's three event-emit calls, Phase 9's stats record and the // gates_mode_flag_override record. Phase 9 said "skip silently"; the override was // prose with no writer; Phase 8's payloads spliced ${PLAN_PATH} (set in an earlier // Bash call that does not survive) and ${WAVE_COUNT} (never assigned) into JSON, // so a literal run printed payload-not-json. Every block runs here in a fresh // shell with NOTHING exported, CLAUDE_PLUGIN_DATA empty and HOME pinned. const TE = 'commands/trekexecute.md'; /** The fenced bash blocks in `sec` that contain `needle`. */ function fencedBlocksWith(sec, needle) { const blocks = []; const re = /```bash\n([\s\S]*?)```/g; let m; while ((m = re.exec(sec)) !== null) if (m[1].includes(needle)) blocks.push(m[1]); return blocks; } /** Fill the prose placeholders an executor fills in, then run the block. */ function runBlock(block, home) { const cmd = block.split('${CLAUDE_PLUGIN_ROOT}').join(ROOT) .split('{plan-path}').join('.claude/projects/2026-10-05-x/plan.md') .split('{wave-count}').join('1'); return spawnSync('bash', ['-c', cmd], { encoding: 'utf8', env: { ...baseEnv(), CLAUDE_PLUGIN_DATA: '', HOME: home }, }); } test('M6: /trekexecute Phase 9 writes through stats-append (code), never "skip silently" (prose)', () => { appendLine(section(TE, 'Phase 9'), 'trekexecute', TE); assert.doesNotMatch(read(TE), /skip (tracking )?silently/i, 'a silent skip is how the record never landed'); }); test('M6: with CLAUDE_PLUGIN_DATA empty the trekexecute record lands where the yardstick reads', () => { const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); try { const line = appendLine(section(TE, 'Phase 9'), 'trekexecute', TE); const r = runLine(line, { ts: '2026-10-05T10:00:00Z', plan: 'plan.md', result: 'completed' }, home); assert.equal(r.status, 0, r.stdout + r.stderr); assert.equal(records(join(home, DATA_REL, 'trekexecute-stats.jsonl')).length, 1); } finally { rmSync(home, { recursive: true, force: true }); } }); test('trekexecute.md names no bare ${CLAUDE_PLUGIN_DATA}/...jsonl write target', () => { const hits = read(TE).split('\n') .map((l, i) => [i + 1, l]).filter(([, l]) => /\$\{CLAUDE_PLUGIN_DATA\}\/[\w-]+\.jsonl/.test(l)); assert.deepEqual(hits, [], 'every stats or event write goes through a writer that resolves the directory'); }); test('no --payload in trekexecute.md expands a shell variable', () => { const hits = read(TE).split('\n') .map((l, i) => [i + 1, l]).filter(([, l]) => /--payload\b.*\$\{/.test(l)); assert.deepEqual(hits, [], 'a payload built from shell state that does not survive between Bash calls never parses'); }); test('Phase 8: each event block, run as written, lands one known event', () => { const blocks = fencedBlocksWith(section(TE, 'Phase 8'), 'event-emit.mjs'); assert.equal(blocks.length, 3, 'main-merge-gate, main-merge-approved, main-merge-declined'); for (const block of blocks) { const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); try { const r = runBlock(block, home); assert.equal(r.status, 0, `${block}\n${r.stdout}${r.stderr}`); assert.equal(JSON.parse(r.stdout.trim()).written, true, r.stdout); const [rec] = records(join(home, DATA_REL, 'trekexecute-stats.jsonl')); assert.equal(rec.known_event, true, rec.event); } finally { rmSync(home, { recursive: true, force: true }); } } }); test('Phase 8: a written:false is printed in one line and the run continues', () => { const sec = section(TE, 'Phase 8'); const flat = sec.replace(/\s+/g, ' '); assert.match(flat, /On `"written": false` \(exit 1\), print its `reason` in one line and continue/); assert.match(flat, /never chained with `&&`/); }); test('gates_mode_flag_override is written through event-emit with the documented shape', () => { const sec = section(TE, 'Composition rule (v5.1)'); const blocks = fencedBlocksWith(sec, '--event gates_mode_flag_override'); assert.equal(blocks.length, 1, 'the override needs a writer, not a prose target'); assert.match(sec, /\{ts, event, known_event, payload:\{flag, signal_effort\}\}/); const home = mkdtempSync(join(tmpdir(), 'stats-landing-')); try { const r = runBlock(blocks[0], home); assert.equal(r.status, 0, r.stdout + r.stderr); const [rec] = records(join(home, DATA_REL, 'trekexecute-stats.jsonl')); assert.deepEqual(Object.keys(rec).sort(), ['event', 'known_event', 'payload', 'ts']); assert.equal(rec.known_event, true); assert.deepEqual(Object.keys(rec.payload).sort(), ['flag', 'signal_effort']); } finally { rmSync(home, { recursive: true, force: true }); } });