feat(harness): add orchestration plan helpers
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { repoRoot } from "./core.mjs";
|
||||
|
||||
const orchExecutableName = process.platform === "win32" ? "orch.exe" : "orch";
|
||||
|
||||
export const defaultDbPath = path.join(repoRoot, ".artifacts", "orch", "coord.db");
|
||||
export const defaultWorkspaceRoot = path.join(repoRoot, ".artifacts", "orch", "worktrees");
|
||||
export const defaultTaskBodyRoot = path.join(repoRoot, ".artifacts", "orch", "task-bodies");
|
||||
export const legacyOrchBinPath = path.join(
|
||||
os.homedir(),
|
||||
".codex",
|
||||
"skills",
|
||||
"orch",
|
||||
"assets",
|
||||
orchExecutableName
|
||||
);
|
||||
|
||||
function searchPathForExecutable(pathValue, existsSync = fs.existsSync) {
|
||||
if (!pathValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const entry of pathValue.split(path.delimiter)) {
|
||||
if (!entry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const candidate = path.join(entry, orchExecutableName);
|
||||
|
||||
if (existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resolveOrchBinary({ env = process.env, existsSync = fs.existsSync } = {}) {
|
||||
const checkedPaths = [];
|
||||
const explicitPath = env.CADENCE_UI_ORCH_BIN ? path.resolve(env.CADENCE_UI_ORCH_BIN) : null;
|
||||
|
||||
if (explicitPath) {
|
||||
checkedPaths.push(explicitPath);
|
||||
|
||||
if (existsSync(explicitPath)) {
|
||||
return {
|
||||
binaryPath: explicitPath,
|
||||
checkedPaths,
|
||||
source: "CADENCE_UI_ORCH_BIN"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const pathMatch = searchPathForExecutable(env.PATH ?? "", existsSync);
|
||||
|
||||
if (pathMatch) {
|
||||
checkedPaths.push(pathMatch);
|
||||
return {
|
||||
binaryPath: pathMatch,
|
||||
checkedPaths,
|
||||
source: "PATH"
|
||||
};
|
||||
}
|
||||
|
||||
checkedPaths.push(legacyOrchBinPath);
|
||||
|
||||
if (existsSync(legacyOrchBinPath)) {
|
||||
return {
|
||||
binaryPath: legacyOrchBinPath,
|
||||
checkedPaths,
|
||||
source: "legacy"
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
binaryPath: null,
|
||||
checkedPaths,
|
||||
source: null
|
||||
};
|
||||
}
|
||||
|
||||
function parseTaskToken(token) {
|
||||
const [taskIdPart, dependencyPart] = token.split("->").map((value) => value.trim());
|
||||
|
||||
return {
|
||||
dependsOn: dependencyPart
|
||||
? dependencyPart
|
||||
.split(",")
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean)
|
||||
: [],
|
||||
id: taskIdPart
|
||||
};
|
||||
}
|
||||
|
||||
export function parsePlanTaskSketch(markdown) {
|
||||
const lines = markdown.split(/\r?\n/);
|
||||
const planTitle =
|
||||
lines
|
||||
.find((line) => line.startsWith("# "))
|
||||
?.slice(2)
|
||||
.trim() ?? "Execution Plan";
|
||||
const tasks = [];
|
||||
let inTaskSketch = false;
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("## ")) {
|
||||
if (inTaskSketch) {
|
||||
break;
|
||||
}
|
||||
|
||||
inTaskSketch = line.trim() === "## Orchestration Task Sketch";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!inTaskSketch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const match = line.match(/^\s*-\s+(?:`([^`]+)`|([^:]+)):\s+(.+)$/);
|
||||
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const taskToken = (match[1] ?? match[2]).trim();
|
||||
const taskTitle = match[3].trim();
|
||||
const task = parseTaskToken(taskToken);
|
||||
|
||||
tasks.push({
|
||||
...task,
|
||||
title: taskTitle
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
planTitle,
|
||||
tasks
|
||||
};
|
||||
}
|
||||
|
||||
export function readPlanFile(planFile) {
|
||||
const absolutePath = path.isAbsolute(planFile) ? planFile : path.join(repoRoot, planFile);
|
||||
const markdown = fs.readFileSync(absolutePath, "utf8");
|
||||
const parsed = parsePlanTaskSketch(markdown);
|
||||
|
||||
return {
|
||||
...parsed,
|
||||
absolutePath,
|
||||
relativePath: path.relative(repoRoot, absolutePath)
|
||||
};
|
||||
}
|
||||
|
||||
export function renderTaskBody({ plan, task }) {
|
||||
return `# ${task.id} — ${task.title}
|
||||
|
||||
- Source plan: \`${plan.relativePath}\`
|
||||
- Plan title: ${plan.planTitle}
|
||||
- Task id: ${task.id}
|
||||
- Dependencies: ${task.dependsOn.length > 0 ? task.dependsOn.join(", ") : "none"}
|
||||
|
||||
## Execution Notes
|
||||
|
||||
- Read the source execution plan before editing.
|
||||
- Keep this task scoped to the work implied by the task title and dependencies.
|
||||
- Run the narrowest useful harness suites first, then report what broader validation remains.
|
||||
- Return any shared integration follow-up instead of editing unrelated surfaces opportunistically.
|
||||
`;
|
||||
}
|
||||
|
||||
function sanitizeSegment(value) {
|
||||
return value.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
||||
}
|
||||
|
||||
export function writeTaskBodyFile({ bodyRoot = defaultTaskBodyRoot, plan, runId, task }) {
|
||||
const runDir = path.join(bodyRoot, sanitizeSegment(runId));
|
||||
const outputPath = path.join(runDir, `${sanitizeSegment(task.id)}.md`);
|
||||
|
||||
fs.mkdirSync(runDir, { recursive: true });
|
||||
fs.writeFileSync(outputPath, `${renderTaskBody({ plan, task })}\n`, "utf8");
|
||||
|
||||
return outputPath;
|
||||
}
|
||||
Reference in New Issue
Block a user