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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
// @vitest-environment node
|
||||||
|
|
||||||
|
import fs from "node:fs";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import {
|
||||||
|
legacyOrchBinPath,
|
||||||
|
parsePlanTaskSketch,
|
||||||
|
resolveOrchBinary,
|
||||||
|
writeTaskBodyFile
|
||||||
|
} from "./orch-support.mjs";
|
||||||
|
|
||||||
|
const tempDirs: string[] = [];
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
for (const directory of tempDirs.splice(0)) {
|
||||||
|
fs.rmSync(directory, { force: true, recursive: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function createTempDir(prefix: string) {
|
||||||
|
const directory = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||||
|
tempDirs.push(directory);
|
||||||
|
return directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("parsePlanTaskSketch", () => {
|
||||||
|
it("parses plan tasks and inline dependencies from the task sketch", () => {
|
||||||
|
const parsed = parsePlanTaskSketch(`# Example Plan
|
||||||
|
|
||||||
|
## Orchestration Task Sketch
|
||||||
|
|
||||||
|
- \`T1\`: stabilize smoke coverage
|
||||||
|
- \`T2 -> T1, T3\`: reconcile browser validation
|
||||||
|
|
||||||
|
## Status Log
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(parsed.planTitle).toBe("Example Plan");
|
||||||
|
expect(parsed.tasks).toEqual([
|
||||||
|
{
|
||||||
|
dependsOn: [],
|
||||||
|
id: "T1",
|
||||||
|
title: "stabilize smoke coverage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dependsOn: ["T1", "T3"],
|
||||||
|
id: "T2",
|
||||||
|
title: "reconcile browser validation"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("resolveOrchBinary", () => {
|
||||||
|
it("prefers CADENCE_UI_ORCH_BIN when it points to an existing binary", () => {
|
||||||
|
const tempDir = createTempDir("cadence-orch-explicit-");
|
||||||
|
const explicitBinary = path.join(tempDir, process.platform === "win32" ? "orch.exe" : "orch");
|
||||||
|
|
||||||
|
fs.writeFileSync(explicitBinary, "", "utf8");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveOrchBinary({
|
||||||
|
env: {
|
||||||
|
CADENCE_UI_ORCH_BIN: explicitBinary,
|
||||||
|
PATH: ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
binaryPath: explicitBinary,
|
||||||
|
source: "CADENCE_UI_ORCH_BIN"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to PATH before the legacy location", () => {
|
||||||
|
const tempDir = createTempDir("cadence-orch-path-");
|
||||||
|
const pathBinary = path.join(tempDir, process.platform === "win32" ? "orch.exe" : "orch");
|
||||||
|
|
||||||
|
fs.writeFileSync(pathBinary, "", "utf8");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveOrchBinary({
|
||||||
|
env: {
|
||||||
|
PATH: tempDir
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
binaryPath: pathBinary,
|
||||||
|
source: "PATH"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns the checked locations when no binary exists", () => {
|
||||||
|
expect(
|
||||||
|
resolveOrchBinary({
|
||||||
|
env: {
|
||||||
|
CADENCE_UI_ORCH_BIN: "/missing/orch",
|
||||||
|
PATH: ""
|
||||||
|
},
|
||||||
|
existsSync: () => false
|
||||||
|
})
|
||||||
|
).toEqual({
|
||||||
|
binaryPath: null,
|
||||||
|
checkedPaths: [path.resolve("/missing/orch"), legacyOrchBinPath],
|
||||||
|
source: null
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("writeTaskBodyFile", () => {
|
||||||
|
it("writes a generated task body under the run-specific artifact directory", () => {
|
||||||
|
const bodyRoot = createTempDir("cadence-orch-bodies-");
|
||||||
|
const outputPath = writeTaskBodyFile({
|
||||||
|
bodyRoot,
|
||||||
|
plan: {
|
||||||
|
planTitle: "Harness Hardening",
|
||||||
|
relativePath: "docs/exec-plans/2026-03-24-harness-control-plane-hardening.md"
|
||||||
|
},
|
||||||
|
runId: "cadence ui demo",
|
||||||
|
task: {
|
||||||
|
dependsOn: ["T0"],
|
||||||
|
id: "T1",
|
||||||
|
title: "stabilize changed-suite selection"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(outputPath).toContain(path.join("cadence-ui-demo", "T1.md"));
|
||||||
|
expect(fs.readFileSync(outputPath, "utf8")).toContain("# T1 — stabilize changed-suite selection");
|
||||||
|
expect(fs.readFileSync(outputPath, "utf8")).toContain("Dependencies: T0");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,23 +3,28 @@ import path from "node:path";
|
|||||||
import { execFileSync, spawnSync } from "node:child_process";
|
import { execFileSync, spawnSync } from "node:child_process";
|
||||||
|
|
||||||
import { repoRoot } from "./core.mjs";
|
import { repoRoot } from "./core.mjs";
|
||||||
|
import {
|
||||||
const orchBin =
|
defaultDbPath,
|
||||||
process.env.CADENCE_UI_ORCH_BIN ?? "/Users/xd/.codex/skills/orch/assets/orch";
|
defaultWorkspaceRoot,
|
||||||
const defaultDbPath = path.join(repoRoot, ".artifacts", "orch", "coord.db");
|
readPlanFile,
|
||||||
const defaultWorkspaceRoot = path.join(repoRoot, ".artifacts", "orch", "worktrees");
|
resolveOrchBinary,
|
||||||
|
writeTaskBodyFile
|
||||||
|
} from "./orch-support.mjs";
|
||||||
|
|
||||||
function printHelp() {
|
function printHelp() {
|
||||||
process.stdout.write(`Cadence UI orchestration wrapper
|
process.stdout.write(`Cadence UI orchestration wrapper
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
pnpm harness:orch -- <orch command> [flags]
|
pnpm harness:orch -- <orch command> [flags]
|
||||||
|
pnpm harness:orch -- doctor [--plan-file docs/exec-plans/foo.md]
|
||||||
|
pnpm harness:orch -- plan inspect --plan-file docs/exec-plans/foo.md
|
||||||
|
pnpm harness:orch -- plan body --run cadence_ui_demo --task T1 --plan-file docs/exec-plans/foo.md
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
pnpm harness:orch -- run init --run cadence_ui_demo --goal "Refine release UX" --summary "Break work into isolated tasks"
|
pnpm harness:orch -- run init --run cadence_ui_demo --goal "Refine release UX" --summary "Break work into isolated tasks"
|
||||||
pnpm harness:orch -- task add --run cadence_ui_demo --task T1 --title "Stabilize smoke tests" --summary "Fix Storybook smoke drift"
|
pnpm harness:orch -- task add --run cadence_ui_demo --task T1 --title "Stabilize smoke tests" --summary "Fix Storybook smoke drift"
|
||||||
pnpm harness:orch -- dispatch --run cadence_ui_demo --task T1 --to default-worker --body-file docs/exec-plans/task-t1.md
|
pnpm harness:orch -- dispatch --run cadence_ui_demo --task T1 --to default-worker --plan-file docs/exec-plans/task-plan.md
|
||||||
pnpm harness:orch -- status --run cadence_ui_demo
|
pnpm harness:orch -- doctor --plan-file docs/exec-plans/task-plan.md
|
||||||
|
|
||||||
Defaults applied by this wrapper:
|
Defaults applied by this wrapper:
|
||||||
--db ${path.relative(repoRoot, defaultDbPath)}
|
--db ${path.relative(repoRoot, defaultDbPath)}
|
||||||
@@ -27,13 +32,45 @@ Defaults applied by this wrapper:
|
|||||||
dispatch --workspace-root ${path.relative(repoRoot, defaultWorkspaceRoot)}
|
dispatch --workspace-root ${path.relative(repoRoot, defaultWorkspaceRoot)}
|
||||||
dispatch --strict-worktree
|
dispatch --strict-worktree
|
||||||
dispatch --base-ref <current branch>
|
dispatch --base-ref <current branch>
|
||||||
|
|
||||||
|
Plan-linked helpers:
|
||||||
|
doctor Resolve the orch binary, show defaults, and inspect an execution plan.
|
||||||
|
plan inspect Print orchestration tasks parsed from a plan's task sketch.
|
||||||
|
plan body Generate a task body file from a plan and task id.
|
||||||
|
dispatch --plan-file Generate --body-file automatically from the referenced execution plan.
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fail(message) {
|
||||||
|
process.stderr.write(`[harness:orch] ${message}\n`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
function hasFlag(args, flag) {
|
function hasFlag(args, flag) {
|
||||||
return args.includes(flag);
|
return args.includes(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFlagValue(args, flag) {
|
||||||
|
const index = args.indexOf(flag);
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return args[index + 1] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function consumeFlagValue(args, flag) {
|
||||||
|
const index = args.indexOf(flag);
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [value] = args.splice(index, 2).slice(1);
|
||||||
|
return value ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
function getCurrentBranch() {
|
function getCurrentBranch() {
|
||||||
try {
|
try {
|
||||||
return execFileSync("git", ["branch", "--show-current"], {
|
return execFileSync("git", ["branch", "--show-current"], {
|
||||||
@@ -45,6 +82,112 @@ function getCurrentBranch() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function printPlanTasks(plan) {
|
||||||
|
process.stdout.write(`[harness:orch] Plan: ${plan.relativePath}\n`);
|
||||||
|
process.stdout.write(`[harness:orch] Title: ${plan.planTitle}\n`);
|
||||||
|
|
||||||
|
if (plan.tasks.length === 0) {
|
||||||
|
process.stdout.write("[harness:orch] No orchestration task sketch entries found.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write("[harness:orch] Parsed tasks:\n");
|
||||||
|
|
||||||
|
for (const task of plan.tasks) {
|
||||||
|
const dependencySuffix = task.dependsOn.length > 0 ? ` (depends on ${task.dependsOn.join(", ")})` : "";
|
||||||
|
process.stdout.write(`- ${task.id}: ${task.title}${dependencySuffix}\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPlanTask(plan, taskId) {
|
||||||
|
return plan.tasks.find((task) => task.id === taskId) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeHandleDoctor(rawArgs) {
|
||||||
|
if (rawArgs[0] !== "doctor") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const planFile = getFlagValue(rawArgs, "--plan-file");
|
||||||
|
const resolution = resolveOrchBinary();
|
||||||
|
|
||||||
|
process.stdout.write("[harness:orch] Doctor\n");
|
||||||
|
process.stdout.write(`- repo root: ${repoRoot}\n`);
|
||||||
|
process.stdout.write(`- default db: ${path.relative(repoRoot, defaultDbPath)}\n`);
|
||||||
|
process.stdout.write(`- default workspace root: ${path.relative(repoRoot, defaultWorkspaceRoot)}\n`);
|
||||||
|
process.stdout.write(`- current branch: ${getCurrentBranch()}\n`);
|
||||||
|
process.stdout.write(
|
||||||
|
`- orch binary: ${resolution.binaryPath ? `${resolution.binaryPath} (${resolution.source})` : "not found"}\n`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resolution.binaryPath) {
|
||||||
|
process.stdout.write("- checked locations:\n");
|
||||||
|
|
||||||
|
for (const checkedPath of resolution.checkedPaths) {
|
||||||
|
process.stdout.write(` - ${checkedPath}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write(
|
||||||
|
"[harness:orch] Set CADENCE_UI_ORCH_BIN to an installed orch binary or add `orch` to PATH.\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (planFile) {
|
||||||
|
const plan = readPlanFile(planFile);
|
||||||
|
printPlanTasks(plan);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(resolution.binaryPath ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeHandlePlanCommand(rawArgs) {
|
||||||
|
if (rawArgs[0] !== "plan") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subcommand = rawArgs[1];
|
||||||
|
const planFile = getFlagValue(rawArgs, "--plan-file");
|
||||||
|
|
||||||
|
if (!planFile) {
|
||||||
|
fail("Expected --plan-file for plan commands.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const plan = readPlanFile(planFile);
|
||||||
|
|
||||||
|
if (subcommand === "inspect") {
|
||||||
|
printPlanTasks(plan);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subcommand === "body") {
|
||||||
|
const taskId = getFlagValue(rawArgs, "--task");
|
||||||
|
const runId = getFlagValue(rawArgs, "--run") ?? "adhoc";
|
||||||
|
|
||||||
|
if (!taskId) {
|
||||||
|
fail("Expected --task when generating a plan-backed task body.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const task = findPlanTask(plan, taskId);
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
fail(`Task ${taskId} was not found in ${plan.relativePath}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputPath = writeTaskBodyFile({
|
||||||
|
plan,
|
||||||
|
runId,
|
||||||
|
task
|
||||||
|
});
|
||||||
|
|
||||||
|
process.stdout.write(
|
||||||
|
`[harness:orch] Wrote ${path.relative(repoRoot, outputPath)} from ${plan.relativePath}\n`
|
||||||
|
);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fail(`Unknown plan subcommand: ${subcommand ?? "<missing>"}`);
|
||||||
|
}
|
||||||
|
|
||||||
const rawArgs = process.argv.slice(2).filter((value) => value !== "--");
|
const rawArgs = process.argv.slice(2).filter((value) => value !== "--");
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -57,6 +200,9 @@ if (
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maybeHandleDoctor(rawArgs);
|
||||||
|
maybeHandlePlanCommand(rawArgs);
|
||||||
|
|
||||||
fs.mkdirSync(path.dirname(defaultDbPath), { recursive: true });
|
fs.mkdirSync(path.dirname(defaultDbPath), { recursive: true });
|
||||||
fs.mkdirSync(defaultWorkspaceRoot, { recursive: true });
|
fs.mkdirSync(defaultWorkspaceRoot, { recursive: true });
|
||||||
|
|
||||||
@@ -69,6 +215,35 @@ if (!hasFlag(orchArgs, "--db")) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (command === "dispatch") {
|
if (command === "dispatch") {
|
||||||
|
const planFile = consumeFlagValue(orchArgs, "--plan-file");
|
||||||
|
|
||||||
|
if (planFile && !hasFlag(orchArgs, "--body-file")) {
|
||||||
|
const taskId = getFlagValue(orchArgs, "--task");
|
||||||
|
const runId = getFlagValue(orchArgs, "--run") ?? "adhoc";
|
||||||
|
|
||||||
|
if (!taskId) {
|
||||||
|
fail("Expected --task when using dispatch --plan-file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const plan = readPlanFile(planFile);
|
||||||
|
const task = findPlanTask(plan, taskId);
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
fail(`Task ${taskId} was not found in ${plan.relativePath}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputPath = writeTaskBodyFile({
|
||||||
|
plan,
|
||||||
|
runId,
|
||||||
|
task
|
||||||
|
});
|
||||||
|
|
||||||
|
process.stdout.write(
|
||||||
|
`[harness:orch] Generated ${path.relative(repoRoot, outputPath)} from ${plan.relativePath}\n`
|
||||||
|
);
|
||||||
|
orchArgs.push("--body-file", outputPath);
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasFlag(orchArgs, "--repo-path")) {
|
if (!hasFlag(orchArgs, "--repo-path")) {
|
||||||
orchArgs.push("--repo-path", repoRoot);
|
orchArgs.push("--repo-path", repoRoot);
|
||||||
}
|
}
|
||||||
@@ -86,7 +261,15 @@ if (command === "dispatch") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = spawnSync(orchBin, orchArgs, {
|
const resolution = resolveOrchBinary();
|
||||||
|
|
||||||
|
if (!resolution.binaryPath) {
|
||||||
|
fail(
|
||||||
|
`Unable to locate orch. Checked ${resolution.checkedPaths.join(", ")}. Set CADENCE_UI_ORCH_BIN or add orch to PATH.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = spawnSync(resolution.binaryPath, orchArgs, {
|
||||||
cwd: repoRoot,
|
cwd: repoRoot,
|
||||||
stdio: "inherit"
|
stdio: "inherit"
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user