@jterrats/open-orchestra 0.4.2-beta.2 → 0.5.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -5
- package/dist/advisory-artifacts.d.ts +6 -0
- package/dist/advisory-artifacts.js +37 -1
- package/dist/advisory-artifacts.js.map +1 -1
- package/dist/assets/web-console.js +297 -4
- package/dist/cli.js +13 -118
- package/dist/cli.js.map +1 -1
- package/dist/command-manifest.d.ts +3 -0
- package/dist/command-manifest.js +132 -42
- package/dist/command-manifest.js.map +1 -1
- package/dist/command-utils.d.ts +5 -0
- package/dist/command-utils.js +23 -0
- package/dist/command-utils.js.map +1 -1
- package/dist/commands.d.ts +6 -42
- package/dist/commands.js +204 -1372
- package/dist/commands.js.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/delivery-commands.d.ts +10 -0
- package/dist/delivery-commands.js +152 -0
- package/dist/delivery-commands.js.map +1 -0
- package/dist/github.d.ts +50 -1
- package/dist/github.js +234 -0
- package/dist/github.js.map +1 -1
- package/dist/health-checks.d.ts +1 -0
- package/dist/health-checks.js +11 -1
- package/dist/health-checks.js.map +1 -1
- package/dist/health-commands.js +2 -0
- package/dist/health-commands.js.map +1 -1
- package/dist/memory.d.ts +2 -1
- package/dist/memory.js +71 -10
- package/dist/memory.js.map +1 -1
- package/dist/package-update-check.d.ts +5 -1
- package/dist/package-update-check.js +20 -8
- package/dist/package-update-check.js.map +1 -1
- package/dist/planning-commands.d.ts +14 -0
- package/dist/planning-commands.js +372 -0
- package/dist/planning-commands.js.map +1 -0
- package/dist/release-candidate.d.ts +2 -0
- package/dist/release-candidate.js +9 -14
- package/dist/release-candidate.js.map +1 -1
- package/dist/release-commands.d.ts +2 -0
- package/dist/release-commands.js +58 -6
- package/dist/release-commands.js.map +1 -1
- package/dist/release-readiness.d.ts +49 -0
- package/dist/release-readiness.js +172 -0
- package/dist/release-readiness.js.map +1 -0
- package/dist/runtime-commands.js +2 -5
- package/dist/runtime-commands.js.map +1 -1
- package/dist/setup-agents-import.js +1 -3
- package/dist/setup-agents-import.js.map +1 -1
- package/dist/skills-catalog-service.d.ts +2 -0
- package/dist/skills-catalog-service.js +8 -0
- package/dist/skills-catalog-service.js.map +1 -0
- package/dist/skills-catalog.d.ts +2 -0
- package/dist/skills-catalog.js +389 -0
- package/dist/skills-catalog.js.map +1 -0
- package/dist/skills-commands.js +1 -11
- package/dist/skills-commands.js.map +1 -1
- package/dist/skills-events.d.ts +9 -0
- package/dist/skills-events.js +50 -0
- package/dist/skills-events.js.map +1 -0
- package/dist/skills-memory.d.ts +18 -0
- package/dist/skills-memory.js +127 -0
- package/dist/skills-memory.js.map +1 -0
- package/dist/skills-planning.d.ts +2 -0
- package/dist/skills-planning.js +87 -0
- package/dist/skills-planning.js.map +1 -0
- package/dist/skills-render.d.ts +14 -0
- package/dist/skills-render.js +83 -0
- package/dist/skills-render.js.map +1 -0
- package/dist/skills-validation.d.ts +2 -0
- package/dist/skills-validation.js +49 -0
- package/dist/skills-validation.js.map +1 -0
- package/dist/skills.d.ts +6 -42
- package/dist/skills.js +6 -773
- package/dist/skills.js.map +1 -1
- package/dist/task-graph-commands.d.ts +14 -0
- package/dist/task-graph-commands.js +367 -0
- package/dist/task-graph-commands.js.map +1 -0
- package/dist/types/context.d.ts +12 -0
- package/dist/types/context.js +2 -0
- package/dist/types/context.js.map +1 -0
- package/dist/types/metrics.d.ts +114 -0
- package/dist/types/metrics.js +2 -0
- package/dist/types/metrics.js.map +1 -0
- package/dist/types/model-config.d.ts +212 -0
- package/dist/types/model-config.js +2 -0
- package/dist/types/model-config.js.map +1 -0
- package/dist/types/runtime.d.ts +93 -0
- package/dist/types/runtime.js +2 -0
- package/dist/types/runtime.js.map +1 -0
- package/dist/types/skills.d.ts +147 -0
- package/dist/types/skills.js +2 -0
- package/dist/types/skills.js.map +1 -0
- package/dist/types/tasks.d.ts +171 -0
- package/dist/types/tasks.js +2 -0
- package/dist/types/tasks.js.map +1 -0
- package/dist/types/workflow-run.d.ts +79 -0
- package/dist/types/workflow-run.js +2 -0
- package/dist/types/workflow-run.js.map +1 -0
- package/dist/types.d.ts +12 -833
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/upgrade-commands.d.ts +2 -0
- package/dist/upgrade-commands.js +65 -0
- package/dist/upgrade-commands.js.map +1 -0
- package/dist/web-api-read-routes.d.ts +5 -0
- package/dist/web-api-read-routes.js +37 -0
- package/dist/web-api-read-routes.js.map +1 -0
- package/dist/web-api.d.ts +1 -3
- package/dist/web-api.js +23 -45
- package/dist/web-api.js.map +1 -1
- package/dist/web-console-sections.d.ts +2 -0
- package/dist/web-console-sections.js +7 -0
- package/dist/web-console-sections.js.map +1 -0
- package/dist/web-console.js +23 -3
- package/dist/web-console.js.map +1 -1
- package/dist/workflow-approval-service.d.ts +9 -0
- package/dist/workflow-approval-service.js +126 -0
- package/dist/workflow-approval-service.js.map +1 -0
- package/dist/workflow-approval-utils.d.ts +10 -0
- package/dist/workflow-approval-utils.js +82 -0
- package/dist/workflow-approval-utils.js.map +1 -0
- package/dist/workflow-budget-utils.d.ts +7 -0
- package/dist/workflow-budget-utils.js +96 -0
- package/dist/workflow-budget-utils.js.map +1 -0
- package/dist/workflow-evidence-service.d.ts +7 -0
- package/dist/workflow-evidence-service.js +100 -0
- package/dist/workflow-evidence-service.js.map +1 -0
- package/dist/workflow-run-commands.d.ts +8 -0
- package/dist/workflow-run-commands.js +479 -0
- package/dist/workflow-run-commands.js.map +1 -0
- package/dist/workflow-services.d.ts +5 -17
- package/dist/workflow-services.js +26 -479
- package/dist/workflow-services.js.map +1 -1
- package/dist/workflow-summary-service.d.ts +4 -0
- package/dist/workflow-summary-service.js +82 -0
- package/dist/workflow-summary-service.js.map +1 -0
- package/dist/workspace.d.ts +18 -1
- package/dist/workspace.js +66 -4
- package/dist/workspace.js.map +1 -1
- package/docs/orchestra-mvp.md +158 -114
- package/docs/package-naming.md +20 -0
- package/docs/persona-workflows.md +209 -0
- package/docs/runtime-adapters.md +15 -14
- package/docs/runtime-llm-flow.md +29 -28
- package/package.json +3 -2
package/dist/commands.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { initWorkspace, inspectInitWorkspace } from "./workspace.js";
|
|
2
4
|
import { requireArg } from "./args.js";
|
|
3
|
-
import {
|
|
4
|
-
import { queryMemory, recordMemoryEvent, renderMemoryPacket, } from "./memory.js";
|
|
5
|
-
import { AUTONOMOUS_PHASE_SEQUENCE, autonomousRunsPath, checkArchitectSizing, closePhase, cancelRun, createAutonomousRun, initPhase, listActiveAutonomousRuns, listAutonomousRuns, markRunDone, markRunFailed, readAutonomousRun, resumePhaseIndex, suspendPhaseForClarification, resumePhaseFromClarification, } from "./autonomous-workflow.js";
|
|
6
|
-
import { listClarifications, listClarificationsByTask, openClarification, answerClarification, resolveClarificationRole, } from "./clarification.js";
|
|
7
|
-
import { executePhaseWithLlm } from "./phase-executor.js";
|
|
8
|
-
import { addEvidence, addPlaywrightEvidence, addTask, archiveTask, approveWorkflowGate, checkReadiness, checkTaskDependencies, claimLock, createHandoff, deleteTask, evaluateWorkflowGate, executeNextReadyTask, executePlanWithBudgetPreflight, executeReadyTaskBatch, generateExecutionPlan, generatePlaywrightTestPlan, generatePullRequestSummary, generateTaskGraphPlan, getWorkflowStatus, getWorkflowSummary, getTaskContext, getWorkflowConfig, listEvidence, listDecisions, listLocks, listReviews, listRoles, listTasks, previewTaskGraphRun, recordReview, recordDecision, releaseLock, updateTask, validatePreRun, } from "./workflow-services.js";
|
|
5
|
+
import { addTask, generatePullRequestSummary, getWorkflowStatus, getWorkflowSummary, listRoles, validatePreRun, } from "./workflow-services.js";
|
|
9
6
|
import { validateWorkspace } from "./workspace-validator.js";
|
|
10
7
|
import { getWebServerAddress, startWebApiServer } from "./web-api.js";
|
|
11
|
-
import {
|
|
12
|
-
import { listCollaborationFlows, recommendCollaborationFlow, } from "./collaboration-flows.js";
|
|
13
|
-
import { listWorkflowTemplates, renderWorkflowTemplates, selectWorkflowTemplates, validateWorkflowTemplates, } from "./workflow-templates.js";
|
|
8
|
+
import { syncGitHubIssueToTask } from "./github.js";
|
|
14
9
|
import { parseRuntimeTarget } from "./runtime-adapters.js";
|
|
15
10
|
import { importSetupAgentsArtifacts } from "./setup-agents-import.js";
|
|
11
|
+
import { numberOption, parseCsv, removeUndefined, stringOption, targetRootOption, } from "./command-utils.js";
|
|
16
12
|
export { instructionsApplyCommand, instructionsBlockCommand, instructionsImportsCommand, instructionsStaleCommand, } from "./instruction-commands.js";
|
|
17
13
|
export { benchmarkCommand, estimateCommand } from "./metrics-commands.js";
|
|
18
14
|
export { burndownCommand, calibrationCommand, sprintCommand, velocityCommand, } from "./sprint-commands.js";
|
|
@@ -20,10 +16,17 @@ export { approvalsApproveCommand, approvalsListCommand, approvalsRejectCommand,
|
|
|
20
16
|
export { commandsManifestCommand, protocolBlockCommand, protocolRenderCommand, runtimeAdaptersCommand, runtimeBootstrapCommand, runtimeBriefCommand, runtimeDelegatePlanCommand, runtimeHandoffCommand, } from "./runtime-commands.js";
|
|
21
17
|
export { lessonsAddCommand, lessonsDeleteCommand, lessonsListCommand, lessonsPromoteCommand, skillsListCommand, skillsPlanCommand, skillsRenderCommand, skillsValidateCommand, sourcesListCommand, } from "./skills-commands.js";
|
|
22
18
|
export { diagramsLintCommand, mcpOAuthProxyEvaluateCommand, mcpOAuthProxyStartCommand, mcpOAuthProxyTokenCommand, } from "./tool-commands.js";
|
|
19
|
+
export { decisionAddCommand, decisionListCommand, evidenceCommand, evidenceListCommand, gateCommand, handoffCommand, readinessCommand, reviewCommand, reviewListCommand, } from "./delivery-commands.js";
|
|
20
|
+
export { graphPlanCommand, graphRunNextCommand, graphRunReadyCommand, lockClaimCommand, lockListCommand, lockReleaseCommand, taskAddCommand, taskArchiveCommand, taskDeleteCommand, taskDepsCommand, taskListCommand, taskShowCommand, taskUpdateCommand, } from "./task-graph-commands.js";
|
|
21
|
+
export { collaborationFlowsCommand, collaborationRecommendCommand, contextCommand, delegationDecideCommand, memoryHookCommand, memoryQueryCommand, planCommand, playwrightEvidenceCommand, playwrightPlanCommand, runCommand, workflowTemplateRenderCommand, workflowTemplateSelectCommand, workflowTemplatesCommand, } from "./planning-commands.js";
|
|
23
22
|
export { telemetryDisableCommand, telemetryEnableCommand, telemetryEvalDatasetCommand, telemetryExportCommand, telemetryStatusCommand, telemetrySubmitCommand, } from "./telemetry-commands.js";
|
|
24
|
-
|
|
23
|
+
export { workflowCancelCommand, workflowClarifyCommand, workflowClarifyListCommand, workflowClarifyRespondCommand, workflowGateApproveCommand, workflowRunCommand, workflowRunListCommand, } from "./workflow-run-commands.js";
|
|
25
24
|
export async function initCommand(options, io) {
|
|
26
|
-
|
|
25
|
+
if (options.help || options.h) {
|
|
26
|
+
io.log(renderInitHelp());
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const root = targetRootOption(options);
|
|
27
30
|
const input = {
|
|
28
31
|
root,
|
|
29
32
|
force: Boolean(options.force),
|
|
@@ -32,27 +35,45 @@ export async function initCommand(options, io) {
|
|
|
32
35
|
};
|
|
33
36
|
const bootstrapTargetFile = stringOption(options["bootstrap-file"]);
|
|
34
37
|
const runtimeTargets = parseRuntimeTargetOptions(options);
|
|
35
|
-
const
|
|
38
|
+
const preflight = await inspectInitWorkspace({
|
|
36
39
|
...input,
|
|
37
40
|
...(bootstrapTargetFile ? { bootstrapTargetFile } : {}),
|
|
38
41
|
...(runtimeTargets.length > 0 ? { runtimeTargets } : {}),
|
|
39
42
|
});
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
if (options.check || options["dry-run"]) {
|
|
44
|
+
if (options.json) {
|
|
45
|
+
io.log(JSON.stringify(preflight, null, 2));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
io.log(renderInitPreflight(preflight));
|
|
49
|
+
return;
|
|
43
50
|
}
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
const base = await initWorkspace({
|
|
52
|
+
...input,
|
|
53
|
+
...(bootstrapTargetFile ? { bootstrapTargetFile } : {}),
|
|
54
|
+
...(runtimeTargets.length > 0 ? { runtimeTargets } : {}),
|
|
55
|
+
});
|
|
56
|
+
const result = {
|
|
57
|
+
...preflight,
|
|
58
|
+
workflowRoot: base,
|
|
59
|
+
initialized: true,
|
|
60
|
+
nextSteps: initNextSteps(preflight.mode, root),
|
|
61
|
+
};
|
|
62
|
+
if (options.json) {
|
|
63
|
+
io.log(JSON.stringify(result, null, 2));
|
|
64
|
+
return;
|
|
46
65
|
}
|
|
47
|
-
io.log(
|
|
48
|
-
io.log("Next: add tasks to .agent-workflow/tasks.json and run orchestra status");
|
|
66
|
+
io.log(renderInitSuccess(result));
|
|
49
67
|
}
|
|
50
68
|
export async function statusCommand(options, io) {
|
|
51
|
-
const
|
|
69
|
+
const root = targetRootOption(options);
|
|
70
|
+
const status = await getWorkflowStatus(root);
|
|
52
71
|
if (options.json) {
|
|
53
|
-
io.log(JSON.stringify(status, null, 2));
|
|
72
|
+
io.log(JSON.stringify({ workspaceRoot: root, ...status }, null, 2));
|
|
54
73
|
return;
|
|
55
74
|
}
|
|
75
|
+
io.log(`Workspace: ${root}`);
|
|
76
|
+
io.log(`Mode: ${status.mode ?? "project"}`);
|
|
56
77
|
io.log(`Tasks: ${status.tasks.total}`);
|
|
57
78
|
for (const [name, count] of Object.entries(status.tasks.byStatus)) {
|
|
58
79
|
io.log(` ${name}: ${count}`);
|
|
@@ -65,6 +86,38 @@ export async function statusCommand(options, io) {
|
|
|
65
86
|
}
|
|
66
87
|
}
|
|
67
88
|
}
|
|
89
|
+
export async function advisoryConvertCommand(options, io) {
|
|
90
|
+
const root = targetRootOption(options);
|
|
91
|
+
const taskFile = stringOption(options.file) ??
|
|
92
|
+
path.join(root, ".agent-workflow", "advisory", "project-task.json");
|
|
93
|
+
const payload = JSON.parse(await readFile(taskFile, "utf8"));
|
|
94
|
+
const task = await addTask(removeUndefined({
|
|
95
|
+
id: stringOption(options.id) ?? requiredString(payload.id, "id"),
|
|
96
|
+
title: requiredString(payload.title, "title"),
|
|
97
|
+
ownerRole: requiredString(payload.ownerRole, "ownerRole"),
|
|
98
|
+
dependencies: [],
|
|
99
|
+
backlogItem: stringOption(options.backlog),
|
|
100
|
+
goal: optionalString(payload.goal) ??
|
|
101
|
+
"Convert advisory findings into a project delivery task",
|
|
102
|
+
scope: optionalString(payload.scope) ??
|
|
103
|
+
"Advisory findings, decisions, role guides, and conversion path",
|
|
104
|
+
acceptanceCriteria: requiredStringArray(payload.acceptanceCriteria, "acceptanceCriteria"),
|
|
105
|
+
assumptions: optionalStringArray(payload.assumptions) ?? [
|
|
106
|
+
"Target project repository has been confirmed before conversion.",
|
|
107
|
+
],
|
|
108
|
+
risks: optionalStringArray(payload.risks) ?? [
|
|
109
|
+
"Advisory findings may require project-owner validation.",
|
|
110
|
+
],
|
|
111
|
+
paths: optionalStringArray(payload.paths) ?? [".agent-workflow/advisory"],
|
|
112
|
+
testStrategy: optionalString(payload.testStrategy) ??
|
|
113
|
+
"Review advisory artifacts and attach project evidence.",
|
|
114
|
+
}), root);
|
|
115
|
+
if (options.json) {
|
|
116
|
+
io.log(JSON.stringify({ task, source: taskFile }, null, 2));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
io.log(`Converted advisory task ${task.id} from ${taskFile}`);
|
|
120
|
+
}
|
|
68
121
|
export async function validateCommand(options, io) {
|
|
69
122
|
if (options["pre-run"]) {
|
|
70
123
|
const report = await validatePreRun(requireArg(options, "task"), removeUndefined({
|
|
@@ -83,11 +136,13 @@ export async function validateCommand(options, io) {
|
|
|
83
136
|
}
|
|
84
137
|
return;
|
|
85
138
|
}
|
|
86
|
-
const
|
|
139
|
+
const root = targetRootOption(options);
|
|
140
|
+
const report = await validateWorkspace(root);
|
|
87
141
|
if (options.json) {
|
|
88
|
-
io.log(JSON.stringify(report, null, 2));
|
|
142
|
+
io.log(JSON.stringify({ workspaceRoot: root, ...report }, null, 2));
|
|
89
143
|
}
|
|
90
144
|
else {
|
|
145
|
+
io.log(`Workspace: ${root}`);
|
|
91
146
|
io.log(report.valid ? "Workspace valid" : "Workspace invalid");
|
|
92
147
|
for (const error of report.errors) {
|
|
93
148
|
io.log(`ERROR: ${error}`);
|
|
@@ -100,6 +155,112 @@ export async function validateCommand(options, io) {
|
|
|
100
155
|
throw new Error("workspace validation failed");
|
|
101
156
|
}
|
|
102
157
|
}
|
|
158
|
+
function renderInitHelp() {
|
|
159
|
+
return `orchestra init
|
|
160
|
+
|
|
161
|
+
Usage:
|
|
162
|
+
orchestra init [--force] [--advisory] [--confirm-unknown] [--target-dir <dir>] [--target <csv>] [--bootstrap-file <file>] [--check|--dry-run] [--json]
|
|
163
|
+
|
|
164
|
+
Examples:
|
|
165
|
+
orchestra init
|
|
166
|
+
orchestra init --confirm-unknown
|
|
167
|
+
orchestra init --target-dir ../my-project
|
|
168
|
+
orchestra init --target claude,cursor,windsurf
|
|
169
|
+
orchestra init --advisory
|
|
170
|
+
orchestra init --check
|
|
171
|
+
orchestra init --force
|
|
172
|
+
|
|
173
|
+
After init:
|
|
174
|
+
orchestra health --json
|
|
175
|
+
orchestra task add --id <id> --title <title> --owner <role>
|
|
176
|
+
orchestra status
|
|
177
|
+
orchestra web`;
|
|
178
|
+
}
|
|
179
|
+
function renderInitPreflight(report) {
|
|
180
|
+
const lines = [
|
|
181
|
+
`Workspace: ${report.root}`,
|
|
182
|
+
`Mode: ${report.mode}`,
|
|
183
|
+
`Workflow root: ${report.workflowRoot}`,
|
|
184
|
+
report.workflowExists
|
|
185
|
+
? "Workflow state already exists."
|
|
186
|
+
: "Workflow state will be created.",
|
|
187
|
+
];
|
|
188
|
+
if (report.existingArtifacts.length > 0) {
|
|
189
|
+
lines.push("Existing managed artifacts:");
|
|
190
|
+
for (const artifact of report.existingArtifacts) {
|
|
191
|
+
lines.push(` ${artifact}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
lines.push(report.force
|
|
195
|
+
? "Force: generated files will be refreshed."
|
|
196
|
+
: "Force: disabled; existing workflow state is preserved.");
|
|
197
|
+
lines.push("Generated artifacts:");
|
|
198
|
+
for (const artifact of report.generatedArtifacts) {
|
|
199
|
+
lines.push(` ${artifact}`);
|
|
200
|
+
}
|
|
201
|
+
return lines.join("\n");
|
|
202
|
+
}
|
|
203
|
+
function renderInitSuccess(result) {
|
|
204
|
+
const lines = [
|
|
205
|
+
`Initialized ${result.workflowRoot}`,
|
|
206
|
+
result.mode === "advisory"
|
|
207
|
+
? "Advisory mode enabled; root instruction files were not written unless explicitly targeted."
|
|
208
|
+
: "Prompt registry scaffolded in .generated-prompts/",
|
|
209
|
+
"Source catalog and agent lessons scaffolded in .agent-workflow/",
|
|
210
|
+
];
|
|
211
|
+
if (result.force) {
|
|
212
|
+
lines.push("Force mode refreshed generated workflow artifacts.");
|
|
213
|
+
}
|
|
214
|
+
lines.push("Generated artifacts:");
|
|
215
|
+
for (const artifact of result.generatedArtifacts) {
|
|
216
|
+
lines.push(` ${artifact}`);
|
|
217
|
+
}
|
|
218
|
+
lines.push("Next steps:");
|
|
219
|
+
for (const [index, step] of result.nextSteps.entries()) {
|
|
220
|
+
lines.push(` ${index + 1}. ${step}`);
|
|
221
|
+
}
|
|
222
|
+
return lines.join("\n");
|
|
223
|
+
}
|
|
224
|
+
function initNextSteps(mode, root) {
|
|
225
|
+
const targetSuffix = root === process.cwd() ? "" : ` --target-dir ${root}`;
|
|
226
|
+
if (mode === "advisory") {
|
|
227
|
+
return [
|
|
228
|
+
`Run orchestra health --json${targetSuffix}`,
|
|
229
|
+
"Review .agent-workflow/advisory/README.md",
|
|
230
|
+
"Convert .agent-workflow/advisory/project-task.json with orchestra advisory convert when the target repo is confirmed",
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
return [
|
|
234
|
+
`Run orchestra health --json${targetSuffix}`,
|
|
235
|
+
"Add a task with orchestra task add --id <id> --title <title> --owner <role>",
|
|
236
|
+
`Check workflow status with orchestra status${targetSuffix}`,
|
|
237
|
+
"Open the console with orchestra web",
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
function requiredString(value, name) {
|
|
241
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
242
|
+
throw new Error(`advisory project task missing ${name}`);
|
|
243
|
+
}
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
function optionalString(value) {
|
|
247
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
return value;
|
|
251
|
+
}
|
|
252
|
+
function requiredStringArray(value, name) {
|
|
253
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
254
|
+
throw new Error(`advisory project task missing ${name}`);
|
|
255
|
+
}
|
|
256
|
+
return value;
|
|
257
|
+
}
|
|
258
|
+
function optionalStringArray(value) {
|
|
259
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
103
264
|
export async function webCommand(options, io) {
|
|
104
265
|
const serverOptions = {};
|
|
105
266
|
const host = stringOption(options.host);
|
|
@@ -112,27 +273,6 @@ export async function webCommand(options, io) {
|
|
|
112
273
|
const server = await startWebApiServer(serverOptions);
|
|
113
274
|
io.log(`Open Orchestra web API listening at ${getWebServerAddress(server)}`);
|
|
114
275
|
}
|
|
115
|
-
export async function taskAddCommand(options, io) {
|
|
116
|
-
const input = removeUndefined({
|
|
117
|
-
id: requireArg(options, "id"),
|
|
118
|
-
title: requireArg(options, "title"),
|
|
119
|
-
ownerRole: requireArg(options, "owner"),
|
|
120
|
-
dependencies: parseCsv(options.depends),
|
|
121
|
-
backlogItem: stringOption(options.backlog),
|
|
122
|
-
goal: stringOption(options.goal),
|
|
123
|
-
scope: stringOption(options.scope),
|
|
124
|
-
acceptanceCriteria: parseCsv(options.acceptance),
|
|
125
|
-
assumptions: parseCsv(options.assumptions),
|
|
126
|
-
risks: parseCsv(options.risks),
|
|
127
|
-
paths: parseCsv(options.paths),
|
|
128
|
-
testStrategy: stringOption(options["test-strategy"]),
|
|
129
|
-
architectureApproval: parseArchitectureApproval(options),
|
|
130
|
-
qaGate: parseQaGate(options),
|
|
131
|
-
riskGate: parseRiskGate(options),
|
|
132
|
-
});
|
|
133
|
-
const task = await addTask(input);
|
|
134
|
-
io.log(`Added task ${task.id}`);
|
|
135
|
-
}
|
|
136
276
|
export async function setupAgentsImportCommand(options, io) {
|
|
137
277
|
const report = await importSetupAgentsArtifacts(removeUndefined({
|
|
138
278
|
source: stringOption(options.source),
|
|
@@ -148,85 +288,6 @@ export async function setupAgentsImportCommand(options, io) {
|
|
|
148
288
|
const handoffCount = report.handoffs.imported.reduce((total, item) => total + item.ids.length, 0);
|
|
149
289
|
io.log(`References: ${evidenceCount} evidence, ${handoffCount} handoffs preserved`);
|
|
150
290
|
}
|
|
151
|
-
export async function taskListCommand(options, io) {
|
|
152
|
-
if (options.help || options.h) {
|
|
153
|
-
io.log("task list [--json] [--status <csv>] [--owner <role>] [--filter <text>] [--top-level] [--include-archived]");
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
const tasks = filterTasks(await listTasks(), options);
|
|
157
|
-
if (options.json) {
|
|
158
|
-
io.log(JSON.stringify(tasks, null, 2));
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
if (tasks.length === 0) {
|
|
162
|
-
io.log("No tasks");
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
for (const task of tasks) {
|
|
166
|
-
io.log(`${task.id} [${task.status}] ${task.title} (${task.ownerRole})`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
function filterTasks(tasks, options) {
|
|
170
|
-
const statuses = parseTaskStatusFilter(options.status);
|
|
171
|
-
const owner = stringOption(options.owner);
|
|
172
|
-
const textFilter = stringOption(options.filter)?.toLowerCase();
|
|
173
|
-
return tasks.filter((task) => matchesStatus(task, statuses) &&
|
|
174
|
-
(options["include-archived"] || task.status !== "archived") &&
|
|
175
|
-
(!owner || task.ownerRole === owner) &&
|
|
176
|
-
(!textFilter || taskSearchText(task).includes(textFilter)) &&
|
|
177
|
-
(!options["top-level"] || isTopLevelTask(task)));
|
|
178
|
-
}
|
|
179
|
-
function parseTaskStatusFilter(value) {
|
|
180
|
-
const statuses = parseCsv(value);
|
|
181
|
-
const invalid = statuses.filter((status) => !TASK_STATUSES.includes(status));
|
|
182
|
-
if (invalid.length > 0) {
|
|
183
|
-
throw new Error(`unknown task status: ${invalid.join(", ")}. Valid statuses: ${TASK_STATUSES.join(", ")}`);
|
|
184
|
-
}
|
|
185
|
-
return new Set(statuses);
|
|
186
|
-
}
|
|
187
|
-
function matchesStatus(task, statuses) {
|
|
188
|
-
return statuses.size === 0 || statuses.has(task.status);
|
|
189
|
-
}
|
|
190
|
-
function taskSearchText(task) {
|
|
191
|
-
return `${task.id} ${task.title}`.toLowerCase();
|
|
192
|
-
}
|
|
193
|
-
function isTopLevelTask(task) {
|
|
194
|
-
return !["pm", "po", "architect", "developer", "qa", "release"].some((phase) => task.id.includes(`-${phase}-`));
|
|
195
|
-
}
|
|
196
|
-
export async function taskShowCommand(options, io) {
|
|
197
|
-
const id = requireArg(options, "id");
|
|
198
|
-
const task = (await listTasks()).find((candidate) => candidate.id === id);
|
|
199
|
-
if (!task) {
|
|
200
|
-
throw new Error(`unknown task: ${id}`);
|
|
201
|
-
}
|
|
202
|
-
if (options.json) {
|
|
203
|
-
io.log(JSON.stringify(task, null, 2));
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
for (const line of renderTaskDetails(task)) {
|
|
207
|
-
io.log(line);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
export async function taskDeleteCommand(options, io) {
|
|
211
|
-
const task = await deleteTask(requireArg(options, "id"), {
|
|
212
|
-
force: Boolean(options.force),
|
|
213
|
-
});
|
|
214
|
-
if (options.json) {
|
|
215
|
-
io.log(JSON.stringify(task, null, 2));
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
io.log(`Deleted task ${task.id}`);
|
|
219
|
-
}
|
|
220
|
-
export async function taskArchiveCommand(options, io) {
|
|
221
|
-
const task = await archiveTask(requireArg(options, "id"), {
|
|
222
|
-
force: Boolean(options.force),
|
|
223
|
-
});
|
|
224
|
-
if (options.json) {
|
|
225
|
-
io.log(JSON.stringify(task, null, 2));
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
io.log(`Archived task ${task.id}`);
|
|
229
|
-
}
|
|
230
291
|
export async function rolesListCommand(options, io) {
|
|
231
292
|
const roles = await listRoles();
|
|
232
293
|
if (options.json) {
|
|
@@ -242,225 +303,6 @@ export async function rolesListCommand(options, io) {
|
|
|
242
303
|
io.log(`${role.id} - ${role.name} (gates: ${gates})`);
|
|
243
304
|
}
|
|
244
305
|
}
|
|
245
|
-
export async function taskUpdateCommand(options, io) {
|
|
246
|
-
const id = requireArg(options, "id");
|
|
247
|
-
await updateTask(removeUndefined({
|
|
248
|
-
id,
|
|
249
|
-
title: stringOption(options.title),
|
|
250
|
-
ownerRole: stringOption(options.owner),
|
|
251
|
-
goal: stringOption(options.goal),
|
|
252
|
-
scope: stringOption(options.scope),
|
|
253
|
-
paths: csvOption(options.paths),
|
|
254
|
-
acceptanceCriteria: singleValueArrayOption(options.acceptance),
|
|
255
|
-
assumptions: singleValueArrayOption(options.assumptions),
|
|
256
|
-
risks: singleValueArrayOption(options.risks),
|
|
257
|
-
testStrategy: stringOption(options["test-strategy"]),
|
|
258
|
-
status: stringOption(options.status),
|
|
259
|
-
blockedReason: stringOption(options["blocked-reason"]),
|
|
260
|
-
}));
|
|
261
|
-
io.log(`Updated task ${id}`);
|
|
262
|
-
}
|
|
263
|
-
export async function taskDepsCommand(options, io) {
|
|
264
|
-
const report = await checkTaskDependencies(requireArg(options, "id"));
|
|
265
|
-
if (options.json) {
|
|
266
|
-
io.log(JSON.stringify(report, null, 2));
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
io.log(renderDependencyReport(report));
|
|
270
|
-
}
|
|
271
|
-
export async function graphPlanCommand(options, io) {
|
|
272
|
-
const plan = await generateTaskGraphPlan();
|
|
273
|
-
if (options.json) {
|
|
274
|
-
io.log(JSON.stringify(plan, null, 2));
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
io.log(renderTaskGraphPlan(plan));
|
|
278
|
-
}
|
|
279
|
-
export async function graphRunNextCommand(options, io) {
|
|
280
|
-
if (options["dry-run"]) {
|
|
281
|
-
const preview = await previewTaskGraphRun("run-next");
|
|
282
|
-
if (options.json) {
|
|
283
|
-
io.log(JSON.stringify(preview, null, 2));
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
io.log(renderTaskGraphDryRunPreview(preview));
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
const run = await executeNextReadyTask();
|
|
290
|
-
if (options.json) {
|
|
291
|
-
io.log(JSON.stringify(run, null, 2));
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
io.log(renderExecutionRunMarkdown(run));
|
|
295
|
-
}
|
|
296
|
-
export async function graphRunReadyCommand(options, io) {
|
|
297
|
-
if (options["dry-run"]) {
|
|
298
|
-
const preview = await previewTaskGraphRun("run-ready");
|
|
299
|
-
if (options.json) {
|
|
300
|
-
io.log(JSON.stringify(preview, null, 2));
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
io.log(renderTaskGraphDryRunPreview(preview));
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
const batch = await executeReadyTaskBatch();
|
|
307
|
-
if (options.json) {
|
|
308
|
-
io.log(JSON.stringify(batch, null, 2));
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
io.log(renderTaskGraphBatchRun(batch));
|
|
312
|
-
if (batch.failure) {
|
|
313
|
-
throw new Error(`graph batch failed: ${batch.failure.taskId}`);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
export async function lockListCommand(options, io) {
|
|
317
|
-
const locks = await listLocks();
|
|
318
|
-
if (options.json) {
|
|
319
|
-
io.log(JSON.stringify(locks, null, 2));
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
if (locks.length === 0) {
|
|
323
|
-
io.log("No locks");
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
for (const lock of locks) {
|
|
327
|
-
io.log(`${lock.id} ${lock.path} (${lock.taskId}, ${lock.ownerRole})`);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
export async function lockClaimCommand(options, io) {
|
|
331
|
-
const lock = await claimLock(removeUndefined({
|
|
332
|
-
id: stringOption(options.id),
|
|
333
|
-
taskId: requireArg(options, "task"),
|
|
334
|
-
ownerRole: requireArg(options, "role"),
|
|
335
|
-
path: requireArg(options, "path"),
|
|
336
|
-
reason: requireArg(options, "reason"),
|
|
337
|
-
expiresAt: stringOption(options.expires),
|
|
338
|
-
}));
|
|
339
|
-
io.log(`Claimed lock ${lock.id}`);
|
|
340
|
-
}
|
|
341
|
-
export async function lockReleaseCommand(options, io) {
|
|
342
|
-
const id = requireArg(options, "id");
|
|
343
|
-
await releaseLock(id);
|
|
344
|
-
io.log(`Released lock ${id}`);
|
|
345
|
-
}
|
|
346
|
-
export async function readinessCommand(options, io) {
|
|
347
|
-
const taskId = requireArg(options, "task");
|
|
348
|
-
const { task, report } = await checkReadiness(taskId);
|
|
349
|
-
if (options.markdown) {
|
|
350
|
-
io.log(renderReadinessMarkdown(task, report));
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
io.log(JSON.stringify(report, null, 2));
|
|
354
|
-
}
|
|
355
|
-
export async function gateCommand(options, io) {
|
|
356
|
-
const gateId = requireArg(options, "gate");
|
|
357
|
-
const taskId = requireArg(options, "task");
|
|
358
|
-
const result = await evaluateWorkflowGate(gateId, taskId);
|
|
359
|
-
if (options.markdown) {
|
|
360
|
-
io.log(renderGateMarkdown(result));
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
io.log(JSON.stringify(result, null, 2));
|
|
364
|
-
}
|
|
365
|
-
export async function handoffCommand(options, io) {
|
|
366
|
-
const { artifact } = await createHandoff(removeUndefined({
|
|
367
|
-
task: requireArg(options, "task"),
|
|
368
|
-
from: requireArg(options, "from"),
|
|
369
|
-
to: requireArg(options, "to"),
|
|
370
|
-
changed: requireArg(options, "changed"),
|
|
371
|
-
behavior: requireArg(options, "behavior"),
|
|
372
|
-
tests: requireArg(options, "tests"),
|
|
373
|
-
commands: requireArg(options, "commands"),
|
|
374
|
-
status: stringOption(options.status),
|
|
375
|
-
gaps: stringOption(options.gaps),
|
|
376
|
-
risks: stringOption(options.risks),
|
|
377
|
-
playwright: stringOption(options.playwright),
|
|
378
|
-
updateOwner: options["update-owner"] ? true : undefined,
|
|
379
|
-
}));
|
|
380
|
-
io.log(`Created ${artifact}`);
|
|
381
|
-
}
|
|
382
|
-
export async function reviewCommand(options, io) {
|
|
383
|
-
const input = {
|
|
384
|
-
task: requireArg(options, "task"),
|
|
385
|
-
role: requireArg(options, "role"),
|
|
386
|
-
result: requireArg(options, "result"),
|
|
387
|
-
severity: stringOption(options.severity) ?? "info",
|
|
388
|
-
findings: requireArg(options, "findings"),
|
|
389
|
-
recommendation: requireArg(options, "recommendation"),
|
|
390
|
-
};
|
|
391
|
-
const { artifact } = await recordReview(input);
|
|
392
|
-
io.log(`Created ${artifact}`);
|
|
393
|
-
}
|
|
394
|
-
export async function decisionAddCommand(options, io) {
|
|
395
|
-
const decision = await recordDecision({
|
|
396
|
-
task: requireArg(options, "task"),
|
|
397
|
-
title: requireArg(options, "title"),
|
|
398
|
-
context: requireArg(options, "context"),
|
|
399
|
-
decision: requireArg(options, "decision"),
|
|
400
|
-
consequences: requireArg(options, "consequences"),
|
|
401
|
-
status: (stringOption(options.status) ?? "accepted"),
|
|
402
|
-
owner: requireArg(options, "owner"),
|
|
403
|
-
});
|
|
404
|
-
io.log(`Created ${decision.artifact}`);
|
|
405
|
-
}
|
|
406
|
-
export async function decisionListCommand(options, io) {
|
|
407
|
-
const decisions = await listDecisions(stringOption(options.task));
|
|
408
|
-
if (options.json) {
|
|
409
|
-
io.log(JSON.stringify(decisions, null, 2));
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
if (decisions.length === 0) {
|
|
413
|
-
io.log("No decisions");
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
for (const decision of decisions) {
|
|
417
|
-
io.log(`${decision.taskId ?? "no-task"} ${decision.actor}: ${decision.summary}`);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
export async function reviewListCommand(options, io) {
|
|
421
|
-
const reviews = await listReviews(stringOption(options.task));
|
|
422
|
-
if (options.json) {
|
|
423
|
-
io.log(JSON.stringify(reviews, null, 2));
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
if (reviews.length === 0) {
|
|
427
|
-
io.log("No reviews");
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
for (const review of reviews) {
|
|
431
|
-
const result = String(review.metadata.result ?? "unknown");
|
|
432
|
-
const severity = String(review.metadata.severity ?? "unknown");
|
|
433
|
-
io.log(`${review.taskId ?? "no-task"} ${review.actor} ${result} (${severity})`);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
export async function evidenceCommand(options, io) {
|
|
437
|
-
const input = removeUndefined({
|
|
438
|
-
task: requireArg(options, "task"),
|
|
439
|
-
role: requireArg(options, "role"),
|
|
440
|
-
type: requireArg(options, "type"),
|
|
441
|
-
summary: requireArg(options, "summary"),
|
|
442
|
-
path: options.path,
|
|
443
|
-
command: options.command,
|
|
444
|
-
exitCode: options["exit-code"],
|
|
445
|
-
});
|
|
446
|
-
const { artifact } = await addEvidence(input);
|
|
447
|
-
io.log(`Created ${artifact}`);
|
|
448
|
-
}
|
|
449
|
-
export async function evidenceListCommand(options, io) {
|
|
450
|
-
const evidence = await listEvidence(stringOption(options.task));
|
|
451
|
-
if (options.json) {
|
|
452
|
-
io.log(JSON.stringify(evidence, null, 2));
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
if (evidence.length === 0) {
|
|
456
|
-
io.log("No evidence");
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
for (const item of evidence) {
|
|
460
|
-
const type = String(item.metadata.type ?? "unknown");
|
|
461
|
-
io.log(`${item.taskId ?? "no-task"} ${item.actor} ${type}: ${item.summary}`);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
306
|
export async function summaryCommand(options, io) {
|
|
465
307
|
const summary = await getWorkflowSummary();
|
|
466
308
|
if (options.json) {
|
|
@@ -477,676 +319,30 @@ export async function prSummaryCommand(options, io) {
|
|
|
477
319
|
}
|
|
478
320
|
io.log(renderPullRequestSummaryMarkdown(summary));
|
|
479
321
|
}
|
|
480
|
-
export async function
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const context = await getTaskContext(requireArg(options, "task"), process.cwd(), {
|
|
492
|
-
tokenBudget: numberOption(options.budget, 3000),
|
|
493
|
-
});
|
|
494
|
-
if (options.json) {
|
|
495
|
-
io.log(JSON.stringify(context, null, 2));
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
io.log(renderTaskContextMarkdown(context));
|
|
499
|
-
}
|
|
500
|
-
export async function memoryQueryCommand(options, io) {
|
|
501
|
-
const hook = parseMemoryHook(stringOption(options.hook));
|
|
502
|
-
const taskId = stringOption(options.task);
|
|
503
|
-
const query = stringOption(options.query);
|
|
504
|
-
const packet = await queryMemory({
|
|
505
|
-
...(taskId ? { taskId } : {}),
|
|
506
|
-
hook,
|
|
507
|
-
...(query ? { query } : {}),
|
|
508
|
-
tokenBudget: numberOption(options.budget, 900),
|
|
509
|
-
});
|
|
510
|
-
await recordMemoryEvent(process.cwd(), packet, "MEMORY_QUERIED");
|
|
511
|
-
if (options.json) {
|
|
512
|
-
io.log(JSON.stringify(packet, null, 2));
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
io.log(renderMemoryPacket(packet));
|
|
516
|
-
}
|
|
517
|
-
export async function memoryHookCommand(options, io) {
|
|
518
|
-
const point = parseMemoryHook(requireArg(options, "point"));
|
|
519
|
-
const taskId = stringOption(options.task);
|
|
520
|
-
const query = stringOption(options.query);
|
|
521
|
-
const packet = await queryMemory({
|
|
522
|
-
...(taskId ? { taskId } : {}),
|
|
523
|
-
hook: point,
|
|
524
|
-
...(query ? { query } : {}),
|
|
525
|
-
tokenBudget: numberOption(options.budget, 900),
|
|
526
|
-
});
|
|
527
|
-
await recordMemoryEvent(process.cwd(), packet, "MEMORY_HOOK_RAN");
|
|
528
|
-
if (options.json) {
|
|
529
|
-
io.log(JSON.stringify(packet, null, 2));
|
|
530
|
-
return;
|
|
531
|
-
}
|
|
532
|
-
io.log(renderMemoryPacket(packet));
|
|
533
|
-
}
|
|
534
|
-
export async function planCommand(options, io) {
|
|
535
|
-
const plan = await generateExecutionPlan(requireArg(options, "task"));
|
|
536
|
-
if (options.json) {
|
|
537
|
-
io.log(JSON.stringify(plan, null, 2));
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
io.log(renderExecutionPlanMarkdown(plan));
|
|
541
|
-
}
|
|
542
|
-
export async function runCommand(options, io) {
|
|
543
|
-
const taskId = requireArg(options, "task");
|
|
544
|
-
const run = await executePlanWithBudgetPreflight(taskId, process.cwd(), parseBudgetEscalationDecision(options), removeUndefined({
|
|
545
|
-
estimatedCostUsd: stringOption(options["estimate-cost"])
|
|
546
|
-
? numberOption(options["estimate-cost"], 0)
|
|
547
|
-
: undefined,
|
|
548
|
-
override: options.yes ? true : undefined,
|
|
549
|
-
}));
|
|
550
|
-
if (options.json) {
|
|
551
|
-
io.log(JSON.stringify(run, null, 2));
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
io.log(renderExecutionRunMarkdown(run));
|
|
555
|
-
}
|
|
556
|
-
export async function playwrightPlanCommand(options, io) {
|
|
557
|
-
const plan = await generatePlaywrightTestPlan(requireArg(options, "task"));
|
|
558
|
-
if (options.json) {
|
|
559
|
-
io.log(JSON.stringify(plan, null, 2));
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
io.log(renderPlaywrightPlanMarkdown(plan));
|
|
563
|
-
}
|
|
564
|
-
export async function playwrightEvidenceCommand(options, io) {
|
|
565
|
-
const { artifact } = await addPlaywrightEvidence(removeUndefined({
|
|
566
|
-
task: requireArg(options, "task"),
|
|
567
|
-
kind: requireArg(options, "kind"),
|
|
568
|
-
path: requireArg(options, "path"),
|
|
569
|
-
summary: requireArg(options, "summary"),
|
|
570
|
-
runId: stringOption(options["run-id"]),
|
|
322
|
+
export async function githubSyncCommand(options, io) {
|
|
323
|
+
const result = await syncGitHubIssueToTask(removeUndefined({
|
|
324
|
+
issue: Number(requireArg(options, "issue")),
|
|
325
|
+
taskId: stringOption(options.task),
|
|
326
|
+
ownerRole: stringOption(options.owner),
|
|
327
|
+
status: stringOption(options.status),
|
|
328
|
+
comment: Boolean(options.comment),
|
|
329
|
+
close: Boolean(options.close),
|
|
330
|
+
acceptedRisk: stringOption(options["accepted-risk"]),
|
|
331
|
+
dryRun: Boolean(options["dry-run"]),
|
|
332
|
+
transport: stringOption(options.transport) === "mcp-skill" ? "mcp-skill" : "gh",
|
|
571
333
|
}));
|
|
572
|
-
io.log(`Created ${artifact}`);
|
|
573
|
-
}
|
|
574
|
-
export async function collaborationFlowsCommand(options, io) {
|
|
575
|
-
const flows = listCollaborationFlows();
|
|
576
|
-
if (options.json) {
|
|
577
|
-
io.log(JSON.stringify(flows, null, 2));
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
for (const flow of flows) {
|
|
581
|
-
io.log(flow.id + " - " + flow.name);
|
|
582
|
-
io.log(" roles: " + flow.roles.join(", "));
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
export async function collaborationRecommendCommand(options, io) {
|
|
586
|
-
const context = await getTaskContext(requireArg(options, "task"));
|
|
587
|
-
const recommendation = recommendCollaborationFlow(context.task, [
|
|
588
|
-
...context.decisions,
|
|
589
|
-
...context.handoffs,
|
|
590
|
-
...context.reviews,
|
|
591
|
-
...context.evidence,
|
|
592
|
-
...context.gates,
|
|
593
|
-
]);
|
|
594
|
-
if (options.json) {
|
|
595
|
-
io.log(JSON.stringify(recommendation, null, 2));
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
if (!recommendation) {
|
|
599
|
-
io.log("No collaboration flow recommended");
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
io.log(recommendation.flow.id + " - " + recommendation.flow.name);
|
|
603
|
-
io.log("Missing artifacts: " +
|
|
604
|
-
(recommendation.missingArtifacts.join(", ") || "none"));
|
|
605
|
-
io.log("Reviewers: " + (recommendation.optionalReviewers.join(", ") || "none"));
|
|
606
|
-
}
|
|
607
|
-
export async function workflowTemplatesCommand(options, io) {
|
|
608
|
-
const templates = listWorkflowTemplates();
|
|
609
|
-
const validationErrors = validateWorkflowTemplates();
|
|
610
|
-
if (options.json) {
|
|
611
|
-
io.log(JSON.stringify({ templates, validationErrors }, null, 2));
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
for (const template of templates) {
|
|
615
|
-
io.log(template.id + " - " + template.name);
|
|
616
|
-
io.log(" roles: " + template.roles.join(", "));
|
|
617
|
-
}
|
|
618
|
-
if (validationErrors.length > 0) {
|
|
619
|
-
io.log("Validation errors: " + validationErrors.join("; "));
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
export async function workflowTemplateSelectCommand(options, io) {
|
|
623
|
-
const context = await getTaskContext(requireArg(options, "task"));
|
|
624
|
-
const events = [
|
|
625
|
-
...context.decisions,
|
|
626
|
-
...context.handoffs,
|
|
627
|
-
...context.reviews,
|
|
628
|
-
...context.evidence,
|
|
629
|
-
...context.gates,
|
|
630
|
-
];
|
|
631
|
-
const selection = selectWorkflowTemplates(context.task, events);
|
|
632
|
-
if (options.json) {
|
|
633
|
-
io.log(JSON.stringify(selection, null, 2));
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
for (const item of selection) {
|
|
637
|
-
io.log(item.template.id + " (score " + item.score + ")");
|
|
638
|
-
io.log(" missing evidence: " + (item.missingEvidence.join(", ") || "none"));
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
export async function workflowTemplateRenderCommand(options, io) {
|
|
642
|
-
const context = await getTaskContext(requireArg(options, "task"), process.cwd(), {
|
|
643
|
-
tokenBudget: numberOption(options.budget, 2500),
|
|
644
|
-
});
|
|
645
|
-
const rendered = renderWorkflowTemplates({
|
|
646
|
-
task: context.task,
|
|
647
|
-
target: parseSkillRenderTarget(stringOption(options.target) ?? "generic"),
|
|
648
|
-
templates: context.workflowTemplates,
|
|
649
|
-
contextBudget: context.contextBudget,
|
|
650
|
-
});
|
|
651
|
-
if (options.json) {
|
|
652
|
-
io.log(JSON.stringify(rendered, null, 2));
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
io.log(rendered.content);
|
|
656
|
-
}
|
|
657
|
-
// --- Autonomous workflow commands ---
|
|
658
|
-
const GATE_MODES = ["none", "phase", "all"];
|
|
659
|
-
export async function workflowRunCommand(options, io) {
|
|
660
|
-
const cwd = process.cwd();
|
|
661
|
-
const taskId = requireArg(options, "task");
|
|
662
|
-
const gates = (stringOption(options.gates) ?? "phase");
|
|
663
|
-
const maxIterations = numberOption(options["max-iterations"], 5);
|
|
664
|
-
const timeoutMinutes = numberOption(options["timeout-minutes"], 0);
|
|
665
|
-
const file = autonomousRunsPath(cwd);
|
|
666
|
-
const phaseSelection = await resolveWorkflowPhaseSelection(cwd, options);
|
|
667
|
-
const config = await getWorkflowConfig(cwd);
|
|
668
|
-
const phaseTimeoutMinutes = config.workflow?.phaseTimeoutMinutes ?? 0;
|
|
669
|
-
if (!GATE_MODES.includes(gates)) {
|
|
670
|
-
throw new Error(`--gates must be one of: ${GATE_MODES.join(", ")}`);
|
|
671
|
-
}
|
|
672
|
-
if (timeoutMinutes < 0) {
|
|
673
|
-
throw new Error("--timeout-minutes must be 0 or greater");
|
|
674
|
-
}
|
|
675
|
-
if (phaseTimeoutMinutes < 0) {
|
|
676
|
-
throw new Error("workflow.phaseTimeoutMinutes must be 0 or greater");
|
|
677
|
-
}
|
|
678
|
-
if (options["dry-run"]) {
|
|
679
|
-
return workflowDryRun(options, io, taskId, gates, maxIterations, phaseSelection);
|
|
680
|
-
}
|
|
681
|
-
let run;
|
|
682
|
-
let startIndex;
|
|
683
|
-
if (options.resume) {
|
|
684
|
-
const existing = await readAutonomousRun(cwd, String(options.resume));
|
|
685
|
-
if (!existing)
|
|
686
|
-
throw new Error(`workflow run not found: ${String(options.resume)}`);
|
|
687
|
-
run = existing;
|
|
688
|
-
if (run.status === "canceled" || run.status === "failed") {
|
|
689
|
-
throw new Error(`workflow run ${run.id} is ${run.status} and cannot be resumed`);
|
|
690
|
-
}
|
|
691
|
-
const resumeSequence = phaseSequenceForRun(run, phaseSelection.sequence);
|
|
692
|
-
startIndex = resumePhaseIndex(run, resumeSequence);
|
|
693
|
-
if (startIndex === -1) {
|
|
694
|
-
io.log(`Workflow ${run.id} is already complete`);
|
|
695
|
-
if (options.json)
|
|
696
|
-
io.log(JSON.stringify({ run, file, cwd }, null, 2));
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
const pausedGate = run.phases.findLast((phase) => phase.status === "gate_paused");
|
|
700
|
-
if (pausedGate && !pausedGate.approvedBy) {
|
|
701
|
-
io.log(`WARN: gate ${pausedGate.gateId ?? gateIdForPausedPhase(pausedGate.phase, resumeSequence)} has no recorded approval; continuing for backward compatibility`);
|
|
702
|
-
}
|
|
703
|
-
io.log(`Resuming run ${run.id} from phase ${resumeSequence[startIndex]?.phase}`);
|
|
704
|
-
run = { ...run, phaseSequence: resumeSequence.map((phase) => phase.phase) };
|
|
705
|
-
}
|
|
706
|
-
else {
|
|
707
|
-
run = await createAutonomousRun(cwd, {
|
|
708
|
-
taskId,
|
|
709
|
-
gates,
|
|
710
|
-
maxIterations,
|
|
711
|
-
phaseSequence: phaseSelection.sequence.map((phase) => phase.phase),
|
|
712
|
-
skippedPhases: phaseSelection.skipped,
|
|
713
|
-
});
|
|
714
|
-
startIndex = 0;
|
|
715
|
-
io.log(`Started autonomous workflow ${run.id} for task ${taskId} [gates=${gates}]`);
|
|
716
|
-
}
|
|
717
|
-
run = await executePhases(cwd, run, startIndex, io, phaseSequenceForRun(run, phaseSelection.sequence), {
|
|
718
|
-
runTimeoutMinutes: timeoutMinutes,
|
|
719
|
-
phaseTimeoutMinutes,
|
|
720
|
-
});
|
|
721
|
-
const result = { run, file, cwd };
|
|
722
334
|
if (options.json) {
|
|
723
335
|
io.log(JSON.stringify(result, null, 2));
|
|
724
336
|
return;
|
|
725
337
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
io.log(
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
export async function workflowRunListCommand(options, io) {
|
|
737
|
-
const cwd = process.cwd();
|
|
738
|
-
const runs = options.active
|
|
739
|
-
? await listActiveAutonomousRuns(cwd)
|
|
740
|
-
: await listAutonomousRuns(cwd);
|
|
741
|
-
if (options.json) {
|
|
742
|
-
io.log(JSON.stringify(runs, null, 2));
|
|
743
|
-
return;
|
|
744
|
-
}
|
|
745
|
-
if (runs.length === 0) {
|
|
746
|
-
io.log("No workflow runs");
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
for (const run of runs) {
|
|
750
|
-
const phases = run.phases.map((p) => `${p.phase}:${p.status}`).join(" → ");
|
|
751
|
-
io.log(`${run.id} [${run.status}] task=${run.taskId} gates=${run.gates}`);
|
|
752
|
-
if (phases)
|
|
753
|
-
io.log(` ${phases}`);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
export async function workflowCancelCommand(options, io) {
|
|
757
|
-
const run = await cancelRun(process.cwd(), requireArg(options, "run"), stringOption(options.reason) ?? "Workflow run canceled");
|
|
758
|
-
if (options.json) {
|
|
759
|
-
io.log(JSON.stringify(run, null, 2));
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
io.log(`Workflow canceled [run=${run.id}]`);
|
|
763
|
-
}
|
|
764
|
-
export async function workflowGateApproveCommand(options, io) {
|
|
765
|
-
const approval = await approveWorkflowGate({
|
|
766
|
-
runId: requireArg(options, "run"),
|
|
767
|
-
gateId: requireArg(options, "gate"),
|
|
768
|
-
approver: requireArg(options, "approver"),
|
|
769
|
-
rationale: requireArg(options, "rationale"),
|
|
770
|
-
});
|
|
771
|
-
if (options.json) {
|
|
772
|
-
io.log(JSON.stringify(approval, null, 2));
|
|
773
|
-
return;
|
|
774
|
-
}
|
|
775
|
-
io.log(approval.alreadyApproved
|
|
776
|
-
? `Gate ${approval.gateId} already approved by ${approval.approver}`
|
|
777
|
-
: `Approved gate ${approval.gateId} for run ${approval.run.id}`);
|
|
778
|
-
}
|
|
779
|
-
export async function workflowClarifyCommand(options, io) {
|
|
780
|
-
const cwd = process.cwd();
|
|
781
|
-
const runId = requireArg(options, "run");
|
|
782
|
-
const from = await resolveClarificationRole(cwd, requireArg(options, "from"));
|
|
783
|
-
const to = await resolveClarificationRole(cwd, requireArg(options, "to"));
|
|
784
|
-
const question = requireArg(options, "question");
|
|
785
|
-
const run = await readAutonomousRun(cwd, runId);
|
|
786
|
-
if (!run)
|
|
787
|
-
throw new Error(`workflow run not found: ${runId}`);
|
|
788
|
-
// Find the active phase (running or already awaiting_clarification)
|
|
789
|
-
const activePhaseIdx = run.phases.findIndex((p) => p.role === from &&
|
|
790
|
-
(p.status === "running" || p.status === "awaiting_clarification"));
|
|
791
|
-
if (activePhaseIdx === -1) {
|
|
792
|
-
throw new Error(`no active phase found for role ${from} in run ${runId}`);
|
|
793
|
-
}
|
|
794
|
-
const activePhase = run.phases[activePhaseIdx];
|
|
795
|
-
// Open clarification record
|
|
796
|
-
const record = await openClarification(cwd, {
|
|
797
|
-
runId,
|
|
798
|
-
taskId: run.taskId,
|
|
799
|
-
fromRole: from,
|
|
800
|
-
to,
|
|
801
|
-
question,
|
|
802
|
-
});
|
|
803
|
-
// Suspend the phase if it's still running
|
|
804
|
-
if (activePhase.status === "running") {
|
|
805
|
-
const sequence = phaseSequenceForRun(run);
|
|
806
|
-
const phaseSeqIdx = sequence.findIndex((d) => d.phase === activePhase.phase);
|
|
807
|
-
await suspendPhaseForClarification(cwd, run, phaseSeqIdx, sequence);
|
|
808
|
-
}
|
|
809
|
-
if (options.json) {
|
|
810
|
-
io.log(JSON.stringify(record, null, 2));
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
io.log(`Clarification ${record.id} opened — waiting for ${record.toRole}`);
|
|
814
|
-
io.log(` Question: ${record.question}`);
|
|
815
|
-
io.log(` Respond with:`);
|
|
816
|
-
io.log(` orchestra workflow clarify-respond --run ${runId} --clarification ${record.id} --answer "<text>"`);
|
|
817
|
-
}
|
|
818
|
-
export async function workflowClarifyRespondCommand(options, io) {
|
|
819
|
-
const cwd = process.cwd();
|
|
820
|
-
const runId = requireArg(options, "run");
|
|
821
|
-
const clarificationId = requireArg(options, "clarification");
|
|
822
|
-
const answer = requireArg(options, "answer");
|
|
823
|
-
const run = await readAutonomousRun(cwd, runId);
|
|
824
|
-
if (!run)
|
|
825
|
-
throw new Error(`workflow run not found: ${runId}`);
|
|
826
|
-
const record = await answerClarification(cwd, {
|
|
827
|
-
id: clarificationId,
|
|
828
|
-
answer,
|
|
829
|
-
});
|
|
830
|
-
// Resume the suspended phase back to running
|
|
831
|
-
const sequence = phaseSequenceForRun(run);
|
|
832
|
-
const phaseSeqIdx = sequence.findIndex((d) => d.role === record.fromRole);
|
|
833
|
-
if (phaseSeqIdx !== -1) {
|
|
834
|
-
const phase = run.phases.find((p) => p.role === record.fromRole);
|
|
835
|
-
if (phase?.status === "awaiting_clarification") {
|
|
836
|
-
await resumePhaseFromClarification(cwd, run, phaseSeqIdx, sequence);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
if (options.json) {
|
|
840
|
-
io.log(JSON.stringify(record, null, 2));
|
|
841
|
-
return;
|
|
842
|
-
}
|
|
843
|
-
io.log(`Clarification ${record.id} answered`);
|
|
844
|
-
io.log(` Answer: ${record.answer}`);
|
|
845
|
-
io.log(` Resume with:`);
|
|
846
|
-
io.log(` orchestra workflow run --task ${run.taskId} --resume ${runId}`);
|
|
847
|
-
}
|
|
848
|
-
export async function workflowClarifyListCommand(options, io) {
|
|
849
|
-
const cwd = process.cwd();
|
|
850
|
-
const runId = stringOption(options.run);
|
|
851
|
-
const taskId = stringOption(options.task);
|
|
852
|
-
if (runId && taskId) {
|
|
853
|
-
throw new Error("workflow clarify-list accepts either --run or --task, not both");
|
|
854
|
-
}
|
|
855
|
-
if (!runId && !taskId) {
|
|
856
|
-
throw new Error("workflow clarify-list requires --run or --task");
|
|
857
|
-
}
|
|
858
|
-
const clarifications = runId
|
|
859
|
-
? await listClarifications(cwd, runId)
|
|
860
|
-
: await listClarificationsByTask(cwd, taskId);
|
|
861
|
-
if (options.json) {
|
|
862
|
-
io.log(JSON.stringify(clarifications, null, 2));
|
|
863
|
-
return;
|
|
864
|
-
}
|
|
865
|
-
if (clarifications.length === 0) {
|
|
866
|
-
io.log(runId
|
|
867
|
-
? `No clarifications for run ${runId}`
|
|
868
|
-
: `No clarifications for task ${taskId}`);
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
for (const c of clarifications) {
|
|
872
|
-
const status = c.status === "answered" ? "✓" : "⏳";
|
|
873
|
-
io.log(`${status} [${c.id}] ${c.fromRole} → ${c.toRole}`);
|
|
874
|
-
io.log(` Q: ${c.question}`);
|
|
875
|
-
if (c.answer)
|
|
876
|
-
io.log(` A: ${c.answer}`);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
const MEMORY_HOOK_POINTS = [
|
|
880
|
-
"before_plan",
|
|
881
|
-
"before_implementation",
|
|
882
|
-
"before_handoff",
|
|
883
|
-
"before_final",
|
|
884
|
-
"after_failure",
|
|
885
|
-
];
|
|
886
|
-
function parseMemoryHook(value) {
|
|
887
|
-
const hook = value ?? "before_implementation";
|
|
888
|
-
if (!MEMORY_HOOK_POINTS.includes(hook)) {
|
|
889
|
-
throw new Error(`memory hook must be one of: ${MEMORY_HOOK_POINTS.join(", ")}`);
|
|
890
|
-
}
|
|
891
|
-
return hook;
|
|
892
|
-
}
|
|
893
|
-
async function resolveWorkflowPhaseSelection(root, options) {
|
|
894
|
-
const config = await getWorkflowConfig(root);
|
|
895
|
-
const configuredIds = config.workflow?.phaseSequence;
|
|
896
|
-
const baseSequence = configuredIds
|
|
897
|
-
? phaseSequenceFromIds(configuredIds, "workflow.phaseSequence")
|
|
898
|
-
: AUTONOMOUS_PHASE_SEQUENCE;
|
|
899
|
-
const skipIds = parseCsv(options.skip);
|
|
900
|
-
validatePhaseIds(skipIds, "--skip");
|
|
901
|
-
const skipSet = new Set(skipIds);
|
|
902
|
-
const sequence = baseSequence.filter((phase) => !skipSet.has(phase.phase));
|
|
903
|
-
validateWorkflowPhaseSequence(sequence);
|
|
904
|
-
const sequenceIds = new Set(sequence.map((phase) => phase.phase));
|
|
905
|
-
const skipped = AUTONOMOUS_PHASE_SEQUENCE.filter((phase) => !sequenceIds.has(phase.phase));
|
|
906
|
-
return { sequence, skipped };
|
|
907
|
-
}
|
|
908
|
-
function phaseSequenceForRun(run, fallback = AUTONOMOUS_PHASE_SEQUENCE) {
|
|
909
|
-
return run.phaseSequence
|
|
910
|
-
? phaseSequenceFromIds(run.phaseSequence, "run.phaseSequence")
|
|
911
|
-
: fallback;
|
|
912
|
-
}
|
|
913
|
-
function phaseSequenceFromIds(ids, source) {
|
|
914
|
-
validatePhaseIds(ids, source);
|
|
915
|
-
const byId = new Map(AUTONOMOUS_PHASE_SEQUENCE.map((phase) => [phase.phase, phase]));
|
|
916
|
-
return ids.map((id) => byId.get(id));
|
|
917
|
-
}
|
|
918
|
-
function validatePhaseIds(ids, source) {
|
|
919
|
-
const valid = new Set(AUTONOMOUS_PHASE_SEQUENCE.map((phase) => phase.phase));
|
|
920
|
-
const unknown = ids.filter((id) => !valid.has(id));
|
|
921
|
-
if (unknown.length > 0) {
|
|
922
|
-
throw new Error(`${source} contains invalid phase(s): ${unknown.join(", ")}. Valid phases: ${[...valid].join(", ")}`);
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
function validateWorkflowPhaseSequence(sequence) {
|
|
926
|
-
if (sequence.length === 0) {
|
|
927
|
-
throw new Error("workflow phase sequence cannot be empty");
|
|
928
|
-
}
|
|
929
|
-
const ids = sequence.map((phase) => phase.phase);
|
|
930
|
-
const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
|
|
931
|
-
if (duplicates.length > 0) {
|
|
932
|
-
throw new Error(`workflow phase sequence contains duplicate phase(s): ${[...new Set(duplicates)].join(", ")}`);
|
|
933
|
-
}
|
|
934
|
-
const qaIndex = ids.indexOf("qa");
|
|
935
|
-
const developerIndex = ids.indexOf("developer");
|
|
936
|
-
if (qaIndex !== -1 && developerIndex === -1) {
|
|
937
|
-
throw new Error("workflow phase sequence with qa must include developer");
|
|
938
|
-
}
|
|
939
|
-
if (qaIndex !== -1 && developerIndex > qaIndex) {
|
|
940
|
-
throw new Error("workflow phase sequence must place developer before qa");
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
function phaseExceededTimeout(run, phaseId, timeoutMs) {
|
|
944
|
-
const phase = run.phases.find((candidate) => candidate.phase === phaseId &&
|
|
945
|
-
(candidate.status === "running" ||
|
|
946
|
-
candidate.status === "awaiting_clarification"));
|
|
947
|
-
if (!phase) {
|
|
948
|
-
return false;
|
|
949
|
-
}
|
|
950
|
-
return Date.now() - new Date(phase.startedAt).getTime() > timeoutMs;
|
|
951
|
-
}
|
|
952
|
-
async function recordWorkflowTimeout(root, run, input) {
|
|
953
|
-
await appendEvent(root, {
|
|
954
|
-
type: "WORKFLOW_TIMEOUT",
|
|
955
|
-
taskId: run.taskId,
|
|
956
|
-
actor: "parent",
|
|
957
|
-
summary: input.reason,
|
|
958
|
-
metadata: {
|
|
959
|
-
runId: run.id,
|
|
960
|
-
phase: input.phase,
|
|
961
|
-
timeoutMinutes: input.timeoutMinutes,
|
|
962
|
-
},
|
|
963
|
-
});
|
|
964
|
-
}
|
|
965
|
-
function gateIdForPausedPhase(phase, sequence = AUTONOMOUS_PHASE_SEQUENCE) {
|
|
966
|
-
const sequenceIndex = sequence.findIndex((candidate) => candidate.phase === phase);
|
|
967
|
-
const next = sequence[sequenceIndex + 1]?.phase ?? "end";
|
|
968
|
-
return `${phase}->${next}`;
|
|
969
|
-
}
|
|
970
|
-
async function executePhases(cwd, run, startIndex, io, sequence, timeoutOptions) {
|
|
971
|
-
let current = run;
|
|
972
|
-
let qaFailNotes;
|
|
973
|
-
const devPhaseIndex = sequence.findIndex((d) => d.phase === "developer");
|
|
974
|
-
const runDeadlineMs = timeoutOptions.runTimeoutMinutes > 0
|
|
975
|
-
? new Date(run.createdAt).getTime() +
|
|
976
|
-
timeoutOptions.runTimeoutMinutes * 60 * 1000
|
|
977
|
-
: undefined;
|
|
978
|
-
const phaseTimeoutMs = timeoutOptions.phaseTimeoutMinutes > 0
|
|
979
|
-
? timeoutOptions.phaseTimeoutMinutes * 60 * 1000
|
|
980
|
-
: undefined;
|
|
981
|
-
for (let i = startIndex; i < sequence.length; i++) {
|
|
982
|
-
const def = sequence[i];
|
|
983
|
-
// Init phase task (skip if already has a running/done/clarification record from resume)
|
|
984
|
-
const existing = current.phases.find((p) => p.phase === def.phase && p.status !== "qa_failed");
|
|
985
|
-
if (!existing || existing.status === "pending") {
|
|
986
|
-
const retryContext = def.phase === "developer" && qaFailNotes ? qaFailNotes : undefined;
|
|
987
|
-
const phaseRecord = await initPhase(cwd, current, i, retryContext, sequence);
|
|
988
|
-
current = { ...current, phases: [...current.phases, phaseRecord] };
|
|
989
|
-
if (retryContext) {
|
|
990
|
-
io.log(`↺ ${def.phase} (${def.role}) retry — QA findings: ${retryContext.slice(0, 80)}`);
|
|
991
|
-
}
|
|
992
|
-
else {
|
|
993
|
-
io.log(`→ ${def.phase} (${def.role}) task=${phaseRecord.taskId}`);
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
else if (existing.status === "awaiting_clarification") {
|
|
997
|
-
io.log(`↺ ${def.phase} (${def.role}) resuming after clarification`);
|
|
998
|
-
}
|
|
999
|
-
// QA loop guard
|
|
1000
|
-
if (def.phase === "qa" && current.qaIterations >= current.maxIterations) {
|
|
1001
|
-
io.log(`✗ QA failed after ${current.maxIterations} iterations — workflow blocked`);
|
|
1002
|
-
current = await markRunFailed(cwd, current, `Max QA iterations (${current.maxIterations}) reached`);
|
|
1003
|
-
return current;
|
|
1004
|
-
}
|
|
1005
|
-
const llmResult = await executePhaseWithLlm({
|
|
1006
|
-
root: cwd,
|
|
1007
|
-
run: current,
|
|
1008
|
-
phase: def,
|
|
1009
|
-
phaseIndex: i,
|
|
1010
|
-
}).catch(async (error) => {
|
|
1011
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1012
|
-
io.log(`✗ ${def.phase} provider execution failed: ${message}`);
|
|
1013
|
-
current = await markRunFailed(cwd, current, message, def.phase);
|
|
1014
|
-
return null;
|
|
1015
|
-
});
|
|
1016
|
-
if (!llmResult)
|
|
1017
|
-
return current;
|
|
1018
|
-
if (llmResult.mode === "llm") {
|
|
1019
|
-
io.log(` ✓ llm artifact (${llmResult.artifact})`);
|
|
1020
|
-
}
|
|
1021
|
-
if (phaseTimeoutMs !== undefined &&
|
|
1022
|
-
phaseExceededTimeout(current, def.phase, phaseTimeoutMs)) {
|
|
1023
|
-
io.log(`✗ ${def.phase} phase timeout`);
|
|
1024
|
-
await recordWorkflowTimeout(cwd, current, {
|
|
1025
|
-
phase: def.phase,
|
|
1026
|
-
reason: "phase timeout",
|
|
1027
|
-
timeoutMinutes: timeoutOptions.phaseTimeoutMinutes,
|
|
1028
|
-
});
|
|
1029
|
-
current = await markRunFailed(cwd, current, "phase timeout", def.phase);
|
|
1030
|
-
return current;
|
|
1031
|
-
}
|
|
1032
|
-
// Architect sizing gate — always enforced regardless of --gates mode.
|
|
1033
|
-
// In LLM mode, the architect phase records the sizing decision before this check.
|
|
1034
|
-
if (def.phase === "architect") {
|
|
1035
|
-
const sizing = await checkArchitectSizing(cwd, current.taskId);
|
|
1036
|
-
if (!sizing.found) {
|
|
1037
|
-
io.log(`⚠ Architect phase requires a sizing decision before handoff to developer.`);
|
|
1038
|
-
io.log(` Record one with:`);
|
|
1039
|
-
io.log(` orchestra decision add --task ${current.taskId} --owner architect --title "Story sizing" --decision "<xs|s|m|l|xl> [N points]" --context "..." --consequences "..." --status accepted`);
|
|
1040
|
-
io.log(` Then resume: orchestra workflow run --task ${current.taskId} --resume ${current.id}`);
|
|
1041
|
-
current = await markRunFailed(cwd, current, "Architect sizing decision required before developer handoff");
|
|
1042
|
-
return current;
|
|
1043
|
-
}
|
|
1044
|
-
io.log(` ✓ sizing=${sizing.sizing}${sizing.points !== undefined ? ` (${sizing.points} pts)` : ""}`);
|
|
1045
|
-
}
|
|
1046
|
-
const outcome = llmResult.outcome;
|
|
1047
|
-
const result = await closePhase(cwd, current, i, outcome, sequence);
|
|
1048
|
-
current = result.run;
|
|
1049
|
-
if (result.handoffArtifact) {
|
|
1050
|
-
io.log(` ✓ handoff → ${sequence[i + 1]?.role ?? "end"} (${result.handoffArtifact})`);
|
|
1051
|
-
}
|
|
1052
|
-
if (result.reviewArtifact) {
|
|
1053
|
-
const nextPhase = sequence[i + 1]?.phase;
|
|
1054
|
-
io.log(` ⏸ gate ${def.phase}→${nextPhase} — review: ${result.reviewArtifact}`);
|
|
1055
|
-
io.log(` Approve: orchestra workflow gate-approve --run ${current.id} --gate ${def.phase}->${nextPhase} --approver <name> --rationale "<text>"`);
|
|
1056
|
-
io.log(``);
|
|
1057
|
-
io.log(`╔══ GATE PAUSE: ${def.phase.toUpperCase()} → ${nextPhase?.toUpperCase()} ══════════════════════`);
|
|
1058
|
-
io.log(`║ Run: ${current.id}`);
|
|
1059
|
-
io.log(`║ Task: ${current.taskId}`);
|
|
1060
|
-
io.log(`║ Review: ${result.reviewArtifact}`);
|
|
1061
|
-
io.log(`║`);
|
|
1062
|
-
io.log(`║ To approve, run:`);
|
|
1063
|
-
io.log(`║ orchestra workflow gate-approve --run ${current.id} --gate ${def.phase}->${nextPhase} --approver <name> --rationale "<text>"`);
|
|
1064
|
-
io.log(`║ Then resume:`);
|
|
1065
|
-
io.log(`║ orchestra workflow run --task ${current.taskId} --resume ${current.id}`);
|
|
1066
|
-
io.log(`╚══════════════════════════════════════════════════════════════`);
|
|
1067
|
-
return current;
|
|
1068
|
-
}
|
|
1069
|
-
// QA failure — loop back to developer with findings as context
|
|
1070
|
-
const closedPhase = current.phases.find((p) => p.phase === def.phase && p.status === "qa_failed");
|
|
1071
|
-
if (closedPhase) {
|
|
1072
|
-
qaFailNotes = closedPhase.notes ?? "QA findings — see QA phase artifact";
|
|
1073
|
-
io.log(` ✗ qa failed (iteration ${current.qaIterations}/${current.maxIterations}) — routing back to developer`);
|
|
1074
|
-
i = devPhaseIndex - 1; // will be incremented to developer at top of loop
|
|
1075
|
-
continue;
|
|
1076
|
-
}
|
|
1077
|
-
// Release phase: auto-create PR if configured
|
|
1078
|
-
if (def.phase === "release") {
|
|
1079
|
-
const config = await getWorkflowConfig(cwd);
|
|
1080
|
-
if (config.github?.autoCreatePr) {
|
|
1081
|
-
try {
|
|
1082
|
-
const prSummary = await generatePullRequestSummary(current.taskId, cwd);
|
|
1083
|
-
const prBody = buildPrBody(prSummary, current.qaIterations);
|
|
1084
|
-
const prTitle = `${current.taskId}: ${prSummary.task.title}`;
|
|
1085
|
-
const prResult = await createPullRequest({
|
|
1086
|
-
title: prTitle,
|
|
1087
|
-
body: prBody,
|
|
1088
|
-
...(config.github.baseBranch
|
|
1089
|
-
? { baseBranch: config.github.baseBranch }
|
|
1090
|
-
: {}),
|
|
1091
|
-
});
|
|
1092
|
-
await addEvidence({
|
|
1093
|
-
task: current.taskId,
|
|
1094
|
-
role: "release_manager",
|
|
1095
|
-
type: "report",
|
|
1096
|
-
summary: `PR created: ${prResult.url}`,
|
|
1097
|
-
}, cwd);
|
|
1098
|
-
io.log(` ✓ PR created: ${prResult.url}`);
|
|
1099
|
-
}
|
|
1100
|
-
catch (err) {
|
|
1101
|
-
io.log(` ⚠ PR creation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
if (runDeadlineMs !== undefined && Date.now() > runDeadlineMs) {
|
|
1106
|
-
const reason = `wall-clock timeout after ${timeoutOptions.runTimeoutMinutes} minutes`;
|
|
1107
|
-
io.log(`✗ ${reason}`);
|
|
1108
|
-
await recordWorkflowTimeout(cwd, current, {
|
|
1109
|
-
phase: def.phase,
|
|
1110
|
-
reason,
|
|
1111
|
-
timeoutMinutes: timeoutOptions.runTimeoutMinutes,
|
|
1112
|
-
});
|
|
1113
|
-
current = await markRunFailed(cwd, current, reason);
|
|
1114
|
-
return current;
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
current = await markRunDone(cwd, current);
|
|
1118
|
-
return current;
|
|
1119
|
-
}
|
|
1120
|
-
async function workflowDryRun(options, io, taskId, gates, maxIterations, phaseSelection) {
|
|
1121
|
-
const gateTransitions = new Set(["po→architect", "qa→release"]);
|
|
1122
|
-
io.log(`Dry run — no records will be created`);
|
|
1123
|
-
io.log(`Task: ${taskId} gates: ${gates} max-iterations: ${maxIterations}`);
|
|
1124
|
-
io.log(``);
|
|
1125
|
-
for (let i = 0; i < phaseSelection.sequence.length; i++) {
|
|
1126
|
-
const def = phaseSelection.sequence[i];
|
|
1127
|
-
const next = phaseSelection.sequence[i + 1];
|
|
1128
|
-
const transitionKey = next ? `${def.phase}→${next.phase}` : "";
|
|
1129
|
-
const gateLabel = gates === "all"
|
|
1130
|
-
? "gate=yes"
|
|
1131
|
-
: gates === "phase" && gateTransitions.has(transitionKey)
|
|
1132
|
-
? "gate=yes"
|
|
1133
|
-
: "gate=no";
|
|
1134
|
-
io.log(` ${def.phase} (${def.role}) ${gateLabel}`);
|
|
1135
|
-
}
|
|
1136
|
-
if (phaseSelection.skipped.length > 0) {
|
|
1137
|
-
io.log(``);
|
|
1138
|
-
io.log(`Skipped: ${phaseSelection.skipped.map((phase) => phase.phase).join(", ")}`);
|
|
1139
|
-
}
|
|
1140
|
-
if (options.json) {
|
|
1141
|
-
io.log(JSON.stringify({
|
|
1142
|
-
dryRun: true,
|
|
1143
|
-
taskId,
|
|
1144
|
-
gates,
|
|
1145
|
-
maxIterations,
|
|
1146
|
-
phases: phaseSelection.sequence,
|
|
1147
|
-
skipped: phaseSelection.skipped,
|
|
1148
|
-
}, null, 2));
|
|
1149
|
-
}
|
|
338
|
+
io.log(`GitHub issue #${result.issue.number} synced to ${result.taskId} (${result.action})`);
|
|
339
|
+
io.log(`Release readiness: ${result.releaseReadiness.passed ? "passed" : "blocked"}`);
|
|
340
|
+
if (result.comment !== "skipped")
|
|
341
|
+
io.log(`Comment ${result.comment}`);
|
|
342
|
+
if (result.closed)
|
|
343
|
+
io.log("Issue closed");
|
|
344
|
+
if (result.dryRun)
|
|
345
|
+
io.log("Dry run: no mutations were written.");
|
|
1150
346
|
}
|
|
1151
347
|
function parseRuntimeTargetOptions(options) {
|
|
1152
348
|
const targetValues = [
|
|
@@ -1155,132 +351,6 @@ function parseRuntimeTargetOptions(options) {
|
|
|
1155
351
|
];
|
|
1156
352
|
return [...new Set(targetValues)].map((target) => parseRuntimeTarget(target));
|
|
1157
353
|
}
|
|
1158
|
-
function parseSkillRenderTarget(value) {
|
|
1159
|
-
return parseRuntimeTarget(value);
|
|
1160
|
-
}
|
|
1161
|
-
function parseCsv(value) {
|
|
1162
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
1163
|
-
return [];
|
|
1164
|
-
}
|
|
1165
|
-
return value
|
|
1166
|
-
.split(",")
|
|
1167
|
-
.map((item) => item.trim())
|
|
1168
|
-
.filter(Boolean);
|
|
1169
|
-
}
|
|
1170
|
-
function csvOption(value) {
|
|
1171
|
-
if (typeof value !== "string") {
|
|
1172
|
-
return undefined;
|
|
1173
|
-
}
|
|
1174
|
-
return parseCsv(value);
|
|
1175
|
-
}
|
|
1176
|
-
function singleValueArrayOption(value) {
|
|
1177
|
-
const option = stringOption(value);
|
|
1178
|
-
return option ? [option] : undefined;
|
|
1179
|
-
}
|
|
1180
|
-
function stringOption(value) {
|
|
1181
|
-
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
1182
|
-
}
|
|
1183
|
-
function removeUndefined(value) {
|
|
1184
|
-
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
1185
|
-
}
|
|
1186
|
-
function parseArchitectureApproval(options) {
|
|
1187
|
-
const approval = removeUndefined({
|
|
1188
|
-
proposal: stringOption(options["architecture-proposal"]),
|
|
1189
|
-
userApproved: options["user-approved"] ? true : undefined,
|
|
1190
|
-
approvedBy: stringOption(options["architecture-approved-by"]),
|
|
1191
|
-
approvedAt: stringOption(options["architecture-approved-at"]),
|
|
1192
|
-
});
|
|
1193
|
-
return Object.keys(approval).length > 0 ? approval : undefined;
|
|
1194
|
-
}
|
|
1195
|
-
function parseQaGate(options) {
|
|
1196
|
-
const qaGate = removeUndefined({
|
|
1197
|
-
plan: stringOption(options["qa-plan"]),
|
|
1198
|
-
executionStatus: stringOption(options["qa-execution-status"]),
|
|
1199
|
-
deferredRationale: stringOption(options["qa-deferred-rationale"]),
|
|
1200
|
-
deferredOwner: stringOption(options["qa-deferred-owner"]),
|
|
1201
|
-
});
|
|
1202
|
-
return Object.keys(qaGate).length > 0 ? qaGate : undefined;
|
|
1203
|
-
}
|
|
1204
|
-
function parseRiskGate(options) {
|
|
1205
|
-
const impactAreas = parseCsv(options["risk-areas"]);
|
|
1206
|
-
const riskGate = removeUndefined({
|
|
1207
|
-
impactAreas: impactAreas.length > 0 ? impactAreas : undefined,
|
|
1208
|
-
acceptance: parseRiskAcceptance(options),
|
|
1209
|
-
});
|
|
1210
|
-
return Object.keys(riskGate).length > 0 ? riskGate : undefined;
|
|
1211
|
-
}
|
|
1212
|
-
function parseRiskAcceptance(options) {
|
|
1213
|
-
const acceptance = removeUndefined({
|
|
1214
|
-
acceptedBy: stringOption(options["risk-accepted-by"]),
|
|
1215
|
-
rationale: stringOption(options["risk-acceptance-rationale"]),
|
|
1216
|
-
});
|
|
1217
|
-
return Object.keys(acceptance).length > 0 ? acceptance : undefined;
|
|
1218
|
-
}
|
|
1219
|
-
function parseBudgetEscalationDecision(options) {
|
|
1220
|
-
if (options["approve-budget-fallback"]) {
|
|
1221
|
-
return removeUndefined({
|
|
1222
|
-
approved: true,
|
|
1223
|
-
approver: stringOption(options.approver),
|
|
1224
|
-
rationale: stringOption(options.rationale),
|
|
1225
|
-
});
|
|
1226
|
-
}
|
|
1227
|
-
if (options["reject-budget-fallback"]) {
|
|
1228
|
-
return removeUndefined({
|
|
1229
|
-
approved: false,
|
|
1230
|
-
approver: stringOption(options.approver),
|
|
1231
|
-
rationale: stringOption(options.rationale),
|
|
1232
|
-
});
|
|
1233
|
-
}
|
|
1234
|
-
return undefined;
|
|
1235
|
-
}
|
|
1236
|
-
function numberOption(value, fallback) {
|
|
1237
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
1238
|
-
return fallback;
|
|
1239
|
-
}
|
|
1240
|
-
const parsed = Number(value);
|
|
1241
|
-
if (!Number.isFinite(parsed)) {
|
|
1242
|
-
throw new Error(`invalid number: ${value}`);
|
|
1243
|
-
}
|
|
1244
|
-
return parsed;
|
|
1245
|
-
}
|
|
1246
|
-
function renderReadinessMarkdown(task, report) {
|
|
1247
|
-
const status = report.isReady ? "passed" : "blocked";
|
|
1248
|
-
const missing = report.missing.length > 0
|
|
1249
|
-
? report.missing.map((field) => `- ${field}`).join("\n")
|
|
1250
|
-
: "- none";
|
|
1251
|
-
return [
|
|
1252
|
-
`# Readiness ${task.id}`,
|
|
1253
|
-
"",
|
|
1254
|
-
`Status: ${status}`,
|
|
1255
|
-
"",
|
|
1256
|
-
"Missing fields:",
|
|
1257
|
-
missing,
|
|
1258
|
-
"",
|
|
1259
|
-
].join("\n");
|
|
1260
|
-
}
|
|
1261
|
-
function renderGateMarkdown(report) {
|
|
1262
|
-
const status = report.passed ? "passed" : "blocked";
|
|
1263
|
-
const missing = report.missing.length > 0
|
|
1264
|
-
? report.missing.map((field) => `- ${field}`).join("\n")
|
|
1265
|
-
: "- none";
|
|
1266
|
-
return [
|
|
1267
|
-
`# Gate ${report.gateId}`,
|
|
1268
|
-
"",
|
|
1269
|
-
`Status: ${status}`,
|
|
1270
|
-
"",
|
|
1271
|
-
"Missing fields:",
|
|
1272
|
-
missing,
|
|
1273
|
-
"",
|
|
1274
|
-
].join("\n");
|
|
1275
|
-
}
|
|
1276
|
-
function renderDependencyReport(report) {
|
|
1277
|
-
return [
|
|
1278
|
-
`Dependencies for ${report.taskId}: ${report.isSatisfied ? "satisfied" : "blocked"}`,
|
|
1279
|
-
...(report.dependencies.length === 0
|
|
1280
|
-
? ["- none"]
|
|
1281
|
-
: report.dependencies.map((dependency) => `- ${dependency.id}: ${dependency.status} (${dependency.isComplete ? "complete" : "incomplete"})`)),
|
|
1282
|
-
].join("\n");
|
|
1283
|
-
}
|
|
1284
354
|
function renderPreRunValidation(report) {
|
|
1285
355
|
const status = report.allowed ? "allowed" : "blocked";
|
|
1286
356
|
const missing = report.missing.length > 0 ? report.missing.join(", ") : "none";
|
|
@@ -1299,84 +369,6 @@ function renderPreRunValidation(report) {
|
|
|
1299
369
|
: []),
|
|
1300
370
|
].join("\n");
|
|
1301
371
|
}
|
|
1302
|
-
function renderTaskGraphPlan(plan) {
|
|
1303
|
-
return [
|
|
1304
|
-
"Task graph plan",
|
|
1305
|
-
"",
|
|
1306
|
-
"Ready:",
|
|
1307
|
-
...taskGraphReadyLines(plan.ready),
|
|
1308
|
-
"",
|
|
1309
|
-
"Blocked:",
|
|
1310
|
-
...taskGraphBlockedLines(plan.blocked),
|
|
1311
|
-
"",
|
|
1312
|
-
"Locked:",
|
|
1313
|
-
...taskGraphLockedLines(plan.locked),
|
|
1314
|
-
"",
|
|
1315
|
-
"Complete:",
|
|
1316
|
-
...taskGraphReadyLines(plan.complete),
|
|
1317
|
-
].join("\n");
|
|
1318
|
-
}
|
|
1319
|
-
function renderTaskGraphDryRunPreview(preview) {
|
|
1320
|
-
return [
|
|
1321
|
-
"Task graph dry run",
|
|
1322
|
-
`- Mode: ${preview.mode}`,
|
|
1323
|
-
`- Would mutate: ${String(preview.wouldMutate)}`,
|
|
1324
|
-
`- Selected: ${preview.selectedTaskIds.join(", ")}`,
|
|
1325
|
-
"",
|
|
1326
|
-
"Tasks:",
|
|
1327
|
-
...preview.tasks.map((task) => {
|
|
1328
|
-
const budget = task.budget
|
|
1329
|
-
? ` budget=${task.budget.passed ? "pass" : "fail"} scopes=${task.budget.appliedBudgets.join(",") || "none"}`
|
|
1330
|
-
: " budget=not-configured";
|
|
1331
|
-
return `- ${task.id} ${task.title} (${task.ownerRole}) ${task.provider}/${task.model} fallbacks=${task.fallbacks.join(",") || "none"}${budget}`;
|
|
1332
|
-
}),
|
|
1333
|
-
"",
|
|
1334
|
-
"Locked:",
|
|
1335
|
-
...taskGraphLockedLines(preview.locked),
|
|
1336
|
-
"",
|
|
1337
|
-
"Blocked:",
|
|
1338
|
-
...taskGraphBlockedLines(preview.blocked),
|
|
1339
|
-
].join("\n");
|
|
1340
|
-
}
|
|
1341
|
-
function renderTaskGraphBatchRun(batch) {
|
|
1342
|
-
return [
|
|
1343
|
-
"Task graph batch run",
|
|
1344
|
-
`- Selected: ${batch.selectedTaskIds.join(", ")}`,
|
|
1345
|
-
`- Completed runs: ${batch.runs.length}`,
|
|
1346
|
-
`- Locked: ${batch.locked.map((task) => task.id).join(", ") || "none"}`,
|
|
1347
|
-
`- Artifact: ${batch.artifact ?? "none"}`,
|
|
1348
|
-
...(batch.failure
|
|
1349
|
-
? [`- Failure: ${batch.failure.taskId}: ${batch.failure.error}`]
|
|
1350
|
-
: ["- Failure: none"]),
|
|
1351
|
-
].join("\n");
|
|
1352
|
-
}
|
|
1353
|
-
function taskGraphReadyLines(items) {
|
|
1354
|
-
return items.length === 0
|
|
1355
|
-
? ["- none"]
|
|
1356
|
-
: items.map((item) => `- ${item.id} [${item.status}] ${item.title} (${item.ownerRole})`);
|
|
1357
|
-
}
|
|
1358
|
-
function taskGraphBlockedLines(items) {
|
|
1359
|
-
return items.length === 0
|
|
1360
|
-
? ["- none"]
|
|
1361
|
-
: items.map((item) => {
|
|
1362
|
-
const blockers = item.incomplete
|
|
1363
|
-
.map((dependency) => `${dependency.id}:${dependency.status}`)
|
|
1364
|
-
.join(", ");
|
|
1365
|
-
return `- ${item.id} [${item.status}] ${item.title} blocked by ${blockers}`;
|
|
1366
|
-
});
|
|
1367
|
-
}
|
|
1368
|
-
function taskGraphLockedLines(items) {
|
|
1369
|
-
return items.length === 0
|
|
1370
|
-
? ["- none"]
|
|
1371
|
-
: items.map((item) => {
|
|
1372
|
-
const locks = item.locks
|
|
1373
|
-
.map((lock) => lock.conflictPath
|
|
1374
|
-
? `${lock.id}:${lock.path}->${lock.conflictPath}`
|
|
1375
|
-
: `${lock.id}:${lock.path}`)
|
|
1376
|
-
.join(", ");
|
|
1377
|
-
return `- ${item.id} [${item.status}] ${item.title} locked by ${locks}`;
|
|
1378
|
-
});
|
|
1379
|
-
}
|
|
1380
372
|
function renderSummaryMarkdown(summary) {
|
|
1381
373
|
return [
|
|
1382
374
|
"# Agent Workflow Summary",
|
|
@@ -1465,166 +457,6 @@ function renderPullRequestSummaryMarkdown(summary) {
|
|
|
1465
457
|
"",
|
|
1466
458
|
].join("\n");
|
|
1467
459
|
}
|
|
1468
|
-
function renderDelegationDecisionMarkdown(decision) {
|
|
1469
|
-
return [
|
|
1470
|
-
`# Delegation Decision: ${decision.taskId}`,
|
|
1471
|
-
"",
|
|
1472
|
-
`- Recommendation: ${decision.recommendation}`,
|
|
1473
|
-
`- Complexity score: ${decision.complexityScore}`,
|
|
1474
|
-
`- Urgency: ${decision.urgency}`,
|
|
1475
|
-
`- Disjoint write scopes: ${String(decision.disjointWriteScopes)}`,
|
|
1476
|
-
"",
|
|
1477
|
-
"## Rationale",
|
|
1478
|
-
...listOrNone(decision.rationale),
|
|
1479
|
-
"",
|
|
1480
|
-
"## Blocking Conditions",
|
|
1481
|
-
...listOrNone(decision.blockingConditions),
|
|
1482
|
-
"",
|
|
1483
|
-
"## Context Bundle",
|
|
1484
|
-
...listOrNone(decision.contextBundle),
|
|
1485
|
-
"",
|
|
1486
|
-
"## Delegates",
|
|
1487
|
-
...(decision.delegates.length === 0
|
|
1488
|
-
? ["- none"]
|
|
1489
|
-
: decision.delegates.map((delegate) => `- ${delegate.role} [${delegate.mode}] scopes=${delegate.writeScopes.join(",") || "none"}`)),
|
|
1490
|
-
"",
|
|
1491
|
-
"## Expected Outputs",
|
|
1492
|
-
...listOrNone(decision.expectedOutputs),
|
|
1493
|
-
"",
|
|
1494
|
-
].join("\n");
|
|
1495
|
-
}
|
|
1496
|
-
function renderTaskContextMarkdown(context) {
|
|
1497
|
-
return [
|
|
1498
|
-
`# Task Context: ${context.task.id}`,
|
|
1499
|
-
"",
|
|
1500
|
-
`- Title: ${context.task.title}`,
|
|
1501
|
-
`- Owner: ${context.task.ownerRole}`,
|
|
1502
|
-
`- Status: ${context.task.status}`,
|
|
1503
|
-
`- Dependencies: ${context.dependencies.isSatisfied ? "satisfied" : "blocked"}`,
|
|
1504
|
-
`- Locks: ${context.locks.length}`,
|
|
1505
|
-
`- Risks: ${context.risks.length}`,
|
|
1506
|
-
`- Delegation: ${context.delegation?.recommendation ?? "not decided"}`,
|
|
1507
|
-
`- Estimated tokens: ${context.contextBudget.estimatedTokens}/${context.contextBudget.tokenBudget}`,
|
|
1508
|
-
`- Trimmed: ${context.contextBudget.trimmed}`,
|
|
1509
|
-
"",
|
|
1510
|
-
"## Context Budget",
|
|
1511
|
-
...context.contextBudget.sections.map((section) => `- ${section.section}: kept ${section.keptCount}/${section.originalCount}, dropped ${section.droppedCount}`),
|
|
1512
|
-
"",
|
|
1513
|
-
"## Collaboration Flow",
|
|
1514
|
-
...(context.collaborationFlow
|
|
1515
|
-
? [
|
|
1516
|
-
`- ${context.collaborationFlow.flow.id}: ${context.collaborationFlow.flow.name}`,
|
|
1517
|
-
`- Missing artifacts: ${context.collaborationFlow.missingArtifacts.join(", ") || "none"}`,
|
|
1518
|
-
`- Reviewers: ${context.collaborationFlow.optionalReviewers.join(", ") || "none"}`,
|
|
1519
|
-
]
|
|
1520
|
-
: ["- none"]),
|
|
1521
|
-
"",
|
|
1522
|
-
"## Workflow Templates",
|
|
1523
|
-
...(context.workflowTemplates.length === 0
|
|
1524
|
-
? ["- none"]
|
|
1525
|
-
: context.workflowTemplates.map((item) => `- ${item.template.id} (score ${item.score}, missing evidence ${item.missingEvidence.join(", ") || "none"})`)),
|
|
1526
|
-
"",
|
|
1527
|
-
"## Skills",
|
|
1528
|
-
...(context.skills.selected.length === 0
|
|
1529
|
-
? ["- none"]
|
|
1530
|
-
: context.skills.selected.map((item) => `- ${item.skill.id} (score ${item.score}): ${item.rationale.join("; ")}`)),
|
|
1531
|
-
`- Source groups: ${context.skills.sourceGroups.join(", ") || "none"}`,
|
|
1532
|
-
"",
|
|
1533
|
-
renderMemoryPacket(context.memory),
|
|
1534
|
-
"",
|
|
1535
|
-
"## Decisions",
|
|
1536
|
-
...eventLines(context.decisions),
|
|
1537
|
-
"",
|
|
1538
|
-
"## Handoffs",
|
|
1539
|
-
...eventLines(context.handoffs),
|
|
1540
|
-
"",
|
|
1541
|
-
"## Reviews",
|
|
1542
|
-
...eventLines(context.reviews),
|
|
1543
|
-
"",
|
|
1544
|
-
"## Evidence",
|
|
1545
|
-
...eventLines(context.evidence),
|
|
1546
|
-
"",
|
|
1547
|
-
"## Gates",
|
|
1548
|
-
...eventLines(context.gates),
|
|
1549
|
-
"",
|
|
1550
|
-
"## Model Provenance",
|
|
1551
|
-
...(context.modelProvenance.length === 0
|
|
1552
|
-
? ["- none"]
|
|
1553
|
-
: context.modelProvenance.map((record) => `- ${record.role}: ${record.provider}/${record.model} ${record.responseId}`)),
|
|
1554
|
-
"",
|
|
1555
|
-
].join("\n");
|
|
1556
|
-
}
|
|
1557
|
-
function renderExecutionPlanMarkdown(plan) {
|
|
1558
|
-
return [
|
|
1559
|
-
`# Execution Plan: ${plan.taskId}`,
|
|
1560
|
-
"",
|
|
1561
|
-
...plan.steps.map((step) => {
|
|
1562
|
-
const gate = step.gate ? ` gate=${step.gate}` : "";
|
|
1563
|
-
const dependsOn = step.dependsOn.length > 0
|
|
1564
|
-
? ` dependsOn=${step.dependsOn.join(",")}`
|
|
1565
|
-
: "";
|
|
1566
|
-
return `- ${step.id}: ${step.role}${gate}${dependsOn} - ${step.action}`;
|
|
1567
|
-
}),
|
|
1568
|
-
"",
|
|
1569
|
-
].join("\n");
|
|
1570
|
-
}
|
|
1571
|
-
function renderExecutionRunMarkdown(run) {
|
|
1572
|
-
return [
|
|
1573
|
-
`# Execution Run: ${run.taskId}`,
|
|
1574
|
-
"",
|
|
1575
|
-
`- Provider: ${run.provider}`,
|
|
1576
|
-
`- Model: ${run.model}`,
|
|
1577
|
-
"",
|
|
1578
|
-
...run.steps.map((step) => `- ${step.id}: ${step.role} ${step.status} ${step.responseId ?? step.error ?? ""}`.trim()),
|
|
1579
|
-
"",
|
|
1580
|
-
].join("\n");
|
|
1581
|
-
}
|
|
1582
|
-
function renderPlaywrightPlanMarkdown(plan) {
|
|
1583
|
-
return [
|
|
1584
|
-
`# Playwright Test Plan: ${plan.taskId}`,
|
|
1585
|
-
"",
|
|
1586
|
-
`- Title: ${plan.title}`,
|
|
1587
|
-
`- Target user: ${plan.targetUser}`,
|
|
1588
|
-
"",
|
|
1589
|
-
"## Scenarios",
|
|
1590
|
-
...plan.scenarios.flatMap((scenario) => [
|
|
1591
|
-
`### ${scenario.name}`,
|
|
1592
|
-
`- Source: ${scenario.source}`,
|
|
1593
|
-
`- Page object: ${scenario.pageObject}`,
|
|
1594
|
-
`- Selectors: ${scenario.selectors.join("; ")}`,
|
|
1595
|
-
`- Assertions: ${scenario.assertions.join("; ")}`,
|
|
1596
|
-
`- Evidence: ${scenario.evidence.join(", ")}`,
|
|
1597
|
-
"",
|
|
1598
|
-
]),
|
|
1599
|
-
"## Fixtures",
|
|
1600
|
-
...listOrNone(plan.fixtures),
|
|
1601
|
-
"",
|
|
1602
|
-
"## Notes",
|
|
1603
|
-
...listOrNone(plan.notes),
|
|
1604
|
-
"",
|
|
1605
|
-
].join("\n");
|
|
1606
|
-
}
|
|
1607
|
-
function renderTaskDetails(task) {
|
|
1608
|
-
return [
|
|
1609
|
-
`ID: ${task.id}`,
|
|
1610
|
-
`Title: ${task.title}`,
|
|
1611
|
-
`Owner: ${task.ownerRole}`,
|
|
1612
|
-
`Status: ${task.status}`,
|
|
1613
|
-
`Goal: ${task.goal ?? "—"}`,
|
|
1614
|
-
`Scope: ${task.scope ?? "—"}`,
|
|
1615
|
-
`Acceptance Criteria: ${renderInlineList(task.acceptanceCriteria)}`,
|
|
1616
|
-
`Assumptions: ${renderInlineList(task.assumptions)}`,
|
|
1617
|
-
`Risks: ${renderInlineList(task.risks)}`,
|
|
1618
|
-
`Paths: ${renderInlineList(task.paths)}`,
|
|
1619
|
-
`Test Strategy: ${task.testStrategy ?? "—"}`,
|
|
1620
|
-
`Blocked Reason: ${task.blockedReason ?? "—"}`,
|
|
1621
|
-
`Created: ${task.createdAt}`,
|
|
1622
|
-
`Updated: ${task.updatedAt}`,
|
|
1623
|
-
];
|
|
1624
|
-
}
|
|
1625
|
-
function renderInlineList(values) {
|
|
1626
|
-
return values && values.length > 0 ? values.join(", ") : "—";
|
|
1627
|
-
}
|
|
1628
460
|
function eventLines(events) {
|
|
1629
461
|
return events.length === 0
|
|
1630
462
|
? ["- none"]
|