131 lines
4.2 KiB
TypeScript
131 lines
4.2 KiB
TypeScript
import { expect, test } from 'bun:test';
|
|
import { mkdtempSync, rmSync, existsSync, readFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { MockLanguageModelV4, simulateReadableStream } from 'ai/test';
|
|
import type { LanguageModelV4StreamPart } from '@ai-sdk/provider';
|
|
import { scaffoldMissingAuto, projectTracksAt } from '../src/scaffold';
|
|
import { usageOf } from './helpers';
|
|
|
|
const usage = usageOf(5, 3);
|
|
|
|
function textParts(body: string): LanguageModelV4StreamPart[] {
|
|
return [
|
|
{ type: 'text-start', id: '0' },
|
|
{ type: 'text-delta', id: '0', delta: body },
|
|
{ type: 'text-end', id: '0' },
|
|
{ type: 'finish', finishReason: { unified: 'stop', raw: 'stop' }, usage },
|
|
];
|
|
}
|
|
|
|
function stream(body: string) {
|
|
const parts = textParts(body);
|
|
return { stream: simulateReadableStream({ chunks: parts, chunkDelayInMs: null, initialDelayInMs: null }) };
|
|
}
|
|
|
|
function makeRepo(): { dir: string; cleanup: () => void } {
|
|
const dir = mkdtempSync(join(tmpdir(), 'shiro-scaffold-'));
|
|
const cleanup = () => rmSync(dir, { recursive: true, force: true });
|
|
return { dir, cleanup };
|
|
}
|
|
|
|
const ALL_FOUR = `===FILE TODO.md===
|
|
# TODO
|
|
|
|
## Now
|
|
- first
|
|
|
|
## Next
|
|
- second
|
|
|
|
===FILE ROADMAP.md===
|
|
# Roadmap
|
|
|
|
## Next
|
|
- plan
|
|
|
|
===FILE docs/README.md===
|
|
# Docs
|
|
|
|
Developer docs.
|
|
|
|
===FILE AGENTS.md===
|
|
# AGENTS
|
|
|
|
This project is a test.
|
|
`;
|
|
|
|
test('projectTracksAt is false for an empty repo and true when a tracker exists', async () => {
|
|
const { dir, cleanup } = makeRepo();
|
|
try {
|
|
expect(projectTracksAt(dir)).toBe(false);
|
|
await Bun.write(join(dir, 'TODO.md'), '# Todo\n');
|
|
expect(projectTracksAt(dir)).toBe(true);
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
});
|
|
|
|
test('scaffoldMissingAuto writes all four files when the repo has none', async () => {
|
|
const { dir, cleanup } = makeRepo();
|
|
try {
|
|
const model = new MockLanguageModelV4({ doStream: async () => stream(ALL_FOUR) });
|
|
const written = await scaffoldMissingAuto(dir, model);
|
|
// Expect 4 files written.
|
|
expect(written.length).toBe(4);
|
|
expect(existsSync(join(dir, 'TODO.md'))).toBe(true);
|
|
expect(existsSync(join(dir, 'ROADMAP.md'))).toBe(true);
|
|
expect(existsSync(join(dir, 'docs', 'README.md'))).toBe(true);
|
|
expect(existsSync(join(dir, 'AGENTS.md'))).toBe(true);
|
|
// Content came from the model, not the empty template.
|
|
expect(readFileSync(join(dir, 'TODO.md'), 'utf8')).toContain('- first');
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
});
|
|
|
|
test('scaffoldMissingAuto bails when a repo already tracks progress', async () => {
|
|
const { dir, cleanup } = makeRepo();
|
|
try {
|
|
await Bun.write(join(dir, 'TODO.md'), '# Todo\n- [ ] existing\n');
|
|
const model = new MockLanguageModelV4({ doStream: async () => stream('') });
|
|
const written = await scaffoldMissingAuto(dir, model);
|
|
expect(written).toEqual([]);
|
|
expect(existsSync(join(dir, 'ROADMAP.md'))).toBe(false);
|
|
expect(readFileSync(join(dir, 'TODO.md'), 'utf8')).toBe('# Todo\n- [ ] existing\n');
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
});
|
|
|
|
test('scaffoldMissingAuto degrades to empty templates when the model fails', async () => {
|
|
const { dir, cleanup } = makeRepo();
|
|
try {
|
|
const model = new MockLanguageModelV4({ doStream: async () => { throw new Error('model down'); } });
|
|
const written = await scaffoldMissingAuto(dir, model);
|
|
// The fallback writes the empty-tracker templates so the session never dies.
|
|
expect(written.length).toBeGreaterThanOrEqual(3);
|
|
expect(existsSync(join(dir, 'TODO.md'))).toBe(true);
|
|
expect(existsSync(join(dir, 'ROADMAP.md'))).toBe(true);
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
});
|
|
|
|
test('scaffoldMissingAuto bails entirely when any tracking file exists', async () => {
|
|
const { dir, cleanup } = makeRepo();
|
|
try {
|
|
await Bun.write(join(dir, 'TODO.md'), 'precious');
|
|
const model = new MockLanguageModelV4({
|
|
doStream: async () =>
|
|
stream('===FILE TODO.md===\n# TODO\noverwrite me\n===FILE ROADMAP.md===\n# Roadmap\n'),
|
|
});
|
|
const written = await scaffoldMissingAuto(dir, model);
|
|
// A repo with any tracker is left alone — nothing new is written.
|
|
expect(written).toEqual([]);
|
|
expect(readFileSync(join(dir, 'TODO.md'), 'utf8')).toBe('precious');
|
|
expect(existsSync(join(dir, 'ROADMAP.md'))).toBe(false);
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
}); |