@jterrats/open-orchestra 0.4.1 → 0.4.2-beta.1
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/dist/assets/web-console.js +91 -1
- package/dist/autonomous-phase-lifecycle.d.ts +3 -2
- package/dist/autonomous-phase-lifecycle.js +28 -8
- package/dist/autonomous-phase-lifecycle.js.map +1 -1
- package/dist/autonomous-run-state.d.ts +6 -4
- package/dist/autonomous-run-state.js +82 -11
- package/dist/autonomous-run-state.js.map +1 -1
- package/dist/autonomous-run-store.d.ts +5 -0
- package/dist/autonomous-run-store.js +27 -2
- package/dist/autonomous-run-store.js.map +1 -1
- package/dist/autonomous-workflow-constants.d.ts +1 -0
- package/dist/autonomous-workflow-constants.js.map +1 -1
- package/dist/autonomous-workflow.d.ts +2 -2
- package/dist/autonomous-workflow.js +2 -2
- package/dist/autonomous-workflow.js.map +1 -1
- package/dist/benchmark.js +16 -0
- package/dist/benchmark.js.map +1 -1
- package/dist/clarification.d.ts +2 -0
- package/dist/clarification.js +23 -5
- package/dist/clarification.js.map +1 -1
- package/dist/cli.js +89 -14
- package/dist/cli.js.map +1 -1
- package/dist/command-manifest.js +8 -0
- package/dist/command-manifest.js.map +1 -1
- package/dist/commands.d.ts +9 -2
- package/dist/commands.js +456 -47
- package/dist/commands.js.map +1 -1
- package/dist/constants.js +11 -0
- package/dist/constants.js.map +1 -1
- package/dist/mcp-oauth-proxy.d.ts +47 -0
- package/dist/mcp-oauth-proxy.js +276 -0
- package/dist/mcp-oauth-proxy.js.map +1 -1
- package/dist/memory.d.ts +11 -0
- package/dist/memory.js +224 -0
- package/dist/memory.js.map +1 -0
- package/dist/metrics-commands.js +9 -0
- package/dist/metrics-commands.js.map +1 -1
- package/dist/model-commands.js +18 -1
- package/dist/model-commands.js.map +1 -1
- package/dist/notifications.d.ts +25 -0
- package/dist/notifications.js +187 -11
- package/dist/notifications.js.map +1 -1
- package/dist/package-update-check.d.ts +19 -0
- package/dist/package-update-check.js +123 -0
- package/dist/package-update-check.js.map +1 -0
- package/dist/runtime-bootstrap.js +4 -2
- package/dist/runtime-bootstrap.js.map +1 -1
- package/dist/runtime-execution-renderer.js +3 -0
- package/dist/runtime-execution-renderer.js.map +1 -1
- package/dist/runtime-execution.js +6 -0
- package/dist/runtime-execution.js.map +1 -1
- package/dist/skills-commands.d.ts +1 -0
- package/dist/skills-commands.js +9 -1
- package/dist/skills-commands.js.map +1 -1
- package/dist/skills.d.ts +7 -1
- package/dist/skills.js +120 -11
- package/dist/skills.js.map +1 -1
- package/dist/subagent-protocol.js +6 -0
- package/dist/subagent-protocol.js.map +1 -1
- package/dist/tool-commands.d.ts +2 -0
- package/dist/tool-commands.js +105 -13
- package/dist/tool-commands.js.map +1 -1
- package/dist/types.d.ts +127 -4
- package/dist/types.js.map +1 -1
- package/dist/web-api.js +93 -1
- package/dist/web-api.js.map +1 -1
- package/dist/web-chart-contracts.d.ts +3 -1
- package/dist/web-chart-contracts.js +6 -0
- package/dist/web-chart-contracts.js.map +1 -1
- package/dist/web-console.js +2 -2
- package/dist/workflow-services.d.ts +21 -3
- package/dist/workflow-services.js +474 -10
- package/dist/workflow-services.js.map +1 -1
- package/package.json +2 -1
- package/skills/proactive-orchestra/SKILL.md +27 -0
- package/skills/proactive-orchestra/manifest.json +41 -0
package/dist/commands.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { initWorkspace } from "./workspace.js";
|
|
1
|
+
import { appendEvent, initWorkspace } from "./workspace.js";
|
|
2
2
|
import { requireArg } from "./args.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { TASK_STATUSES } from "./constants.js";
|
|
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";
|
|
5
7
|
import { executePhaseWithLlm } from "./phase-executor.js";
|
|
6
|
-
import { addEvidence, addPlaywrightEvidence, addTask, checkReadiness, checkTaskDependencies, claimLock, createHandoff, evaluateWorkflowGate, executeNextReadyTask, executePlanWithBudgetPreflight, executeReadyTaskBatch, generateExecutionPlan, generatePlaywrightTestPlan, generatePullRequestSummary, generateTaskGraphPlan, getWorkflowStatus, getWorkflowSummary, getTaskContext, getWorkflowConfig, listEvidence, listDecisions, listLocks, listReviews, listRoles, listTasks, recordReview, recordDecision, releaseLock, updateTask, } from "./workflow-services.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";
|
|
7
9
|
import { validateWorkspace } from "./workspace-validator.js";
|
|
8
10
|
import { getWebServerAddress, startWebApiServer } from "./web-api.js";
|
|
9
11
|
import { decideTaskDelegation } from "./delegation-decision.js";
|
|
@@ -15,8 +17,8 @@ export { benchmarkCommand, estimateCommand } from "./metrics-commands.js";
|
|
|
15
17
|
export { burndownCommand, calibrationCommand, sprintCommand, velocityCommand, } from "./sprint-commands.js";
|
|
16
18
|
export { approvalsApproveCommand, approvalsListCommand, approvalsRejectCommand, approvalsShowCommand, budgetCheckCommand, configShowCommand, modelCompleteFakeCommand, modelProvidersCommand, modelProvenanceAddCommand, modelProvenanceListCommand, modelSetRoleCommand, usageCommand, } from "./model-commands.js";
|
|
17
19
|
export { commandsManifestCommand, protocolBlockCommand, protocolRenderCommand, runtimeAdaptersCommand, runtimeBootstrapCommand, runtimeBriefCommand, runtimeDelegatePlanCommand, runtimeHandoffCommand, } from "./runtime-commands.js";
|
|
18
|
-
export { lessonsAddCommand, lessonsListCommand, lessonsPromoteCommand, skillsListCommand, skillsPlanCommand, skillsRenderCommand, skillsValidateCommand, sourcesListCommand, } from "./skills-commands.js";
|
|
19
|
-
export { diagramsLintCommand, mcpOAuthProxyEvaluateCommand, } from "./tool-commands.js";
|
|
20
|
+
export { lessonsAddCommand, lessonsDeleteCommand, lessonsListCommand, lessonsPromoteCommand, skillsListCommand, skillsPlanCommand, skillsRenderCommand, skillsValidateCommand, sourcesListCommand, } from "./skills-commands.js";
|
|
21
|
+
export { diagramsLintCommand, mcpOAuthProxyEvaluateCommand, mcpOAuthProxyStartCommand, mcpOAuthProxyTokenCommand, } from "./tool-commands.js";
|
|
20
22
|
export { telemetryDisableCommand, telemetryEnableCommand, telemetryEvalDatasetCommand, telemetryExportCommand, telemetryStatusCommand, telemetrySubmitCommand, } from "./telemetry-commands.js";
|
|
21
23
|
import { buildPrBody, createPullRequest } from "./github.js";
|
|
22
24
|
export async function initCommand(options, io) {
|
|
@@ -63,6 +65,23 @@ export async function statusCommand(options, io) {
|
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
export async function validateCommand(options, io) {
|
|
68
|
+
if (options["pre-run"]) {
|
|
69
|
+
const report = await validatePreRun(requireArg(options, "task"), removeUndefined({
|
|
70
|
+
bypass: Boolean(options.bypass),
|
|
71
|
+
bypassOwner: stringOption(options["bypass-owner"]),
|
|
72
|
+
bypassRationale: stringOption(options["bypass-rationale"]),
|
|
73
|
+
}));
|
|
74
|
+
if (options.json) {
|
|
75
|
+
io.log(JSON.stringify(report, null, 2));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
io.log(renderPreRunValidation(report));
|
|
79
|
+
}
|
|
80
|
+
if (!report.allowed) {
|
|
81
|
+
throw new Error(`pre-run validation failed: ${report.missing.join(", ")}`);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
66
85
|
const report = await validateWorkspace();
|
|
67
86
|
if (options.json) {
|
|
68
87
|
io.log(JSON.stringify(report, null, 2));
|
|
@@ -114,7 +133,11 @@ export async function taskAddCommand(options, io) {
|
|
|
114
133
|
io.log(`Added task ${task.id}`);
|
|
115
134
|
}
|
|
116
135
|
export async function taskListCommand(options, io) {
|
|
117
|
-
|
|
136
|
+
if (options.help || options.h) {
|
|
137
|
+
io.log("task list [--json] [--status <csv>] [--owner <role>] [--filter <text>] [--top-level] [--include-archived]");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const tasks = filterTasks(await listTasks(), options);
|
|
118
141
|
if (options.json) {
|
|
119
142
|
io.log(JSON.stringify(tasks, null, 2));
|
|
120
143
|
return;
|
|
@@ -127,6 +150,67 @@ export async function taskListCommand(options, io) {
|
|
|
127
150
|
io.log(`${task.id} [${task.status}] ${task.title} (${task.ownerRole})`);
|
|
128
151
|
}
|
|
129
152
|
}
|
|
153
|
+
function filterTasks(tasks, options) {
|
|
154
|
+
const statuses = parseTaskStatusFilter(options.status);
|
|
155
|
+
const owner = stringOption(options.owner);
|
|
156
|
+
const textFilter = stringOption(options.filter)?.toLowerCase();
|
|
157
|
+
return tasks.filter((task) => matchesStatus(task, statuses) &&
|
|
158
|
+
(options["include-archived"] || task.status !== "archived") &&
|
|
159
|
+
(!owner || task.ownerRole === owner) &&
|
|
160
|
+
(!textFilter || taskSearchText(task).includes(textFilter)) &&
|
|
161
|
+
(!options["top-level"] || isTopLevelTask(task)));
|
|
162
|
+
}
|
|
163
|
+
function parseTaskStatusFilter(value) {
|
|
164
|
+
const statuses = parseCsv(value);
|
|
165
|
+
const invalid = statuses.filter((status) => !TASK_STATUSES.includes(status));
|
|
166
|
+
if (invalid.length > 0) {
|
|
167
|
+
throw new Error(`unknown task status: ${invalid.join(", ")}. Valid statuses: ${TASK_STATUSES.join(", ")}`);
|
|
168
|
+
}
|
|
169
|
+
return new Set(statuses);
|
|
170
|
+
}
|
|
171
|
+
function matchesStatus(task, statuses) {
|
|
172
|
+
return statuses.size === 0 || statuses.has(task.status);
|
|
173
|
+
}
|
|
174
|
+
function taskSearchText(task) {
|
|
175
|
+
return `${task.id} ${task.title}`.toLowerCase();
|
|
176
|
+
}
|
|
177
|
+
function isTopLevelTask(task) {
|
|
178
|
+
return !["pm", "po", "architect", "developer", "qa", "release"].some((phase) => task.id.includes(`-${phase}-`));
|
|
179
|
+
}
|
|
180
|
+
export async function taskShowCommand(options, io) {
|
|
181
|
+
const id = requireArg(options, "id");
|
|
182
|
+
const task = (await listTasks()).find((candidate) => candidate.id === id);
|
|
183
|
+
if (!task) {
|
|
184
|
+
throw new Error(`unknown task: ${id}`);
|
|
185
|
+
}
|
|
186
|
+
if (options.json) {
|
|
187
|
+
io.log(JSON.stringify(task, null, 2));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
for (const line of renderTaskDetails(task)) {
|
|
191
|
+
io.log(line);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
export async function taskDeleteCommand(options, io) {
|
|
195
|
+
const task = await deleteTask(requireArg(options, "id"), {
|
|
196
|
+
force: Boolean(options.force),
|
|
197
|
+
});
|
|
198
|
+
if (options.json) {
|
|
199
|
+
io.log(JSON.stringify(task, null, 2));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
io.log(`Deleted task ${task.id}`);
|
|
203
|
+
}
|
|
204
|
+
export async function taskArchiveCommand(options, io) {
|
|
205
|
+
const task = await archiveTask(requireArg(options, "id"), {
|
|
206
|
+
force: Boolean(options.force),
|
|
207
|
+
});
|
|
208
|
+
if (options.json) {
|
|
209
|
+
io.log(JSON.stringify(task, null, 2));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
io.log(`Archived task ${task.id}`);
|
|
213
|
+
}
|
|
130
214
|
export async function rolesListCommand(options, io) {
|
|
131
215
|
const roles = await listRoles();
|
|
132
216
|
if (options.json) {
|
|
@@ -146,6 +230,15 @@ export async function taskUpdateCommand(options, io) {
|
|
|
146
230
|
const id = requireArg(options, "id");
|
|
147
231
|
await updateTask(removeUndefined({
|
|
148
232
|
id,
|
|
233
|
+
title: stringOption(options.title),
|
|
234
|
+
ownerRole: stringOption(options.owner),
|
|
235
|
+
goal: stringOption(options.goal),
|
|
236
|
+
scope: stringOption(options.scope),
|
|
237
|
+
paths: csvOption(options.paths),
|
|
238
|
+
acceptanceCriteria: singleValueArrayOption(options.acceptance),
|
|
239
|
+
assumptions: singleValueArrayOption(options.assumptions),
|
|
240
|
+
risks: singleValueArrayOption(options.risks),
|
|
241
|
+
testStrategy: stringOption(options["test-strategy"]),
|
|
149
242
|
status: stringOption(options.status),
|
|
150
243
|
blockedReason: stringOption(options["blocked-reason"]),
|
|
151
244
|
}));
|
|
@@ -168,6 +261,15 @@ export async function graphPlanCommand(options, io) {
|
|
|
168
261
|
io.log(renderTaskGraphPlan(plan));
|
|
169
262
|
}
|
|
170
263
|
export async function graphRunNextCommand(options, io) {
|
|
264
|
+
if (options["dry-run"]) {
|
|
265
|
+
const preview = await previewTaskGraphRun("run-next");
|
|
266
|
+
if (options.json) {
|
|
267
|
+
io.log(JSON.stringify(preview, null, 2));
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
io.log(renderTaskGraphDryRunPreview(preview));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
171
273
|
const run = await executeNextReadyTask();
|
|
172
274
|
if (options.json) {
|
|
173
275
|
io.log(JSON.stringify(run, null, 2));
|
|
@@ -176,6 +278,15 @@ export async function graphRunNextCommand(options, io) {
|
|
|
176
278
|
io.log(renderExecutionRunMarkdown(run));
|
|
177
279
|
}
|
|
178
280
|
export async function graphRunReadyCommand(options, io) {
|
|
281
|
+
if (options["dry-run"]) {
|
|
282
|
+
const preview = await previewTaskGraphRun("run-ready");
|
|
283
|
+
if (options.json) {
|
|
284
|
+
io.log(JSON.stringify(preview, null, 2));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
io.log(renderTaskGraphDryRunPreview(preview));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
179
290
|
const batch = await executeReadyTaskBatch();
|
|
180
291
|
if (options.json) {
|
|
181
292
|
io.log(JSON.stringify(batch, null, 2));
|
|
@@ -248,6 +359,7 @@ export async function handoffCommand(options, io) {
|
|
|
248
359
|
gaps: stringOption(options.gaps),
|
|
249
360
|
risks: stringOption(options.risks),
|
|
250
361
|
playwright: stringOption(options.playwright),
|
|
362
|
+
updateOwner: options["update-owner"] ? true : undefined,
|
|
251
363
|
}));
|
|
252
364
|
io.log(`Created ${artifact}`);
|
|
253
365
|
}
|
|
@@ -367,6 +479,40 @@ export async function contextCommand(options, io) {
|
|
|
367
479
|
}
|
|
368
480
|
io.log(renderTaskContextMarkdown(context));
|
|
369
481
|
}
|
|
482
|
+
export async function memoryQueryCommand(options, io) {
|
|
483
|
+
const hook = parseMemoryHook(stringOption(options.hook));
|
|
484
|
+
const taskId = stringOption(options.task);
|
|
485
|
+
const query = stringOption(options.query);
|
|
486
|
+
const packet = await queryMemory({
|
|
487
|
+
...(taskId ? { taskId } : {}),
|
|
488
|
+
hook,
|
|
489
|
+
...(query ? { query } : {}),
|
|
490
|
+
tokenBudget: numberOption(options.budget, 900),
|
|
491
|
+
});
|
|
492
|
+
await recordMemoryEvent(process.cwd(), packet, "MEMORY_QUERIED");
|
|
493
|
+
if (options.json) {
|
|
494
|
+
io.log(JSON.stringify(packet, null, 2));
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
io.log(renderMemoryPacket(packet));
|
|
498
|
+
}
|
|
499
|
+
export async function memoryHookCommand(options, io) {
|
|
500
|
+
const point = parseMemoryHook(requireArg(options, "point"));
|
|
501
|
+
const taskId = stringOption(options.task);
|
|
502
|
+
const query = stringOption(options.query);
|
|
503
|
+
const packet = await queryMemory({
|
|
504
|
+
...(taskId ? { taskId } : {}),
|
|
505
|
+
hook: point,
|
|
506
|
+
...(query ? { query } : {}),
|
|
507
|
+
tokenBudget: numberOption(options.budget, 900),
|
|
508
|
+
});
|
|
509
|
+
await recordMemoryEvent(process.cwd(), packet, "MEMORY_HOOK_RAN");
|
|
510
|
+
if (options.json) {
|
|
511
|
+
io.log(JSON.stringify(packet, null, 2));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
io.log(renderMemoryPacket(packet));
|
|
515
|
+
}
|
|
370
516
|
export async function planCommand(options, io) {
|
|
371
517
|
const plan = await generateExecutionPlan(requireArg(options, "task"));
|
|
372
518
|
if (options.json) {
|
|
@@ -377,7 +523,12 @@ export async function planCommand(options, io) {
|
|
|
377
523
|
}
|
|
378
524
|
export async function runCommand(options, io) {
|
|
379
525
|
const taskId = requireArg(options, "task");
|
|
380
|
-
const run = await executePlanWithBudgetPreflight(taskId, process.cwd(), parseBudgetEscalationDecision(options)
|
|
526
|
+
const run = await executePlanWithBudgetPreflight(taskId, process.cwd(), parseBudgetEscalationDecision(options), removeUndefined({
|
|
527
|
+
estimatedCostUsd: stringOption(options["estimate-cost"])
|
|
528
|
+
? numberOption(options["estimate-cost"], 0)
|
|
529
|
+
: undefined,
|
|
530
|
+
override: options.yes ? true : undefined,
|
|
531
|
+
}));
|
|
381
532
|
if (options.json) {
|
|
382
533
|
io.log(JSON.stringify(run, null, 2));
|
|
383
534
|
return;
|
|
@@ -489,12 +640,22 @@ export async function workflowRunCommand(options, io) {
|
|
|
489
640
|
const taskId = requireArg(options, "task");
|
|
490
641
|
const gates = (stringOption(options.gates) ?? "phase");
|
|
491
642
|
const maxIterations = numberOption(options["max-iterations"], 5);
|
|
643
|
+
const timeoutMinutes = numberOption(options["timeout-minutes"], 0);
|
|
492
644
|
const file = autonomousRunsPath(cwd);
|
|
645
|
+
const phaseSelection = await resolveWorkflowPhaseSelection(cwd, options);
|
|
646
|
+
const config = await getWorkflowConfig(cwd);
|
|
647
|
+
const phaseTimeoutMinutes = config.workflow?.phaseTimeoutMinutes ?? 0;
|
|
493
648
|
if (!GATE_MODES.includes(gates)) {
|
|
494
649
|
throw new Error(`--gates must be one of: ${GATE_MODES.join(", ")}`);
|
|
495
650
|
}
|
|
651
|
+
if (timeoutMinutes < 0) {
|
|
652
|
+
throw new Error("--timeout-minutes must be 0 or greater");
|
|
653
|
+
}
|
|
654
|
+
if (phaseTimeoutMinutes < 0) {
|
|
655
|
+
throw new Error("workflow.phaseTimeoutMinutes must be 0 or greater");
|
|
656
|
+
}
|
|
496
657
|
if (options["dry-run"]) {
|
|
497
|
-
return workflowDryRun(options, io, taskId, gates, maxIterations);
|
|
658
|
+
return workflowDryRun(options, io, taskId, gates, maxIterations, phaseSelection);
|
|
498
659
|
}
|
|
499
660
|
let run;
|
|
500
661
|
let startIndex;
|
|
@@ -503,21 +664,39 @@ export async function workflowRunCommand(options, io) {
|
|
|
503
664
|
if (!existing)
|
|
504
665
|
throw new Error(`workflow run not found: ${String(options.resume)}`);
|
|
505
666
|
run = existing;
|
|
506
|
-
|
|
667
|
+
if (run.status === "canceled" || run.status === "failed") {
|
|
668
|
+
throw new Error(`workflow run ${run.id} is ${run.status} and cannot be resumed`);
|
|
669
|
+
}
|
|
670
|
+
const resumeSequence = phaseSequenceForRun(run, phaseSelection.sequence);
|
|
671
|
+
startIndex = resumePhaseIndex(run, resumeSequence);
|
|
507
672
|
if (startIndex === -1) {
|
|
508
673
|
io.log(`Workflow ${run.id} is already complete`);
|
|
509
674
|
if (options.json)
|
|
510
675
|
io.log(JSON.stringify({ run, file, cwd }, null, 2));
|
|
511
676
|
return;
|
|
512
677
|
}
|
|
513
|
-
|
|
678
|
+
const pausedGate = run.phases.findLast((phase) => phase.status === "gate_paused");
|
|
679
|
+
if (pausedGate && !pausedGate.approvedBy) {
|
|
680
|
+
io.log(`WARN: gate ${pausedGate.gateId ?? gateIdForPausedPhase(pausedGate.phase, resumeSequence)} has no recorded approval; continuing for backward compatibility`);
|
|
681
|
+
}
|
|
682
|
+
io.log(`Resuming run ${run.id} from phase ${resumeSequence[startIndex]?.phase}`);
|
|
683
|
+
run = { ...run, phaseSequence: resumeSequence.map((phase) => phase.phase) };
|
|
514
684
|
}
|
|
515
685
|
else {
|
|
516
|
-
run = await createAutonomousRun(cwd, {
|
|
686
|
+
run = await createAutonomousRun(cwd, {
|
|
687
|
+
taskId,
|
|
688
|
+
gates,
|
|
689
|
+
maxIterations,
|
|
690
|
+
phaseSequence: phaseSelection.sequence.map((phase) => phase.phase),
|
|
691
|
+
skippedPhases: phaseSelection.skipped,
|
|
692
|
+
});
|
|
517
693
|
startIndex = 0;
|
|
518
694
|
io.log(`Started autonomous workflow ${run.id} for task ${taskId} [gates=${gates}]`);
|
|
519
695
|
}
|
|
520
|
-
run = await executePhases(cwd, run, startIndex, io)
|
|
696
|
+
run = await executePhases(cwd, run, startIndex, io, phaseSequenceForRun(run, phaseSelection.sequence), {
|
|
697
|
+
runTimeoutMinutes: timeoutMinutes,
|
|
698
|
+
phaseTimeoutMinutes,
|
|
699
|
+
});
|
|
521
700
|
const result = { run, file, cwd };
|
|
522
701
|
if (options.json) {
|
|
523
702
|
io.log(JSON.stringify(result, null, 2));
|
|
@@ -535,7 +714,9 @@ export async function workflowRunCommand(options, io) {
|
|
|
535
714
|
}
|
|
536
715
|
export async function workflowRunListCommand(options, io) {
|
|
537
716
|
const cwd = process.cwd();
|
|
538
|
-
const runs =
|
|
717
|
+
const runs = options.active
|
|
718
|
+
? await listActiveAutonomousRuns(cwd)
|
|
719
|
+
: await listAutonomousRuns(cwd);
|
|
539
720
|
if (options.json) {
|
|
540
721
|
io.log(JSON.stringify(runs, null, 2));
|
|
541
722
|
return;
|
|
@@ -551,17 +732,35 @@ export async function workflowRunListCommand(options, io) {
|
|
|
551
732
|
io.log(` ${phases}`);
|
|
552
733
|
}
|
|
553
734
|
}
|
|
554
|
-
|
|
555
|
-
const
|
|
735
|
+
export async function workflowCancelCommand(options, io) {
|
|
736
|
+
const run = await cancelRun(process.cwd(), requireArg(options, "run"), stringOption(options.reason) ?? "Workflow run canceled");
|
|
737
|
+
if (options.json) {
|
|
738
|
+
io.log(JSON.stringify(run, null, 2));
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
io.log(`Workflow canceled [run=${run.id}]`);
|
|
742
|
+
}
|
|
743
|
+
export async function workflowGateApproveCommand(options, io) {
|
|
744
|
+
const approval = await approveWorkflowGate({
|
|
745
|
+
runId: requireArg(options, "run"),
|
|
746
|
+
gateId: requireArg(options, "gate"),
|
|
747
|
+
approver: requireArg(options, "approver"),
|
|
748
|
+
rationale: requireArg(options, "rationale"),
|
|
749
|
+
});
|
|
750
|
+
if (options.json) {
|
|
751
|
+
io.log(JSON.stringify(approval, null, 2));
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
io.log(approval.alreadyApproved
|
|
755
|
+
? `Gate ${approval.gateId} already approved by ${approval.approver}`
|
|
756
|
+
: `Approved gate ${approval.gateId} for run ${approval.run.id}`);
|
|
757
|
+
}
|
|
556
758
|
export async function workflowClarifyCommand(options, io) {
|
|
557
759
|
const cwd = process.cwd();
|
|
558
760
|
const runId = requireArg(options, "run");
|
|
559
|
-
const from = requireArg(options, "from");
|
|
560
|
-
const to = requireArg(options, "to");
|
|
761
|
+
const from = await resolveClarificationRole(cwd, requireArg(options, "from"));
|
|
762
|
+
const to = await resolveClarificationRole(cwd, requireArg(options, "to"));
|
|
561
763
|
const question = requireArg(options, "question");
|
|
562
|
-
if (!CLARIFICATION_TARGETS.includes(to)) {
|
|
563
|
-
throw new Error(`--to must be one of: ${CLARIFICATION_TARGETS.join(", ")}`);
|
|
564
|
-
}
|
|
565
764
|
const run = await readAutonomousRun(cwd, runId);
|
|
566
765
|
if (!run)
|
|
567
766
|
throw new Error(`workflow run not found: ${runId}`);
|
|
@@ -572,9 +771,6 @@ export async function workflowClarifyCommand(options, io) {
|
|
|
572
771
|
throw new Error(`no active phase found for role ${from} in run ${runId}`);
|
|
573
772
|
}
|
|
574
773
|
const activePhase = run.phases[activePhaseIdx];
|
|
575
|
-
if (!CLARIFICATION_ALLOWED_PHASES.has(activePhase.phase)) {
|
|
576
|
-
throw new Error(`clarification is only allowed from developer or qa phases (active: ${activePhase.phase})`);
|
|
577
|
-
}
|
|
578
774
|
// Open clarification record
|
|
579
775
|
const record = await openClarification(cwd, {
|
|
580
776
|
runId,
|
|
@@ -585,8 +781,9 @@ export async function workflowClarifyCommand(options, io) {
|
|
|
585
781
|
});
|
|
586
782
|
// Suspend the phase if it's still running
|
|
587
783
|
if (activePhase.status === "running") {
|
|
588
|
-
const
|
|
589
|
-
|
|
784
|
+
const sequence = phaseSequenceForRun(run);
|
|
785
|
+
const phaseSeqIdx = sequence.findIndex((d) => d.phase === activePhase.phase);
|
|
786
|
+
await suspendPhaseForClarification(cwd, run, phaseSeqIdx, sequence);
|
|
590
787
|
}
|
|
591
788
|
if (options.json) {
|
|
592
789
|
io.log(JSON.stringify(record, null, 2));
|
|
@@ -610,11 +807,12 @@ export async function workflowClarifyRespondCommand(options, io) {
|
|
|
610
807
|
answer,
|
|
611
808
|
});
|
|
612
809
|
// Resume the suspended phase back to running
|
|
613
|
-
const
|
|
810
|
+
const sequence = phaseSequenceForRun(run);
|
|
811
|
+
const phaseSeqIdx = sequence.findIndex((d) => d.role === record.fromRole);
|
|
614
812
|
if (phaseSeqIdx !== -1) {
|
|
615
813
|
const phase = run.phases.find((p) => p.role === record.fromRole);
|
|
616
814
|
if (phase?.status === "awaiting_clarification") {
|
|
617
|
-
await resumePhaseFromClarification(cwd, run, phaseSeqIdx);
|
|
815
|
+
await resumePhaseFromClarification(cwd, run, phaseSeqIdx, sequence);
|
|
618
816
|
}
|
|
619
817
|
}
|
|
620
818
|
if (options.json) {
|
|
@@ -628,14 +826,25 @@ export async function workflowClarifyRespondCommand(options, io) {
|
|
|
628
826
|
}
|
|
629
827
|
export async function workflowClarifyListCommand(options, io) {
|
|
630
828
|
const cwd = process.cwd();
|
|
631
|
-
const runId =
|
|
632
|
-
const
|
|
829
|
+
const runId = stringOption(options.run);
|
|
830
|
+
const taskId = stringOption(options.task);
|
|
831
|
+
if (runId && taskId) {
|
|
832
|
+
throw new Error("workflow clarify-list accepts either --run or --task, not both");
|
|
833
|
+
}
|
|
834
|
+
if (!runId && !taskId) {
|
|
835
|
+
throw new Error("workflow clarify-list requires --run or --task");
|
|
836
|
+
}
|
|
837
|
+
const clarifications = runId
|
|
838
|
+
? await listClarifications(cwd, runId)
|
|
839
|
+
: await listClarificationsByTask(cwd, taskId);
|
|
633
840
|
if (options.json) {
|
|
634
841
|
io.log(JSON.stringify(clarifications, null, 2));
|
|
635
842
|
return;
|
|
636
843
|
}
|
|
637
844
|
if (clarifications.length === 0) {
|
|
638
|
-
io.log(
|
|
845
|
+
io.log(runId
|
|
846
|
+
? `No clarifications for run ${runId}`
|
|
847
|
+
: `No clarifications for task ${taskId}`);
|
|
639
848
|
return;
|
|
640
849
|
}
|
|
641
850
|
for (const c of clarifications) {
|
|
@@ -646,17 +855,115 @@ export async function workflowClarifyListCommand(options, io) {
|
|
|
646
855
|
io.log(` A: ${c.answer}`);
|
|
647
856
|
}
|
|
648
857
|
}
|
|
649
|
-
const
|
|
650
|
-
|
|
858
|
+
const MEMORY_HOOK_POINTS = [
|
|
859
|
+
"before_plan",
|
|
860
|
+
"before_implementation",
|
|
861
|
+
"before_handoff",
|
|
862
|
+
"before_final",
|
|
863
|
+
"after_failure",
|
|
864
|
+
];
|
|
865
|
+
function parseMemoryHook(value) {
|
|
866
|
+
const hook = value ?? "before_implementation";
|
|
867
|
+
if (!MEMORY_HOOK_POINTS.includes(hook)) {
|
|
868
|
+
throw new Error(`memory hook must be one of: ${MEMORY_HOOK_POINTS.join(", ")}`);
|
|
869
|
+
}
|
|
870
|
+
return hook;
|
|
871
|
+
}
|
|
872
|
+
async function resolveWorkflowPhaseSelection(root, options) {
|
|
873
|
+
const config = await getWorkflowConfig(root);
|
|
874
|
+
const configuredIds = config.workflow?.phaseSequence;
|
|
875
|
+
const baseSequence = configuredIds
|
|
876
|
+
? phaseSequenceFromIds(configuredIds, "workflow.phaseSequence")
|
|
877
|
+
: AUTONOMOUS_PHASE_SEQUENCE;
|
|
878
|
+
const skipIds = parseCsv(options.skip);
|
|
879
|
+
validatePhaseIds(skipIds, "--skip");
|
|
880
|
+
const skipSet = new Set(skipIds);
|
|
881
|
+
const sequence = baseSequence.filter((phase) => !skipSet.has(phase.phase));
|
|
882
|
+
validateWorkflowPhaseSequence(sequence);
|
|
883
|
+
const sequenceIds = new Set(sequence.map((phase) => phase.phase));
|
|
884
|
+
const skipped = AUTONOMOUS_PHASE_SEQUENCE.filter((phase) => !sequenceIds.has(phase.phase));
|
|
885
|
+
return { sequence, skipped };
|
|
886
|
+
}
|
|
887
|
+
function phaseSequenceForRun(run, fallback = AUTONOMOUS_PHASE_SEQUENCE) {
|
|
888
|
+
return run.phaseSequence
|
|
889
|
+
? phaseSequenceFromIds(run.phaseSequence, "run.phaseSequence")
|
|
890
|
+
: fallback;
|
|
891
|
+
}
|
|
892
|
+
function phaseSequenceFromIds(ids, source) {
|
|
893
|
+
validatePhaseIds(ids, source);
|
|
894
|
+
const byId = new Map(AUTONOMOUS_PHASE_SEQUENCE.map((phase) => [phase.phase, phase]));
|
|
895
|
+
return ids.map((id) => byId.get(id));
|
|
896
|
+
}
|
|
897
|
+
function validatePhaseIds(ids, source) {
|
|
898
|
+
const valid = new Set(AUTONOMOUS_PHASE_SEQUENCE.map((phase) => phase.phase));
|
|
899
|
+
const unknown = ids.filter((id) => !valid.has(id));
|
|
900
|
+
if (unknown.length > 0) {
|
|
901
|
+
throw new Error(`${source} contains invalid phase(s): ${unknown.join(", ")}. Valid phases: ${[...valid].join(", ")}`);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
function validateWorkflowPhaseSequence(sequence) {
|
|
905
|
+
if (sequence.length === 0) {
|
|
906
|
+
throw new Error("workflow phase sequence cannot be empty");
|
|
907
|
+
}
|
|
908
|
+
const ids = sequence.map((phase) => phase.phase);
|
|
909
|
+
const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
|
|
910
|
+
if (duplicates.length > 0) {
|
|
911
|
+
throw new Error(`workflow phase sequence contains duplicate phase(s): ${[...new Set(duplicates)].join(", ")}`);
|
|
912
|
+
}
|
|
913
|
+
const qaIndex = ids.indexOf("qa");
|
|
914
|
+
const developerIndex = ids.indexOf("developer");
|
|
915
|
+
if (qaIndex !== -1 && developerIndex === -1) {
|
|
916
|
+
throw new Error("workflow phase sequence with qa must include developer");
|
|
917
|
+
}
|
|
918
|
+
if (qaIndex !== -1 && developerIndex > qaIndex) {
|
|
919
|
+
throw new Error("workflow phase sequence must place developer before qa");
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
function phaseExceededTimeout(run, phaseId, timeoutMs) {
|
|
923
|
+
const phase = run.phases.find((candidate) => candidate.phase === phaseId &&
|
|
924
|
+
(candidate.status === "running" ||
|
|
925
|
+
candidate.status === "awaiting_clarification"));
|
|
926
|
+
if (!phase) {
|
|
927
|
+
return false;
|
|
928
|
+
}
|
|
929
|
+
return Date.now() - new Date(phase.startedAt).getTime() > timeoutMs;
|
|
930
|
+
}
|
|
931
|
+
async function recordWorkflowTimeout(root, run, input) {
|
|
932
|
+
await appendEvent(root, {
|
|
933
|
+
type: "WORKFLOW_TIMEOUT",
|
|
934
|
+
taskId: run.taskId,
|
|
935
|
+
actor: "parent",
|
|
936
|
+
summary: input.reason,
|
|
937
|
+
metadata: {
|
|
938
|
+
runId: run.id,
|
|
939
|
+
phase: input.phase,
|
|
940
|
+
timeoutMinutes: input.timeoutMinutes,
|
|
941
|
+
},
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
function gateIdForPausedPhase(phase, sequence = AUTONOMOUS_PHASE_SEQUENCE) {
|
|
945
|
+
const sequenceIndex = sequence.findIndex((candidate) => candidate.phase === phase);
|
|
946
|
+
const next = sequence[sequenceIndex + 1]?.phase ?? "end";
|
|
947
|
+
return `${phase}->${next}`;
|
|
948
|
+
}
|
|
949
|
+
async function executePhases(cwd, run, startIndex, io, sequence, timeoutOptions) {
|
|
651
950
|
let current = run;
|
|
652
951
|
let qaFailNotes;
|
|
653
|
-
|
|
654
|
-
|
|
952
|
+
const devPhaseIndex = sequence.findIndex((d) => d.phase === "developer");
|
|
953
|
+
const runDeadlineMs = timeoutOptions.runTimeoutMinutes > 0
|
|
954
|
+
? new Date(run.createdAt).getTime() +
|
|
955
|
+
timeoutOptions.runTimeoutMinutes * 60 * 1000
|
|
956
|
+
: undefined;
|
|
957
|
+
const phaseTimeoutMs = timeoutOptions.phaseTimeoutMinutes > 0
|
|
958
|
+
? timeoutOptions.phaseTimeoutMinutes * 60 * 1000
|
|
959
|
+
: undefined;
|
|
960
|
+
for (let i = startIndex; i < sequence.length; i++) {
|
|
961
|
+
const def = sequence[i];
|
|
655
962
|
// Init phase task (skip if already has a running/done/clarification record from resume)
|
|
656
963
|
const existing = current.phases.find((p) => p.phase === def.phase && p.status !== "qa_failed");
|
|
657
964
|
if (!existing || existing.status === "pending") {
|
|
658
965
|
const retryContext = def.phase === "developer" && qaFailNotes ? qaFailNotes : undefined;
|
|
659
|
-
const phaseRecord = await initPhase(cwd, current, i, retryContext);
|
|
966
|
+
const phaseRecord = await initPhase(cwd, current, i, retryContext, sequence);
|
|
660
967
|
current = { ...current, phases: [...current.phases, phaseRecord] };
|
|
661
968
|
if (retryContext) {
|
|
662
969
|
io.log(`↺ ${def.phase} (${def.role}) retry — QA findings: ${retryContext.slice(0, 80)}`);
|
|
@@ -682,7 +989,7 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
682
989
|
}).catch(async (error) => {
|
|
683
990
|
const message = error instanceof Error ? error.message : String(error);
|
|
684
991
|
io.log(`✗ ${def.phase} provider execution failed: ${message}`);
|
|
685
|
-
current = await markRunFailed(cwd, current, message);
|
|
992
|
+
current = await markRunFailed(cwd, current, message, def.phase);
|
|
686
993
|
return null;
|
|
687
994
|
});
|
|
688
995
|
if (!llmResult)
|
|
@@ -690,6 +997,17 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
690
997
|
if (llmResult.mode === "llm") {
|
|
691
998
|
io.log(` ✓ llm artifact (${llmResult.artifact})`);
|
|
692
999
|
}
|
|
1000
|
+
if (phaseTimeoutMs !== undefined &&
|
|
1001
|
+
phaseExceededTimeout(current, def.phase, phaseTimeoutMs)) {
|
|
1002
|
+
io.log(`✗ ${def.phase} phase timeout`);
|
|
1003
|
+
await recordWorkflowTimeout(cwd, current, {
|
|
1004
|
+
phase: def.phase,
|
|
1005
|
+
reason: "phase timeout",
|
|
1006
|
+
timeoutMinutes: timeoutOptions.phaseTimeoutMinutes,
|
|
1007
|
+
});
|
|
1008
|
+
current = await markRunFailed(cwd, current, "phase timeout", def.phase);
|
|
1009
|
+
return current;
|
|
1010
|
+
}
|
|
693
1011
|
// Architect sizing gate — always enforced regardless of --gates mode.
|
|
694
1012
|
// In LLM mode, the architect phase records the sizing decision before this check.
|
|
695
1013
|
if (def.phase === "architect") {
|
|
@@ -705,15 +1023,15 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
705
1023
|
io.log(` ✓ sizing=${sizing.sizing}${sizing.points !== undefined ? ` (${sizing.points} pts)` : ""}`);
|
|
706
1024
|
}
|
|
707
1025
|
const outcome = llmResult.outcome;
|
|
708
|
-
const result = await closePhase(cwd, current, i, outcome);
|
|
1026
|
+
const result = await closePhase(cwd, current, i, outcome, sequence);
|
|
709
1027
|
current = result.run;
|
|
710
1028
|
if (result.handoffArtifact) {
|
|
711
|
-
io.log(` ✓ handoff → ${
|
|
1029
|
+
io.log(` ✓ handoff → ${sequence[i + 1]?.role ?? "end"} (${result.handoffArtifact})`);
|
|
712
1030
|
}
|
|
713
1031
|
if (result.reviewArtifact) {
|
|
714
|
-
const nextPhase =
|
|
1032
|
+
const nextPhase = sequence[i + 1]?.phase;
|
|
715
1033
|
io.log(` ⏸ gate ${def.phase}→${nextPhase} — review: ${result.reviewArtifact}`);
|
|
716
|
-
io.log(` Approve: orchestra workflow
|
|
1034
|
+
io.log(` Approve: orchestra workflow gate-approve --run ${current.id} --gate ${def.phase}->${nextPhase} --approver <name> --rationale "<text>"`);
|
|
717
1035
|
io.log(``);
|
|
718
1036
|
io.log(`╔══ GATE PAUSE: ${def.phase.toUpperCase()} → ${nextPhase?.toUpperCase()} ══════════════════════`);
|
|
719
1037
|
io.log(`║ Run: ${current.id}`);
|
|
@@ -721,6 +1039,8 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
721
1039
|
io.log(`║ Review: ${result.reviewArtifact}`);
|
|
722
1040
|
io.log(`║`);
|
|
723
1041
|
io.log(`║ To approve, run:`);
|
|
1042
|
+
io.log(`║ orchestra workflow gate-approve --run ${current.id} --gate ${def.phase}->${nextPhase} --approver <name> --rationale "<text>"`);
|
|
1043
|
+
io.log(`║ Then resume:`);
|
|
724
1044
|
io.log(`║ orchestra workflow run --task ${current.taskId} --resume ${current.id}`);
|
|
725
1045
|
io.log(`╚══════════════════════════════════════════════════════════════`);
|
|
726
1046
|
return current;
|
|
@@ -730,7 +1050,7 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
730
1050
|
if (closedPhase) {
|
|
731
1051
|
qaFailNotes = closedPhase.notes ?? "QA findings — see QA phase artifact";
|
|
732
1052
|
io.log(` ✗ qa failed (iteration ${current.qaIterations}/${current.maxIterations}) — routing back to developer`);
|
|
733
|
-
i =
|
|
1053
|
+
i = devPhaseIndex - 1; // will be incremented to developer at top of loop
|
|
734
1054
|
continue;
|
|
735
1055
|
}
|
|
736
1056
|
// Release phase: auto-create PR if configured
|
|
@@ -761,18 +1081,29 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
761
1081
|
}
|
|
762
1082
|
}
|
|
763
1083
|
}
|
|
1084
|
+
if (runDeadlineMs !== undefined && Date.now() > runDeadlineMs) {
|
|
1085
|
+
const reason = `wall-clock timeout after ${timeoutOptions.runTimeoutMinutes} minutes`;
|
|
1086
|
+
io.log(`✗ ${reason}`);
|
|
1087
|
+
await recordWorkflowTimeout(cwd, current, {
|
|
1088
|
+
phase: def.phase,
|
|
1089
|
+
reason,
|
|
1090
|
+
timeoutMinutes: timeoutOptions.runTimeoutMinutes,
|
|
1091
|
+
});
|
|
1092
|
+
current = await markRunFailed(cwd, current, reason);
|
|
1093
|
+
return current;
|
|
1094
|
+
}
|
|
764
1095
|
}
|
|
765
1096
|
current = await markRunDone(cwd, current);
|
|
766
1097
|
return current;
|
|
767
1098
|
}
|
|
768
|
-
async function workflowDryRun(options, io, taskId, gates, maxIterations) {
|
|
1099
|
+
async function workflowDryRun(options, io, taskId, gates, maxIterations, phaseSelection) {
|
|
769
1100
|
const gateTransitions = new Set(["po→architect", "qa→release"]);
|
|
770
1101
|
io.log(`Dry run — no records will be created`);
|
|
771
1102
|
io.log(`Task: ${taskId} gates: ${gates} max-iterations: ${maxIterations}`);
|
|
772
1103
|
io.log(``);
|
|
773
|
-
for (let i = 0; i <
|
|
774
|
-
const def =
|
|
775
|
-
const next =
|
|
1104
|
+
for (let i = 0; i < phaseSelection.sequence.length; i++) {
|
|
1105
|
+
const def = phaseSelection.sequence[i];
|
|
1106
|
+
const next = phaseSelection.sequence[i + 1];
|
|
776
1107
|
const transitionKey = next ? `${def.phase}→${next.phase}` : "";
|
|
777
1108
|
const gateLabel = gates === "all"
|
|
778
1109
|
? "gate=yes"
|
|
@@ -781,13 +1112,18 @@ async function workflowDryRun(options, io, taskId, gates, maxIterations) {
|
|
|
781
1112
|
: "gate=no";
|
|
782
1113
|
io.log(` ${def.phase} (${def.role}) ${gateLabel}`);
|
|
783
1114
|
}
|
|
1115
|
+
if (phaseSelection.skipped.length > 0) {
|
|
1116
|
+
io.log(``);
|
|
1117
|
+
io.log(`Skipped: ${phaseSelection.skipped.map((phase) => phase.phase).join(", ")}`);
|
|
1118
|
+
}
|
|
784
1119
|
if (options.json) {
|
|
785
1120
|
io.log(JSON.stringify({
|
|
786
1121
|
dryRun: true,
|
|
787
1122
|
taskId,
|
|
788
1123
|
gates,
|
|
789
1124
|
maxIterations,
|
|
790
|
-
phases:
|
|
1125
|
+
phases: phaseSelection.sequence,
|
|
1126
|
+
skipped: phaseSelection.skipped,
|
|
791
1127
|
}, null, 2));
|
|
792
1128
|
}
|
|
793
1129
|
}
|
|
@@ -810,6 +1146,16 @@ function parseCsv(value) {
|
|
|
810
1146
|
.map((item) => item.trim())
|
|
811
1147
|
.filter(Boolean);
|
|
812
1148
|
}
|
|
1149
|
+
function csvOption(value) {
|
|
1150
|
+
if (typeof value !== "string") {
|
|
1151
|
+
return undefined;
|
|
1152
|
+
}
|
|
1153
|
+
return parseCsv(value);
|
|
1154
|
+
}
|
|
1155
|
+
function singleValueArrayOption(value) {
|
|
1156
|
+
const option = stringOption(value);
|
|
1157
|
+
return option ? [option] : undefined;
|
|
1158
|
+
}
|
|
813
1159
|
function stringOption(value) {
|
|
814
1160
|
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
815
1161
|
}
|
|
@@ -914,6 +1260,24 @@ function renderDependencyReport(report) {
|
|
|
914
1260
|
: report.dependencies.map((dependency) => `- ${dependency.id}: ${dependency.status} (${dependency.isComplete ? "complete" : "incomplete"})`)),
|
|
915
1261
|
].join("\n");
|
|
916
1262
|
}
|
|
1263
|
+
function renderPreRunValidation(report) {
|
|
1264
|
+
const status = report.allowed ? "allowed" : "blocked";
|
|
1265
|
+
const missing = report.missing.length > 0 ? report.missing.join(", ") : "none";
|
|
1266
|
+
return [
|
|
1267
|
+
`Pre-run validation for ${report.taskId}: ${status}`,
|
|
1268
|
+
`- Ready: ${String(report.isReady)}`,
|
|
1269
|
+
`- Bypassed: ${String(report.isBypassed)}`,
|
|
1270
|
+
`- Missing: ${missing}`,
|
|
1271
|
+
`- Task: ${String(report.checks.task)}`,
|
|
1272
|
+
`- Estimate: ${String(report.checks.estimate)}`,
|
|
1273
|
+
`- Workflow run: ${String(report.checks.workflowRun)}`,
|
|
1274
|
+
`- Evidence: ${String(report.checks.evidence)}`,
|
|
1275
|
+
`- Review: ${String(report.checks.review)}`,
|
|
1276
|
+
...(report.bypassDecisionArtifact
|
|
1277
|
+
? [`- Bypass decision: ${report.bypassDecisionArtifact}`]
|
|
1278
|
+
: []),
|
|
1279
|
+
].join("\n");
|
|
1280
|
+
}
|
|
917
1281
|
function renderTaskGraphPlan(plan) {
|
|
918
1282
|
return [
|
|
919
1283
|
"Task graph plan",
|
|
@@ -931,6 +1295,28 @@ function renderTaskGraphPlan(plan) {
|
|
|
931
1295
|
...taskGraphReadyLines(plan.complete),
|
|
932
1296
|
].join("\n");
|
|
933
1297
|
}
|
|
1298
|
+
function renderTaskGraphDryRunPreview(preview) {
|
|
1299
|
+
return [
|
|
1300
|
+
"Task graph dry run",
|
|
1301
|
+
`- Mode: ${preview.mode}`,
|
|
1302
|
+
`- Would mutate: ${String(preview.wouldMutate)}`,
|
|
1303
|
+
`- Selected: ${preview.selectedTaskIds.join(", ")}`,
|
|
1304
|
+
"",
|
|
1305
|
+
"Tasks:",
|
|
1306
|
+
...preview.tasks.map((task) => {
|
|
1307
|
+
const budget = task.budget
|
|
1308
|
+
? ` budget=${task.budget.passed ? "pass" : "fail"} scopes=${task.budget.appliedBudgets.join(",") || "none"}`
|
|
1309
|
+
: " budget=not-configured";
|
|
1310
|
+
return `- ${task.id} ${task.title} (${task.ownerRole}) ${task.provider}/${task.model} fallbacks=${task.fallbacks.join(",") || "none"}${budget}`;
|
|
1311
|
+
}),
|
|
1312
|
+
"",
|
|
1313
|
+
"Locked:",
|
|
1314
|
+
...taskGraphLockedLines(preview.locked),
|
|
1315
|
+
"",
|
|
1316
|
+
"Blocked:",
|
|
1317
|
+
...taskGraphBlockedLines(preview.blocked),
|
|
1318
|
+
].join("\n");
|
|
1319
|
+
}
|
|
934
1320
|
function renderTaskGraphBatchRun(batch) {
|
|
935
1321
|
return [
|
|
936
1322
|
"Task graph batch run",
|
|
@@ -1118,6 +1504,8 @@ function renderTaskContextMarkdown(context) {
|
|
|
1118
1504
|
: context.skills.selected.map((item) => `- ${item.skill.id} (score ${item.score}): ${item.rationale.join("; ")}`)),
|
|
1119
1505
|
`- Source groups: ${context.skills.sourceGroups.join(", ") || "none"}`,
|
|
1120
1506
|
"",
|
|
1507
|
+
renderMemoryPacket(context.memory),
|
|
1508
|
+
"",
|
|
1121
1509
|
"## Decisions",
|
|
1122
1510
|
...eventLines(context.decisions),
|
|
1123
1511
|
"",
|
|
@@ -1190,6 +1578,27 @@ function renderPlaywrightPlanMarkdown(plan) {
|
|
|
1190
1578
|
"",
|
|
1191
1579
|
].join("\n");
|
|
1192
1580
|
}
|
|
1581
|
+
function renderTaskDetails(task) {
|
|
1582
|
+
return [
|
|
1583
|
+
`ID: ${task.id}`,
|
|
1584
|
+
`Title: ${task.title}`,
|
|
1585
|
+
`Owner: ${task.ownerRole}`,
|
|
1586
|
+
`Status: ${task.status}`,
|
|
1587
|
+
`Goal: ${task.goal ?? "—"}`,
|
|
1588
|
+
`Scope: ${task.scope ?? "—"}`,
|
|
1589
|
+
`Acceptance Criteria: ${renderInlineList(task.acceptanceCriteria)}`,
|
|
1590
|
+
`Assumptions: ${renderInlineList(task.assumptions)}`,
|
|
1591
|
+
`Risks: ${renderInlineList(task.risks)}`,
|
|
1592
|
+
`Paths: ${renderInlineList(task.paths)}`,
|
|
1593
|
+
`Test Strategy: ${task.testStrategy ?? "—"}`,
|
|
1594
|
+
`Blocked Reason: ${task.blockedReason ?? "—"}`,
|
|
1595
|
+
`Created: ${task.createdAt}`,
|
|
1596
|
+
`Updated: ${task.updatedAt}`,
|
|
1597
|
+
];
|
|
1598
|
+
}
|
|
1599
|
+
function renderInlineList(values) {
|
|
1600
|
+
return values && values.length > 0 ? values.join(", ") : "—";
|
|
1601
|
+
}
|
|
1193
1602
|
function eventLines(events) {
|
|
1194
1603
|
return events.length === 0
|
|
1195
1604
|
? ["- none"]
|