chore: initialize beadboard baseline

This commit is contained in:
zenchantlive 2026-02-11 17:42:51 -08:00
commit 292a72f861
30 changed files with 2983 additions and 0 deletions

30
tests/bootstrap.test.mjs Normal file
View file

@ -0,0 +1,30 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
const requiredFiles = [
'package.json',
'tsconfig.json',
'next.config.ts',
'src/app/layout.tsx',
'src/app/page.tsx',
];
test('bootstrap scaffold files exist', () => {
for (const file of requiredFiles) {
assert.equal(fs.existsSync(file), true, `missing file: ${file}`);
}
});
test('package.json has next/react/typescript scripts and deps', () => {
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
assert.equal(pkg.dependencies.next?.startsWith('15'), true, 'next@15 required');
assert.equal(pkg.dependencies.react?.startsWith('19'), true, 'react@19 required');
assert.equal(pkg.dependencies['react-dom']?.startsWith('19'), true, 'react-dom@19 required');
assert.equal(pkg.devDependencies.typescript?.length > 0, true, 'typescript required');
assert.equal(typeof pkg.scripts.dev, 'string', 'dev script required');
assert.equal(typeof pkg.scripts.build, 'string', 'build script required');
assert.equal(typeof pkg.scripts.start, 'string', 'start script required');
assert.equal(typeof pkg.scripts.typecheck, 'string', 'typecheck script required');
});

View file

@ -0,0 +1,9 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { scanForDirectIssuesJsonlWrites } from '../../tools/guardrails/no-direct-jsonl-write.mjs';
test('source tree contains no direct write calls targeting .beads/issues.jsonl', () => {
const violations = scanForDirectIssuesJsonlWrites('src');
assert.deepEqual(violations, []);
});

51
tests/lib/parser.test.ts Normal file
View file

@ -0,0 +1,51 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { parseIssuesJsonl } from '../../src/lib/parser';
test('parseIssuesJsonl applies defaults and preserves priority 0', () => {
const input = [
JSON.stringify({ id: 'bb-1', title: 'One', priority: 0 }),
JSON.stringify({ id: 'bb-2', title: 'Two' }),
].join('\n');
const result = parseIssuesJsonl(input);
assert.equal(result.length, 2);
assert.equal(result[0].priority, 0);
assert.equal(result[0].status, 'open');
assert.equal(result[0].issue_type, 'task');
assert.equal(result[1].priority, 2);
});
test('parseIssuesJsonl skips malformed and blank lines', () => {
const input = [' ', '{bad json', JSON.stringify({ id: 'bb-3', title: 'Three' })].join('\n');
const result = parseIssuesJsonl(input);
assert.equal(result.length, 1);
assert.equal(result[0].id, 'bb-3');
});
test('parseIssuesJsonl filters tombstones by default', () => {
const input = [
JSON.stringify({ id: 'bb-4', title: 'Live', status: 'open' }),
JSON.stringify({ id: 'bb-5', title: 'Gone', status: 'tombstone' }),
].join('\n');
const result = parseIssuesJsonl(input);
assert.equal(result.length, 1);
assert.equal(result[0].id, 'bb-4');
});
test('parseIssuesJsonl can include tombstones when requested', () => {
const input = [
JSON.stringify({ id: 'bb-4', title: 'Live', status: 'open' }),
JSON.stringify({ id: 'bb-5', title: 'Gone', status: 'tombstone' }),
].join('\n');
const result = parseIssuesJsonl(input, { includeTombstones: true });
assert.equal(result.length, 2);
});

30
tests/lib/pathing.test.ts Normal file
View file

@ -0,0 +1,30 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {
canonicalizeWindowsPath,
windowsPathKey,
toDisplayPath,
sameWindowsPath,
} from '../../src/lib/pathing';
test('canonicalizeWindowsPath normalizes separators and drive casing', () => {
const input = 'c:/Users/Zenchant/codex/beadboard/';
const result = canonicalizeWindowsPath(input);
assert.equal(result, 'C:\\Users\\Zenchant\\codex\\beadboard');
});
test('windowsPathKey is case-insensitive stable key', () => {
const a = windowsPathKey('C:/Users/Zenchant/codex/beadboard');
const b = windowsPathKey('c:\\users\\zenchant\\codex\\beadboard\\');
assert.equal(a, b);
});
test('toDisplayPath renders forward slashes for UI readability', () => {
const display = toDisplayPath('C:\\Users\\Zenchant\\codex\\beadboard');
assert.equal(display, 'C:/Users/Zenchant/codex/beadboard');
});
test('sameWindowsPath handles case/separator differences', () => {
assert.equal(sameWindowsPath('D:/Repos/One', 'd:\\repos\\one\\'), true);
});

View file

@ -0,0 +1,49 @@
import type {
BeadIssue,
BeadStatus,
BeadDependencyType,
BeadIssueType,
BeadDependency,
ParseableBeadIssue,
} from '../../src/lib/types';
const status: BeadStatus = 'open';
const depType: BeadDependencyType = 'blocks';
const issueType: BeadIssueType = 'task';
const dependency: BeadDependency = {
type: depType,
target: 'bb-123',
};
const issue: BeadIssue = {
id: 'bb-123',
title: 'Test issue',
status,
priority: 0,
issue_type: issueType,
description: 'schema contract',
assignee: 'agent',
owner: 'owner@example.com',
labels: ['test'],
dependencies: [dependency],
created_at: '2026-02-12T00:00:00Z',
updated_at: '2026-02-12T00:00:00Z',
closed_at: null,
close_reason: null,
closed_by_session: null,
created_by: 'zenchantlive',
due_at: null,
estimated_minutes: null,
external_ref: null,
metadata: {},
};
const parseable: ParseableBeadIssue = {
id: issue.id,
title: issue.title,
};
if (!parseable.id || !parseable.title) {
throw new Error('invalid parseable issue contract');
}