66 lines
3.1 KiB
TypeScript
66 lines
3.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { chooseNextAutonomyWork, planParallelAutonomyWork } from "../src/services/autonomyScheduler.js";
|
|
import { parseRoadmapMarkdown } from "../src/repositories/autonomyRepository.js";
|
|
|
|
describe("smart autonomy scheduler", () => {
|
|
it("chooses the independent item that unlocks the most downstream work", () => {
|
|
const roadmap = parseRoadmapMarkdown("## Delivery\n- [ ] A\n- [ ] B\n- [ ] C\n- [ ] D");
|
|
const [a, b, c, d] = roadmap.phases[0].items;
|
|
c.dependsOn = [a.id];
|
|
d.dependsOn = [a.id];
|
|
b.priority = 80;
|
|
const selected = chooseNextAutonomyWork(roadmap.phases, 0);
|
|
expect(selected?.item.id).toBe(a.id);
|
|
expect(selected?.downstreamCount).toBe(2);
|
|
});
|
|
|
|
it("does not group likely path-conflicting work in parallel", () => {
|
|
const roadmap = parseRoadmapMarkdown("## Delivery\n- [ ] A\n- [ ] B\n- [ ] C");
|
|
const [a, b, c] = roadmap.phases[0].items;
|
|
a.touchedPaths = ["src/shared/project.ts"];
|
|
b.touchedPaths = ["src/shared/project.ts"];
|
|
c.touchedPaths = ["src/other.ts"];
|
|
const candidates = chooseNextAutonomyWork(roadmap.phases, 0);
|
|
expect(candidates?.item).toBeTruthy();
|
|
const all = roadmap.phases[0].items.map((item) => ({ item, phase: roadmap.phases[0], phaseIndex: 0, score: 1, downstreamCount: 0, conflictKeys: (item.touchedPaths ?? []).map((path) => `path:${path}`) }));
|
|
const groups = planParallelAutonomyWork(all);
|
|
expect(groups.some((group) => group.includes(a.id) && group.includes(b.id))).toBe(false);
|
|
});
|
|
|
|
it("can deliberately favor downstream impact when a command changes scheduling policy", () => {
|
|
const roadmap = parseRoadmapMarkdown("## Delivery\n- [ ] A\n- [ ] B\n- [ ] C\n- [ ] D");
|
|
const [a, b, c, d] = roadmap.phases[0].items;
|
|
c.dependsOn = [a.id];
|
|
d.dependsOn = [a.id];
|
|
b.priority = 100;
|
|
expect(chooseNextAutonomyWork(roadmap.phases, 0, "downstream-impact")?.item.id).toBe(a.id);
|
|
});
|
|
|
|
it("honors explicit manual queue order before automatic scoring", () => {
|
|
const roadmap = parseRoadmapMarkdown("## Delivery\n- [ ] High score\n- [ ] Owner first");
|
|
const [highScore, ownerFirst] = roadmap.phases[0].items;
|
|
highScore.priority = 100;
|
|
ownerFirst.priority = 0;
|
|
highScore.manualOrder = 1;
|
|
ownerFirst.manualOrder = 0;
|
|
|
|
expect(chooseNextAutonomyWork(roadmap.phases, 0)?.item.id).toBe(ownerFirst.id);
|
|
});
|
|
|
|
it("never lets manual ordering bypass an unmet dependency", () => {
|
|
const roadmap = parseRoadmapMarkdown("## Delivery\n- [ ] Prerequisite\n- [ ] Manually first");
|
|
const [prerequisite, manuallyFirst] = roadmap.phases[0].items;
|
|
manuallyFirst.dependsOn = [prerequisite.id];
|
|
manuallyFirst.manualOrder = 0;
|
|
prerequisite.manualOrder = 1;
|
|
|
|
expect(chooseNextAutonomyWork(roadmap.phases, 0)?.item.id).toBe(prerequisite.id);
|
|
});
|
|
|
|
it("does not claim work while its external dependency is unavailable", () => {
|
|
const roadmap = parseRoadmapMarkdown("## Delivery\n- [ ] A\n- [ ] B");
|
|
roadmap.phases[0].items[0].waitingExternal = true;
|
|
expect(chooseNextAutonomyWork(roadmap.phases, 0)?.item.id).toBe(roadmap.phases[0].items[1].id);
|
|
});
|
|
});
|