chore: initialize beadboard baseline
This commit is contained in:
commit
292a72f861
30 changed files with 2983 additions and 0 deletions
30
tests/bootstrap.test.mjs
Normal file
30
tests/bootstrap.test.mjs
Normal 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');
|
||||
});
|
||||
9
tests/guards/no-direct-jsonl-write.test.mjs
Normal file
9
tests/guards/no-direct-jsonl-write.test.mjs
Normal 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
51
tests/lib/parser.test.ts
Normal 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
30
tests/lib/pathing.test.ts
Normal 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);
|
||||
});
|
||||
49
tests/types/beads-types-contract.ts
Normal file
49
tests/types/beads-types-contract.ts
Normal 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');
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue