@jterrats/open-orchestra 0.4.1 → 0.4.2-beta.2
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/AGENTS.md +5 -3
- package/dist/advisory-artifacts.d.ts +14 -0
- package/dist/advisory-artifacts.js +100 -0
- package/dist/advisory-artifacts.js.map +1 -0
- package/dist/assets/web-console.js +230 -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 +97 -18
- package/dist/cli.js.map +1 -1
- package/dist/command-manifest.d.ts +3 -0
- package/dist/command-manifest.js +17 -1
- package/dist/command-manifest.js.map +1 -1
- package/dist/commands.d.ts +10 -2
- package/dist/commands.js +484 -49
- package/dist/commands.js.map +1 -1
- package/dist/constants.js +13 -0
- package/dist/constants.js.map +1 -1
- package/dist/context-budget.d.ts +4 -0
- package/dist/context-budget.js +119 -0
- package/dist/context-budget.js.map +1 -0
- package/dist/mcp-oauth-proxy.d.ts +79 -0
- package/dist/mcp-oauth-proxy.js +396 -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-commands.js +10 -0
- package/dist/runtime-commands.js.map +1 -1
- package/dist/runtime-execution-renderer.js +5 -0
- package/dist/runtime-execution-renderer.js.map +1 -1
- package/dist/runtime-execution.d.ts +4 -2
- package/dist/runtime-execution.js +17 -4
- package/dist/runtime-execution.js.map +1 -1
- package/dist/setup-agents-import.d.ts +42 -0
- package/dist/setup-agents-import.js +337 -0
- package/dist/setup-agents-import.js.map +1 -0
- 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 +113 -13
- package/dist/tool-commands.js.map +1 -1
- package/dist/types.d.ts +163 -4
- package/dist/types.js.map +1 -1
- package/dist/web-api.js +216 -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 +24 -4
- package/dist/workflow-services.js +478 -12
- package/dist/workflow-services.js.map +1 -1
- package/dist/workflow-templates.d.ts +1 -0
- package/dist/workflow-templates.js +1 -0
- package/dist/workflow-templates.js.map +1 -1
- package/dist/workspace.js +6 -0
- package/dist/workspace.js.map +1 -1
- package/docs/command-contracts.md +22 -0
- package/docs/mcp-oauth-proxy-evaluation.md +14 -0
- package/docs/runtime-adapters.md +4 -0
- package/docs/setup-agents-bridge.md +61 -0
- package/docs/traceability-flow.md +89 -0
- package/package.json +8 -6
- package/skills/proactive-orchestra/SKILL.md +27 -0
- package/skills/proactive-orchestra/manifest.json +41 -0
package/dist/commands.js
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
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";
|
|
10
12
|
import { listCollaborationFlows, recommendCollaborationFlow, } from "./collaboration-flows.js";
|
|
11
13
|
import { listWorkflowTemplates, renderWorkflowTemplates, selectWorkflowTemplates, validateWorkflowTemplates, } from "./workflow-templates.js";
|
|
12
14
|
import { parseRuntimeTarget } from "./runtime-adapters.js";
|
|
15
|
+
import { importSetupAgentsArtifacts } from "./setup-agents-import.js";
|
|
13
16
|
export { instructionsApplyCommand, instructionsBlockCommand, instructionsImportsCommand, instructionsStaleCommand, } from "./instruction-commands.js";
|
|
14
17
|
export { benchmarkCommand, estimateCommand } from "./metrics-commands.js";
|
|
15
18
|
export { burndownCommand, calibrationCommand, sprintCommand, velocityCommand, } from "./sprint-commands.js";
|
|
16
19
|
export { approvalsApproveCommand, approvalsListCommand, approvalsRejectCommand, approvalsShowCommand, budgetCheckCommand, configShowCommand, modelCompleteFakeCommand, modelProvidersCommand, modelProvenanceAddCommand, modelProvenanceListCommand, modelSetRoleCommand, usageCommand, } from "./model-commands.js";
|
|
17
20
|
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";
|
|
21
|
+
export { lessonsAddCommand, lessonsDeleteCommand, lessonsListCommand, lessonsPromoteCommand, skillsListCommand, skillsPlanCommand, skillsRenderCommand, skillsValidateCommand, sourcesListCommand, } from "./skills-commands.js";
|
|
22
|
+
export { diagramsLintCommand, mcpOAuthProxyEvaluateCommand, mcpOAuthProxyStartCommand, mcpOAuthProxyTokenCommand, } from "./tool-commands.js";
|
|
20
23
|
export { telemetryDisableCommand, telemetryEnableCommand, telemetryEvalDatasetCommand, telemetryExportCommand, telemetryStatusCommand, telemetrySubmitCommand, } from "./telemetry-commands.js";
|
|
21
24
|
import { buildPrBody, createPullRequest } from "./github.js";
|
|
22
25
|
export async function initCommand(options, io) {
|
|
@@ -63,6 +66,23 @@ export async function statusCommand(options, io) {
|
|
|
63
66
|
}
|
|
64
67
|
}
|
|
65
68
|
export async function validateCommand(options, io) {
|
|
69
|
+
if (options["pre-run"]) {
|
|
70
|
+
const report = await validatePreRun(requireArg(options, "task"), removeUndefined({
|
|
71
|
+
bypass: Boolean(options.bypass),
|
|
72
|
+
bypassOwner: stringOption(options["bypass-owner"]),
|
|
73
|
+
bypassRationale: stringOption(options["bypass-rationale"]),
|
|
74
|
+
}));
|
|
75
|
+
if (options.json) {
|
|
76
|
+
io.log(JSON.stringify(report, null, 2));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
io.log(renderPreRunValidation(report));
|
|
80
|
+
}
|
|
81
|
+
if (!report.allowed) {
|
|
82
|
+
throw new Error(`pre-run validation failed: ${report.missing.join(", ")}`);
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
66
86
|
const report = await validateWorkspace();
|
|
67
87
|
if (options.json) {
|
|
68
88
|
io.log(JSON.stringify(report, null, 2));
|
|
@@ -113,8 +133,27 @@ export async function taskAddCommand(options, io) {
|
|
|
113
133
|
const task = await addTask(input);
|
|
114
134
|
io.log(`Added task ${task.id}`);
|
|
115
135
|
}
|
|
136
|
+
export async function setupAgentsImportCommand(options, io) {
|
|
137
|
+
const report = await importSetupAgentsArtifacts(removeUndefined({
|
|
138
|
+
source: stringOption(options.source),
|
|
139
|
+
}));
|
|
140
|
+
if (options.json) {
|
|
141
|
+
io.log(JSON.stringify(report, null, 2));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
io.log(`Imported setup-agents artifacts from ${report.sourceRoot}`);
|
|
145
|
+
io.log(`Profiles: ${report.profiles.imported.length} imported, ${report.profiles.conflicts.length} conflicts`);
|
|
146
|
+
io.log(`Tasks: ${report.tasks.imported.length} imported, ${report.tasks.skipped.length} skipped, ${report.tasks.conflicts.length} conflicts`);
|
|
147
|
+
const evidenceCount = report.evidence.imported.reduce((total, item) => total + item.ids.length, 0);
|
|
148
|
+
const handoffCount = report.handoffs.imported.reduce((total, item) => total + item.ids.length, 0);
|
|
149
|
+
io.log(`References: ${evidenceCount} evidence, ${handoffCount} handoffs preserved`);
|
|
150
|
+
}
|
|
116
151
|
export async function taskListCommand(options, io) {
|
|
117
|
-
|
|
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);
|
|
118
157
|
if (options.json) {
|
|
119
158
|
io.log(JSON.stringify(tasks, null, 2));
|
|
120
159
|
return;
|
|
@@ -127,6 +166,67 @@ export async function taskListCommand(options, io) {
|
|
|
127
166
|
io.log(`${task.id} [${task.status}] ${task.title} (${task.ownerRole})`);
|
|
128
167
|
}
|
|
129
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
|
+
}
|
|
130
230
|
export async function rolesListCommand(options, io) {
|
|
131
231
|
const roles = await listRoles();
|
|
132
232
|
if (options.json) {
|
|
@@ -146,6 +246,15 @@ export async function taskUpdateCommand(options, io) {
|
|
|
146
246
|
const id = requireArg(options, "id");
|
|
147
247
|
await updateTask(removeUndefined({
|
|
148
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"]),
|
|
149
258
|
status: stringOption(options.status),
|
|
150
259
|
blockedReason: stringOption(options["blocked-reason"]),
|
|
151
260
|
}));
|
|
@@ -168,6 +277,15 @@ export async function graphPlanCommand(options, io) {
|
|
|
168
277
|
io.log(renderTaskGraphPlan(plan));
|
|
169
278
|
}
|
|
170
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
|
+
}
|
|
171
289
|
const run = await executeNextReadyTask();
|
|
172
290
|
if (options.json) {
|
|
173
291
|
io.log(JSON.stringify(run, null, 2));
|
|
@@ -176,6 +294,15 @@ export async function graphRunNextCommand(options, io) {
|
|
|
176
294
|
io.log(renderExecutionRunMarkdown(run));
|
|
177
295
|
}
|
|
178
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
|
+
}
|
|
179
306
|
const batch = await executeReadyTaskBatch();
|
|
180
307
|
if (options.json) {
|
|
181
308
|
io.log(JSON.stringify(batch, null, 2));
|
|
@@ -248,6 +375,7 @@ export async function handoffCommand(options, io) {
|
|
|
248
375
|
gaps: stringOption(options.gaps),
|
|
249
376
|
risks: stringOption(options.risks),
|
|
250
377
|
playwright: stringOption(options.playwright),
|
|
378
|
+
updateOwner: options["update-owner"] ? true : undefined,
|
|
251
379
|
}));
|
|
252
380
|
io.log(`Created ${artifact}`);
|
|
253
381
|
}
|
|
@@ -360,13 +488,49 @@ export async function delegationDecideCommand(options, io) {
|
|
|
360
488
|
io.log(renderDelegationDecisionMarkdown(decision));
|
|
361
489
|
}
|
|
362
490
|
export async function contextCommand(options, io) {
|
|
363
|
-
const context = await getTaskContext(requireArg(options, "task"))
|
|
491
|
+
const context = await getTaskContext(requireArg(options, "task"), process.cwd(), {
|
|
492
|
+
tokenBudget: numberOption(options.budget, 3000),
|
|
493
|
+
});
|
|
364
494
|
if (options.json) {
|
|
365
495
|
io.log(JSON.stringify(context, null, 2));
|
|
366
496
|
return;
|
|
367
497
|
}
|
|
368
498
|
io.log(renderTaskContextMarkdown(context));
|
|
369
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
|
+
}
|
|
370
534
|
export async function planCommand(options, io) {
|
|
371
535
|
const plan = await generateExecutionPlan(requireArg(options, "task"));
|
|
372
536
|
if (options.json) {
|
|
@@ -377,7 +541,12 @@ export async function planCommand(options, io) {
|
|
|
377
541
|
}
|
|
378
542
|
export async function runCommand(options, io) {
|
|
379
543
|
const taskId = requireArg(options, "task");
|
|
380
|
-
const run = await executePlanWithBudgetPreflight(taskId, process.cwd(), parseBudgetEscalationDecision(options)
|
|
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
|
+
}));
|
|
381
550
|
if (options.json) {
|
|
382
551
|
io.log(JSON.stringify(run, null, 2));
|
|
383
552
|
return;
|
|
@@ -470,11 +639,14 @@ export async function workflowTemplateSelectCommand(options, io) {
|
|
|
470
639
|
}
|
|
471
640
|
}
|
|
472
641
|
export async function workflowTemplateRenderCommand(options, io) {
|
|
473
|
-
const context = await getTaskContext(requireArg(options, "task"))
|
|
642
|
+
const context = await getTaskContext(requireArg(options, "task"), process.cwd(), {
|
|
643
|
+
tokenBudget: numberOption(options.budget, 2500),
|
|
644
|
+
});
|
|
474
645
|
const rendered = renderWorkflowTemplates({
|
|
475
646
|
task: context.task,
|
|
476
647
|
target: parseSkillRenderTarget(stringOption(options.target) ?? "generic"),
|
|
477
648
|
templates: context.workflowTemplates,
|
|
649
|
+
contextBudget: context.contextBudget,
|
|
478
650
|
});
|
|
479
651
|
if (options.json) {
|
|
480
652
|
io.log(JSON.stringify(rendered, null, 2));
|
|
@@ -489,12 +661,22 @@ export async function workflowRunCommand(options, io) {
|
|
|
489
661
|
const taskId = requireArg(options, "task");
|
|
490
662
|
const gates = (stringOption(options.gates) ?? "phase");
|
|
491
663
|
const maxIterations = numberOption(options["max-iterations"], 5);
|
|
664
|
+
const timeoutMinutes = numberOption(options["timeout-minutes"], 0);
|
|
492
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;
|
|
493
669
|
if (!GATE_MODES.includes(gates)) {
|
|
494
670
|
throw new Error(`--gates must be one of: ${GATE_MODES.join(", ")}`);
|
|
495
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
|
+
}
|
|
496
678
|
if (options["dry-run"]) {
|
|
497
|
-
return workflowDryRun(options, io, taskId, gates, maxIterations);
|
|
679
|
+
return workflowDryRun(options, io, taskId, gates, maxIterations, phaseSelection);
|
|
498
680
|
}
|
|
499
681
|
let run;
|
|
500
682
|
let startIndex;
|
|
@@ -503,21 +685,39 @@ export async function workflowRunCommand(options, io) {
|
|
|
503
685
|
if (!existing)
|
|
504
686
|
throw new Error(`workflow run not found: ${String(options.resume)}`);
|
|
505
687
|
run = existing;
|
|
506
|
-
|
|
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);
|
|
507
693
|
if (startIndex === -1) {
|
|
508
694
|
io.log(`Workflow ${run.id} is already complete`);
|
|
509
695
|
if (options.json)
|
|
510
696
|
io.log(JSON.stringify({ run, file, cwd }, null, 2));
|
|
511
697
|
return;
|
|
512
698
|
}
|
|
513
|
-
|
|
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) };
|
|
514
705
|
}
|
|
515
706
|
else {
|
|
516
|
-
run = await createAutonomousRun(cwd, {
|
|
707
|
+
run = await createAutonomousRun(cwd, {
|
|
708
|
+
taskId,
|
|
709
|
+
gates,
|
|
710
|
+
maxIterations,
|
|
711
|
+
phaseSequence: phaseSelection.sequence.map((phase) => phase.phase),
|
|
712
|
+
skippedPhases: phaseSelection.skipped,
|
|
713
|
+
});
|
|
517
714
|
startIndex = 0;
|
|
518
715
|
io.log(`Started autonomous workflow ${run.id} for task ${taskId} [gates=${gates}]`);
|
|
519
716
|
}
|
|
520
|
-
run = await executePhases(cwd, run, startIndex, io)
|
|
717
|
+
run = await executePhases(cwd, run, startIndex, io, phaseSequenceForRun(run, phaseSelection.sequence), {
|
|
718
|
+
runTimeoutMinutes: timeoutMinutes,
|
|
719
|
+
phaseTimeoutMinutes,
|
|
720
|
+
});
|
|
521
721
|
const result = { run, file, cwd };
|
|
522
722
|
if (options.json) {
|
|
523
723
|
io.log(JSON.stringify(result, null, 2));
|
|
@@ -535,7 +735,9 @@ export async function workflowRunCommand(options, io) {
|
|
|
535
735
|
}
|
|
536
736
|
export async function workflowRunListCommand(options, io) {
|
|
537
737
|
const cwd = process.cwd();
|
|
538
|
-
const runs =
|
|
738
|
+
const runs = options.active
|
|
739
|
+
? await listActiveAutonomousRuns(cwd)
|
|
740
|
+
: await listAutonomousRuns(cwd);
|
|
539
741
|
if (options.json) {
|
|
540
742
|
io.log(JSON.stringify(runs, null, 2));
|
|
541
743
|
return;
|
|
@@ -551,17 +753,35 @@ export async function workflowRunListCommand(options, io) {
|
|
|
551
753
|
io.log(` ${phases}`);
|
|
552
754
|
}
|
|
553
755
|
}
|
|
554
|
-
|
|
555
|
-
const
|
|
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
|
+
}
|
|
556
779
|
export async function workflowClarifyCommand(options, io) {
|
|
557
780
|
const cwd = process.cwd();
|
|
558
781
|
const runId = requireArg(options, "run");
|
|
559
|
-
const from = requireArg(options, "from");
|
|
560
|
-
const to = requireArg(options, "to");
|
|
782
|
+
const from = await resolveClarificationRole(cwd, requireArg(options, "from"));
|
|
783
|
+
const to = await resolveClarificationRole(cwd, requireArg(options, "to"));
|
|
561
784
|
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
785
|
const run = await readAutonomousRun(cwd, runId);
|
|
566
786
|
if (!run)
|
|
567
787
|
throw new Error(`workflow run not found: ${runId}`);
|
|
@@ -572,9 +792,6 @@ export async function workflowClarifyCommand(options, io) {
|
|
|
572
792
|
throw new Error(`no active phase found for role ${from} in run ${runId}`);
|
|
573
793
|
}
|
|
574
794
|
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
795
|
// Open clarification record
|
|
579
796
|
const record = await openClarification(cwd, {
|
|
580
797
|
runId,
|
|
@@ -585,8 +802,9 @@ export async function workflowClarifyCommand(options, io) {
|
|
|
585
802
|
});
|
|
586
803
|
// Suspend the phase if it's still running
|
|
587
804
|
if (activePhase.status === "running") {
|
|
588
|
-
const
|
|
589
|
-
|
|
805
|
+
const sequence = phaseSequenceForRun(run);
|
|
806
|
+
const phaseSeqIdx = sequence.findIndex((d) => d.phase === activePhase.phase);
|
|
807
|
+
await suspendPhaseForClarification(cwd, run, phaseSeqIdx, sequence);
|
|
590
808
|
}
|
|
591
809
|
if (options.json) {
|
|
592
810
|
io.log(JSON.stringify(record, null, 2));
|
|
@@ -610,11 +828,12 @@ export async function workflowClarifyRespondCommand(options, io) {
|
|
|
610
828
|
answer,
|
|
611
829
|
});
|
|
612
830
|
// Resume the suspended phase back to running
|
|
613
|
-
const
|
|
831
|
+
const sequence = phaseSequenceForRun(run);
|
|
832
|
+
const phaseSeqIdx = sequence.findIndex((d) => d.role === record.fromRole);
|
|
614
833
|
if (phaseSeqIdx !== -1) {
|
|
615
834
|
const phase = run.phases.find((p) => p.role === record.fromRole);
|
|
616
835
|
if (phase?.status === "awaiting_clarification") {
|
|
617
|
-
await resumePhaseFromClarification(cwd, run, phaseSeqIdx);
|
|
836
|
+
await resumePhaseFromClarification(cwd, run, phaseSeqIdx, sequence);
|
|
618
837
|
}
|
|
619
838
|
}
|
|
620
839
|
if (options.json) {
|
|
@@ -628,14 +847,25 @@ export async function workflowClarifyRespondCommand(options, io) {
|
|
|
628
847
|
}
|
|
629
848
|
export async function workflowClarifyListCommand(options, io) {
|
|
630
849
|
const cwd = process.cwd();
|
|
631
|
-
const runId =
|
|
632
|
-
const
|
|
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);
|
|
633
861
|
if (options.json) {
|
|
634
862
|
io.log(JSON.stringify(clarifications, null, 2));
|
|
635
863
|
return;
|
|
636
864
|
}
|
|
637
865
|
if (clarifications.length === 0) {
|
|
638
|
-
io.log(
|
|
866
|
+
io.log(runId
|
|
867
|
+
? `No clarifications for run ${runId}`
|
|
868
|
+
: `No clarifications for task ${taskId}`);
|
|
639
869
|
return;
|
|
640
870
|
}
|
|
641
871
|
for (const c of clarifications) {
|
|
@@ -646,17 +876,115 @@ export async function workflowClarifyListCommand(options, io) {
|
|
|
646
876
|
io.log(` A: ${c.answer}`);
|
|
647
877
|
}
|
|
648
878
|
}
|
|
649
|
-
const
|
|
650
|
-
|
|
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) {
|
|
651
971
|
let current = run;
|
|
652
972
|
let qaFailNotes;
|
|
653
|
-
|
|
654
|
-
|
|
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];
|
|
655
983
|
// Init phase task (skip if already has a running/done/clarification record from resume)
|
|
656
984
|
const existing = current.phases.find((p) => p.phase === def.phase && p.status !== "qa_failed");
|
|
657
985
|
if (!existing || existing.status === "pending") {
|
|
658
986
|
const retryContext = def.phase === "developer" && qaFailNotes ? qaFailNotes : undefined;
|
|
659
|
-
const phaseRecord = await initPhase(cwd, current, i, retryContext);
|
|
987
|
+
const phaseRecord = await initPhase(cwd, current, i, retryContext, sequence);
|
|
660
988
|
current = { ...current, phases: [...current.phases, phaseRecord] };
|
|
661
989
|
if (retryContext) {
|
|
662
990
|
io.log(`↺ ${def.phase} (${def.role}) retry — QA findings: ${retryContext.slice(0, 80)}`);
|
|
@@ -682,7 +1010,7 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
682
1010
|
}).catch(async (error) => {
|
|
683
1011
|
const message = error instanceof Error ? error.message : String(error);
|
|
684
1012
|
io.log(`✗ ${def.phase} provider execution failed: ${message}`);
|
|
685
|
-
current = await markRunFailed(cwd, current, message);
|
|
1013
|
+
current = await markRunFailed(cwd, current, message, def.phase);
|
|
686
1014
|
return null;
|
|
687
1015
|
});
|
|
688
1016
|
if (!llmResult)
|
|
@@ -690,6 +1018,17 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
690
1018
|
if (llmResult.mode === "llm") {
|
|
691
1019
|
io.log(` ✓ llm artifact (${llmResult.artifact})`);
|
|
692
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
|
+
}
|
|
693
1032
|
// Architect sizing gate — always enforced regardless of --gates mode.
|
|
694
1033
|
// In LLM mode, the architect phase records the sizing decision before this check.
|
|
695
1034
|
if (def.phase === "architect") {
|
|
@@ -705,15 +1044,15 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
705
1044
|
io.log(` ✓ sizing=${sizing.sizing}${sizing.points !== undefined ? ` (${sizing.points} pts)` : ""}`);
|
|
706
1045
|
}
|
|
707
1046
|
const outcome = llmResult.outcome;
|
|
708
|
-
const result = await closePhase(cwd, current, i, outcome);
|
|
1047
|
+
const result = await closePhase(cwd, current, i, outcome, sequence);
|
|
709
1048
|
current = result.run;
|
|
710
1049
|
if (result.handoffArtifact) {
|
|
711
|
-
io.log(` ✓ handoff → ${
|
|
1050
|
+
io.log(` ✓ handoff → ${sequence[i + 1]?.role ?? "end"} (${result.handoffArtifact})`);
|
|
712
1051
|
}
|
|
713
1052
|
if (result.reviewArtifact) {
|
|
714
|
-
const nextPhase =
|
|
1053
|
+
const nextPhase = sequence[i + 1]?.phase;
|
|
715
1054
|
io.log(` ⏸ gate ${def.phase}→${nextPhase} — review: ${result.reviewArtifact}`);
|
|
716
|
-
io.log(` Approve: orchestra workflow
|
|
1055
|
+
io.log(` Approve: orchestra workflow gate-approve --run ${current.id} --gate ${def.phase}->${nextPhase} --approver <name> --rationale "<text>"`);
|
|
717
1056
|
io.log(``);
|
|
718
1057
|
io.log(`╔══ GATE PAUSE: ${def.phase.toUpperCase()} → ${nextPhase?.toUpperCase()} ══════════════════════`);
|
|
719
1058
|
io.log(`║ Run: ${current.id}`);
|
|
@@ -721,6 +1060,8 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
721
1060
|
io.log(`║ Review: ${result.reviewArtifact}`);
|
|
722
1061
|
io.log(`║`);
|
|
723
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:`);
|
|
724
1065
|
io.log(`║ orchestra workflow run --task ${current.taskId} --resume ${current.id}`);
|
|
725
1066
|
io.log(`╚══════════════════════════════════════════════════════════════`);
|
|
726
1067
|
return current;
|
|
@@ -730,7 +1071,7 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
730
1071
|
if (closedPhase) {
|
|
731
1072
|
qaFailNotes = closedPhase.notes ?? "QA findings — see QA phase artifact";
|
|
732
1073
|
io.log(` ✗ qa failed (iteration ${current.qaIterations}/${current.maxIterations}) — routing back to developer`);
|
|
733
|
-
i =
|
|
1074
|
+
i = devPhaseIndex - 1; // will be incremented to developer at top of loop
|
|
734
1075
|
continue;
|
|
735
1076
|
}
|
|
736
1077
|
// Release phase: auto-create PR if configured
|
|
@@ -761,18 +1102,29 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
761
1102
|
}
|
|
762
1103
|
}
|
|
763
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
|
+
}
|
|
764
1116
|
}
|
|
765
1117
|
current = await markRunDone(cwd, current);
|
|
766
1118
|
return current;
|
|
767
1119
|
}
|
|
768
|
-
async function workflowDryRun(options, io, taskId, gates, maxIterations) {
|
|
1120
|
+
async function workflowDryRun(options, io, taskId, gates, maxIterations, phaseSelection) {
|
|
769
1121
|
const gateTransitions = new Set(["po→architect", "qa→release"]);
|
|
770
1122
|
io.log(`Dry run — no records will be created`);
|
|
771
1123
|
io.log(`Task: ${taskId} gates: ${gates} max-iterations: ${maxIterations}`);
|
|
772
1124
|
io.log(``);
|
|
773
|
-
for (let i = 0; i <
|
|
774
|
-
const def =
|
|
775
|
-
const next =
|
|
1125
|
+
for (let i = 0; i < phaseSelection.sequence.length; i++) {
|
|
1126
|
+
const def = phaseSelection.sequence[i];
|
|
1127
|
+
const next = phaseSelection.sequence[i + 1];
|
|
776
1128
|
const transitionKey = next ? `${def.phase}→${next.phase}` : "";
|
|
777
1129
|
const gateLabel = gates === "all"
|
|
778
1130
|
? "gate=yes"
|
|
@@ -781,13 +1133,18 @@ async function workflowDryRun(options, io, taskId, gates, maxIterations) {
|
|
|
781
1133
|
: "gate=no";
|
|
782
1134
|
io.log(` ${def.phase} (${def.role}) ${gateLabel}`);
|
|
783
1135
|
}
|
|
1136
|
+
if (phaseSelection.skipped.length > 0) {
|
|
1137
|
+
io.log(``);
|
|
1138
|
+
io.log(`Skipped: ${phaseSelection.skipped.map((phase) => phase.phase).join(", ")}`);
|
|
1139
|
+
}
|
|
784
1140
|
if (options.json) {
|
|
785
1141
|
io.log(JSON.stringify({
|
|
786
1142
|
dryRun: true,
|
|
787
1143
|
taskId,
|
|
788
1144
|
gates,
|
|
789
1145
|
maxIterations,
|
|
790
|
-
phases:
|
|
1146
|
+
phases: phaseSelection.sequence,
|
|
1147
|
+
skipped: phaseSelection.skipped,
|
|
791
1148
|
}, null, 2));
|
|
792
1149
|
}
|
|
793
1150
|
}
|
|
@@ -810,6 +1167,16 @@ function parseCsv(value) {
|
|
|
810
1167
|
.map((item) => item.trim())
|
|
811
1168
|
.filter(Boolean);
|
|
812
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
|
+
}
|
|
813
1180
|
function stringOption(value) {
|
|
814
1181
|
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
815
1182
|
}
|
|
@@ -914,6 +1281,24 @@ function renderDependencyReport(report) {
|
|
|
914
1281
|
: report.dependencies.map((dependency) => `- ${dependency.id}: ${dependency.status} (${dependency.isComplete ? "complete" : "incomplete"})`)),
|
|
915
1282
|
].join("\n");
|
|
916
1283
|
}
|
|
1284
|
+
function renderPreRunValidation(report) {
|
|
1285
|
+
const status = report.allowed ? "allowed" : "blocked";
|
|
1286
|
+
const missing = report.missing.length > 0 ? report.missing.join(", ") : "none";
|
|
1287
|
+
return [
|
|
1288
|
+
`Pre-run validation for ${report.taskId}: ${status}`,
|
|
1289
|
+
`- Ready: ${String(report.isReady)}`,
|
|
1290
|
+
`- Bypassed: ${String(report.isBypassed)}`,
|
|
1291
|
+
`- Missing: ${missing}`,
|
|
1292
|
+
`- Task: ${String(report.checks.task)}`,
|
|
1293
|
+
`- Estimate: ${String(report.checks.estimate)}`,
|
|
1294
|
+
`- Workflow run: ${String(report.checks.workflowRun)}`,
|
|
1295
|
+
`- Evidence: ${String(report.checks.evidence)}`,
|
|
1296
|
+
`- Review: ${String(report.checks.review)}`,
|
|
1297
|
+
...(report.bypassDecisionArtifact
|
|
1298
|
+
? [`- Bypass decision: ${report.bypassDecisionArtifact}`]
|
|
1299
|
+
: []),
|
|
1300
|
+
].join("\n");
|
|
1301
|
+
}
|
|
917
1302
|
function renderTaskGraphPlan(plan) {
|
|
918
1303
|
return [
|
|
919
1304
|
"Task graph plan",
|
|
@@ -931,6 +1316,28 @@ function renderTaskGraphPlan(plan) {
|
|
|
931
1316
|
...taskGraphReadyLines(plan.complete),
|
|
932
1317
|
].join("\n");
|
|
933
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
|
+
}
|
|
934
1341
|
function renderTaskGraphBatchRun(batch) {
|
|
935
1342
|
return [
|
|
936
1343
|
"Task graph batch run",
|
|
@@ -1097,6 +1504,11 @@ function renderTaskContextMarkdown(context) {
|
|
|
1097
1504
|
`- Locks: ${context.locks.length}`,
|
|
1098
1505
|
`- Risks: ${context.risks.length}`,
|
|
1099
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}`),
|
|
1100
1512
|
"",
|
|
1101
1513
|
"## Collaboration Flow",
|
|
1102
1514
|
...(context.collaborationFlow
|
|
@@ -1118,6 +1530,8 @@ function renderTaskContextMarkdown(context) {
|
|
|
1118
1530
|
: context.skills.selected.map((item) => `- ${item.skill.id} (score ${item.score}): ${item.rationale.join("; ")}`)),
|
|
1119
1531
|
`- Source groups: ${context.skills.sourceGroups.join(", ") || "none"}`,
|
|
1120
1532
|
"",
|
|
1533
|
+
renderMemoryPacket(context.memory),
|
|
1534
|
+
"",
|
|
1121
1535
|
"## Decisions",
|
|
1122
1536
|
...eventLines(context.decisions),
|
|
1123
1537
|
"",
|
|
@@ -1190,6 +1604,27 @@ function renderPlaywrightPlanMarkdown(plan) {
|
|
|
1190
1604
|
"",
|
|
1191
1605
|
].join("\n");
|
|
1192
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
|
+
}
|
|
1193
1628
|
function eventLines(events) {
|
|
1194
1629
|
return events.length === 0
|
|
1195
1630
|
? ["- none"]
|