Files
shiro-neko/test/todo.test.ts
T
Muhammad Zakir Ramadhan 5b8503fcd9 Initial commit: shiro-neko 0.1.0-beta.1
Agentic coding CLI on Bun, Ink, and the AI SDK.

Core: streamText loop with SDK-level tool approval so a denied call provably never executes; endpoint fallback for OpenAI reasoning models; retry with backoff.

Tools: read/write/edit/glob/grep/bash, path-jailed, gitignore-aware, ripgrep with a JS fallback, binary rejection, live bash streaming.

Agents: five variants crossing thinking level with tool restriction; plan and review withhold mutating tools from the model.

Extensibility: frontmatter skills with on-demand bodies, plugin host with blocking hooks, MCP stdio and HTTP, read-only subagents.

State: durable per-project memory, session task lists, session persistence, compaction that repairs provider-item dependencies.

Distribution: five-platform cross-compiled binaries with checksums, install scripts, CI on three operating systems.

404 tests, typecheck clean.
2026-09-02 17:30:18 +07:00

101 lines
3.6 KiB
TypeScript

import { expect, test } from 'bun:test';
import { Notebook, TODO_MARK } from '../src/notebook';
import type { ToolSet } from 'ai';
const call = (tools: ToolSet, todos: unknown) =>
Promise.resolve(
tools['todo_write']!.execute!({ todos } as never, { toolCallId: 'x', messages: [] } as never),
) as Promise<string>;
test('all four statuses have distinct markers', () => {
expect(new Set(Object.values(TODO_MARK)).size).toBe(4);
expect(TODO_MARK.blocked).toBe('[!]');
});
test('progress counts done, total, blocked, and the current task', async () => {
const nb = new Notebook();
await call(nb.tools(), [
{ content: 'a', status: 'done' },
{ content: 'b', status: 'in_progress' },
{ content: 'c', status: 'blocked', note: 'waiting on review' },
{ content: 'd', status: 'pending' },
]);
const p = nb.progress();
expect(p).toMatchObject({ done: 1, total: 4, blocked: 1 });
expect(p.current?.content).toBe('b');
});
test('a blocked task carries its note into the render', async () => {
const nb = new Notebook();
await call(nb.tools(), [{ content: 'deploy', status: 'blocked', note: 'no credentials' }]);
expect(nb.render()).toContain('[!] deploy (no credentials)');
expect(nb.render()).toContain('1 blocked');
});
test('blocked with no note is called out', async () => {
const nb = new Notebook();
const out = await call(nb.tools(), [{ content: 'deploy', status: 'blocked' }]);
expect(out).toContain('blocked with no note');
});
test('nothing in progress while work remains is called out', async () => {
const nb = new Notebook();
const out = await call(nb.tools(), [
{ content: 'a', status: 'done' },
{ content: 'b', status: 'pending' },
]);
expect(out).toContain('nothing is in_progress');
});
test('a fully done list draws no warning', async () => {
const nb = new Notebook();
const out = await call(nb.tools(), [
{ content: 'a', status: 'done' },
{ content: 'b', status: 'done' },
]);
expect(out).not.toContain('Warning');
expect(out).toContain('2/2 done');
});
test('an all-blocked list draws no in_progress warning', async () => {
const nb = new Notebook();
const out = await call(nb.tools(), [{ content: 'a', status: 'blocked', note: 'upstream is down' }]);
expect(out).not.toContain('nothing is in_progress');
});
test('two in_progress tasks are still flagged', async () => {
const nb = new Notebook();
const out = await call(nb.tools(), [
{ content: 'a', status: 'in_progress' },
{ content: 'b', status: 'in_progress' },
]);
expect(out).toContain('2 tasks are in_progress');
});
test('the header reports blocked count only when there is one', async () => {
const nb = new Notebook();
await call(nb.tools(), [{ content: 'a', status: 'in_progress' }]);
expect(nb.render()).not.toContain('blocked');
});
test('state is a copy, so a caller cannot mutate the notebook', async () => {
const nb = new Notebook();
await call(nb.tools(), [{ content: 'original', status: 'pending' }]);
const snapshot = nb.state();
snapshot.todos[0]!.content = 'tampered';
expect(nb.state().todos[0]?.content).toBe('original');
});
test('a restored blocked task keeps its note', () => {
const nb = new Notebook();
nb.restore({ todos: [{ content: 'x', status: 'blocked', note: 'kept' }] });
expect(nb.state().todos[0]?.note).toBe('kept');
});
test('an unknown status is filtered out on restore', () => {
const nb = new Notebook();
nb.restore({ todos: [{ content: 'ok', status: 'pending' }, { content: 'bad', status: 'sideways' }] as never });
expect(nb.state().todos).toEqual([{ content: 'ok', status: 'pending' }]);
});