@jterrats/open-orchestra 0.4.0 → 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/CHANGELOG.md +21 -0
- package/dist/assets/web-console.js +91 -1
- package/dist/autonomous-phase-lifecycle.d.ts +26 -0
- package/dist/autonomous-phase-lifecycle.js +214 -0
- package/dist/autonomous-phase-lifecycle.js.map +1 -0
- package/dist/autonomous-run-state.d.ts +8 -0
- package/dist/autonomous-run-state.js +162 -0
- package/dist/autonomous-run-state.js.map +1 -0
- package/dist/autonomous-run-store.d.ts +17 -0
- package/dist/autonomous-run-store.js +89 -0
- package/dist/autonomous-run-store.js.map +1 -0
- package/dist/autonomous-workflow-constants.d.ts +7 -0
- package/dist/autonomous-workflow-constants.js +36 -0
- package/dist/autonomous-workflow-constants.js.map +1 -0
- package/dist/autonomous-workflow.d.ts +6 -45
- package/dist/autonomous-workflow.js +4 -394
- 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 +101 -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/command-utils.d.ts +6 -0
- package/dist/command-utils.js +19 -0
- package/dist/command-utils.js.map +1 -0
- package/dist/commands.d.ts +15 -43
- package/dist/commands.js +466 -912
- package/dist/commands.js.map +1 -1
- package/dist/constants.js +20 -0
- package/dist/constants.js.map +1 -1
- package/dist/defaults.d.ts +11 -0
- package/dist/defaults.js +11 -0
- package/dist/defaults.js.map +1 -1
- package/dist/instruction-commands.d.ts +5 -0
- package/dist/instruction-commands.js +98 -0
- package/dist/instruction-commands.js.map +1 -0
- 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.d.ts +3 -0
- package/dist/metrics-commands.js +123 -0
- package/dist/metrics-commands.js.map +1 -0
- package/dist/model-commands.d.ts +13 -0
- package/dist/model-commands.js +216 -0
- package/dist/model-commands.js.map +1 -0
- 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-adapters.d.ts +5 -1
- package/dist/runtime-adapters.js +27 -0
- package/dist/runtime-adapters.js.map +1 -1
- package/dist/runtime-bootstrap.js +4 -2
- package/dist/runtime-bootstrap.js.map +1 -1
- package/dist/runtime-commands.d.ts +9 -0
- package/dist/runtime-commands.js +156 -0
- package/dist/runtime-commands.js.map +1 -0
- package/dist/runtime-execution-adapters.d.ts +2 -0
- package/dist/runtime-execution-adapters.js +163 -0
- package/dist/runtime-execution-adapters.js.map +1 -0
- package/dist/runtime-execution-renderer.d.ts +10 -0
- package/dist/runtime-execution-renderer.js +113 -0
- package/dist/runtime-execution-renderer.js.map +1 -0
- package/dist/runtime-execution.d.ts +27 -0
- package/dist/runtime-execution.js +153 -0
- package/dist/runtime-execution.js.map +1 -0
- package/dist/skills-commands.d.ts +10 -0
- package/dist/skills-commands.js +138 -0
- package/dist/skills-commands.js.map +1 -0
- package/dist/skills.d.ts +7 -1
- package/dist/skills.js +120 -11
- package/dist/skills.js.map +1 -1
- package/dist/sprint-commands.d.ts +5 -0
- package/dist/sprint-commands.js +120 -0
- package/dist/sprint-commands.js.map +1 -0
- package/dist/subagent-protocol.js +6 -0
- package/dist/subagent-protocol.js.map +1 -1
- package/dist/telemetry-commands.d.ts +7 -0
- package/dist/telemetry-commands.js +82 -0
- package/dist/telemetry-commands.js.map +1 -0
- package/dist/tool-commands.d.ts +5 -0
- package/dist/tool-commands.js +159 -0
- package/dist/tool-commands.js.map +1 -0
- package/dist/types.d.ts +206 -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/docs/runtime-llm-flow.md +46 -0
- 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,31 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { initWorkspace } from "./workspace.js";
|
|
1
|
+
import { appendEvent, initWorkspace } from "./workspace.js";
|
|
4
2
|
import { requireArg } from "./args.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { calibrationReport, closeSprint, startSprint, velocityTrend, } from "./sprint-metrics.js";
|
|
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";
|
|
10
7
|
import { executePhaseWithLlm } from "./phase-executor.js";
|
|
11
|
-
import {
|
|
12
|
-
import { resolveWorkspaceWritePath } from "./fs-utils.js";
|
|
13
|
-
import { addEvidence, addPlaywrightEvidence, addTask, approveApproval, checkUsageBudget, checkReadiness, checkTaskDependencies, claimLock, completeWithProviderFallback, createHandoff, evaluateWorkflowGate, executeNextReadyTask, executePlanWithBudgetPreflight, executeReadyTaskBatch, generateExecutionPlan, generatePlaywrightTestPlan, generatePullRequestSummary, generateTaskGraphPlan, getUsageReport, getWorkflowStatus, getWorkflowSummary, getTaskContext, getWorkflowConfig, listEvidence, listConfiguredModelProviders, listDecisions, listLocks, listApprovals, listReviews, listRoles, listTasks, rejectApproval, recordReview, recordModelProvenance, recordDecision, releaseLock, setRoleModelProvider, showApproval, updateTask, listModelProvenance, } 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";
|
|
14
9
|
import { validateWorkspace } from "./workspace-validator.js";
|
|
15
|
-
import { addAgentLesson, listAgentLessons, listSkills, planSkillsForTask, promoteAgentLessons, readSourceOfTruth, recordSkillPlan, recordSkillRender, renderSkills, validateSkills, } from "./skills.js";
|
|
16
10
|
import { getWebServerAddress, startWebApiServer } from "./web-api.js";
|
|
17
11
|
import { decideTaskDelegation } from "./delegation-decision.js";
|
|
18
|
-
import { lintMermaidDiagram, recordDiagramLintEvidence, } from "./diagram-validation.js";
|
|
19
|
-
import { evaluateMcpOAuthProxy, } from "./mcp-oauth-proxy.js";
|
|
20
|
-
import { detectStaleInstructionFilesFromManifest, resolveInstructionImportsFromFile, updateManagedInstructionFile, } from "./instruction-updates.js";
|
|
21
|
-
import { applyInstructionUpdatesFromManifest } from "./instruction-apply.js";
|
|
22
|
-
import { renderSubagentProtocol, upsertSubagentProtocolBlock, } from "./subagent-protocol.js";
|
|
23
12
|
import { listCollaborationFlows, recommendCollaborationFlow, } from "./collaboration-flows.js";
|
|
24
13
|
import { listWorkflowTemplates, renderWorkflowTemplates, selectWorkflowTemplates, validateWorkflowTemplates, } from "./workflow-templates.js";
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
import { parseRuntimeTarget } from "./runtime-adapters.js";
|
|
15
|
+
export { instructionsApplyCommand, instructionsBlockCommand, instructionsImportsCommand, instructionsStaleCommand, } from "./instruction-commands.js";
|
|
16
|
+
export { benchmarkCommand, estimateCommand } from "./metrics-commands.js";
|
|
17
|
+
export { burndownCommand, calibrationCommand, sprintCommand, velocityCommand, } from "./sprint-commands.js";
|
|
18
|
+
export { approvalsApproveCommand, approvalsListCommand, approvalsRejectCommand, approvalsShowCommand, budgetCheckCommand, configShowCommand, modelCompleteFakeCommand, modelProvidersCommand, modelProvenanceAddCommand, modelProvenanceListCommand, modelSetRoleCommand, usageCommand, } from "./model-commands.js";
|
|
19
|
+
export { commandsManifestCommand, protocolBlockCommand, protocolRenderCommand, runtimeAdaptersCommand, runtimeBootstrapCommand, runtimeBriefCommand, runtimeDelegatePlanCommand, runtimeHandoffCommand, } from "./runtime-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";
|
|
22
|
+
export { telemetryDisableCommand, telemetryEnableCommand, telemetryEvalDatasetCommand, telemetryExportCommand, telemetryStatusCommand, telemetrySubmitCommand, } from "./telemetry-commands.js";
|
|
29
23
|
import { buildPrBody, createPullRequest } from "./github.js";
|
|
30
24
|
export async function initCommand(options, io) {
|
|
31
25
|
const root = stringOption(options["target-dir"]) ?? process.cwd();
|
|
@@ -71,6 +65,23 @@ export async function statusCommand(options, io) {
|
|
|
71
65
|
}
|
|
72
66
|
}
|
|
73
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
|
+
}
|
|
74
85
|
const report = await validateWorkspace();
|
|
75
86
|
if (options.json) {
|
|
76
87
|
io.log(JSON.stringify(report, null, 2));
|
|
@@ -122,7 +133,11 @@ export async function taskAddCommand(options, io) {
|
|
|
122
133
|
io.log(`Added task ${task.id}`);
|
|
123
134
|
}
|
|
124
135
|
export async function taskListCommand(options, io) {
|
|
125
|
-
|
|
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);
|
|
126
141
|
if (options.json) {
|
|
127
142
|
io.log(JSON.stringify(tasks, null, 2));
|
|
128
143
|
return;
|
|
@@ -135,6 +150,67 @@ export async function taskListCommand(options, io) {
|
|
|
135
150
|
io.log(`${task.id} [${task.status}] ${task.title} (${task.ownerRole})`);
|
|
136
151
|
}
|
|
137
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
|
+
}
|
|
138
214
|
export async function rolesListCommand(options, io) {
|
|
139
215
|
const roles = await listRoles();
|
|
140
216
|
if (options.json) {
|
|
@@ -154,6 +230,15 @@ export async function taskUpdateCommand(options, io) {
|
|
|
154
230
|
const id = requireArg(options, "id");
|
|
155
231
|
await updateTask(removeUndefined({
|
|
156
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"]),
|
|
157
242
|
status: stringOption(options.status),
|
|
158
243
|
blockedReason: stringOption(options["blocked-reason"]),
|
|
159
244
|
}));
|
|
@@ -176,6 +261,15 @@ export async function graphPlanCommand(options, io) {
|
|
|
176
261
|
io.log(renderTaskGraphPlan(plan));
|
|
177
262
|
}
|
|
178
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
|
+
}
|
|
179
273
|
const run = await executeNextReadyTask();
|
|
180
274
|
if (options.json) {
|
|
181
275
|
io.log(JSON.stringify(run, null, 2));
|
|
@@ -184,6 +278,15 @@ export async function graphRunNextCommand(options, io) {
|
|
|
184
278
|
io.log(renderExecutionRunMarkdown(run));
|
|
185
279
|
}
|
|
186
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
|
+
}
|
|
187
290
|
const batch = await executeReadyTaskBatch();
|
|
188
291
|
if (options.json) {
|
|
189
292
|
io.log(JSON.stringify(batch, null, 2));
|
|
@@ -256,6 +359,7 @@ export async function handoffCommand(options, io) {
|
|
|
256
359
|
gaps: stringOption(options.gaps),
|
|
257
360
|
risks: stringOption(options.risks),
|
|
258
361
|
playwright: stringOption(options.playwright),
|
|
362
|
+
updateOwner: options["update-owner"] ? true : undefined,
|
|
259
363
|
}));
|
|
260
364
|
io.log(`Created ${artifact}`);
|
|
261
365
|
}
|
|
@@ -349,56 +453,6 @@ export async function summaryCommand(options, io) {
|
|
|
349
453
|
}
|
|
350
454
|
io.log(renderSummaryMarkdown(summary));
|
|
351
455
|
}
|
|
352
|
-
export async function usageCommand(options, io) {
|
|
353
|
-
const report = await getUsageReport(stringOption(options.task));
|
|
354
|
-
if (options.json) {
|
|
355
|
-
io.log(JSON.stringify(report, null, 2));
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
io.log(renderUsageReport(report));
|
|
359
|
-
}
|
|
360
|
-
export async function budgetCheckCommand(options, io) {
|
|
361
|
-
const result = await checkUsageBudget(stringOption(options.task));
|
|
362
|
-
if (options.json) {
|
|
363
|
-
io.log(JSON.stringify(result, null, 2));
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
io.log(renderBudgetCheck(result));
|
|
367
|
-
}
|
|
368
|
-
if (!result.passed) {
|
|
369
|
-
throw new Error("budget check failed");
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
export async function approvalsListCommand(options, io) {
|
|
373
|
-
const approvals = await listApprovals(stringOption(options.task));
|
|
374
|
-
if (options.json) {
|
|
375
|
-
io.log(JSON.stringify(approvals, null, 2));
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
if (approvals.length === 0) {
|
|
379
|
-
io.log("No approvals");
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
for (const approval of approvals) {
|
|
383
|
-
io.log(`${approval.id} [${approval.status}] ${approval.taskId ?? "no-task"} ${approval.artifact}`);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
export async function approvalsShowCommand(options, io) {
|
|
387
|
-
const approval = await showApproval(requireArg(options, "id"));
|
|
388
|
-
if (options.json) {
|
|
389
|
-
io.log(JSON.stringify(approval, null, 2));
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
io.log(approval.content);
|
|
393
|
-
}
|
|
394
|
-
export async function approvalsApproveCommand(options, io) {
|
|
395
|
-
const approval = await approveApproval(parseApprovalDecision(options));
|
|
396
|
-
io.log(`Approved ${approval.id}`);
|
|
397
|
-
}
|
|
398
|
-
export async function approvalsRejectCommand(options, io) {
|
|
399
|
-
const approval = await rejectApproval(parseApprovalDecision(options));
|
|
400
|
-
io.log(`Rejected ${approval.id}`);
|
|
401
|
-
}
|
|
402
456
|
export async function prSummaryCommand(options, io) {
|
|
403
457
|
const summary = await generatePullRequestSummary(requireArg(options, "task"));
|
|
404
458
|
if (options.json) {
|
|
@@ -425,6 +479,40 @@ export async function contextCommand(options, io) {
|
|
|
425
479
|
}
|
|
426
480
|
io.log(renderTaskContextMarkdown(context));
|
|
427
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
|
+
}
|
|
428
516
|
export async function planCommand(options, io) {
|
|
429
517
|
const plan = await generateExecutionPlan(requireArg(options, "task"));
|
|
430
518
|
if (options.json) {
|
|
@@ -435,7 +523,12 @@ export async function planCommand(options, io) {
|
|
|
435
523
|
}
|
|
436
524
|
export async function runCommand(options, io) {
|
|
437
525
|
const taskId = requireArg(options, "task");
|
|
438
|
-
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
|
+
}));
|
|
439
532
|
if (options.json) {
|
|
440
533
|
io.log(JSON.stringify(run, null, 2));
|
|
441
534
|
return;
|
|
@@ -460,254 +553,6 @@ export async function playwrightEvidenceCommand(options, io) {
|
|
|
460
553
|
}));
|
|
461
554
|
io.log(`Created ${artifact}`);
|
|
462
555
|
}
|
|
463
|
-
export async function configShowCommand(options, io) {
|
|
464
|
-
const config = await getWorkflowConfig();
|
|
465
|
-
if (options.json) {
|
|
466
|
-
io.log(JSON.stringify(config, null, 2));
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
io.log(`Tools: ${Object.keys(config.tools ?? {}).length}`);
|
|
470
|
-
io.log(`Role routes: ${Object.keys(config.providers?.byRole ?? {}).length}`);
|
|
471
|
-
}
|
|
472
|
-
export async function modelProvidersCommand(options, io) {
|
|
473
|
-
const providers = await listConfiguredModelProviders();
|
|
474
|
-
if (options.json) {
|
|
475
|
-
io.log(JSON.stringify(providers, null, 2));
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
if (providers.length === 0) {
|
|
479
|
-
io.log("No configured model providers");
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
for (const provider of providers) {
|
|
483
|
-
io.log(`${provider.scope}: ${provider.provider}/${provider.model} (${provider.timeoutMs}ms)`);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
export async function modelSetRoleCommand(options, io) {
|
|
487
|
-
const role = requireArg(options, "role");
|
|
488
|
-
const routing = parseProviderRouting(options);
|
|
489
|
-
await setRoleModelProvider(role, routing);
|
|
490
|
-
io.log(`Configured model provider for ${role}`);
|
|
491
|
-
}
|
|
492
|
-
export async function modelProvenanceAddCommand(options, io) {
|
|
493
|
-
const record = await recordModelProvenance({
|
|
494
|
-
task: requireArg(options, "task"),
|
|
495
|
-
role: requireArg(options, "role"),
|
|
496
|
-
provider: requireArg(options, "provider"),
|
|
497
|
-
model: requireArg(options, "model"),
|
|
498
|
-
promptId: requireArg(options, "prompt-id"),
|
|
499
|
-
responseId: requireArg(options, "response-id"),
|
|
500
|
-
inputTokens: numberOption(options["input-tokens"], 0),
|
|
501
|
-
outputTokens: numberOption(options["output-tokens"], 0),
|
|
502
|
-
estimatedCostUsd: numberOption(options["estimated-cost-usd"], 0),
|
|
503
|
-
finishReason: requireArg(options, "finish-reason"),
|
|
504
|
-
});
|
|
505
|
-
io.log(`Recorded model provenance ${record.responseId}`);
|
|
506
|
-
}
|
|
507
|
-
export async function modelProvenanceListCommand(options, io) {
|
|
508
|
-
const records = await listModelProvenance(stringOption(options.task));
|
|
509
|
-
if (options.json) {
|
|
510
|
-
io.log(JSON.stringify(records, null, 2));
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
if (records.length === 0) {
|
|
514
|
-
io.log("No model provenance");
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
for (const record of records) {
|
|
518
|
-
io.log(`${record.task} ${record.role} ${record.provider}/${record.model} ${record.responseId}`);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
export async function modelCompleteFakeCommand(options, io) {
|
|
522
|
-
const result = await completeWithProviderFallback({
|
|
523
|
-
provider: requireArg(options, "provider"),
|
|
524
|
-
model: requireArg(options, "model"),
|
|
525
|
-
fallbacks: parseCsv(options.fallbacks),
|
|
526
|
-
maxTokens: 0,
|
|
527
|
-
maxCostUsd: 0,
|
|
528
|
-
timeoutMs: 30000,
|
|
529
|
-
retries: 0,
|
|
530
|
-
requiredCapabilities: [],
|
|
531
|
-
}, requireArg(options, "prompt"), removeUndefined({
|
|
532
|
-
failingProviders: parseCsv(options["fail-provider"]),
|
|
533
|
-
taskId: stringOption(options.task),
|
|
534
|
-
role: stringOption(options.role) ?? "parent",
|
|
535
|
-
}));
|
|
536
|
-
if (options.json) {
|
|
537
|
-
io.log(JSON.stringify(result, null, 2));
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
io.log(`Fake completion used ${result.provider}/${result.model} fallback=${String(result.fallbackUsed)}`);
|
|
541
|
-
}
|
|
542
|
-
export async function telemetryStatusCommand(options, io) {
|
|
543
|
-
const telemetry = await getTelemetryConsent();
|
|
544
|
-
if (options.json) {
|
|
545
|
-
io.log(JSON.stringify(telemetry, null, 2));
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
io.log("Telemetry: " + (telemetry.enabled ? "enabled" : "off"));
|
|
549
|
-
io.log("Level: " + telemetry.level);
|
|
550
|
-
io.log("Policy: " + telemetry.policyVersion);
|
|
551
|
-
}
|
|
552
|
-
export async function telemetryEnableCommand(options, io) {
|
|
553
|
-
const level = parseTelemetryLevel(stringOption(options.level) ?? "metadata");
|
|
554
|
-
if (requiresSensitiveTelemetryOptIn(level) && !options["confirm-sensitive"]) {
|
|
555
|
-
throw new Error("prompt-sample and eval-dataset telemetry require --confirm-sensitive");
|
|
556
|
-
}
|
|
557
|
-
const telemetry = await enableTelemetryConsent({
|
|
558
|
-
level,
|
|
559
|
-
actor: stringOption(options.actor) ?? "user",
|
|
560
|
-
});
|
|
561
|
-
if (options.json) {
|
|
562
|
-
io.log(JSON.stringify(telemetry, null, 2));
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
io.log("Telemetry enabled at level " + telemetry.level);
|
|
566
|
-
}
|
|
567
|
-
export async function telemetryExportCommand(options, io) {
|
|
568
|
-
const result = await exportTelemetryDataset({
|
|
569
|
-
dryRun: Boolean(options["dry-run"]),
|
|
570
|
-
});
|
|
571
|
-
if (options.json) {
|
|
572
|
-
io.log(JSON.stringify(result, null, 2));
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
io.log((result.dryRun ? "Telemetry export dry run" : "Telemetry exported") +
|
|
576
|
-
": " +
|
|
577
|
-
result.recordCount +
|
|
578
|
-
" record(s)");
|
|
579
|
-
if (result.file) {
|
|
580
|
-
io.log("File: " + result.file);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
export async function telemetrySubmitCommand(options, io) {
|
|
584
|
-
const result = await submitTelemetryDataset({
|
|
585
|
-
file: requireArg(options, "file"),
|
|
586
|
-
endpoint: requireArg(options, "endpoint"),
|
|
587
|
-
});
|
|
588
|
-
if (options.json) {
|
|
589
|
-
io.log(JSON.stringify(result, null, 2));
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
io.log("Telemetry submission recorded: " + result.fileHash);
|
|
593
|
-
}
|
|
594
|
-
export async function telemetryEvalDatasetCommand(options, io) {
|
|
595
|
-
const result = await exportTelemetryEvalDataset({
|
|
596
|
-
dryRun: Boolean(options["dry-run"]),
|
|
597
|
-
});
|
|
598
|
-
if (options.json) {
|
|
599
|
-
io.log(JSON.stringify(result, null, 2));
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
io.log((result.dryRun ? "Eval dataset dry run" : "Eval dataset exported") +
|
|
603
|
-
": " +
|
|
604
|
-
result.recordCount +
|
|
605
|
-
" record(s)");
|
|
606
|
-
if (result.file) {
|
|
607
|
-
io.log("File: " + result.file);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
export async function telemetryDisableCommand(options, io) {
|
|
611
|
-
const telemetry = await disableTelemetryConsent({
|
|
612
|
-
actor: stringOption(options.actor) ?? "user",
|
|
613
|
-
});
|
|
614
|
-
if (options.json) {
|
|
615
|
-
io.log(JSON.stringify(telemetry, null, 2));
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
io.log("Telemetry disabled");
|
|
619
|
-
}
|
|
620
|
-
export async function instructionsApplyCommand(options, io) {
|
|
621
|
-
const report = await applyInstructionUpdatesFromManifest({
|
|
622
|
-
manifestPath: requireArg(options, "manifest"),
|
|
623
|
-
check: Boolean(options.check),
|
|
624
|
-
dryRun: Boolean(options["dry-run"]),
|
|
625
|
-
force: Boolean(options.force),
|
|
626
|
-
});
|
|
627
|
-
if (options.json) {
|
|
628
|
-
io.log(JSON.stringify(report, null, 2));
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
io.log("Instruction update " +
|
|
632
|
-
report.mode +
|
|
633
|
-
": " +
|
|
634
|
-
report.totals.changed +
|
|
635
|
-
" changed, " +
|
|
636
|
-
report.totals.unchanged +
|
|
637
|
-
" unchanged, " +
|
|
638
|
-
report.totals.blocked +
|
|
639
|
-
" blocked");
|
|
640
|
-
for (const item of report.items) {
|
|
641
|
-
io.log(item.status +
|
|
642
|
-
" " +
|
|
643
|
-
item.filePath +
|
|
644
|
-
"#" +
|
|
645
|
-
item.blockId +
|
|
646
|
-
" - " +
|
|
647
|
-
item.reason);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
export async function instructionsStaleCommand(options, io) {
|
|
651
|
-
const report = await detectStaleInstructionFilesFromManifest({
|
|
652
|
-
manifestPath: requireArg(options, "manifest"),
|
|
653
|
-
});
|
|
654
|
-
if (options.json) {
|
|
655
|
-
io.log(JSON.stringify(report, null, 2));
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
io.log("Instruction file status:");
|
|
659
|
-
for (const item of report.items) {
|
|
660
|
-
io.log(item.status +
|
|
661
|
-
" " +
|
|
662
|
-
item.filePath +
|
|
663
|
-
"#" +
|
|
664
|
-
item.blockId +
|
|
665
|
-
" - " +
|
|
666
|
-
item.reason);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
export async function instructionsBlockCommand(options, io) {
|
|
670
|
-
const result = await updateManagedInstructionFile({
|
|
671
|
-
filePath: resolveWorkspaceWritePath(process.cwd(), requireArg(options, "file")),
|
|
672
|
-
contentPath: resolveWorkspaceWritePath(process.cwd(), requireArg(options, "content-file")),
|
|
673
|
-
input: {
|
|
674
|
-
blockId: requireArg(options, "block"),
|
|
675
|
-
generator: stringOption(options.generator) ?? "open-orchestra",
|
|
676
|
-
version: stringOption(options.version) ?? "1",
|
|
677
|
-
target: parseInstructionTarget(stringOption(options.target) ?? "generic"),
|
|
678
|
-
sourceManifest: stringOption(options["source-manifest"]) ?? "local",
|
|
679
|
-
},
|
|
680
|
-
check: Boolean(options.check),
|
|
681
|
-
dryRun: Boolean(options["dry-run"]),
|
|
682
|
-
force: Boolean(options.force),
|
|
683
|
-
});
|
|
684
|
-
if (options.json) {
|
|
685
|
-
io.log(JSON.stringify(result, null, 2));
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
if (result.drift) {
|
|
689
|
-
io.log("Drift detected for " + requireArg(options, "block"));
|
|
690
|
-
return;
|
|
691
|
-
}
|
|
692
|
-
io.log(result.changed
|
|
693
|
-
? "Instruction block changed"
|
|
694
|
-
: "Instruction block unchanged");
|
|
695
|
-
}
|
|
696
|
-
export async function instructionsImportsCommand(options, io) {
|
|
697
|
-
const result = await resolveInstructionImportsFromFile({
|
|
698
|
-
registryPath: requireArg(options, "registry"),
|
|
699
|
-
entryId: requireArg(options, "entry"),
|
|
700
|
-
target: parseInstructionTarget(stringOption(options.target) ?? "generic"),
|
|
701
|
-
});
|
|
702
|
-
if (options.json) {
|
|
703
|
-
io.log(JSON.stringify(result, null, 2));
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
|
-
io.log(result.content);
|
|
707
|
-
}
|
|
708
|
-
function parseInstructionTarget(value) {
|
|
709
|
-
return parseRuntimeTarget(value);
|
|
710
|
-
}
|
|
711
556
|
export async function collaborationFlowsCommand(options, io) {
|
|
712
557
|
const flows = listCollaborationFlows();
|
|
713
558
|
if (options.json) {
|
|
@@ -788,283 +633,6 @@ export async function workflowTemplateRenderCommand(options, io) {
|
|
|
788
633
|
}
|
|
789
634
|
io.log(rendered.content);
|
|
790
635
|
}
|
|
791
|
-
export async function commandsManifestCommand(options, io) {
|
|
792
|
-
const manifest = listCommandManifest();
|
|
793
|
-
if (options.json) {
|
|
794
|
-
io.log(JSON.stringify(manifest, null, 2));
|
|
795
|
-
return;
|
|
796
|
-
}
|
|
797
|
-
for (const command of manifest) {
|
|
798
|
-
io.log(command.command + " - " + command.summary);
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
export async function runtimeAdaptersCommand(options, io) {
|
|
802
|
-
const adapters = listRuntimeAdapters();
|
|
803
|
-
if (options.json) {
|
|
804
|
-
io.log(JSON.stringify(adapters, null, 2));
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
for (const adapter of adapters) {
|
|
808
|
-
io.log(adapter.target +
|
|
809
|
-
" - " +
|
|
810
|
-
adapter.label +
|
|
811
|
-
" (" +
|
|
812
|
-
adapter.defaultInstructionFiles.join(", ") +
|
|
813
|
-
")");
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
export async function runtimeBootstrapCommand(options, io) {
|
|
817
|
-
const target = parseSkillRenderTarget(stringOption(options.target) ?? "generic");
|
|
818
|
-
const bootstrap = renderRuntimeBootstrap(target);
|
|
819
|
-
const filePath = stringOption(options.file);
|
|
820
|
-
if (filePath) {
|
|
821
|
-
const resolvedFilePath = resolveWorkspaceWritePath(process.cwd(), path.normalize(filePath));
|
|
822
|
-
const existing = await readFile(resolvedFilePath, "utf8").catch(() => "");
|
|
823
|
-
const result = upsertRuntimeBootstrapBlock(existing, bootstrap, {
|
|
824
|
-
force: Boolean(options.force),
|
|
825
|
-
});
|
|
826
|
-
if (!options.check && !options["dry-run"] && !result.drift) {
|
|
827
|
-
await writeFile(resolvedFilePath, result.content);
|
|
828
|
-
}
|
|
829
|
-
if (options.json) {
|
|
830
|
-
io.log(JSON.stringify(result, null, 2));
|
|
831
|
-
return;
|
|
832
|
-
}
|
|
833
|
-
io.log(result.changed
|
|
834
|
-
? "Runtime bootstrap changed"
|
|
835
|
-
: "Runtime bootstrap unchanged");
|
|
836
|
-
return;
|
|
837
|
-
}
|
|
838
|
-
if (options.json) {
|
|
839
|
-
io.log(JSON.stringify(bootstrap, null, 2));
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
io.log(bootstrap.content);
|
|
843
|
-
}
|
|
844
|
-
export async function protocolRenderCommand(options, io) {
|
|
845
|
-
const protocol = await renderSubagentProtocol(removeUndefined({
|
|
846
|
-
target: parseSkillRenderTarget(stringOption(options.target) ?? "generic"),
|
|
847
|
-
taskId: stringOption(options.task),
|
|
848
|
-
}));
|
|
849
|
-
if (options.json) {
|
|
850
|
-
io.log(JSON.stringify(protocol, null, 2));
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
io.log(protocol.content);
|
|
854
|
-
}
|
|
855
|
-
export async function protocolBlockCommand(options, io) {
|
|
856
|
-
const filePath = requireArg(options, "file");
|
|
857
|
-
const protocol = await renderSubagentProtocol(removeUndefined({
|
|
858
|
-
target: parseSkillRenderTarget(stringOption(options.target) ?? "generic"),
|
|
859
|
-
taskId: stringOption(options.task),
|
|
860
|
-
}));
|
|
861
|
-
const existing = await readFile(filePath, "utf8").catch(() => "");
|
|
862
|
-
const result = upsertSubagentProtocolBlock(existing, protocol, {
|
|
863
|
-
force: Boolean(options.force),
|
|
864
|
-
});
|
|
865
|
-
const shouldWrite = !options.check && !options["dry-run"] && !result.drift && result.changed;
|
|
866
|
-
if (shouldWrite) {
|
|
867
|
-
await writeFile(filePath, result.content);
|
|
868
|
-
}
|
|
869
|
-
if (options.json) {
|
|
870
|
-
io.log(JSON.stringify(result, null, 2));
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
if (result.drift) {
|
|
874
|
-
io.log("Drift detected for subagent protocol block");
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
io.log(result.changed
|
|
878
|
-
? "Subagent protocol block changed"
|
|
879
|
-
: "Subagent protocol block unchanged");
|
|
880
|
-
}
|
|
881
|
-
export async function diagramsLintCommand(options, io) {
|
|
882
|
-
const result = await lintMermaidDiagram({
|
|
883
|
-
filePath: requireArg(options, "file"),
|
|
884
|
-
});
|
|
885
|
-
const evidence = stringOption(options.task)
|
|
886
|
-
? await recordDiagramLintEvidence({
|
|
887
|
-
taskId: stringOption(options.task) ?? "",
|
|
888
|
-
result,
|
|
889
|
-
})
|
|
890
|
-
: undefined;
|
|
891
|
-
const output = removeUndefined({ result, evidence });
|
|
892
|
-
if (options.json) {
|
|
893
|
-
io.log(JSON.stringify(output, null, 2));
|
|
894
|
-
}
|
|
895
|
-
else {
|
|
896
|
-
io.log(result.valid ? "Mermaid diagram valid" : "Mermaid diagram invalid");
|
|
897
|
-
if (result.installHint) {
|
|
898
|
-
io.log(result.installHint);
|
|
899
|
-
}
|
|
900
|
-
if (evidence) {
|
|
901
|
-
io.log("Created " + evidence.artifact);
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
if (!result.valid) {
|
|
905
|
-
throw new Error("diagram lint failed");
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
export async function mcpOAuthProxyEvaluateCommand(options, io) {
|
|
909
|
-
const evaluation = evaluateMcpOAuthProxy(removeUndefined({
|
|
910
|
-
enabled: Boolean(options.enable),
|
|
911
|
-
mode: parseMcpProxyMode(stringOption(options.mode) ?? "stdio-proxy"),
|
|
912
|
-
serverUrl: requireArg(options, "server-url"),
|
|
913
|
-
tokenStorage: parseMcpSecretStorage(stringOption(options["token-storage"]) ?? "keychain"),
|
|
914
|
-
tokenPath: stringOption(options["token-path"]),
|
|
915
|
-
refreshWindowSeconds: Number(stringOption(options["refresh-window"]) ?? 300),
|
|
916
|
-
approvedBy: options.approve
|
|
917
|
-
? (stringOption(options.approver) ?? "local-user")
|
|
918
|
-
: undefined,
|
|
919
|
-
}));
|
|
920
|
-
if (options.json) {
|
|
921
|
-
io.log(JSON.stringify(evaluation, null, 2));
|
|
922
|
-
return;
|
|
923
|
-
}
|
|
924
|
-
io.log(evaluation.approved
|
|
925
|
-
? "MCP proxy plan approved"
|
|
926
|
-
: "MCP proxy plan has risks");
|
|
927
|
-
for (const risk of evaluation.risks) {
|
|
928
|
-
io.log("RISK: " + risk);
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
function parseMcpProxyMode(value) {
|
|
932
|
-
if (["stdio-proxy", "direct-http", "tool-native-oauth"].includes(value)) {
|
|
933
|
-
return value;
|
|
934
|
-
}
|
|
935
|
-
throw new Error("unknown MCP proxy mode: " + value);
|
|
936
|
-
}
|
|
937
|
-
function parseMcpSecretStorage(value) {
|
|
938
|
-
if (["keychain", "libsecret", "windows-credential", "secure-file"].includes(value)) {
|
|
939
|
-
return value;
|
|
940
|
-
}
|
|
941
|
-
throw new Error("unknown MCP secret storage: " + value);
|
|
942
|
-
}
|
|
943
|
-
export async function skillsValidateCommand(options, io) {
|
|
944
|
-
const report = await validateSkills();
|
|
945
|
-
if (options.json) {
|
|
946
|
-
io.log(JSON.stringify(report, null, 2));
|
|
947
|
-
}
|
|
948
|
-
else {
|
|
949
|
-
io.log(report.valid ? "Skills valid" : "Skills invalid");
|
|
950
|
-
for (const error of report.errors) {
|
|
951
|
-
io.log(`ERROR: ${error}`);
|
|
952
|
-
}
|
|
953
|
-
for (const warning of report.warnings) {
|
|
954
|
-
io.log(`WARN: ${warning}`);
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
if (!report.valid) {
|
|
958
|
-
throw new Error("skills validation failed");
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
export async function sourcesListCommand(options, io) {
|
|
962
|
-
const sources = await readSourceOfTruth();
|
|
963
|
-
if (options.json) {
|
|
964
|
-
io.log(JSON.stringify(sources, null, 2));
|
|
965
|
-
return;
|
|
966
|
-
}
|
|
967
|
-
for (const source of sources) {
|
|
968
|
-
io.log(`${source.id ?? "unknown"} - ${source.name ?? "Unnamed source"}`);
|
|
969
|
-
io.log(` ${(source.locations ?? []).join(", ")}`);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
export async function lessonsListCommand(options, io) {
|
|
973
|
-
const lessons = await listAgentLessons();
|
|
974
|
-
if (options.json) {
|
|
975
|
-
io.log(JSON.stringify(lessons, null, 2));
|
|
976
|
-
return;
|
|
977
|
-
}
|
|
978
|
-
if (lessons.length === 0) {
|
|
979
|
-
io.log("No lessons");
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
for (const lesson of lessons) {
|
|
983
|
-
io.log(`${lesson.timestamp} ${lesson.operation}: ${lesson.errorSignature}`);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
export async function lessonsAddCommand(options, io) {
|
|
987
|
-
const lesson = await addAgentLesson(removeUndefined({
|
|
988
|
-
taskId: stringOption(options.task),
|
|
989
|
-
actor: stringOption(options.actor) ?? "parent",
|
|
990
|
-
operation: requireArg(options, "operation"),
|
|
991
|
-
failedAction: requireArg(options, "failed-action"),
|
|
992
|
-
errorSignature: requireArg(options, "error-signature"),
|
|
993
|
-
rootCause: requireArg(options, "root-cause"),
|
|
994
|
-
fix: requireArg(options, "fix"),
|
|
995
|
-
prevention: requireArg(options, "prevention"),
|
|
996
|
-
appliesTo: parseCsv(options["applies-to"]),
|
|
997
|
-
verifiedBy: parseCsv(options["verified-by"]),
|
|
998
|
-
}));
|
|
999
|
-
if (options.json) {
|
|
1000
|
-
io.log(JSON.stringify(lesson, null, 2));
|
|
1001
|
-
return;
|
|
1002
|
-
}
|
|
1003
|
-
io.log(`Recorded lesson ${lesson.errorSignature}`);
|
|
1004
|
-
}
|
|
1005
|
-
export async function lessonsPromoteCommand(options, io) {
|
|
1006
|
-
const result = await promoteAgentLessons(removeUndefined({
|
|
1007
|
-
to: parsePromotionTarget(stringOption(options.to) ?? "doc"),
|
|
1008
|
-
filter: stringOption(options.filter),
|
|
1009
|
-
}));
|
|
1010
|
-
if (options.json) {
|
|
1011
|
-
io.log(JSON.stringify(result, null, 2));
|
|
1012
|
-
return;
|
|
1013
|
-
}
|
|
1014
|
-
io.log(`Created ${result.artifact}`);
|
|
1015
|
-
io.log(`Promoted lessons: ${result.lessons.length}`);
|
|
1016
|
-
}
|
|
1017
|
-
function parsePromotionTarget(value) {
|
|
1018
|
-
if (["skill", "rule", "doc"].includes(value)) {
|
|
1019
|
-
return value;
|
|
1020
|
-
}
|
|
1021
|
-
throw new Error(`unknown promotion target: ${value}`);
|
|
1022
|
-
}
|
|
1023
|
-
export async function skillsListCommand(options, io) {
|
|
1024
|
-
const skills = listSkills();
|
|
1025
|
-
if (options.json) {
|
|
1026
|
-
io.log(JSON.stringify(skills, null, 2));
|
|
1027
|
-
return;
|
|
1028
|
-
}
|
|
1029
|
-
for (const skill of skills) {
|
|
1030
|
-
io.log(`${skill.id} - ${skill.name} (${skill.loadBudget})`);
|
|
1031
|
-
io.log(` ${skill.summary}`);
|
|
1032
|
-
io.log(` sources: ${skill.sourceGroups.join(", ")}`);
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
export async function skillsPlanCommand(options, io) {
|
|
1036
|
-
const plan = await planSkillsForTask(requireArg(options, "task"));
|
|
1037
|
-
await recordSkillPlan(plan);
|
|
1038
|
-
if (options.json) {
|
|
1039
|
-
io.log(JSON.stringify(plan, null, 2));
|
|
1040
|
-
return;
|
|
1041
|
-
}
|
|
1042
|
-
io.log(`Skills for ${plan.taskId}:`);
|
|
1043
|
-
for (const item of plan.selected) {
|
|
1044
|
-
io.log(` ${item.skill.id} (score ${item.score})`);
|
|
1045
|
-
for (const reason of item.rationale) {
|
|
1046
|
-
io.log(` - ${reason}`);
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
io.log(`Source groups: ${plan.sourceGroups.join(", ")}`);
|
|
1050
|
-
}
|
|
1051
|
-
export async function skillsRenderCommand(options, io) {
|
|
1052
|
-
const target = parseSkillRenderTarget(stringOption(options.target) ?? "generic");
|
|
1053
|
-
const rendered = await renderSkills({
|
|
1054
|
-
target,
|
|
1055
|
-
taskId: stringOption(options.task),
|
|
1056
|
-
skillIds: parseCsv(options.skills),
|
|
1057
|
-
});
|
|
1058
|
-
await recordSkillRender(rendered);
|
|
1059
|
-
if (options.json) {
|
|
1060
|
-
io.log(JSON.stringify(rendered, null, 2));
|
|
1061
|
-
return;
|
|
1062
|
-
}
|
|
1063
|
-
io.log(rendered.content);
|
|
1064
|
-
}
|
|
1065
|
-
function parseSkillRenderTarget(value) {
|
|
1066
|
-
return parseRuntimeTarget(value);
|
|
1067
|
-
}
|
|
1068
636
|
// --- Autonomous workflow commands ---
|
|
1069
637
|
const GATE_MODES = ["none", "phase", "all"];
|
|
1070
638
|
export async function workflowRunCommand(options, io) {
|
|
@@ -1072,12 +640,22 @@ export async function workflowRunCommand(options, io) {
|
|
|
1072
640
|
const taskId = requireArg(options, "task");
|
|
1073
641
|
const gates = (stringOption(options.gates) ?? "phase");
|
|
1074
642
|
const maxIterations = numberOption(options["max-iterations"], 5);
|
|
643
|
+
const timeoutMinutes = numberOption(options["timeout-minutes"], 0);
|
|
1075
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;
|
|
1076
648
|
if (!GATE_MODES.includes(gates)) {
|
|
1077
649
|
throw new Error(`--gates must be one of: ${GATE_MODES.join(", ")}`);
|
|
1078
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
|
+
}
|
|
1079
657
|
if (options["dry-run"]) {
|
|
1080
|
-
return workflowDryRun(options, io, taskId, gates, maxIterations);
|
|
658
|
+
return workflowDryRun(options, io, taskId, gates, maxIterations, phaseSelection);
|
|
1081
659
|
}
|
|
1082
660
|
let run;
|
|
1083
661
|
let startIndex;
|
|
@@ -1086,21 +664,39 @@ export async function workflowRunCommand(options, io) {
|
|
|
1086
664
|
if (!existing)
|
|
1087
665
|
throw new Error(`workflow run not found: ${String(options.resume)}`);
|
|
1088
666
|
run = existing;
|
|
1089
|
-
|
|
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);
|
|
1090
672
|
if (startIndex === -1) {
|
|
1091
673
|
io.log(`Workflow ${run.id} is already complete`);
|
|
1092
674
|
if (options.json)
|
|
1093
675
|
io.log(JSON.stringify({ run, file, cwd }, null, 2));
|
|
1094
676
|
return;
|
|
1095
677
|
}
|
|
1096
|
-
|
|
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) };
|
|
1097
684
|
}
|
|
1098
685
|
else {
|
|
1099
|
-
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
|
+
});
|
|
1100
693
|
startIndex = 0;
|
|
1101
694
|
io.log(`Started autonomous workflow ${run.id} for task ${taskId} [gates=${gates}]`);
|
|
1102
695
|
}
|
|
1103
|
-
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
|
+
});
|
|
1104
700
|
const result = { run, file, cwd };
|
|
1105
701
|
if (options.json) {
|
|
1106
702
|
io.log(JSON.stringify(result, null, 2));
|
|
@@ -1118,7 +714,9 @@ export async function workflowRunCommand(options, io) {
|
|
|
1118
714
|
}
|
|
1119
715
|
export async function workflowRunListCommand(options, io) {
|
|
1120
716
|
const cwd = process.cwd();
|
|
1121
|
-
const runs =
|
|
717
|
+
const runs = options.active
|
|
718
|
+
? await listActiveAutonomousRuns(cwd)
|
|
719
|
+
: await listAutonomousRuns(cwd);
|
|
1122
720
|
if (options.json) {
|
|
1123
721
|
io.log(JSON.stringify(runs, null, 2));
|
|
1124
722
|
return;
|
|
@@ -1134,17 +732,35 @@ export async function workflowRunListCommand(options, io) {
|
|
|
1134
732
|
io.log(` ${phases}`);
|
|
1135
733
|
}
|
|
1136
734
|
}
|
|
1137
|
-
|
|
1138
|
-
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
|
+
}
|
|
1139
758
|
export async function workflowClarifyCommand(options, io) {
|
|
1140
759
|
const cwd = process.cwd();
|
|
1141
760
|
const runId = requireArg(options, "run");
|
|
1142
|
-
const from = requireArg(options, "from");
|
|
1143
|
-
const to = requireArg(options, "to");
|
|
761
|
+
const from = await resolveClarificationRole(cwd, requireArg(options, "from"));
|
|
762
|
+
const to = await resolveClarificationRole(cwd, requireArg(options, "to"));
|
|
1144
763
|
const question = requireArg(options, "question");
|
|
1145
|
-
if (!CLARIFICATION_TARGETS.includes(to)) {
|
|
1146
|
-
throw new Error(`--to must be one of: ${CLARIFICATION_TARGETS.join(", ")}`);
|
|
1147
|
-
}
|
|
1148
764
|
const run = await readAutonomousRun(cwd, runId);
|
|
1149
765
|
if (!run)
|
|
1150
766
|
throw new Error(`workflow run not found: ${runId}`);
|
|
@@ -1155,9 +771,6 @@ export async function workflowClarifyCommand(options, io) {
|
|
|
1155
771
|
throw new Error(`no active phase found for role ${from} in run ${runId}`);
|
|
1156
772
|
}
|
|
1157
773
|
const activePhase = run.phases[activePhaseIdx];
|
|
1158
|
-
if (!CLARIFICATION_ALLOWED_PHASES.has(activePhase.phase)) {
|
|
1159
|
-
throw new Error(`clarification is only allowed from developer or qa phases (active: ${activePhase.phase})`);
|
|
1160
|
-
}
|
|
1161
774
|
// Open clarification record
|
|
1162
775
|
const record = await openClarification(cwd, {
|
|
1163
776
|
runId,
|
|
@@ -1168,8 +781,9 @@ export async function workflowClarifyCommand(options, io) {
|
|
|
1168
781
|
});
|
|
1169
782
|
// Suspend the phase if it's still running
|
|
1170
783
|
if (activePhase.status === "running") {
|
|
1171
|
-
const
|
|
1172
|
-
|
|
784
|
+
const sequence = phaseSequenceForRun(run);
|
|
785
|
+
const phaseSeqIdx = sequence.findIndex((d) => d.phase === activePhase.phase);
|
|
786
|
+
await suspendPhaseForClarification(cwd, run, phaseSeqIdx, sequence);
|
|
1173
787
|
}
|
|
1174
788
|
if (options.json) {
|
|
1175
789
|
io.log(JSON.stringify(record, null, 2));
|
|
@@ -1193,11 +807,12 @@ export async function workflowClarifyRespondCommand(options, io) {
|
|
|
1193
807
|
answer,
|
|
1194
808
|
});
|
|
1195
809
|
// Resume the suspended phase back to running
|
|
1196
|
-
const
|
|
810
|
+
const sequence = phaseSequenceForRun(run);
|
|
811
|
+
const phaseSeqIdx = sequence.findIndex((d) => d.role === record.fromRole);
|
|
1197
812
|
if (phaseSeqIdx !== -1) {
|
|
1198
813
|
const phase = run.phases.find((p) => p.role === record.fromRole);
|
|
1199
814
|
if (phase?.status === "awaiting_clarification") {
|
|
1200
|
-
await resumePhaseFromClarification(cwd, run, phaseSeqIdx);
|
|
815
|
+
await resumePhaseFromClarification(cwd, run, phaseSeqIdx, sequence);
|
|
1201
816
|
}
|
|
1202
817
|
}
|
|
1203
818
|
if (options.json) {
|
|
@@ -1211,14 +826,25 @@ export async function workflowClarifyRespondCommand(options, io) {
|
|
|
1211
826
|
}
|
|
1212
827
|
export async function workflowClarifyListCommand(options, io) {
|
|
1213
828
|
const cwd = process.cwd();
|
|
1214
|
-
const runId =
|
|
1215
|
-
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);
|
|
1216
840
|
if (options.json) {
|
|
1217
841
|
io.log(JSON.stringify(clarifications, null, 2));
|
|
1218
842
|
return;
|
|
1219
843
|
}
|
|
1220
844
|
if (clarifications.length === 0) {
|
|
1221
|
-
io.log(
|
|
845
|
+
io.log(runId
|
|
846
|
+
? `No clarifications for run ${runId}`
|
|
847
|
+
: `No clarifications for task ${taskId}`);
|
|
1222
848
|
return;
|
|
1223
849
|
}
|
|
1224
850
|
for (const c of clarifications) {
|
|
@@ -1229,17 +855,115 @@ export async function workflowClarifyListCommand(options, io) {
|
|
|
1229
855
|
io.log(` A: ${c.answer}`);
|
|
1230
856
|
}
|
|
1231
857
|
}
|
|
1232
|
-
const
|
|
1233
|
-
|
|
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) {
|
|
1234
950
|
let current = run;
|
|
1235
951
|
let qaFailNotes;
|
|
1236
|
-
|
|
1237
|
-
|
|
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];
|
|
1238
962
|
// Init phase task (skip if already has a running/done/clarification record from resume)
|
|
1239
963
|
const existing = current.phases.find((p) => p.phase === def.phase && p.status !== "qa_failed");
|
|
1240
964
|
if (!existing || existing.status === "pending") {
|
|
1241
965
|
const retryContext = def.phase === "developer" && qaFailNotes ? qaFailNotes : undefined;
|
|
1242
|
-
const phaseRecord = await initPhase(cwd, current, i, retryContext);
|
|
966
|
+
const phaseRecord = await initPhase(cwd, current, i, retryContext, sequence);
|
|
1243
967
|
current = { ...current, phases: [...current.phases, phaseRecord] };
|
|
1244
968
|
if (retryContext) {
|
|
1245
969
|
io.log(`↺ ${def.phase} (${def.role}) retry — QA findings: ${retryContext.slice(0, 80)}`);
|
|
@@ -1265,7 +989,7 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
1265
989
|
}).catch(async (error) => {
|
|
1266
990
|
const message = error instanceof Error ? error.message : String(error);
|
|
1267
991
|
io.log(`✗ ${def.phase} provider execution failed: ${message}`);
|
|
1268
|
-
current = await markRunFailed(cwd, current, message);
|
|
992
|
+
current = await markRunFailed(cwd, current, message, def.phase);
|
|
1269
993
|
return null;
|
|
1270
994
|
});
|
|
1271
995
|
if (!llmResult)
|
|
@@ -1273,6 +997,17 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
1273
997
|
if (llmResult.mode === "llm") {
|
|
1274
998
|
io.log(` ✓ llm artifact (${llmResult.artifact})`);
|
|
1275
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
|
+
}
|
|
1276
1011
|
// Architect sizing gate — always enforced regardless of --gates mode.
|
|
1277
1012
|
// In LLM mode, the architect phase records the sizing decision before this check.
|
|
1278
1013
|
if (def.phase === "architect") {
|
|
@@ -1288,15 +1023,15 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
1288
1023
|
io.log(` ✓ sizing=${sizing.sizing}${sizing.points !== undefined ? ` (${sizing.points} pts)` : ""}`);
|
|
1289
1024
|
}
|
|
1290
1025
|
const outcome = llmResult.outcome;
|
|
1291
|
-
const result = await closePhase(cwd, current, i, outcome);
|
|
1026
|
+
const result = await closePhase(cwd, current, i, outcome, sequence);
|
|
1292
1027
|
current = result.run;
|
|
1293
1028
|
if (result.handoffArtifact) {
|
|
1294
|
-
io.log(` ✓ handoff → ${
|
|
1029
|
+
io.log(` ✓ handoff → ${sequence[i + 1]?.role ?? "end"} (${result.handoffArtifact})`);
|
|
1295
1030
|
}
|
|
1296
1031
|
if (result.reviewArtifact) {
|
|
1297
|
-
const nextPhase =
|
|
1032
|
+
const nextPhase = sequence[i + 1]?.phase;
|
|
1298
1033
|
io.log(` ⏸ gate ${def.phase}→${nextPhase} — review: ${result.reviewArtifact}`);
|
|
1299
|
-
io.log(` Approve: orchestra workflow
|
|
1034
|
+
io.log(` Approve: orchestra workflow gate-approve --run ${current.id} --gate ${def.phase}->${nextPhase} --approver <name> --rationale "<text>"`);
|
|
1300
1035
|
io.log(``);
|
|
1301
1036
|
io.log(`╔══ GATE PAUSE: ${def.phase.toUpperCase()} → ${nextPhase?.toUpperCase()} ══════════════════════`);
|
|
1302
1037
|
io.log(`║ Run: ${current.id}`);
|
|
@@ -1304,6 +1039,8 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
1304
1039
|
io.log(`║ Review: ${result.reviewArtifact}`);
|
|
1305
1040
|
io.log(`║`);
|
|
1306
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:`);
|
|
1307
1044
|
io.log(`║ orchestra workflow run --task ${current.taskId} --resume ${current.id}`);
|
|
1308
1045
|
io.log(`╚══════════════════════════════════════════════════════════════`);
|
|
1309
1046
|
return current;
|
|
@@ -1313,7 +1050,7 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
1313
1050
|
if (closedPhase) {
|
|
1314
1051
|
qaFailNotes = closedPhase.notes ?? "QA findings — see QA phase artifact";
|
|
1315
1052
|
io.log(` ✗ qa failed (iteration ${current.qaIterations}/${current.maxIterations}) — routing back to developer`);
|
|
1316
|
-
i =
|
|
1053
|
+
i = devPhaseIndex - 1; // will be incremented to developer at top of loop
|
|
1317
1054
|
continue;
|
|
1318
1055
|
}
|
|
1319
1056
|
// Release phase: auto-create PR if configured
|
|
@@ -1344,18 +1081,29 @@ async function executePhases(cwd, run, startIndex, io) {
|
|
|
1344
1081
|
}
|
|
1345
1082
|
}
|
|
1346
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
|
+
}
|
|
1347
1095
|
}
|
|
1348
1096
|
current = await markRunDone(cwd, current);
|
|
1349
1097
|
return current;
|
|
1350
1098
|
}
|
|
1351
|
-
async function workflowDryRun(options, io, taskId, gates, maxIterations) {
|
|
1099
|
+
async function workflowDryRun(options, io, taskId, gates, maxIterations, phaseSelection) {
|
|
1352
1100
|
const gateTransitions = new Set(["po→architect", "qa→release"]);
|
|
1353
1101
|
io.log(`Dry run — no records will be created`);
|
|
1354
1102
|
io.log(`Task: ${taskId} gates: ${gates} max-iterations: ${maxIterations}`);
|
|
1355
1103
|
io.log(``);
|
|
1356
|
-
for (let i = 0; i <
|
|
1357
|
-
const def =
|
|
1358
|
-
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];
|
|
1359
1107
|
const transitionKey = next ? `${def.phase}→${next.phase}` : "";
|
|
1360
1108
|
const gateLabel = gates === "all"
|
|
1361
1109
|
? "gate=yes"
|
|
@@ -1364,235 +1112,21 @@ async function workflowDryRun(options, io, taskId, gates, maxIterations) {
|
|
|
1364
1112
|
: "gate=no";
|
|
1365
1113
|
io.log(` ${def.phase} (${def.role}) ${gateLabel}`);
|
|
1366
1114
|
}
|
|
1115
|
+
if (phaseSelection.skipped.length > 0) {
|
|
1116
|
+
io.log(``);
|
|
1117
|
+
io.log(`Skipped: ${phaseSelection.skipped.map((phase) => phase.phase).join(", ")}`);
|
|
1118
|
+
}
|
|
1367
1119
|
if (options.json) {
|
|
1368
1120
|
io.log(JSON.stringify({
|
|
1369
1121
|
dryRun: true,
|
|
1370
1122
|
taskId,
|
|
1371
1123
|
gates,
|
|
1372
1124
|
maxIterations,
|
|
1373
|
-
phases:
|
|
1125
|
+
phases: phaseSelection.sequence,
|
|
1126
|
+
skipped: phaseSelection.skipped,
|
|
1374
1127
|
}, null, 2));
|
|
1375
1128
|
}
|
|
1376
1129
|
}
|
|
1377
|
-
// --- Estimate & Benchmark commands ---
|
|
1378
|
-
const CONFIDENCE_LEVELS = ["low", "medium", "high"];
|
|
1379
|
-
export async function estimateCommand(options, io) {
|
|
1380
|
-
const cwd = process.cwd();
|
|
1381
|
-
const taskId = requireArg(options, "task");
|
|
1382
|
-
const sizingRaw = requireArg(options, "sizing");
|
|
1383
|
-
const soloDays = numberOption(options["solo-days"], 0);
|
|
1384
|
-
const aiDays = numberOption(options["ai-unguided-days"], 0);
|
|
1385
|
-
const confidence = (stringOption(options.confidence) ??
|
|
1386
|
-
"medium");
|
|
1387
|
-
const declaredBy = stringOption(options["declared-by"]) ?? "pm";
|
|
1388
|
-
if (!SIZING_LABELS.includes(sizingRaw)) {
|
|
1389
|
-
throw new Error(`--sizing must be one of: ${SIZING_LABELS.join(", ")}`);
|
|
1390
|
-
}
|
|
1391
|
-
if (!CONFIDENCE_LEVELS.includes(confidence)) {
|
|
1392
|
-
throw new Error(`--confidence must be one of: ${CONFIDENCE_LEVELS.join(", ")}`);
|
|
1393
|
-
}
|
|
1394
|
-
if (soloDays <= 0)
|
|
1395
|
-
throw new Error(`--solo-days must be > 0`);
|
|
1396
|
-
if (aiDays <= 0)
|
|
1397
|
-
throw new Error(`--ai-unguided-days must be > 0`);
|
|
1398
|
-
const record = await recordEstimate(cwd, {
|
|
1399
|
-
taskId,
|
|
1400
|
-
sizingLabel: sizingRaw,
|
|
1401
|
-
soloEstimateDays: soloDays,
|
|
1402
|
-
aiUnguidedEstimateDays: aiDays,
|
|
1403
|
-
confidence,
|
|
1404
|
-
declaredBy,
|
|
1405
|
-
});
|
|
1406
|
-
if (options.json) {
|
|
1407
|
-
io.log(JSON.stringify(record, null, 2));
|
|
1408
|
-
return;
|
|
1409
|
-
}
|
|
1410
|
-
io.log(`Estimate recorded for ${taskId} [${record.id}]`);
|
|
1411
|
-
io.log(` Sizing: ${record.sizingLabel}`);
|
|
1412
|
-
io.log(` Solo: ${record.soloEstimateDays}d`);
|
|
1413
|
-
io.log(` AI-unguided: ${record.aiUnguidedEstimateDays}d`);
|
|
1414
|
-
io.log(` Confidence: ${record.confidence}`);
|
|
1415
|
-
io.log(` Declared by: ${record.declaredBy}`);
|
|
1416
|
-
}
|
|
1417
|
-
export async function benchmarkCommand(options, io) {
|
|
1418
|
-
const cwd = process.cwd();
|
|
1419
|
-
if (options.summary) {
|
|
1420
|
-
const summary = await summarizeBenchmark(cwd);
|
|
1421
|
-
if (options.json) {
|
|
1422
|
-
io.log(JSON.stringify(summary, null, 2));
|
|
1423
|
-
return;
|
|
1424
|
-
}
|
|
1425
|
-
if (summary.stories.length === 0) {
|
|
1426
|
-
io.log("No estimates recorded yet. Use: orchestra estimate --task <id> ...");
|
|
1427
|
-
return;
|
|
1428
|
-
}
|
|
1429
|
-
const header = `${"Story".padEnd(14)} ${"Size".padEnd(4)} ${"Solo".padEnd(6)} ${"AI".padEnd(6)} ${"Actual".padEnd(8)} ${"vs Solo".padEnd(8)} ${"vs AI".padEnd(8)} ${"QA".padEnd(4)} ${"Rev".padEnd(4)} ${"Blk".padEnd(4)} ${"Ev".padEnd(4)} ${"Les"}`;
|
|
1430
|
-
io.log(header);
|
|
1431
|
-
io.log("─".repeat(header.length));
|
|
1432
|
-
for (const s of summary.stories) {
|
|
1433
|
-
const actual = s.actualDays !== null ? `${s.actualDays}d` : "pending";
|
|
1434
|
-
const vsSolo = s.vsSoloPct !== null
|
|
1435
|
-
? `${s.vsSoloPct > 0 ? "+" : ""}${s.vsSoloPct}%`
|
|
1436
|
-
: "—";
|
|
1437
|
-
const vsAi = s.vsAiUnguidedPct !== null
|
|
1438
|
-
? `${s.vsAiUnguidedPct > 0 ? "+" : ""}${s.vsAiUnguidedPct}%`
|
|
1439
|
-
: "—";
|
|
1440
|
-
io.log(`${s.taskId.padEnd(14)} ${s.sizingLabel.padEnd(4)} ${`${s.soloEstimateDays}d`.padEnd(6)} ${`${s.aiUnguidedEstimateDays}d`.padEnd(6)} ${actual.padEnd(8)} ${vsSolo.padEnd(8)} ${vsAi.padEnd(8)} ${String(s.qaIterations).padEnd(4)} ${String(s.quality.reviewCount).padEnd(4)} ${String(s.quality.blockingReviews).padEnd(4)} ${String(s.quality.evidenceCount).padEnd(4)} ${s.quality.lessonCount}`);
|
|
1441
|
-
}
|
|
1442
|
-
if (summary.totalWithActuals > 0) {
|
|
1443
|
-
io.log("");
|
|
1444
|
-
io.log(`Avg savings vs solo: ${summary.avgVsSoloPct}%`);
|
|
1445
|
-
io.log(`Avg savings vs AI-unguided: ${summary.avgVsAiUnguidedPct}%`);
|
|
1446
|
-
io.log(`Stories with actuals: ${summary.totalWithActuals}/${summary.stories.length}`);
|
|
1447
|
-
}
|
|
1448
|
-
return;
|
|
1449
|
-
}
|
|
1450
|
-
const taskId = requireArg(options, "task");
|
|
1451
|
-
const result = await computeBenchmark(cwd, taskId);
|
|
1452
|
-
if (options.json) {
|
|
1453
|
-
io.log(JSON.stringify(result, null, 2));
|
|
1454
|
-
return;
|
|
1455
|
-
}
|
|
1456
|
-
io.log(`Benchmark: ${taskId} [${result.status}]`);
|
|
1457
|
-
io.log(` Sizing: ${result.sizingLabel}`);
|
|
1458
|
-
io.log(` Solo: ${result.soloEstimateDays}d (declared)`);
|
|
1459
|
-
io.log(` AI-unguided: ${result.aiUnguidedEstimateDays}d (declared)`);
|
|
1460
|
-
io.log(` Actual: ${result.actualDays !== null ? `${result.actualDays}d` : "pending — run not complete"}`);
|
|
1461
|
-
if (result.vsSoloPct !== null) {
|
|
1462
|
-
const sign = result.vsSoloPct > 0 ? "+" : "";
|
|
1463
|
-
io.log(` vs Solo: ${sign}${result.vsSoloPct}%`);
|
|
1464
|
-
io.log(` vs AI: ${result.vsAiUnguidedPct !== null ? `${result.vsAiUnguidedPct > 0 ? "+" : ""}${result.vsAiUnguidedPct}%` : "—"}`);
|
|
1465
|
-
}
|
|
1466
|
-
io.log(` QA loops: ${result.qaIterations}`);
|
|
1467
|
-
io.log(` Reviews: ${result.quality.reviewCount} (${result.quality.blockingReviews} blocking)`);
|
|
1468
|
-
io.log(` Evidence: ${result.quality.evidenceCount} artifacts`);
|
|
1469
|
-
io.log(` Gate blocks: ${result.quality.gateBlockCount}`);
|
|
1470
|
-
io.log(` Lessons: ${result.quality.lessonCount}`);
|
|
1471
|
-
if (result.quality.totalInputTokens > 0 ||
|
|
1472
|
-
result.quality.totalOutputTokens > 0) {
|
|
1473
|
-
io.log(` Tokens: ${result.quality.totalInputTokens}in / ${result.quality.totalOutputTokens}out`);
|
|
1474
|
-
io.log(` Cost: $${result.quality.estimatedCostUsd}`);
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
// --- Velocity command ---
|
|
1478
|
-
export async function velocityCommand(options, io) {
|
|
1479
|
-
const cwd = process.cwd();
|
|
1480
|
-
if (typeof options.sprints === "string") {
|
|
1481
|
-
const limit = Number(options.sprints);
|
|
1482
|
-
if (!Number.isInteger(limit) || limit <= 0) {
|
|
1483
|
-
throw new Error("--sprints must be a positive integer");
|
|
1484
|
-
}
|
|
1485
|
-
const report = await velocityTrend(cwd, limit);
|
|
1486
|
-
if (options.json) {
|
|
1487
|
-
io.log(JSON.stringify(report, null, 2));
|
|
1488
|
-
return;
|
|
1489
|
-
}
|
|
1490
|
-
io.log(`Sprint Velocity Report`);
|
|
1491
|
-
io.log(` Trend: ${report.trend}`);
|
|
1492
|
-
for (const sprint of report.sprints) {
|
|
1493
|
-
io.log(` ${sprint.sprintId} ${sprint.storiesCompleted} stories ${sprint.pointsCompleted} pts`);
|
|
1494
|
-
}
|
|
1495
|
-
return;
|
|
1496
|
-
}
|
|
1497
|
-
const report = await computeVelocity(cwd);
|
|
1498
|
-
if (options.json) {
|
|
1499
|
-
io.log(JSON.stringify(report, null, 2));
|
|
1500
|
-
return;
|
|
1501
|
-
}
|
|
1502
|
-
io.log(`Velocity Report`);
|
|
1503
|
-
io.log(` Stories completed: ${report.totalStoriesCompleted}`);
|
|
1504
|
-
io.log(` Points completed: ${report.totalPointsCompleted}`);
|
|
1505
|
-
io.log(` Avg actual days: ${report.avgActualDaysPerStory !== null ? report.avgActualDaysPerStory + "d" : "n/a"}`);
|
|
1506
|
-
io.log(` vs Solo estimate: ${report.avgVsSoloPct !== null ? report.avgVsSoloPct + "%" : "n/a"}`);
|
|
1507
|
-
io.log(` vs AI estimate: ${report.avgVsAiUnguidedPct !== null ? report.avgVsAiUnguidedPct + "%" : "n/a"}`);
|
|
1508
|
-
io.log(` Trend: ${report.trend}`);
|
|
1509
|
-
if (report.weeks.length > 0) {
|
|
1510
|
-
io.log(`\nWeekly breakdown:`);
|
|
1511
|
-
for (const week of report.weeks) {
|
|
1512
|
-
io.log(` ${week.week} ${week.storiesCompleted} stories ${week.pointsCompleted} pts`);
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
// --- Sprint command ---
|
|
1517
|
-
export async function sprintCommand(subcommand, options, io) {
|
|
1518
|
-
const cwd = process.cwd();
|
|
1519
|
-
if (subcommand === "start") {
|
|
1520
|
-
const id = requireArg(options, "id");
|
|
1521
|
-
const taskIds = requireArg(options, "tasks")
|
|
1522
|
-
.split(",")
|
|
1523
|
-
.map((taskId) => taskId.trim())
|
|
1524
|
-
.filter(Boolean);
|
|
1525
|
-
const sprint = await startSprint(cwd, id, taskIds);
|
|
1526
|
-
if (options.json) {
|
|
1527
|
-
io.log(JSON.stringify(sprint, null, 2));
|
|
1528
|
-
return;
|
|
1529
|
-
}
|
|
1530
|
-
io.log(`Sprint started: ${sprint.id} (${sprint.taskIds.length} tasks)`);
|
|
1531
|
-
return;
|
|
1532
|
-
}
|
|
1533
|
-
if (subcommand === "close") {
|
|
1534
|
-
const id = requireArg(options, "id");
|
|
1535
|
-
const sprint = await closeSprint(cwd, id);
|
|
1536
|
-
if (options.json) {
|
|
1537
|
-
io.log(JSON.stringify(sprint, null, 2));
|
|
1538
|
-
return;
|
|
1539
|
-
}
|
|
1540
|
-
io.log(`Sprint closed: ${sprint.id}`);
|
|
1541
|
-
return;
|
|
1542
|
-
}
|
|
1543
|
-
throw new Error(`unknown sprint command: ${subcommand ?? ""}`.trim());
|
|
1544
|
-
}
|
|
1545
|
-
// --- Calibration command ---
|
|
1546
|
-
export async function calibrationCommand(options, io) {
|
|
1547
|
-
const report = await calibrationReport(process.cwd());
|
|
1548
|
-
const roleFilter = typeof options.role === "string" ? options.role : null;
|
|
1549
|
-
const filtered = roleFilter
|
|
1550
|
-
? { roles: report.roles.filter((role) => role.role === roleFilter) }
|
|
1551
|
-
: report;
|
|
1552
|
-
if (options.json) {
|
|
1553
|
-
io.log(JSON.stringify(filtered, null, 2));
|
|
1554
|
-
return;
|
|
1555
|
-
}
|
|
1556
|
-
io.log(`Calibration Report`);
|
|
1557
|
-
for (const role of filtered.roles) {
|
|
1558
|
-
io.log(` ${role.role} stories=${role.stories} declared=${role.avgDeclaredDays}d actual=${role.avgActualDays}d error=${role.avgErrorPct}% bias=${role.bias}`);
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
// --- Burndown command ---
|
|
1562
|
-
export async function burndownCommand(options, io) {
|
|
1563
|
-
const cwd = process.cwd();
|
|
1564
|
-
const sprintCsv = requireArg(options, "sprint");
|
|
1565
|
-
const sprintTaskIds = sprintCsv
|
|
1566
|
-
.split(",")
|
|
1567
|
-
.map((s) => s.trim())
|
|
1568
|
-
.filter(Boolean);
|
|
1569
|
-
if (sprintTaskIds.length === 0)
|
|
1570
|
-
throw new Error(`--sprint requires at least one task id`);
|
|
1571
|
-
const series = await computeSprintBurndown(cwd, sprintTaskIds);
|
|
1572
|
-
if (options.json) {
|
|
1573
|
-
io.log(JSON.stringify(series, null, 2));
|
|
1574
|
-
return;
|
|
1575
|
-
}
|
|
1576
|
-
for (const w of series.warnings)
|
|
1577
|
-
io.log(`⚠ ${w}`);
|
|
1578
|
-
if (series.totalPoints === 0) {
|
|
1579
|
-
io.log("No points to chart — record estimates first with: orchestra estimate --task <id> ...");
|
|
1580
|
-
return;
|
|
1581
|
-
}
|
|
1582
|
-
io.log(`Sprint burndown total=${series.totalPoints} pts tasks=${series.sprintTaskIds.length}`);
|
|
1583
|
-
io.log("");
|
|
1584
|
-
io.log(renderBurndownAscii(series));
|
|
1585
|
-
io.log("");
|
|
1586
|
-
io.log("Task breakdown:");
|
|
1587
|
-
for (const t of series.taskBreakdown) {
|
|
1588
|
-
const arch = t.architectPoints !== null ? `arch=${t.architectPoints}` : "arch=—";
|
|
1589
|
-
const dev = t.developerPoints !== null ? `dev=${t.developerPoints}` : "dev=—";
|
|
1590
|
-
const done = t.completedAt
|
|
1591
|
-
? `done ${t.completedAt.slice(0, 10)}`
|
|
1592
|
-
: "pending";
|
|
1593
|
-
io.log(` ${t.taskId.padEnd(14)} ${arch.padEnd(10)} ${dev.padEnd(10)} resolved=${t.resolvedPoints} ${done}`);
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
1130
|
function parseRuntimeTargetOptions(options) {
|
|
1597
1131
|
const targetValues = [
|
|
1598
1132
|
...parseCsv(options.target),
|
|
@@ -1600,6 +1134,9 @@ function parseRuntimeTargetOptions(options) {
|
|
|
1600
1134
|
];
|
|
1601
1135
|
return [...new Set(targetValues)].map((target) => parseRuntimeTarget(target));
|
|
1602
1136
|
}
|
|
1137
|
+
function parseSkillRenderTarget(value) {
|
|
1138
|
+
return parseRuntimeTarget(value);
|
|
1139
|
+
}
|
|
1603
1140
|
function parseCsv(value) {
|
|
1604
1141
|
if (typeof value !== "string" || value.trim() === "") {
|
|
1605
1142
|
return [];
|
|
@@ -1609,6 +1146,16 @@ function parseCsv(value) {
|
|
|
1609
1146
|
.map((item) => item.trim())
|
|
1610
1147
|
.filter(Boolean);
|
|
1611
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
|
+
}
|
|
1612
1159
|
function stringOption(value) {
|
|
1613
1160
|
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
1614
1161
|
}
|
|
@@ -1648,18 +1195,6 @@ function parseRiskAcceptance(options) {
|
|
|
1648
1195
|
});
|
|
1649
1196
|
return Object.keys(acceptance).length > 0 ? acceptance : undefined;
|
|
1650
1197
|
}
|
|
1651
|
-
function parseProviderRouting(options) {
|
|
1652
|
-
return {
|
|
1653
|
-
provider: requireArg(options, "provider"),
|
|
1654
|
-
model: requireArg(options, "model"),
|
|
1655
|
-
fallbacks: parseCsv(options.fallbacks),
|
|
1656
|
-
maxTokens: numberOption(options["max-tokens"], 0),
|
|
1657
|
-
maxCostUsd: numberOption(options["max-cost-usd"], 0),
|
|
1658
|
-
timeoutMs: numberOption(options["timeout-ms"], 30000),
|
|
1659
|
-
retries: numberOption(options.retries, 0),
|
|
1660
|
-
requiredCapabilities: parseCsv(options.capabilities),
|
|
1661
|
-
};
|
|
1662
|
-
}
|
|
1663
1198
|
function parseBudgetEscalationDecision(options) {
|
|
1664
1199
|
if (options["approve-budget-fallback"]) {
|
|
1665
1200
|
return removeUndefined({
|
|
@@ -1677,13 +1212,6 @@ function parseBudgetEscalationDecision(options) {
|
|
|
1677
1212
|
}
|
|
1678
1213
|
return undefined;
|
|
1679
1214
|
}
|
|
1680
|
-
function parseApprovalDecision(options) {
|
|
1681
|
-
return {
|
|
1682
|
-
id: requireArg(options, "id"),
|
|
1683
|
-
approver: requireArg(options, "approver"),
|
|
1684
|
-
rationale: requireArg(options, "rationale"),
|
|
1685
|
-
};
|
|
1686
|
-
}
|
|
1687
1215
|
function numberOption(value, fallback) {
|
|
1688
1216
|
if (typeof value !== "string" || value.trim() === "") {
|
|
1689
1217
|
return fallback;
|
|
@@ -1732,6 +1260,24 @@ function renderDependencyReport(report) {
|
|
|
1732
1260
|
: report.dependencies.map((dependency) => `- ${dependency.id}: ${dependency.status} (${dependency.isComplete ? "complete" : "incomplete"})`)),
|
|
1733
1261
|
].join("\n");
|
|
1734
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
|
+
}
|
|
1735
1281
|
function renderTaskGraphPlan(plan) {
|
|
1736
1282
|
return [
|
|
1737
1283
|
"Task graph plan",
|
|
@@ -1749,6 +1295,28 @@ function renderTaskGraphPlan(plan) {
|
|
|
1749
1295
|
...taskGraphReadyLines(plan.complete),
|
|
1750
1296
|
].join("\n");
|
|
1751
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
|
+
}
|
|
1752
1320
|
function renderTaskGraphBatchRun(batch) {
|
|
1753
1321
|
return [
|
|
1754
1322
|
"Task graph batch run",
|
|
@@ -1820,43 +1388,6 @@ function renderSummaryMarkdown(summary) {
|
|
|
1820
1388
|
"",
|
|
1821
1389
|
].join("\n");
|
|
1822
1390
|
}
|
|
1823
|
-
function renderUsageReport(report) {
|
|
1824
|
-
return [
|
|
1825
|
-
`Usage${report.taskId ? ` for ${report.taskId}` : ""}`,
|
|
1826
|
-
`- Requests: ${report.totals.requests}`,
|
|
1827
|
-
`- Input tokens: ${report.totals.inputTokens}`,
|
|
1828
|
-
`- Output tokens: ${report.totals.outputTokens}`,
|
|
1829
|
-
`- Total tokens: ${report.totals.totalTokens}`,
|
|
1830
|
-
`- Estimated cost USD: ${report.totals.estimatedCostUsd}`,
|
|
1831
|
-
"",
|
|
1832
|
-
"By role:",
|
|
1833
|
-
...usageLines(report.byRole),
|
|
1834
|
-
"",
|
|
1835
|
-
"By provider:",
|
|
1836
|
-
...usageLines(report.byProvider),
|
|
1837
|
-
].join("\n");
|
|
1838
|
-
}
|
|
1839
|
-
function renderBudgetCheck(result) {
|
|
1840
|
-
return [
|
|
1841
|
-
`Budget check${result.taskId ? ` for ${result.taskId}` : ""}: ${result.passed ? "passed" : "failed"}`,
|
|
1842
|
-
`- Applied budgets: ${result.appliedBudgets.length > 0
|
|
1843
|
-
? result.appliedBudgets.join(", ")
|
|
1844
|
-
: "none"}`,
|
|
1845
|
-
`- Requests: ${result.usage.totals.requests}`,
|
|
1846
|
-
`- Total tokens: ${result.usage.totals.totalTokens}`,
|
|
1847
|
-
`- Estimated cost USD: ${result.usage.totals.estimatedCostUsd}`,
|
|
1848
|
-
"",
|
|
1849
|
-
"Violations:",
|
|
1850
|
-
...(result.violations.length === 0
|
|
1851
|
-
? ["- none"]
|
|
1852
|
-
: result.violations.map((violation) => `- ${violation.scope} ${violation.metric}: ${violation.actual} > ${violation.limit}`)),
|
|
1853
|
-
].join("\n");
|
|
1854
|
-
}
|
|
1855
|
-
function usageLines(items) {
|
|
1856
|
-
return items.length === 0
|
|
1857
|
-
? ["- none"]
|
|
1858
|
-
: items.map((item) => `- ${item.key}: ${item.requests} requests, ${item.totalTokens} tokens, $${item.estimatedCostUsd}`);
|
|
1859
|
-
}
|
|
1860
1391
|
function renderPullRequestSummaryMarkdown(summary) {
|
|
1861
1392
|
return [
|
|
1862
1393
|
`# PR Summary: ${summary.task.id}`,
|
|
@@ -1973,6 +1504,8 @@ function renderTaskContextMarkdown(context) {
|
|
|
1973
1504
|
: context.skills.selected.map((item) => `- ${item.skill.id} (score ${item.score}): ${item.rationale.join("; ")}`)),
|
|
1974
1505
|
`- Source groups: ${context.skills.sourceGroups.join(", ") || "none"}`,
|
|
1975
1506
|
"",
|
|
1507
|
+
renderMemoryPacket(context.memory),
|
|
1508
|
+
"",
|
|
1976
1509
|
"## Decisions",
|
|
1977
1510
|
...eventLines(context.decisions),
|
|
1978
1511
|
"",
|
|
@@ -2045,6 +1578,27 @@ function renderPlaywrightPlanMarkdown(plan) {
|
|
|
2045
1578
|
"",
|
|
2046
1579
|
].join("\n");
|
|
2047
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
|
+
}
|
|
2048
1602
|
function eventLines(events) {
|
|
2049
1603
|
return events.length === 0
|
|
2050
1604
|
? ["- none"]
|