Files
2026-03-24 18:34:56 +08:00

187 lines
4.4 KiB
JavaScript

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;
}