Switch to double quotes and reformat codebase with Biome

This commit is contained in:
Mohamed Boudra
2026-03-21 20:22:15 +07:00
parent ab40786882
commit f6579dbeb4
805 changed files with 38132 additions and 40175 deletions
+60 -45
View File
@@ -4,10 +4,7 @@ import { spawnSync } from "node:child_process";
import { appendFileSync, existsSync, openSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
import { FileTaskStore } from "./task-store.js";
import {
computeExecutionOrder,
buildSortedChildrenMap,
} from "./execution-order.js";
import { computeExecutionOrder, buildSortedChildrenMap } from "./execution-order.js";
import { resolvePackageVersion } from "../server/package-version.js";
import type { AgentType, ModelName, Task } from "./types.js";
@@ -164,7 +161,7 @@ Acceptance Criteria vs Body Guidance:
"git status shows clean working tree" to each subtask's criteria.
This makes process requirements verifiable at each step, not just at the end.
`
`,
);
program
@@ -177,7 +174,12 @@ program
.option("--assignee <agent>", "Agent to assign (claude or codex)")
.option("--draft", "Create as draft (not actionable)")
.option("-p, --priority <n>", "Priority (lower number = higher priority)")
.option("-a, --accept <criterion>", "Acceptance criterion (repeatable)", (val: string, prev: string[]) => prev.concat(val), [] as string[])
.option(
"-a, --accept <criterion>",
"Acceptance criterion (repeatable)",
(val: string, prev: string[]) => prev.concat(val),
[] as string[],
)
.action(async (title, opts) => {
let body = opts.body ?? "";
if (body === "-") {
@@ -186,9 +188,7 @@ program
const task = await store.create(title, {
body,
deps: opts.deps
? opts.deps.split(",").map((s: string) => s.trim())
: [],
deps: opts.deps ? opts.deps.split(",").map((s: string) => s.trim()) : [],
parentId: opts.parent,
status: opts.draft ? "draft" : "open",
assignee: opts.assignee as AgentType | undefined,
@@ -207,9 +207,7 @@ program
.option("--roots", "Show only root tasks (no parent)")
.action(async (opts) => {
const tasks = await store.list();
let filtered = opts.status
? tasks.filter((t) => t.status === opts.status)
: tasks;
let filtered = opts.status ? tasks.filter((t) => t.status === opts.status) : tasks;
if (opts.roots) {
filtered = filtered.filter((t) => !t.parentId);
@@ -220,7 +218,9 @@ program
const assignee = t.assignee ? ` @${t.assignee}` : "";
const parent = t.parentId ? ` ^${t.parentId}` : "";
const priority = t.priority !== undefined ? ` !${t.priority}` : "";
process.stdout.write(`${t.id} [${t.status}] ${t.title}${priority}${assignee}${parent}${deps}\n`);
process.stdout.write(
`${t.id} [${t.status}] ${t.title}${priority}${assignee}${parent}${deps}\n`,
);
}
});
@@ -326,10 +326,7 @@ program
.option("--flat", "Show flat list instead of tree")
.option("-d, --depth <n>", "Limit tree depth (0 = root only)")
.action(async (scopeId: string | undefined, opts) => {
const { timeline, orderMap, blocked } = await computeExecutionOrder(
store,
scopeId
);
const { timeline, orderMap, blocked } = await computeExecutionOrder(store, scopeId);
const allTasks = await store.list();
const taskMap = new Map(allTasks.map((t) => [t.id, t]));
const maxDepth = opts.depth !== undefined ? parseInt(opts.depth, 10) : Infinity;
@@ -347,9 +344,7 @@ program
const num = String(idx + 1).padStart(3, " ");
const mark = t.status === "done" ? "✓" : " ";
const deps = formatDeps(t);
process.stdout.write(
`${mark}${num}. ${priority}${t.id} ${t.title}${assignee}${deps}\n`
);
process.stdout.write(`${mark}${num}. ${priority}${t.id} ${t.title}${assignee}${deps}\n`);
};
for (let i = 0; i < timeline.length; i++) {
@@ -370,30 +365,23 @@ program
const sortedChildrenMap = buildSortedChildrenMap(allTasks, orderMap);
const printTask = (
task: Task,
prefix: string,
connector: string,
depth: number
) => {
const printTask = (task: Task, prefix: string, connector: string, depth: number) => {
const assignee = task.assignee ? ` @${task.assignee}` : "";
const priority =
task.priority !== undefined ? ` !${task.priority}` : "";
const priority = task.priority !== undefined ? ` !${task.priority}` : "";
const mark = task.status === "done" ? "✓ " : " ";
const deps = formatDeps(task);
process.stdout.write(
`${mark}${prefix}${connector}${task.id} [${task.status}] ${task.title}${priority}${assignee}${deps}\n`
`${mark}${prefix}${connector}${task.id} [${task.status}] ${task.title}${priority}${assignee}${deps}\n`,
);
};
// Print root
const rootAssignee = root.assignee ? ` @${root.assignee}` : "";
const rootPriority =
root.priority !== undefined ? ` !${root.priority}` : "";
const rootPriority = root.priority !== undefined ? ` !${root.priority}` : "";
const rootMark = root.status === "done" ? "✓ " : " ";
const rootDeps = formatDeps(root);
process.stdout.write(
`${rootMark}${root.id} [${root.status}] ${root.title}${rootPriority}${rootAssignee}${rootDeps}\n`
`${rootMark}${root.id} [${root.status}] ${root.title}${rootPriority}${rootAssignee}${rootDeps}\n`,
);
// Recursively print children in execution order
@@ -450,7 +438,12 @@ program
.option("-p, --priority <n>", "Priority (lower number = higher priority)")
.option("-s, --status <status>", "Set status (draft, open, in_progress, done, failed)")
.option("--clear-acceptance", "Clear all acceptance criteria (combine with -a to replace)")
.option("-a, --accept <criterion>", "Add acceptance criterion (repeatable)", (val: string, prev: string[]) => prev.concat(val), [] as string[])
.option(
"-a, --accept <criterion>",
"Add acceptance criterion (repeatable)",
(val: string, prev: string[]) => prev.concat(val),
[] as string[],
)
.action(async (id, opts) => {
const task = await store.get(id);
if (!task) {
@@ -478,7 +471,9 @@ program
if (opts.status) {
if (!VALID_STATUSES.includes(opts.status)) {
process.stderr.write(`Invalid status: ${opts.status}. Must be one of: ${VALID_STATUSES.join(", ")}\n`);
process.stderr.write(
`Invalid status: ${opts.status}. Must be one of: ${VALID_STATUSES.join(", ")}\n`,
);
process.exit(1);
}
changes.status = opts.status as Task["status"];
@@ -547,7 +542,9 @@ program
for (const child of children) {
const assignee = child.assignee ? ` @${child.assignee}` : "";
const priority = child.priority !== undefined ? ` !${child.priority}` : "";
process.stdout.write(`${child.id} [${child.status}] ${child.title}${priority}${assignee}\n`);
process.stdout.write(
`${child.id} [${child.status}] ${child.title}${priority}${assignee}\n`,
);
}
});
@@ -644,7 +641,7 @@ function getAgentConfig(modelStr: string): AgentConfig {
function runAgentWithModel(
prompt: string,
modelStr: string,
logFile: string
logFile: string,
): { success: boolean; output: string } {
const config = getAgentConfig(modelStr);
let args: string[];
@@ -1070,7 +1067,11 @@ program
.description("Run agent loop on tasks with planner/worker/judge")
.option("--plan", "Enable planner agent")
.option("--planner <model>", "Planner model (default: gpt-5.2)", "gpt-5.2")
.option("--worker-model <model>", "Worker model (default: sonnet). For GPT models, append effort: gpt-5.2-high", "sonnet")
.option(
"--worker-model <model>",
"Worker model (default: sonnet). For GPT models, append effort: gpt-5.2-high",
"sonnet",
)
.option("--judge-model <model>", "Judge model (default: haiku)", "haiku")
.option("--max-iterations <n>", "Max worker/judge iterations per task (0 = no limit)", "0")
.option("-w, --watch", "Keep running and wait for new tasks")
@@ -1092,7 +1093,10 @@ program
process.stdout.write(`Log: ${logFile}\n`);
process.stdout.write("\n");
log(logFile, `Started with planner=${enablePlanner ? plannerModel : "disabled"} worker=${baseWorkerModel} judge=${judgeModel} maxIter=${maxIterations} scope=${scopeId || "all"}`);
log(
logFile,
`Started with planner=${enablePlanner ? plannerModel : "disabled"} worker=${baseWorkerModel} judge=${judgeModel} maxIter=${maxIterations} scope=${scopeId || "all"}`,
);
const runPlanner = async (task: Task, reason: string): Promise<boolean> => {
log(logFile, `[PLANNER] Running ${plannerModel} (${reason})...`);
@@ -1121,12 +1125,17 @@ program
// First check for in_progress tasks (resuming from crash)
const allTasks = await store.list();
let candidates = scopeId
? [await store.get(scopeId), ...(await store.getDescendants(scopeId))].filter(Boolean) as Task[]
? ([await store.get(scopeId), ...(await store.getDescendants(scopeId))].filter(
Boolean,
) as Task[])
: allTasks;
const inProgress = candidates.filter((t) => t.status === "in_progress");
if (inProgress.length > 0) {
log(logFile, `Found ${inProgress.length} in_progress task(s) from previous run, resuming...`);
log(
logFile,
`Found ${inProgress.length} in_progress task(s) from previous run, resuming...`,
);
for (const t of inProgress) {
await store.update(t.id, { status: "open" });
log(logFile, `Reset ${t.id} to open`);
@@ -1172,7 +1181,8 @@ program
let consecutiveNotDone = 0;
// Track steering notes on scope (not leaf task) since that's where user adds them
const scopeTaskForSteer = scopeId ? await store.get(scopeId) : null;
let lastSeenSteerCount = scopeTaskForSteer?.notes.filter(n => n.content.startsWith("STEER:")).length ?? 0;
let lastSeenSteerCount =
scopeTaskForSteer?.notes.filter((n) => n.content.startsWith("STEER:")).length ?? 0;
while ((maxIterations === 0 || iteration <= maxIterations) && !taskDone) {
const iterLabel = maxIterations === 0 ? `${iteration}` : `${iteration}/${maxIterations}`;
@@ -1182,12 +1192,14 @@ program
if (enablePlanner && scopeId) {
const freshScope = await store.get(scopeId);
if (freshScope) {
const currentSteerCount = freshScope.notes.filter(n => n.content.startsWith("STEER:")).length;
const currentSteerCount = freshScope.notes.filter((n) =>
n.content.startsWith("STEER:"),
).length;
if (currentSteerCount > lastSeenSteerCount) {
const newSteers = freshScope.notes
.filter(n => n.content.startsWith("STEER:"))
.filter((n) => n.content.startsWith("STEER:"))
.slice(lastSeenSteerCount)
.map(n => n.content.replace(/^STEER:\s*/, ""));
.map((n) => n.content.replace(/^STEER:\s*/, ""));
log(logFile, `[STEER] New steering note detected - triggering planner`);
lastSeenSteerCount = currentSteerCount;
const reason = `User steering: ${newSteers.join("; ")}`;
@@ -1236,7 +1248,10 @@ program
}
// Only replan after 5 consecutive NOT_DONEs - use scope for bird's eye view
if (enablePlanner && consecutiveNotDone >= 5 && scopeId) {
log(logFile, `[JUDGE] ${consecutiveNotDone} consecutive NOT_DONE on ${task.id} - triggering planner`);
log(
logFile,
`[JUDGE] ${consecutiveNotDone} consecutive NOT_DONE on ${task.id} - triggering planner`,
);
const scopeTask = await store.get(scopeId);
if (scopeTask) {
const reason = `task ${task.id} "${task.title}" got ${consecutiveNotDone} consecutive NOT_DONE`;
@@ -100,7 +100,7 @@ function makeTask(
deps?: string[];
priority?: number;
created?: string;
} = {}
} = {},
): Task {
return {
id,
@@ -202,17 +202,15 @@ describe("computeExecutionOrder", () => {
parentId: "epic",
priority: 0,
created: "2024-01-02",
})
);
store.addTask(
makeTask("p1-task1", { parentId: "phase1", created: "2024-01-03" })
}),
);
store.addTask(makeTask("p1-task1", { parentId: "phase1", created: "2024-01-03" }));
store.addTask(
makeTask("p1-task2", {
parentId: "phase1",
deps: ["p1-task1"],
created: "2024-01-04",
})
}),
);
// Phase 2 - depends on phase1
@@ -221,14 +219,10 @@ describe("computeExecutionOrder", () => {
parentId: "epic",
deps: ["phase1"],
created: "2024-01-05",
})
);
store.addTask(
makeTask("p2-task1", { parentId: "phase2", created: "2024-01-06" })
);
store.addTask(
makeTask("p2-task2", { parentId: "phase2", created: "2024-01-07" })
}),
);
store.addTask(makeTask("p2-task1", { parentId: "phase2", created: "2024-01-06" }));
store.addTask(makeTask("p2-task2", { parentId: "phase2", created: "2024-01-07" }));
// Cleanup - no priority, depends on phase2
store.addTask(
@@ -236,7 +230,7 @@ describe("computeExecutionOrder", () => {
parentId: "epic",
deps: ["phase2"],
created: "2024-01-08",
})
}),
);
const result = await computeExecutionOrder(store, "epic");
@@ -274,22 +268,20 @@ describe("computeExecutionOrder", () => {
*/
store.addTask(makeTask("parent", { created: "2024-01-01" }));
store.addTask(
makeTask("child-a", { parentId: "parent", created: "2024-01-02" })
);
store.addTask(makeTask("child-a", { parentId: "parent", created: "2024-01-02" }));
store.addTask(
makeTask("child-b", {
parentId: "parent",
priority: 0,
created: "2024-01-03",
})
}),
);
store.addTask(
makeTask("child-c", {
parentId: "parent",
priority: 1,
created: "2024-01-04",
})
}),
);
const result = await computeExecutionOrder(store, "parent");
@@ -300,12 +292,8 @@ describe("computeExecutionOrder", () => {
});
it("should place done tasks first in historical order", async () => {
store.addTask(
makeTask("done-later", { status: "done", created: "2024-01-03" })
);
store.addTask(
makeTask("done-first", { status: "done", created: "2024-01-01" })
);
store.addTask(makeTask("done-later", { status: "done", created: "2024-01-03" }));
store.addTask(makeTask("done-first", { status: "done", created: "2024-01-01" }));
store.addTask(makeTask("pending", { created: "2024-01-02" }));
const result = await computeExecutionOrder(store);
@@ -318,9 +306,7 @@ describe("computeExecutionOrder", () => {
it("should mark tasks with unresolvable deps as blocked", async () => {
// b depends on external-dep which doesn't exist in scope
store.addTask(makeTask("a", { created: "2024-01-01" }));
store.addTask(
makeTask("b", { deps: ["external-dep"], created: "2024-01-02" })
);
store.addTask(makeTask("b", { deps: ["external-dep"], created: "2024-01-02" }));
const result = await computeExecutionOrder(store);
+5 -11
View File
@@ -28,7 +28,7 @@ export interface ExecutionOrderResult {
*/
export async function computeExecutionOrder(
store: TaskStore,
scopeId?: string
scopeId?: string,
): Promise<ExecutionOrderResult> {
const allTasks = await store.list();
const taskMap = new Map(allTasks.map((t) => [t.id, t]));
@@ -57,13 +57,9 @@ export async function computeExecutionOrder(
// Simulate execution: track done status
// Include all done tasks (not just in scope) for dep resolution
const simDone = new Set(
allTasks.filter((t) => t.status === "done").map((t) => t.id)
);
const simDone = new Set(allTasks.filter((t) => t.status === "done").map((t) => t.id));
const remaining = new Set(
candidates
.filter((t) => t.status === "open" || t.status === "in_progress")
.map((t) => t.id)
candidates.filter((t) => t.status === "open" || t.status === "in_progress").map((t) => t.id),
);
const isReady = (taskId: string): boolean => {
@@ -72,9 +68,7 @@ export async function computeExecutionOrder(
// All deps done (deps can be outside scope)
const depsOk = task.deps.every((depId) => simDone.has(depId));
// All children done (only consider children in scope)
const children = (childrenMap.get(taskId) ?? []).filter((c) =>
candidateIds.has(c.id)
);
const children = (childrenMap.get(taskId) ?? []).filter((c) => candidateIds.has(c.id));
const childrenOk = children.every((c) => simDone.has(c.id));
return depsOk && childrenOk;
};
@@ -119,7 +113,7 @@ export async function computeExecutionOrder(
*/
export function buildSortedChildrenMap(
tasks: Task[],
orderMap: Map<string, number>
orderMap: Map<string, number>,
): Map<string, Task[]> {
const childrenMap = new Map<string, Task[]>();
+13 -32
View File
@@ -60,9 +60,7 @@ describe("FileTaskStore", () => {
body: "This is a **long** body\n\nWith multiple lines.",
});
expect(task.body).toBe(
"This is a **long** body\n\nWith multiple lines."
);
expect(task.body).toBe("This is a **long** body\n\nWith multiple lines.");
});
it("creates a task with parentId", async () => {
@@ -73,9 +71,9 @@ describe("FileTaskStore", () => {
});
it("throws when creating task with non-existent parent", async () => {
await expect(
store.create("Child", { parentId: "nonexistent" })
).rejects.toThrow("Parent task not found");
await expect(store.create("Child", { parentId: "nonexistent" })).rejects.toThrow(
"Parent task not found",
);
});
it("creates a task with assignee", async () => {
@@ -174,11 +172,7 @@ describe("FileTaskStore", () => {
const tasks = await store.list();
expect(tasks).toHaveLength(3);
expect(tasks.map((t) => t.title).sort()).toEqual([
"Task 1",
"Task 2",
"Task 3",
]);
expect(tasks.map((t) => t.title).sort()).toEqual(["Task 1", "Task 2", "Task 3"]);
});
});
@@ -227,9 +221,7 @@ describe("FileTaskStore", () => {
});
it("throws for non-existent task", async () => {
await expect(
store.update("nonexistent", { title: "New" })
).rejects.toThrow();
await expect(store.update("nonexistent", { title: "New" })).rejects.toThrow();
});
it("replaces acceptance criteria", async () => {
@@ -907,9 +899,7 @@ describe("FileTaskStore", () => {
const tree = await store.getDepTree(root.id);
expect(tree).toHaveLength(3);
expect(tree.map((t) => t.id).sort()).toEqual(
[shared.id, left.id, right.id].sort()
);
expect(tree.map((t) => t.id).sort()).toEqual([shared.id, left.id, right.id].sort());
});
it("handles circular deps gracefully", async () => {
@@ -1048,9 +1038,7 @@ describe("FileTaskStore", () => {
const children = await store.getChildren(parent.id);
expect(children).toHaveLength(2);
expect(children.map((c) => c.id).sort()).toEqual(
[child1.id, child2.id].sort()
);
expect(children.map((c) => c.id).sort()).toEqual([child1.id, child2.id].sort());
});
it("does not return grandchildren", async () => {
@@ -1100,10 +1088,7 @@ describe("FileTaskStore", () => {
const children = await store.getChildren(parent.id);
expect(children.map((c) => c.id)).toEqual([
withPriority.id,
noPriority.id,
]);
expect(children.map((c) => c.id)).toEqual([withPriority.id, noPriority.id]);
});
});
@@ -1131,16 +1116,14 @@ describe("FileTaskStore", () => {
it("throws for non-existent task", async () => {
const parent = await store.create("Parent");
await expect(store.setParent("nonexistent", parent.id)).rejects.toThrow(
"Task not found"
);
await expect(store.setParent("nonexistent", parent.id)).rejects.toThrow("Task not found");
});
it("throws for non-existent parent", async () => {
const task = await store.create("Task");
await expect(store.setParent(task.id, "nonexistent")).rejects.toThrow(
"Parent task not found"
"Parent task not found",
);
});
@@ -1148,7 +1131,7 @@ describe("FileTaskStore", () => {
const task = await store.create("Task");
await expect(store.setParent(task.id, task.id)).rejects.toThrow(
"Task cannot be its own parent"
"Task cannot be its own parent",
);
});
@@ -1156,9 +1139,7 @@ describe("FileTaskStore", () => {
const parent = await store.create("Parent");
const child = await store.create("Child", { parentId: parent.id });
await expect(store.setParent(parent.id, child.id)).rejects.toThrow(
"circular reference"
);
await expect(store.setParent(parent.id, child.id)).rejects.toThrow("circular reference");
});
});
});
+6 -21
View File
@@ -1,13 +1,7 @@
import { readdir, readFile, writeFile, mkdir, unlink } from "node:fs/promises";
import { join } from "node:path";
import { randomBytes } from "node:crypto";
import type {
Task,
TaskStore,
CreateTaskOptions,
TaskStatus,
AgentType,
} from "./types.js";
import type { Task, TaskStore, CreateTaskOptions, TaskStatus, AgentType } from "./types.js";
function generateId(): string {
return randomBytes(4).toString("hex");
@@ -106,7 +100,7 @@ function parseTask(content: string): Task {
const notesSection = fileBody.match(/## Notes\n([\s\S]*?)$/);
if (notesSection) {
const noteMatches = notesSection[1].matchAll(
/\*\*(\d{4}-\d{2}-\d{2}T[\d:.Z]+)\*\*\n\n([\s\S]*?)(?=\n\*\*\d{4}|$)/g
/\*\*(\d{4}-\d{2}-\d{2}T[\d:.Z]+)\*\*\n\n([\s\S]*?)(?=\n\*\*\d{4}|$)/g,
);
for (const match of noteMatches) {
notes.push({
@@ -118,13 +112,9 @@ function parseTask(content: string): Task {
// Parse acceptance criteria
const acceptanceCriteria: string[] = [];
const criteriaSection = fileBody.match(
/## Acceptance Criteria\n\n([\s\S]*?)(?=\n## Notes|$)/
);
const criteriaSection = fileBody.match(/## Acceptance Criteria\n\n([\s\S]*?)(?=\n## Notes|$)/);
if (criteriaSection) {
const criteriaMatches = criteriaSection[1].matchAll(
/- \[[ x]\] (.+)$/gm
);
const criteriaMatches = criteriaSection[1].matchAll(/- \[[ x]\] (.+)$/gm);
for (const match of criteriaMatches) {
acceptanceCriteria.push(match[1].trim());
}
@@ -267,9 +257,7 @@ export class FileTaskStore implements TaskStore {
async getChildren(id: string): Promise<Task[]> {
const allTasks = await this.list();
return allTasks
.filter((t) => t.parentId === id)
.sort(sortByPriorityThenCreated);
return allTasks.filter((t) => t.parentId === id).sort(sortByPriorityThenCreated);
}
async getDescendants(id: string): Promise<Task[]> {
@@ -397,10 +385,7 @@ export class FileTaskStore implements TaskStore {
return saved!;
}
async update(
id: string,
changes: Partial<Omit<Task, "id" | "created">>
): Promise<Task> {
async update(id: string, changes: Partial<Omit<Task, "id" | "created">>): Promise<Task> {
const task = await this.get(id);
if (!task) {
throw new Error(`Task not found: ${id}`);
+1 -5
View File
@@ -2,11 +2,7 @@ export type TaskStatus = "draft" | "open" | "in_progress" | "done" | "failed";
export type AgentType = "claude" | "codex";
export type ModelName =
| "haiku"
| "sonnet"
| "opus"
| `gpt-${string}`;
export type ModelName = "haiku" | "sonnet" | "opus" | `gpt-${string}`;
export interface Note {
timestamp: string; // ISO date