- Renamed `forum/experiments` to `forum/poc-g1` to designate generation 1. - Created `forum/poc-g2` and a new `lab.ts` runner. - Non-destructively migrated `dag_engine_poc.ts`, `git_storage_poc.ts`, `merkle_diff_poc.ts`, and `frontmatter_poc.ts` to `poc-g2`. - Upgraded migrated PoCs to utilize actual production-ready tools (e.g. `std/yaml` parsing and isolated `Deno.Command` Git repos) per blueprint constraints. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
108 lines
3.0 KiB
TypeScript
108 lines
3.0 KiB
TypeScript
import { assertEquals } from "https://deno.land/std@0.224.0/testing/asserts.ts";
|
|
import { parse } from "https://deno.land/std@0.224.0/yaml/mod.ts";
|
|
|
|
/**
|
|
* Proof of Concept: Semantic Task DAG Engine
|
|
*
|
|
* This module demonstrates how we can parse a strict YAML Directed Acyclic Graph (DAG)
|
|
* of tasks to calculate the critical path and ensure agents are only ever handed
|
|
* explicitly unblocked tasks.
|
|
*/
|
|
|
|
export interface TaskNode {
|
|
id: string;
|
|
title: string;
|
|
blocked_by: string[];
|
|
status: "pending" | "in-progress" | "complete";
|
|
}
|
|
|
|
export class TaskDAG {
|
|
private nodes: Map<string, TaskNode> = new Map();
|
|
|
|
constructor(yamlContent: string) {
|
|
const rawNodes = parse(yamlContent) as TaskNode[];
|
|
for (const node of rawNodes) {
|
|
this.nodes.set(node.id, {
|
|
...node,
|
|
blocked_by: node.blocked_by || [],
|
|
status: node.status || "pending",
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a list of tasks that are fully unblocked and ready to be worked on.
|
|
*/
|
|
getUnblockedTasks(): TaskNode[] {
|
|
const unblocked: TaskNode[] = [];
|
|
for (const node of this.nodes.values()) {
|
|
if (node.status === "complete") continue;
|
|
|
|
const isBlocked = node.blocked_by.some(
|
|
(depId) => this.nodes.get(depId)?.status !== "complete",
|
|
);
|
|
|
|
if (!isBlocked) {
|
|
unblocked.push(node);
|
|
}
|
|
}
|
|
return unblocked;
|
|
}
|
|
|
|
markComplete(taskId: string) {
|
|
const node = this.nodes.get(taskId);
|
|
if (node) {
|
|
node.status = "complete";
|
|
}
|
|
}
|
|
}
|
|
|
|
// In a real scenario, tests would be separated. For this PoC, we will run the tests here.
|
|
if (import.meta.main) {
|
|
console.log("Running DAG Engine PoC tests...");
|
|
|
|
const yamlInput = `
|
|
- id: task_1
|
|
title: Setup Git Notes PoC
|
|
status: complete
|
|
- id: task_2
|
|
title: Setup DAG Engine PoC
|
|
blocked_by: [task_1]
|
|
status: pending
|
|
- id: task_3
|
|
title: Setup Local Intelligence PoC
|
|
blocked_by: [task_1, task_2]
|
|
status: pending
|
|
- id: task_4
|
|
title: Write Assessment Report
|
|
blocked_by: [task_1]
|
|
status: pending
|
|
`;
|
|
|
|
try {
|
|
const dag = new TaskDAG(yamlInput);
|
|
|
|
// Initially, task_2 and task_4 should be unblocked because task_1 is complete.
|
|
let unblocked = dag.getUnblockedTasks();
|
|
assertEquals(unblocked.length, 2);
|
|
assertEquals(unblocked[0].id, "task_2");
|
|
assertEquals(unblocked[1].id, "task_4");
|
|
|
|
// Mark task_2 as complete. Now task_3 should still be blocked because task_4 has no effect,
|
|
// wait, task_3 is blocked by task_1 and task_2. Since both will be complete, task_3 should unlock.
|
|
dag.markComplete("task_2");
|
|
unblocked = dag.getUnblockedTasks();
|
|
|
|
// Unblocked should now be task_3 and task_4
|
|
assertEquals(unblocked.length, 2);
|
|
assertEquals(unblocked.find((t) => t.id === "task_3")?.id, "task_3");
|
|
assertEquals(unblocked.find((t) => t.id === "task_4")?.id, "task_4");
|
|
|
|
console.log(
|
|
"✅ DAG Engine PoC successful: Correctly calculated unblocked tasks.",
|
|
);
|
|
} catch (err) {
|
|
console.error("❌ DAG Engine PoC failed:", err);
|
|
}
|
|
}
|