@jterrats/open-orchestra 0.4.2-beta.1 → 0.5.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/AGENTS.md +5 -3
  2. package/README.md +29 -5
  3. package/dist/advisory-artifacts.d.ts +20 -0
  4. package/dist/advisory-artifacts.js +136 -0
  5. package/dist/advisory-artifacts.js.map +1 -0
  6. package/dist/assets/web-console.js +436 -4
  7. package/dist/cli.js +16 -117
  8. package/dist/cli.js.map +1 -1
  9. package/dist/command-manifest.d.ts +6 -0
  10. package/dist/command-manifest.js +141 -43
  11. package/dist/command-manifest.js.map +1 -1
  12. package/dist/command-utils.d.ts +5 -0
  13. package/dist/command-utils.js +23 -0
  14. package/dist/command-utils.js.map +1 -1
  15. package/dist/commands.d.ts +7 -42
  16. package/dist/commands.js +214 -1356
  17. package/dist/commands.js.map +1 -1
  18. package/dist/constants.js +3 -0
  19. package/dist/constants.js.map +1 -1
  20. package/dist/context-budget.d.ts +4 -0
  21. package/dist/context-budget.js +119 -0
  22. package/dist/context-budget.js.map +1 -0
  23. package/dist/delivery-commands.d.ts +10 -0
  24. package/dist/delivery-commands.js +152 -0
  25. package/dist/delivery-commands.js.map +1 -0
  26. package/dist/github.d.ts +50 -1
  27. package/dist/github.js +234 -0
  28. package/dist/github.js.map +1 -1
  29. package/dist/health-checks.d.ts +1 -0
  30. package/dist/health-checks.js +11 -1
  31. package/dist/health-checks.js.map +1 -1
  32. package/dist/health-commands.js +2 -0
  33. package/dist/health-commands.js.map +1 -1
  34. package/dist/mcp-oauth-proxy.d.ts +32 -0
  35. package/dist/mcp-oauth-proxy.js +120 -0
  36. package/dist/mcp-oauth-proxy.js.map +1 -1
  37. package/dist/memory.d.ts +2 -1
  38. package/dist/memory.js +71 -10
  39. package/dist/memory.js.map +1 -1
  40. package/dist/package-update-check.d.ts +5 -1
  41. package/dist/package-update-check.js +20 -8
  42. package/dist/package-update-check.js.map +1 -1
  43. package/dist/planning-commands.d.ts +14 -0
  44. package/dist/planning-commands.js +372 -0
  45. package/dist/planning-commands.js.map +1 -0
  46. package/dist/release-candidate.d.ts +2 -0
  47. package/dist/release-candidate.js +9 -14
  48. package/dist/release-candidate.js.map +1 -1
  49. package/dist/release-commands.d.ts +2 -0
  50. package/dist/release-commands.js +58 -6
  51. package/dist/release-commands.js.map +1 -1
  52. package/dist/release-readiness.d.ts +49 -0
  53. package/dist/release-readiness.js +172 -0
  54. package/dist/release-readiness.js.map +1 -0
  55. package/dist/runtime-commands.js +11 -4
  56. package/dist/runtime-commands.js.map +1 -1
  57. package/dist/runtime-execution-renderer.js +2 -0
  58. package/dist/runtime-execution-renderer.js.map +1 -1
  59. package/dist/runtime-execution.d.ts +4 -2
  60. package/dist/runtime-execution.js +11 -4
  61. package/dist/runtime-execution.js.map +1 -1
  62. package/dist/setup-agents-import.d.ts +42 -0
  63. package/dist/setup-agents-import.js +335 -0
  64. package/dist/setup-agents-import.js.map +1 -0
  65. package/dist/skills-catalog-service.d.ts +2 -0
  66. package/dist/skills-catalog-service.js +8 -0
  67. package/dist/skills-catalog-service.js.map +1 -0
  68. package/dist/skills-catalog.d.ts +2 -0
  69. package/dist/skills-catalog.js +389 -0
  70. package/dist/skills-catalog.js.map +1 -0
  71. package/dist/skills-commands.js +1 -11
  72. package/dist/skills-commands.js.map +1 -1
  73. package/dist/skills-events.d.ts +9 -0
  74. package/dist/skills-events.js +50 -0
  75. package/dist/skills-events.js.map +1 -0
  76. package/dist/skills-memory.d.ts +18 -0
  77. package/dist/skills-memory.js +127 -0
  78. package/dist/skills-memory.js.map +1 -0
  79. package/dist/skills-planning.d.ts +2 -0
  80. package/dist/skills-planning.js +87 -0
  81. package/dist/skills-planning.js.map +1 -0
  82. package/dist/skills-render.d.ts +14 -0
  83. package/dist/skills-render.js +83 -0
  84. package/dist/skills-render.js.map +1 -0
  85. package/dist/skills-validation.d.ts +2 -0
  86. package/dist/skills-validation.js +49 -0
  87. package/dist/skills-validation.js.map +1 -0
  88. package/dist/skills.d.ts +6 -42
  89. package/dist/skills.js +6 -773
  90. package/dist/skills.js.map +1 -1
  91. package/dist/task-graph-commands.d.ts +14 -0
  92. package/dist/task-graph-commands.js +367 -0
  93. package/dist/task-graph-commands.js.map +1 -0
  94. package/dist/tool-commands.js +8 -0
  95. package/dist/tool-commands.js.map +1 -1
  96. package/dist/types/context.d.ts +12 -0
  97. package/dist/types/context.js +2 -0
  98. package/dist/types/context.js.map +1 -0
  99. package/dist/types/metrics.d.ts +114 -0
  100. package/dist/types/metrics.js +2 -0
  101. package/dist/types/metrics.js.map +1 -0
  102. package/dist/types/model-config.d.ts +212 -0
  103. package/dist/types/model-config.js +2 -0
  104. package/dist/types/model-config.js.map +1 -0
  105. package/dist/types/runtime.d.ts +93 -0
  106. package/dist/types/runtime.js +2 -0
  107. package/dist/types/runtime.js.map +1 -0
  108. package/dist/types/skills.d.ts +147 -0
  109. package/dist/types/skills.js +2 -0
  110. package/dist/types/skills.js.map +1 -0
  111. package/dist/types/tasks.d.ts +171 -0
  112. package/dist/types/tasks.js +2 -0
  113. package/dist/types/tasks.js.map +1 -0
  114. package/dist/types/workflow-run.d.ts +79 -0
  115. package/dist/types/workflow-run.js +2 -0
  116. package/dist/types/workflow-run.js.map +1 -0
  117. package/dist/types.d.ts +13 -798
  118. package/dist/types.js +1 -1
  119. package/dist/types.js.map +1 -1
  120. package/dist/upgrade-commands.d.ts +2 -0
  121. package/dist/upgrade-commands.js +65 -0
  122. package/dist/upgrade-commands.js.map +1 -0
  123. package/dist/web-api-read-routes.d.ts +5 -0
  124. package/dist/web-api-read-routes.js +37 -0
  125. package/dist/web-api-read-routes.js.map +1 -0
  126. package/dist/web-api.d.ts +1 -3
  127. package/dist/web-api.js +145 -44
  128. package/dist/web-api.js.map +1 -1
  129. package/dist/web-console-sections.d.ts +2 -0
  130. package/dist/web-console-sections.js +7 -0
  131. package/dist/web-console-sections.js.map +1 -0
  132. package/dist/web-console.js +23 -3
  133. package/dist/web-console.js.map +1 -1
  134. package/dist/workflow-approval-service.d.ts +9 -0
  135. package/dist/workflow-approval-service.js +126 -0
  136. package/dist/workflow-approval-service.js.map +1 -0
  137. package/dist/workflow-approval-utils.d.ts +10 -0
  138. package/dist/workflow-approval-utils.js +82 -0
  139. package/dist/workflow-approval-utils.js.map +1 -0
  140. package/dist/workflow-budget-utils.d.ts +7 -0
  141. package/dist/workflow-budget-utils.js +96 -0
  142. package/dist/workflow-budget-utils.js.map +1 -0
  143. package/dist/workflow-evidence-service.d.ts +7 -0
  144. package/dist/workflow-evidence-service.js +100 -0
  145. package/dist/workflow-evidence-service.js.map +1 -0
  146. package/dist/workflow-run-commands.d.ts +8 -0
  147. package/dist/workflow-run-commands.js +479 -0
  148. package/dist/workflow-run-commands.js.map +1 -0
  149. package/dist/workflow-services.d.ts +8 -18
  150. package/dist/workflow-services.js +30 -481
  151. package/dist/workflow-services.js.map +1 -1
  152. package/dist/workflow-summary-service.d.ts +4 -0
  153. package/dist/workflow-summary-service.js +82 -0
  154. package/dist/workflow-summary-service.js.map +1 -0
  155. package/dist/workflow-templates.d.ts +1 -0
  156. package/dist/workflow-templates.js +1 -0
  157. package/dist/workflow-templates.js.map +1 -1
  158. package/dist/workspace.d.ts +18 -1
  159. package/dist/workspace.js +72 -4
  160. package/dist/workspace.js.map +1 -1
  161. package/docs/command-contracts.md +22 -0
  162. package/docs/mcp-oauth-proxy-evaluation.md +14 -0
  163. package/docs/orchestra-mvp.md +158 -114
  164. package/docs/package-naming.md +20 -0
  165. package/docs/persona-workflows.md +209 -0
  166. package/docs/runtime-adapters.md +19 -14
  167. package/docs/runtime-llm-flow.md +29 -28
  168. package/docs/setup-agents-bridge.md +61 -0
  169. package/docs/traceability-flow.md +89 -0
  170. package/package.json +9 -7
package/dist/commands.js CHANGED
@@ -1,17 +1,14 @@
1
- import { appendEvent, initWorkspace } from "./workspace.js";
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { initWorkspace, inspectInitWorkspace } from "./workspace.js";
2
4
  import { requireArg } from "./args.js";
3
- import { 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";
7
- import { executePhaseWithLlm } from "./phase-executor.js";
8
- import { addEvidence, addPlaywrightEvidence, addTask, archiveTask, approveWorkflowGate, checkReadiness, checkTaskDependencies, claimLock, createHandoff, deleteTask, evaluateWorkflowGate, executeNextReadyTask, executePlanWithBudgetPreflight, executeReadyTaskBatch, generateExecutionPlan, generatePlaywrightTestPlan, generatePullRequestSummary, generateTaskGraphPlan, getWorkflowStatus, getWorkflowSummary, getTaskContext, getWorkflowConfig, listEvidence, listDecisions, listLocks, listReviews, listRoles, listTasks, previewTaskGraphRun, recordReview, recordDecision, releaseLock, updateTask, validatePreRun, } from "./workflow-services.js";
5
+ import { addTask, generatePullRequestSummary, getWorkflowStatus, getWorkflowSummary, listRoles, validatePreRun, } from "./workflow-services.js";
9
6
  import { validateWorkspace } from "./workspace-validator.js";
10
7
  import { getWebServerAddress, startWebApiServer } from "./web-api.js";
11
- import { decideTaskDelegation } from "./delegation-decision.js";
12
- import { listCollaborationFlows, recommendCollaborationFlow, } from "./collaboration-flows.js";
13
- import { listWorkflowTemplates, renderWorkflowTemplates, selectWorkflowTemplates, validateWorkflowTemplates, } from "./workflow-templates.js";
8
+ import { syncGitHubIssueToTask } from "./github.js";
14
9
  import { parseRuntimeTarget } from "./runtime-adapters.js";
10
+ import { importSetupAgentsArtifacts } from "./setup-agents-import.js";
11
+ import { numberOption, parseCsv, removeUndefined, stringOption, targetRootOption, } from "./command-utils.js";
15
12
  export { instructionsApplyCommand, instructionsBlockCommand, instructionsImportsCommand, instructionsStaleCommand, } from "./instruction-commands.js";
16
13
  export { benchmarkCommand, estimateCommand } from "./metrics-commands.js";
17
14
  export { burndownCommand, calibrationCommand, sprintCommand, velocityCommand, } from "./sprint-commands.js";
@@ -19,10 +16,17 @@ export { approvalsApproveCommand, approvalsListCommand, approvalsRejectCommand,
19
16
  export { commandsManifestCommand, protocolBlockCommand, protocolRenderCommand, runtimeAdaptersCommand, runtimeBootstrapCommand, runtimeBriefCommand, runtimeDelegatePlanCommand, runtimeHandoffCommand, } from "./runtime-commands.js";
20
17
  export { lessonsAddCommand, lessonsDeleteCommand, lessonsListCommand, lessonsPromoteCommand, skillsListCommand, skillsPlanCommand, skillsRenderCommand, skillsValidateCommand, sourcesListCommand, } from "./skills-commands.js";
21
18
  export { diagramsLintCommand, mcpOAuthProxyEvaluateCommand, mcpOAuthProxyStartCommand, mcpOAuthProxyTokenCommand, } from "./tool-commands.js";
19
+ export { decisionAddCommand, decisionListCommand, evidenceCommand, evidenceListCommand, gateCommand, handoffCommand, readinessCommand, reviewCommand, reviewListCommand, } from "./delivery-commands.js";
20
+ export { graphPlanCommand, graphRunNextCommand, graphRunReadyCommand, lockClaimCommand, lockListCommand, lockReleaseCommand, taskAddCommand, taskArchiveCommand, taskDeleteCommand, taskDepsCommand, taskListCommand, taskShowCommand, taskUpdateCommand, } from "./task-graph-commands.js";
21
+ export { collaborationFlowsCommand, collaborationRecommendCommand, contextCommand, delegationDecideCommand, memoryHookCommand, memoryQueryCommand, planCommand, playwrightEvidenceCommand, playwrightPlanCommand, runCommand, workflowTemplateRenderCommand, workflowTemplateSelectCommand, workflowTemplatesCommand, } from "./planning-commands.js";
22
22
  export { telemetryDisableCommand, telemetryEnableCommand, telemetryEvalDatasetCommand, telemetryExportCommand, telemetryStatusCommand, telemetrySubmitCommand, } from "./telemetry-commands.js";
23
- import { buildPrBody, createPullRequest } from "./github.js";
23
+ export { workflowCancelCommand, workflowClarifyCommand, workflowClarifyListCommand, workflowClarifyRespondCommand, workflowGateApproveCommand, workflowRunCommand, workflowRunListCommand, } from "./workflow-run-commands.js";
24
24
  export async function initCommand(options, io) {
25
- const root = stringOption(options["target-dir"]) ?? process.cwd();
25
+ if (options.help || options.h) {
26
+ io.log(renderInitHelp());
27
+ return;
28
+ }
29
+ const root = targetRootOption(options);
26
30
  const input = {
27
31
  root,
28
32
  force: Boolean(options.force),
@@ -31,27 +35,45 @@ export async function initCommand(options, io) {
31
35
  };
32
36
  const bootstrapTargetFile = stringOption(options["bootstrap-file"]);
33
37
  const runtimeTargets = parseRuntimeTargetOptions(options);
34
- const base = await initWorkspace({
38
+ const preflight = await inspectInitWorkspace({
35
39
  ...input,
36
40
  ...(bootstrapTargetFile ? { bootstrapTargetFile } : {}),
37
41
  ...(runtimeTargets.length > 0 ? { runtimeTargets } : {}),
38
42
  });
39
- io.log(`Initialized ${base}`);
40
- if (options.advisory) {
41
- io.log("Advisory mode enabled; root instruction files were not written.");
43
+ if (options.check || options["dry-run"]) {
44
+ if (options.json) {
45
+ io.log(JSON.stringify(preflight, null, 2));
46
+ return;
47
+ }
48
+ io.log(renderInitPreflight(preflight));
49
+ return;
42
50
  }
43
- else {
44
- io.log("Prompt registry scaffolded in .generated-prompts/");
51
+ const base = await initWorkspace({
52
+ ...input,
53
+ ...(bootstrapTargetFile ? { bootstrapTargetFile } : {}),
54
+ ...(runtimeTargets.length > 0 ? { runtimeTargets } : {}),
55
+ });
56
+ const result = {
57
+ ...preflight,
58
+ workflowRoot: base,
59
+ initialized: true,
60
+ nextSteps: initNextSteps(preflight.mode, root),
61
+ };
62
+ if (options.json) {
63
+ io.log(JSON.stringify(result, null, 2));
64
+ return;
45
65
  }
46
- io.log("Source catalog and agent lessons scaffolded in .agent-workflow/");
47
- io.log("Next: add tasks to .agent-workflow/tasks.json and run orchestra status");
66
+ io.log(renderInitSuccess(result));
48
67
  }
49
68
  export async function statusCommand(options, io) {
50
- const status = await getWorkflowStatus();
69
+ const root = targetRootOption(options);
70
+ const status = await getWorkflowStatus(root);
51
71
  if (options.json) {
52
- io.log(JSON.stringify(status, null, 2));
72
+ io.log(JSON.stringify({ workspaceRoot: root, ...status }, null, 2));
53
73
  return;
54
74
  }
75
+ io.log(`Workspace: ${root}`);
76
+ io.log(`Mode: ${status.mode ?? "project"}`);
55
77
  io.log(`Tasks: ${status.tasks.total}`);
56
78
  for (const [name, count] of Object.entries(status.tasks.byStatus)) {
57
79
  io.log(` ${name}: ${count}`);
@@ -64,6 +86,38 @@ export async function statusCommand(options, io) {
64
86
  }
65
87
  }
66
88
  }
89
+ export async function advisoryConvertCommand(options, io) {
90
+ const root = targetRootOption(options);
91
+ const taskFile = stringOption(options.file) ??
92
+ path.join(root, ".agent-workflow", "advisory", "project-task.json");
93
+ const payload = JSON.parse(await readFile(taskFile, "utf8"));
94
+ const task = await addTask(removeUndefined({
95
+ id: stringOption(options.id) ?? requiredString(payload.id, "id"),
96
+ title: requiredString(payload.title, "title"),
97
+ ownerRole: requiredString(payload.ownerRole, "ownerRole"),
98
+ dependencies: [],
99
+ backlogItem: stringOption(options.backlog),
100
+ goal: optionalString(payload.goal) ??
101
+ "Convert advisory findings into a project delivery task",
102
+ scope: optionalString(payload.scope) ??
103
+ "Advisory findings, decisions, role guides, and conversion path",
104
+ acceptanceCriteria: requiredStringArray(payload.acceptanceCriteria, "acceptanceCriteria"),
105
+ assumptions: optionalStringArray(payload.assumptions) ?? [
106
+ "Target project repository has been confirmed before conversion.",
107
+ ],
108
+ risks: optionalStringArray(payload.risks) ?? [
109
+ "Advisory findings may require project-owner validation.",
110
+ ],
111
+ paths: optionalStringArray(payload.paths) ?? [".agent-workflow/advisory"],
112
+ testStrategy: optionalString(payload.testStrategy) ??
113
+ "Review advisory artifacts and attach project evidence.",
114
+ }), root);
115
+ if (options.json) {
116
+ io.log(JSON.stringify({ task, source: taskFile }, null, 2));
117
+ return;
118
+ }
119
+ io.log(`Converted advisory task ${task.id} from ${taskFile}`);
120
+ }
67
121
  export async function validateCommand(options, io) {
68
122
  if (options["pre-run"]) {
69
123
  const report = await validatePreRun(requireArg(options, "task"), removeUndefined({
@@ -82,11 +136,13 @@ export async function validateCommand(options, io) {
82
136
  }
83
137
  return;
84
138
  }
85
- const report = await validateWorkspace();
139
+ const root = targetRootOption(options);
140
+ const report = await validateWorkspace(root);
86
141
  if (options.json) {
87
- io.log(JSON.stringify(report, null, 2));
142
+ io.log(JSON.stringify({ workspaceRoot: root, ...report }, null, 2));
88
143
  }
89
144
  else {
145
+ io.log(`Workspace: ${root}`);
90
146
  io.log(report.valid ? "Workspace valid" : "Workspace invalid");
91
147
  for (const error of report.errors) {
92
148
  io.log(`ERROR: ${error}`);
@@ -99,117 +155,138 @@ export async function validateCommand(options, io) {
99
155
  throw new Error("workspace validation failed");
100
156
  }
101
157
  }
102
- export async function webCommand(options, io) {
103
- const serverOptions = {};
104
- const host = stringOption(options.host);
105
- if (host) {
106
- serverOptions.host = host;
107
- }
108
- if (options.port) {
109
- serverOptions.port = numberOption(options.port, 3717);
110
- }
111
- const server = await startWebApiServer(serverOptions);
112
- io.log(`Open Orchestra web API listening at ${getWebServerAddress(server)}`);
113
- }
114
- export async function taskAddCommand(options, io) {
115
- const input = removeUndefined({
116
- id: requireArg(options, "id"),
117
- title: requireArg(options, "title"),
118
- ownerRole: requireArg(options, "owner"),
119
- dependencies: parseCsv(options.depends),
120
- backlogItem: stringOption(options.backlog),
121
- goal: stringOption(options.goal),
122
- scope: stringOption(options.scope),
123
- acceptanceCriteria: parseCsv(options.acceptance),
124
- assumptions: parseCsv(options.assumptions),
125
- risks: parseCsv(options.risks),
126
- paths: parseCsv(options.paths),
127
- testStrategy: stringOption(options["test-strategy"]),
128
- architectureApproval: parseArchitectureApproval(options),
129
- qaGate: parseQaGate(options),
130
- riskGate: parseRiskGate(options),
131
- });
132
- const task = await addTask(input);
133
- io.log(`Added task ${task.id}`);
134
- }
135
- export async function taskListCommand(options, io) {
136
- if (options.help || options.h) {
137
- io.log("task list [--json] [--status <csv>] [--owner <role>] [--filter <text>] [--top-level] [--include-archived]");
138
- return;
158
+ function renderInitHelp() {
159
+ return `orchestra init
160
+
161
+ Usage:
162
+ orchestra init [--force] [--advisory] [--confirm-unknown] [--target-dir <dir>] [--target <csv>] [--bootstrap-file <file>] [--check|--dry-run] [--json]
163
+
164
+ Examples:
165
+ orchestra init
166
+ orchestra init --confirm-unknown
167
+ orchestra init --target-dir ../my-project
168
+ orchestra init --target claude,cursor,windsurf
169
+ orchestra init --advisory
170
+ orchestra init --check
171
+ orchestra init --force
172
+
173
+ After init:
174
+ orchestra health --json
175
+ orchestra task add --id <id> --title <title> --owner <role>
176
+ orchestra status
177
+ orchestra web`;
178
+ }
179
+ function renderInitPreflight(report) {
180
+ const lines = [
181
+ `Workspace: ${report.root}`,
182
+ `Mode: ${report.mode}`,
183
+ `Workflow root: ${report.workflowRoot}`,
184
+ report.workflowExists
185
+ ? "Workflow state already exists."
186
+ : "Workflow state will be created.",
187
+ ];
188
+ if (report.existingArtifacts.length > 0) {
189
+ lines.push("Existing managed artifacts:");
190
+ for (const artifact of report.existingArtifacts) {
191
+ lines.push(` ${artifact}`);
192
+ }
139
193
  }
140
- const tasks = filterTasks(await listTasks(), options);
141
- if (options.json) {
142
- io.log(JSON.stringify(tasks, null, 2));
143
- return;
194
+ lines.push(report.force
195
+ ? "Force: generated files will be refreshed."
196
+ : "Force: disabled; existing workflow state is preserved.");
197
+ lines.push("Generated artifacts:");
198
+ for (const artifact of report.generatedArtifacts) {
199
+ lines.push(` ${artifact}`);
200
+ }
201
+ return lines.join("\n");
202
+ }
203
+ function renderInitSuccess(result) {
204
+ const lines = [
205
+ `Initialized ${result.workflowRoot}`,
206
+ result.mode === "advisory"
207
+ ? "Advisory mode enabled; root instruction files were not written unless explicitly targeted."
208
+ : "Prompt registry scaffolded in .generated-prompts/",
209
+ "Source catalog and agent lessons scaffolded in .agent-workflow/",
210
+ ];
211
+ if (result.force) {
212
+ lines.push("Force mode refreshed generated workflow artifacts.");
144
213
  }
145
- if (tasks.length === 0) {
146
- io.log("No tasks");
147
- return;
214
+ lines.push("Generated artifacts:");
215
+ for (const artifact of result.generatedArtifacts) {
216
+ lines.push(` ${artifact}`);
148
217
  }
149
- for (const task of tasks) {
150
- io.log(`${task.id} [${task.status}] ${task.title} (${task.ownerRole})`);
218
+ lines.push("Next steps:");
219
+ for (const [index, step] of result.nextSteps.entries()) {
220
+ lines.push(` ${index + 1}. ${step}`);
151
221
  }
222
+ return lines.join("\n");
152
223
  }
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(", ")}`);
224
+ function initNextSteps(mode, root) {
225
+ const targetSuffix = root === process.cwd() ? "" : ` --target-dir ${root}`;
226
+ if (mode === "advisory") {
227
+ return [
228
+ `Run orchestra health --json${targetSuffix}`,
229
+ "Review .agent-workflow/advisory/README.md",
230
+ "Convert .agent-workflow/advisory/project-task.json with orchestra advisory convert when the target repo is confirmed",
231
+ ];
168
232
  }
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();
233
+ return [
234
+ `Run orchestra health --json${targetSuffix}`,
235
+ "Add a task with orchestra task add --id <id> --title <title> --owner <role>",
236
+ `Check workflow status with orchestra status${targetSuffix}`,
237
+ "Open the console with orchestra web",
238
+ ];
176
239
  }
177
- function isTopLevelTask(task) {
178
- return !["pm", "po", "architect", "developer", "qa", "release"].some((phase) => task.id.includes(`-${phase}-`));
240
+ function requiredString(value, name) {
241
+ if (typeof value !== "string" || value.trim() === "") {
242
+ throw new Error(`advisory project task missing ${name}`);
243
+ }
244
+ return value;
179
245
  }
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}`);
246
+ function optionalString(value) {
247
+ if (typeof value !== "string" || value.trim() === "") {
248
+ return undefined;
185
249
  }
186
- if (options.json) {
187
- io.log(JSON.stringify(task, null, 2));
188
- return;
250
+ return value;
251
+ }
252
+ function requiredStringArray(value, name) {
253
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
254
+ throw new Error(`advisory project task missing ${name}`);
189
255
  }
190
- for (const line of renderTaskDetails(task)) {
191
- io.log(line);
256
+ return value;
257
+ }
258
+ function optionalStringArray(value) {
259
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
260
+ return undefined;
192
261
  }
262
+ return value;
193
263
  }
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;
264
+ export async function webCommand(options, io) {
265
+ const serverOptions = {};
266
+ const host = stringOption(options.host);
267
+ if (host) {
268
+ serverOptions.host = host;
201
269
  }
202
- io.log(`Deleted task ${task.id}`);
270
+ if (options.port) {
271
+ serverOptions.port = numberOption(options.port, 3717);
272
+ }
273
+ const server = await startWebApiServer(serverOptions);
274
+ io.log(`Open Orchestra web API listening at ${getWebServerAddress(server)}`);
203
275
  }
204
- export async function taskArchiveCommand(options, io) {
205
- const task = await archiveTask(requireArg(options, "id"), {
206
- force: Boolean(options.force),
207
- });
276
+ export async function setupAgentsImportCommand(options, io) {
277
+ const report = await importSetupAgentsArtifacts(removeUndefined({
278
+ source: stringOption(options.source),
279
+ }));
208
280
  if (options.json) {
209
- io.log(JSON.stringify(task, null, 2));
281
+ io.log(JSON.stringify(report, null, 2));
210
282
  return;
211
283
  }
212
- io.log(`Archived task ${task.id}`);
284
+ io.log(`Imported setup-agents artifacts from ${report.sourceRoot}`);
285
+ io.log(`Profiles: ${report.profiles.imported.length} imported, ${report.profiles.conflicts.length} conflicts`);
286
+ io.log(`Tasks: ${report.tasks.imported.length} imported, ${report.tasks.skipped.length} skipped, ${report.tasks.conflicts.length} conflicts`);
287
+ const evidenceCount = report.evidence.imported.reduce((total, item) => total + item.ids.length, 0);
288
+ const handoffCount = report.handoffs.imported.reduce((total, item) => total + item.ids.length, 0);
289
+ io.log(`References: ${evidenceCount} evidence, ${handoffCount} handoffs preserved`);
213
290
  }
214
291
  export async function rolesListCommand(options, io) {
215
292
  const roles = await listRoles();
@@ -226,225 +303,6 @@ export async function rolesListCommand(options, io) {
226
303
  io.log(`${role.id} - ${role.name} (gates: ${gates})`);
227
304
  }
228
305
  }
229
- export async function taskUpdateCommand(options, io) {
230
- const id = requireArg(options, "id");
231
- await updateTask(removeUndefined({
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"]),
242
- status: stringOption(options.status),
243
- blockedReason: stringOption(options["blocked-reason"]),
244
- }));
245
- io.log(`Updated task ${id}`);
246
- }
247
- export async function taskDepsCommand(options, io) {
248
- const report = await checkTaskDependencies(requireArg(options, "id"));
249
- if (options.json) {
250
- io.log(JSON.stringify(report, null, 2));
251
- return;
252
- }
253
- io.log(renderDependencyReport(report));
254
- }
255
- export async function graphPlanCommand(options, io) {
256
- const plan = await generateTaskGraphPlan();
257
- if (options.json) {
258
- io.log(JSON.stringify(plan, null, 2));
259
- return;
260
- }
261
- io.log(renderTaskGraphPlan(plan));
262
- }
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
- }
273
- const run = await executeNextReadyTask();
274
- if (options.json) {
275
- io.log(JSON.stringify(run, null, 2));
276
- return;
277
- }
278
- io.log(renderExecutionRunMarkdown(run));
279
- }
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
- }
290
- const batch = await executeReadyTaskBatch();
291
- if (options.json) {
292
- io.log(JSON.stringify(batch, null, 2));
293
- return;
294
- }
295
- io.log(renderTaskGraphBatchRun(batch));
296
- if (batch.failure) {
297
- throw new Error(`graph batch failed: ${batch.failure.taskId}`);
298
- }
299
- }
300
- export async function lockListCommand(options, io) {
301
- const locks = await listLocks();
302
- if (options.json) {
303
- io.log(JSON.stringify(locks, null, 2));
304
- return;
305
- }
306
- if (locks.length === 0) {
307
- io.log("No locks");
308
- return;
309
- }
310
- for (const lock of locks) {
311
- io.log(`${lock.id} ${lock.path} (${lock.taskId}, ${lock.ownerRole})`);
312
- }
313
- }
314
- export async function lockClaimCommand(options, io) {
315
- const lock = await claimLock(removeUndefined({
316
- id: stringOption(options.id),
317
- taskId: requireArg(options, "task"),
318
- ownerRole: requireArg(options, "role"),
319
- path: requireArg(options, "path"),
320
- reason: requireArg(options, "reason"),
321
- expiresAt: stringOption(options.expires),
322
- }));
323
- io.log(`Claimed lock ${lock.id}`);
324
- }
325
- export async function lockReleaseCommand(options, io) {
326
- const id = requireArg(options, "id");
327
- await releaseLock(id);
328
- io.log(`Released lock ${id}`);
329
- }
330
- export async function readinessCommand(options, io) {
331
- const taskId = requireArg(options, "task");
332
- const { task, report } = await checkReadiness(taskId);
333
- if (options.markdown) {
334
- io.log(renderReadinessMarkdown(task, report));
335
- return;
336
- }
337
- io.log(JSON.stringify(report, null, 2));
338
- }
339
- export async function gateCommand(options, io) {
340
- const gateId = requireArg(options, "gate");
341
- const taskId = requireArg(options, "task");
342
- const result = await evaluateWorkflowGate(gateId, taskId);
343
- if (options.markdown) {
344
- io.log(renderGateMarkdown(result));
345
- return;
346
- }
347
- io.log(JSON.stringify(result, null, 2));
348
- }
349
- export async function handoffCommand(options, io) {
350
- const { artifact } = await createHandoff(removeUndefined({
351
- task: requireArg(options, "task"),
352
- from: requireArg(options, "from"),
353
- to: requireArg(options, "to"),
354
- changed: requireArg(options, "changed"),
355
- behavior: requireArg(options, "behavior"),
356
- tests: requireArg(options, "tests"),
357
- commands: requireArg(options, "commands"),
358
- status: stringOption(options.status),
359
- gaps: stringOption(options.gaps),
360
- risks: stringOption(options.risks),
361
- playwright: stringOption(options.playwright),
362
- updateOwner: options["update-owner"] ? true : undefined,
363
- }));
364
- io.log(`Created ${artifact}`);
365
- }
366
- export async function reviewCommand(options, io) {
367
- const input = {
368
- task: requireArg(options, "task"),
369
- role: requireArg(options, "role"),
370
- result: requireArg(options, "result"),
371
- severity: stringOption(options.severity) ?? "info",
372
- findings: requireArg(options, "findings"),
373
- recommendation: requireArg(options, "recommendation"),
374
- };
375
- const { artifact } = await recordReview(input);
376
- io.log(`Created ${artifact}`);
377
- }
378
- export async function decisionAddCommand(options, io) {
379
- const decision = await recordDecision({
380
- task: requireArg(options, "task"),
381
- title: requireArg(options, "title"),
382
- context: requireArg(options, "context"),
383
- decision: requireArg(options, "decision"),
384
- consequences: requireArg(options, "consequences"),
385
- status: (stringOption(options.status) ?? "accepted"),
386
- owner: requireArg(options, "owner"),
387
- });
388
- io.log(`Created ${decision.artifact}`);
389
- }
390
- export async function decisionListCommand(options, io) {
391
- const decisions = await listDecisions(stringOption(options.task));
392
- if (options.json) {
393
- io.log(JSON.stringify(decisions, null, 2));
394
- return;
395
- }
396
- if (decisions.length === 0) {
397
- io.log("No decisions");
398
- return;
399
- }
400
- for (const decision of decisions) {
401
- io.log(`${decision.taskId ?? "no-task"} ${decision.actor}: ${decision.summary}`);
402
- }
403
- }
404
- export async function reviewListCommand(options, io) {
405
- const reviews = await listReviews(stringOption(options.task));
406
- if (options.json) {
407
- io.log(JSON.stringify(reviews, null, 2));
408
- return;
409
- }
410
- if (reviews.length === 0) {
411
- io.log("No reviews");
412
- return;
413
- }
414
- for (const review of reviews) {
415
- const result = String(review.metadata.result ?? "unknown");
416
- const severity = String(review.metadata.severity ?? "unknown");
417
- io.log(`${review.taskId ?? "no-task"} ${review.actor} ${result} (${severity})`);
418
- }
419
- }
420
- export async function evidenceCommand(options, io) {
421
- const input = removeUndefined({
422
- task: requireArg(options, "task"),
423
- role: requireArg(options, "role"),
424
- type: requireArg(options, "type"),
425
- summary: requireArg(options, "summary"),
426
- path: options.path,
427
- command: options.command,
428
- exitCode: options["exit-code"],
429
- });
430
- const { artifact } = await addEvidence(input);
431
- io.log(`Created ${artifact}`);
432
- }
433
- export async function evidenceListCommand(options, io) {
434
- const evidence = await listEvidence(stringOption(options.task));
435
- if (options.json) {
436
- io.log(JSON.stringify(evidence, null, 2));
437
- return;
438
- }
439
- if (evidence.length === 0) {
440
- io.log("No evidence");
441
- return;
442
- }
443
- for (const item of evidence) {
444
- const type = String(item.metadata.type ?? "unknown");
445
- io.log(`${item.taskId ?? "no-task"} ${item.actor} ${type}: ${item.summary}`);
446
- }
447
- }
448
306
  export async function summaryCommand(options, io) {
449
307
  const summary = await getWorkflowSummary();
450
308
  if (options.json) {
@@ -461,671 +319,30 @@ export async function prSummaryCommand(options, io) {
461
319
  }
462
320
  io.log(renderPullRequestSummaryMarkdown(summary));
463
321
  }
464
- export async function delegationDecideCommand(options, io) {
465
- const decision = await decideTaskDelegation(requireArg(options, "task"), process.cwd(), {
466
- record: !options["no-record"],
467
- });
468
- if (options.json) {
469
- io.log(JSON.stringify(decision, null, 2));
470
- return;
471
- }
472
- io.log(renderDelegationDecisionMarkdown(decision));
473
- }
474
- export async function contextCommand(options, io) {
475
- const context = await getTaskContext(requireArg(options, "task"));
476
- if (options.json) {
477
- io.log(JSON.stringify(context, null, 2));
478
- return;
479
- }
480
- io.log(renderTaskContextMarkdown(context));
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
- }
516
- export async function planCommand(options, io) {
517
- const plan = await generateExecutionPlan(requireArg(options, "task"));
518
- if (options.json) {
519
- io.log(JSON.stringify(plan, null, 2));
520
- return;
521
- }
522
- io.log(renderExecutionPlanMarkdown(plan));
523
- }
524
- export async function runCommand(options, io) {
525
- const taskId = requireArg(options, "task");
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
- }));
532
- if (options.json) {
533
- io.log(JSON.stringify(run, null, 2));
534
- return;
535
- }
536
- io.log(renderExecutionRunMarkdown(run));
537
- }
538
- export async function playwrightPlanCommand(options, io) {
539
- const plan = await generatePlaywrightTestPlan(requireArg(options, "task"));
540
- if (options.json) {
541
- io.log(JSON.stringify(plan, null, 2));
542
- return;
543
- }
544
- io.log(renderPlaywrightPlanMarkdown(plan));
545
- }
546
- export async function playwrightEvidenceCommand(options, io) {
547
- const { artifact } = await addPlaywrightEvidence(removeUndefined({
548
- task: requireArg(options, "task"),
549
- kind: requireArg(options, "kind"),
550
- path: requireArg(options, "path"),
551
- summary: requireArg(options, "summary"),
552
- runId: stringOption(options["run-id"]),
322
+ export async function githubSyncCommand(options, io) {
323
+ const result = await syncGitHubIssueToTask(removeUndefined({
324
+ issue: Number(requireArg(options, "issue")),
325
+ taskId: stringOption(options.task),
326
+ ownerRole: stringOption(options.owner),
327
+ status: stringOption(options.status),
328
+ comment: Boolean(options.comment),
329
+ close: Boolean(options.close),
330
+ acceptedRisk: stringOption(options["accepted-risk"]),
331
+ dryRun: Boolean(options["dry-run"]),
332
+ transport: stringOption(options.transport) === "mcp-skill" ? "mcp-skill" : "gh",
553
333
  }));
554
- io.log(`Created ${artifact}`);
555
- }
556
- export async function collaborationFlowsCommand(options, io) {
557
- const flows = listCollaborationFlows();
558
- if (options.json) {
559
- io.log(JSON.stringify(flows, null, 2));
560
- return;
561
- }
562
- for (const flow of flows) {
563
- io.log(flow.id + " - " + flow.name);
564
- io.log(" roles: " + flow.roles.join(", "));
565
- }
566
- }
567
- export async function collaborationRecommendCommand(options, io) {
568
- const context = await getTaskContext(requireArg(options, "task"));
569
- const recommendation = recommendCollaborationFlow(context.task, [
570
- ...context.decisions,
571
- ...context.handoffs,
572
- ...context.reviews,
573
- ...context.evidence,
574
- ...context.gates,
575
- ]);
576
- if (options.json) {
577
- io.log(JSON.stringify(recommendation, null, 2));
578
- return;
579
- }
580
- if (!recommendation) {
581
- io.log("No collaboration flow recommended");
582
- return;
583
- }
584
- io.log(recommendation.flow.id + " - " + recommendation.flow.name);
585
- io.log("Missing artifacts: " +
586
- (recommendation.missingArtifacts.join(", ") || "none"));
587
- io.log("Reviewers: " + (recommendation.optionalReviewers.join(", ") || "none"));
588
- }
589
- export async function workflowTemplatesCommand(options, io) {
590
- const templates = listWorkflowTemplates();
591
- const validationErrors = validateWorkflowTemplates();
592
- if (options.json) {
593
- io.log(JSON.stringify({ templates, validationErrors }, null, 2));
594
- return;
595
- }
596
- for (const template of templates) {
597
- io.log(template.id + " - " + template.name);
598
- io.log(" roles: " + template.roles.join(", "));
599
- }
600
- if (validationErrors.length > 0) {
601
- io.log("Validation errors: " + validationErrors.join("; "));
602
- }
603
- }
604
- export async function workflowTemplateSelectCommand(options, io) {
605
- const context = await getTaskContext(requireArg(options, "task"));
606
- const events = [
607
- ...context.decisions,
608
- ...context.handoffs,
609
- ...context.reviews,
610
- ...context.evidence,
611
- ...context.gates,
612
- ];
613
- const selection = selectWorkflowTemplates(context.task, events);
614
- if (options.json) {
615
- io.log(JSON.stringify(selection, null, 2));
616
- return;
617
- }
618
- for (const item of selection) {
619
- io.log(item.template.id + " (score " + item.score + ")");
620
- io.log(" missing evidence: " + (item.missingEvidence.join(", ") || "none"));
621
- }
622
- }
623
- export async function workflowTemplateRenderCommand(options, io) {
624
- const context = await getTaskContext(requireArg(options, "task"));
625
- const rendered = renderWorkflowTemplates({
626
- task: context.task,
627
- target: parseSkillRenderTarget(stringOption(options.target) ?? "generic"),
628
- templates: context.workflowTemplates,
629
- });
630
- if (options.json) {
631
- io.log(JSON.stringify(rendered, null, 2));
632
- return;
633
- }
634
- io.log(rendered.content);
635
- }
636
- // --- Autonomous workflow commands ---
637
- const GATE_MODES = ["none", "phase", "all"];
638
- export async function workflowRunCommand(options, io) {
639
- const cwd = process.cwd();
640
- const taskId = requireArg(options, "task");
641
- const gates = (stringOption(options.gates) ?? "phase");
642
- const maxIterations = numberOption(options["max-iterations"], 5);
643
- const timeoutMinutes = numberOption(options["timeout-minutes"], 0);
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;
648
- if (!GATE_MODES.includes(gates)) {
649
- throw new Error(`--gates must be one of: ${GATE_MODES.join(", ")}`);
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
- }
657
- if (options["dry-run"]) {
658
- return workflowDryRun(options, io, taskId, gates, maxIterations, phaseSelection);
659
- }
660
- let run;
661
- let startIndex;
662
- if (options.resume) {
663
- const existing = await readAutonomousRun(cwd, String(options.resume));
664
- if (!existing)
665
- throw new Error(`workflow run not found: ${String(options.resume)}`);
666
- run = existing;
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);
672
- if (startIndex === -1) {
673
- io.log(`Workflow ${run.id} is already complete`);
674
- if (options.json)
675
- io.log(JSON.stringify({ run, file, cwd }, null, 2));
676
- return;
677
- }
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) };
684
- }
685
- else {
686
- run = await createAutonomousRun(cwd, {
687
- taskId,
688
- gates,
689
- maxIterations,
690
- phaseSequence: phaseSelection.sequence.map((phase) => phase.phase),
691
- skippedPhases: phaseSelection.skipped,
692
- });
693
- startIndex = 0;
694
- io.log(`Started autonomous workflow ${run.id} for task ${taskId} [gates=${gates}]`);
695
- }
696
- run = await executePhases(cwd, run, startIndex, io, phaseSequenceForRun(run, phaseSelection.sequence), {
697
- runTimeoutMinutes: timeoutMinutes,
698
- phaseTimeoutMinutes,
699
- });
700
- const result = { run, file, cwd };
701
334
  if (options.json) {
702
335
  io.log(JSON.stringify(result, null, 2));
703
336
  return;
704
337
  }
705
- if (run.status === "done") {
706
- io.log(`Workflow complete [run=${run.id}]`);
707
- }
708
- else if (run.status === "paused") {
709
- io.log(`Workflow paused — approve gate review then run: orchestra workflow run --task ${taskId} --resume ${run.id}`);
710
- }
711
- else {
712
- io.log(`Workflow ${run.status} [run=${run.id}]`);
713
- }
714
- }
715
- export async function workflowRunListCommand(options, io) {
716
- const cwd = process.cwd();
717
- const runs = options.active
718
- ? await listActiveAutonomousRuns(cwd)
719
- : await listAutonomousRuns(cwd);
720
- if (options.json) {
721
- io.log(JSON.stringify(runs, null, 2));
722
- return;
723
- }
724
- if (runs.length === 0) {
725
- io.log("No workflow runs");
726
- return;
727
- }
728
- for (const run of runs) {
729
- const phases = run.phases.map((p) => `${p.phase}:${p.status}`).join(" → ");
730
- io.log(`${run.id} [${run.status}] task=${run.taskId} gates=${run.gates}`);
731
- if (phases)
732
- io.log(` ${phases}`);
733
- }
734
- }
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
- }
758
- export async function workflowClarifyCommand(options, io) {
759
- const cwd = process.cwd();
760
- const runId = requireArg(options, "run");
761
- const from = await resolveClarificationRole(cwd, requireArg(options, "from"));
762
- const to = await resolveClarificationRole(cwd, requireArg(options, "to"));
763
- const question = requireArg(options, "question");
764
- const run = await readAutonomousRun(cwd, runId);
765
- if (!run)
766
- throw new Error(`workflow run not found: ${runId}`);
767
- // Find the active phase (running or already awaiting_clarification)
768
- const activePhaseIdx = run.phases.findIndex((p) => p.role === from &&
769
- (p.status === "running" || p.status === "awaiting_clarification"));
770
- if (activePhaseIdx === -1) {
771
- throw new Error(`no active phase found for role ${from} in run ${runId}`);
772
- }
773
- const activePhase = run.phases[activePhaseIdx];
774
- // Open clarification record
775
- const record = await openClarification(cwd, {
776
- runId,
777
- taskId: run.taskId,
778
- fromRole: from,
779
- to,
780
- question,
781
- });
782
- // Suspend the phase if it's still running
783
- if (activePhase.status === "running") {
784
- const sequence = phaseSequenceForRun(run);
785
- const phaseSeqIdx = sequence.findIndex((d) => d.phase === activePhase.phase);
786
- await suspendPhaseForClarification(cwd, run, phaseSeqIdx, sequence);
787
- }
788
- if (options.json) {
789
- io.log(JSON.stringify(record, null, 2));
790
- return;
791
- }
792
- io.log(`Clarification ${record.id} opened — waiting for ${record.toRole}`);
793
- io.log(` Question: ${record.question}`);
794
- io.log(` Respond with:`);
795
- io.log(` orchestra workflow clarify-respond --run ${runId} --clarification ${record.id} --answer "<text>"`);
796
- }
797
- export async function workflowClarifyRespondCommand(options, io) {
798
- const cwd = process.cwd();
799
- const runId = requireArg(options, "run");
800
- const clarificationId = requireArg(options, "clarification");
801
- const answer = requireArg(options, "answer");
802
- const run = await readAutonomousRun(cwd, runId);
803
- if (!run)
804
- throw new Error(`workflow run not found: ${runId}`);
805
- const record = await answerClarification(cwd, {
806
- id: clarificationId,
807
- answer,
808
- });
809
- // Resume the suspended phase back to running
810
- const sequence = phaseSequenceForRun(run);
811
- const phaseSeqIdx = sequence.findIndex((d) => d.role === record.fromRole);
812
- if (phaseSeqIdx !== -1) {
813
- const phase = run.phases.find((p) => p.role === record.fromRole);
814
- if (phase?.status === "awaiting_clarification") {
815
- await resumePhaseFromClarification(cwd, run, phaseSeqIdx, sequence);
816
- }
817
- }
818
- if (options.json) {
819
- io.log(JSON.stringify(record, null, 2));
820
- return;
821
- }
822
- io.log(`Clarification ${record.id} answered`);
823
- io.log(` Answer: ${record.answer}`);
824
- io.log(` Resume with:`);
825
- io.log(` orchestra workflow run --task ${run.taskId} --resume ${runId}`);
826
- }
827
- export async function workflowClarifyListCommand(options, io) {
828
- const cwd = process.cwd();
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);
840
- if (options.json) {
841
- io.log(JSON.stringify(clarifications, null, 2));
842
- return;
843
- }
844
- if (clarifications.length === 0) {
845
- io.log(runId
846
- ? `No clarifications for run ${runId}`
847
- : `No clarifications for task ${taskId}`);
848
- return;
849
- }
850
- for (const c of clarifications) {
851
- const status = c.status === "answered" ? "✓" : "⏳";
852
- io.log(`${status} [${c.id}] ${c.fromRole} → ${c.toRole}`);
853
- io.log(` Q: ${c.question}`);
854
- if (c.answer)
855
- io.log(` A: ${c.answer}`);
856
- }
857
- }
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) {
950
- let current = run;
951
- let qaFailNotes;
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];
962
- // Init phase task (skip if already has a running/done/clarification record from resume)
963
- const existing = current.phases.find((p) => p.phase === def.phase && p.status !== "qa_failed");
964
- if (!existing || existing.status === "pending") {
965
- const retryContext = def.phase === "developer" && qaFailNotes ? qaFailNotes : undefined;
966
- const phaseRecord = await initPhase(cwd, current, i, retryContext, sequence);
967
- current = { ...current, phases: [...current.phases, phaseRecord] };
968
- if (retryContext) {
969
- io.log(`↺ ${def.phase} (${def.role}) retry — QA findings: ${retryContext.slice(0, 80)}`);
970
- }
971
- else {
972
- io.log(`→ ${def.phase} (${def.role}) task=${phaseRecord.taskId}`);
973
- }
974
- }
975
- else if (existing.status === "awaiting_clarification") {
976
- io.log(`↺ ${def.phase} (${def.role}) resuming after clarification`);
977
- }
978
- // QA loop guard
979
- if (def.phase === "qa" && current.qaIterations >= current.maxIterations) {
980
- io.log(`✗ QA failed after ${current.maxIterations} iterations — workflow blocked`);
981
- current = await markRunFailed(cwd, current, `Max QA iterations (${current.maxIterations}) reached`);
982
- return current;
983
- }
984
- const llmResult = await executePhaseWithLlm({
985
- root: cwd,
986
- run: current,
987
- phase: def,
988
- phaseIndex: i,
989
- }).catch(async (error) => {
990
- const message = error instanceof Error ? error.message : String(error);
991
- io.log(`✗ ${def.phase} provider execution failed: ${message}`);
992
- current = await markRunFailed(cwd, current, message, def.phase);
993
- return null;
994
- });
995
- if (!llmResult)
996
- return current;
997
- if (llmResult.mode === "llm") {
998
- io.log(` ✓ llm artifact (${llmResult.artifact})`);
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
- }
1011
- // Architect sizing gate — always enforced regardless of --gates mode.
1012
- // In LLM mode, the architect phase records the sizing decision before this check.
1013
- if (def.phase === "architect") {
1014
- const sizing = await checkArchitectSizing(cwd, current.taskId);
1015
- if (!sizing.found) {
1016
- io.log(`⚠ Architect phase requires a sizing decision before handoff to developer.`);
1017
- io.log(` Record one with:`);
1018
- io.log(` orchestra decision add --task ${current.taskId} --owner architect --title "Story sizing" --decision "<xs|s|m|l|xl> [N points]" --context "..." --consequences "..." --status accepted`);
1019
- io.log(` Then resume: orchestra workflow run --task ${current.taskId} --resume ${current.id}`);
1020
- current = await markRunFailed(cwd, current, "Architect sizing decision required before developer handoff");
1021
- return current;
1022
- }
1023
- io.log(` ✓ sizing=${sizing.sizing}${sizing.points !== undefined ? ` (${sizing.points} pts)` : ""}`);
1024
- }
1025
- const outcome = llmResult.outcome;
1026
- const result = await closePhase(cwd, current, i, outcome, sequence);
1027
- current = result.run;
1028
- if (result.handoffArtifact) {
1029
- io.log(` ✓ handoff → ${sequence[i + 1]?.role ?? "end"} (${result.handoffArtifact})`);
1030
- }
1031
- if (result.reviewArtifact) {
1032
- const nextPhase = sequence[i + 1]?.phase;
1033
- io.log(` ⏸ gate ${def.phase}→${nextPhase} — review: ${result.reviewArtifact}`);
1034
- io.log(` Approve: orchestra workflow gate-approve --run ${current.id} --gate ${def.phase}->${nextPhase} --approver <name> --rationale "<text>"`);
1035
- io.log(``);
1036
- io.log(`╔══ GATE PAUSE: ${def.phase.toUpperCase()} → ${nextPhase?.toUpperCase()} ══════════════════════`);
1037
- io.log(`║ Run: ${current.id}`);
1038
- io.log(`║ Task: ${current.taskId}`);
1039
- io.log(`║ Review: ${result.reviewArtifact}`);
1040
- io.log(`║`);
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:`);
1044
- io.log(`║ orchestra workflow run --task ${current.taskId} --resume ${current.id}`);
1045
- io.log(`╚══════════════════════════════════════════════════════════════`);
1046
- return current;
1047
- }
1048
- // QA failure — loop back to developer with findings as context
1049
- const closedPhase = current.phases.find((p) => p.phase === def.phase && p.status === "qa_failed");
1050
- if (closedPhase) {
1051
- qaFailNotes = closedPhase.notes ?? "QA findings — see QA phase artifact";
1052
- io.log(` ✗ qa failed (iteration ${current.qaIterations}/${current.maxIterations}) — routing back to developer`);
1053
- i = devPhaseIndex - 1; // will be incremented to developer at top of loop
1054
- continue;
1055
- }
1056
- // Release phase: auto-create PR if configured
1057
- if (def.phase === "release") {
1058
- const config = await getWorkflowConfig(cwd);
1059
- if (config.github?.autoCreatePr) {
1060
- try {
1061
- const prSummary = await generatePullRequestSummary(current.taskId, cwd);
1062
- const prBody = buildPrBody(prSummary, current.qaIterations);
1063
- const prTitle = `${current.taskId}: ${prSummary.task.title}`;
1064
- const prResult = await createPullRequest({
1065
- title: prTitle,
1066
- body: prBody,
1067
- ...(config.github.baseBranch
1068
- ? { baseBranch: config.github.baseBranch }
1069
- : {}),
1070
- });
1071
- await addEvidence({
1072
- task: current.taskId,
1073
- role: "release_manager",
1074
- type: "report",
1075
- summary: `PR created: ${prResult.url}`,
1076
- }, cwd);
1077
- io.log(` ✓ PR created: ${prResult.url}`);
1078
- }
1079
- catch (err) {
1080
- io.log(` ⚠ PR creation failed: ${err instanceof Error ? err.message : String(err)}`);
1081
- }
1082
- }
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
- }
1095
- }
1096
- current = await markRunDone(cwd, current);
1097
- return current;
1098
- }
1099
- async function workflowDryRun(options, io, taskId, gates, maxIterations, phaseSelection) {
1100
- const gateTransitions = new Set(["po→architect", "qa→release"]);
1101
- io.log(`Dry run — no records will be created`);
1102
- io.log(`Task: ${taskId} gates: ${gates} max-iterations: ${maxIterations}`);
1103
- io.log(``);
1104
- for (let i = 0; i < phaseSelection.sequence.length; i++) {
1105
- const def = phaseSelection.sequence[i];
1106
- const next = phaseSelection.sequence[i + 1];
1107
- const transitionKey = next ? `${def.phase}→${next.phase}` : "";
1108
- const gateLabel = gates === "all"
1109
- ? "gate=yes"
1110
- : gates === "phase" && gateTransitions.has(transitionKey)
1111
- ? "gate=yes"
1112
- : "gate=no";
1113
- io.log(` ${def.phase} (${def.role}) ${gateLabel}`);
1114
- }
1115
- if (phaseSelection.skipped.length > 0) {
1116
- io.log(``);
1117
- io.log(`Skipped: ${phaseSelection.skipped.map((phase) => phase.phase).join(", ")}`);
1118
- }
1119
- if (options.json) {
1120
- io.log(JSON.stringify({
1121
- dryRun: true,
1122
- taskId,
1123
- gates,
1124
- maxIterations,
1125
- phases: phaseSelection.sequence,
1126
- skipped: phaseSelection.skipped,
1127
- }, null, 2));
1128
- }
338
+ io.log(`GitHub issue #${result.issue.number} synced to ${result.taskId} (${result.action})`);
339
+ io.log(`Release readiness: ${result.releaseReadiness.passed ? "passed" : "blocked"}`);
340
+ if (result.comment !== "skipped")
341
+ io.log(`Comment ${result.comment}`);
342
+ if (result.closed)
343
+ io.log("Issue closed");
344
+ if (result.dryRun)
345
+ io.log("Dry run: no mutations were written.");
1129
346
  }
1130
347
  function parseRuntimeTargetOptions(options) {
1131
348
  const targetValues = [
@@ -1134,132 +351,6 @@ function parseRuntimeTargetOptions(options) {
1134
351
  ];
1135
352
  return [...new Set(targetValues)].map((target) => parseRuntimeTarget(target));
1136
353
  }
1137
- function parseSkillRenderTarget(value) {
1138
- return parseRuntimeTarget(value);
1139
- }
1140
- function parseCsv(value) {
1141
- if (typeof value !== "string" || value.trim() === "") {
1142
- return [];
1143
- }
1144
- return value
1145
- .split(",")
1146
- .map((item) => item.trim())
1147
- .filter(Boolean);
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
- }
1159
- function stringOption(value) {
1160
- return typeof value === "string" && value.trim() !== "" ? value : undefined;
1161
- }
1162
- function removeUndefined(value) {
1163
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
1164
- }
1165
- function parseArchitectureApproval(options) {
1166
- const approval = removeUndefined({
1167
- proposal: stringOption(options["architecture-proposal"]),
1168
- userApproved: options["user-approved"] ? true : undefined,
1169
- approvedBy: stringOption(options["architecture-approved-by"]),
1170
- approvedAt: stringOption(options["architecture-approved-at"]),
1171
- });
1172
- return Object.keys(approval).length > 0 ? approval : undefined;
1173
- }
1174
- function parseQaGate(options) {
1175
- const qaGate = removeUndefined({
1176
- plan: stringOption(options["qa-plan"]),
1177
- executionStatus: stringOption(options["qa-execution-status"]),
1178
- deferredRationale: stringOption(options["qa-deferred-rationale"]),
1179
- deferredOwner: stringOption(options["qa-deferred-owner"]),
1180
- });
1181
- return Object.keys(qaGate).length > 0 ? qaGate : undefined;
1182
- }
1183
- function parseRiskGate(options) {
1184
- const impactAreas = parseCsv(options["risk-areas"]);
1185
- const riskGate = removeUndefined({
1186
- impactAreas: impactAreas.length > 0 ? impactAreas : undefined,
1187
- acceptance: parseRiskAcceptance(options),
1188
- });
1189
- return Object.keys(riskGate).length > 0 ? riskGate : undefined;
1190
- }
1191
- function parseRiskAcceptance(options) {
1192
- const acceptance = removeUndefined({
1193
- acceptedBy: stringOption(options["risk-accepted-by"]),
1194
- rationale: stringOption(options["risk-acceptance-rationale"]),
1195
- });
1196
- return Object.keys(acceptance).length > 0 ? acceptance : undefined;
1197
- }
1198
- function parseBudgetEscalationDecision(options) {
1199
- if (options["approve-budget-fallback"]) {
1200
- return removeUndefined({
1201
- approved: true,
1202
- approver: stringOption(options.approver),
1203
- rationale: stringOption(options.rationale),
1204
- });
1205
- }
1206
- if (options["reject-budget-fallback"]) {
1207
- return removeUndefined({
1208
- approved: false,
1209
- approver: stringOption(options.approver),
1210
- rationale: stringOption(options.rationale),
1211
- });
1212
- }
1213
- return undefined;
1214
- }
1215
- function numberOption(value, fallback) {
1216
- if (typeof value !== "string" || value.trim() === "") {
1217
- return fallback;
1218
- }
1219
- const parsed = Number(value);
1220
- if (!Number.isFinite(parsed)) {
1221
- throw new Error(`invalid number: ${value}`);
1222
- }
1223
- return parsed;
1224
- }
1225
- function renderReadinessMarkdown(task, report) {
1226
- const status = report.isReady ? "passed" : "blocked";
1227
- const missing = report.missing.length > 0
1228
- ? report.missing.map((field) => `- ${field}`).join("\n")
1229
- : "- none";
1230
- return [
1231
- `# Readiness ${task.id}`,
1232
- "",
1233
- `Status: ${status}`,
1234
- "",
1235
- "Missing fields:",
1236
- missing,
1237
- "",
1238
- ].join("\n");
1239
- }
1240
- function renderGateMarkdown(report) {
1241
- const status = report.passed ? "passed" : "blocked";
1242
- const missing = report.missing.length > 0
1243
- ? report.missing.map((field) => `- ${field}`).join("\n")
1244
- : "- none";
1245
- return [
1246
- `# Gate ${report.gateId}`,
1247
- "",
1248
- `Status: ${status}`,
1249
- "",
1250
- "Missing fields:",
1251
- missing,
1252
- "",
1253
- ].join("\n");
1254
- }
1255
- function renderDependencyReport(report) {
1256
- return [
1257
- `Dependencies for ${report.taskId}: ${report.isSatisfied ? "satisfied" : "blocked"}`,
1258
- ...(report.dependencies.length === 0
1259
- ? ["- none"]
1260
- : report.dependencies.map((dependency) => `- ${dependency.id}: ${dependency.status} (${dependency.isComplete ? "complete" : "incomplete"})`)),
1261
- ].join("\n");
1262
- }
1263
354
  function renderPreRunValidation(report) {
1264
355
  const status = report.allowed ? "allowed" : "blocked";
1265
356
  const missing = report.missing.length > 0 ? report.missing.join(", ") : "none";
@@ -1278,84 +369,6 @@ function renderPreRunValidation(report) {
1278
369
  : []),
1279
370
  ].join("\n");
1280
371
  }
1281
- function renderTaskGraphPlan(plan) {
1282
- return [
1283
- "Task graph plan",
1284
- "",
1285
- "Ready:",
1286
- ...taskGraphReadyLines(plan.ready),
1287
- "",
1288
- "Blocked:",
1289
- ...taskGraphBlockedLines(plan.blocked),
1290
- "",
1291
- "Locked:",
1292
- ...taskGraphLockedLines(plan.locked),
1293
- "",
1294
- "Complete:",
1295
- ...taskGraphReadyLines(plan.complete),
1296
- ].join("\n");
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
- }
1320
- function renderTaskGraphBatchRun(batch) {
1321
- return [
1322
- "Task graph batch run",
1323
- `- Selected: ${batch.selectedTaskIds.join(", ")}`,
1324
- `- Completed runs: ${batch.runs.length}`,
1325
- `- Locked: ${batch.locked.map((task) => task.id).join(", ") || "none"}`,
1326
- `- Artifact: ${batch.artifact ?? "none"}`,
1327
- ...(batch.failure
1328
- ? [`- Failure: ${batch.failure.taskId}: ${batch.failure.error}`]
1329
- : ["- Failure: none"]),
1330
- ].join("\n");
1331
- }
1332
- function taskGraphReadyLines(items) {
1333
- return items.length === 0
1334
- ? ["- none"]
1335
- : items.map((item) => `- ${item.id} [${item.status}] ${item.title} (${item.ownerRole})`);
1336
- }
1337
- function taskGraphBlockedLines(items) {
1338
- return items.length === 0
1339
- ? ["- none"]
1340
- : items.map((item) => {
1341
- const blockers = item.incomplete
1342
- .map((dependency) => `${dependency.id}:${dependency.status}`)
1343
- .join(", ");
1344
- return `- ${item.id} [${item.status}] ${item.title} blocked by ${blockers}`;
1345
- });
1346
- }
1347
- function taskGraphLockedLines(items) {
1348
- return items.length === 0
1349
- ? ["- none"]
1350
- : items.map((item) => {
1351
- const locks = item.locks
1352
- .map((lock) => lock.conflictPath
1353
- ? `${lock.id}:${lock.path}->${lock.conflictPath}`
1354
- : `${lock.id}:${lock.path}`)
1355
- .join(", ");
1356
- return `- ${item.id} [${item.status}] ${item.title} locked by ${locks}`;
1357
- });
1358
- }
1359
372
  function renderSummaryMarkdown(summary) {
1360
373
  return [
1361
374
  "# Agent Workflow Summary",
@@ -1444,161 +457,6 @@ function renderPullRequestSummaryMarkdown(summary) {
1444
457
  "",
1445
458
  ].join("\n");
1446
459
  }
1447
- function renderDelegationDecisionMarkdown(decision) {
1448
- return [
1449
- `# Delegation Decision: ${decision.taskId}`,
1450
- "",
1451
- `- Recommendation: ${decision.recommendation}`,
1452
- `- Complexity score: ${decision.complexityScore}`,
1453
- `- Urgency: ${decision.urgency}`,
1454
- `- Disjoint write scopes: ${String(decision.disjointWriteScopes)}`,
1455
- "",
1456
- "## Rationale",
1457
- ...listOrNone(decision.rationale),
1458
- "",
1459
- "## Blocking Conditions",
1460
- ...listOrNone(decision.blockingConditions),
1461
- "",
1462
- "## Context Bundle",
1463
- ...listOrNone(decision.contextBundle),
1464
- "",
1465
- "## Delegates",
1466
- ...(decision.delegates.length === 0
1467
- ? ["- none"]
1468
- : decision.delegates.map((delegate) => `- ${delegate.role} [${delegate.mode}] scopes=${delegate.writeScopes.join(",") || "none"}`)),
1469
- "",
1470
- "## Expected Outputs",
1471
- ...listOrNone(decision.expectedOutputs),
1472
- "",
1473
- ].join("\n");
1474
- }
1475
- function renderTaskContextMarkdown(context) {
1476
- return [
1477
- `# Task Context: ${context.task.id}`,
1478
- "",
1479
- `- Title: ${context.task.title}`,
1480
- `- Owner: ${context.task.ownerRole}`,
1481
- `- Status: ${context.task.status}`,
1482
- `- Dependencies: ${context.dependencies.isSatisfied ? "satisfied" : "blocked"}`,
1483
- `- Locks: ${context.locks.length}`,
1484
- `- Risks: ${context.risks.length}`,
1485
- `- Delegation: ${context.delegation?.recommendation ?? "not decided"}`,
1486
- "",
1487
- "## Collaboration Flow",
1488
- ...(context.collaborationFlow
1489
- ? [
1490
- `- ${context.collaborationFlow.flow.id}: ${context.collaborationFlow.flow.name}`,
1491
- `- Missing artifacts: ${context.collaborationFlow.missingArtifacts.join(", ") || "none"}`,
1492
- `- Reviewers: ${context.collaborationFlow.optionalReviewers.join(", ") || "none"}`,
1493
- ]
1494
- : ["- none"]),
1495
- "",
1496
- "## Workflow Templates",
1497
- ...(context.workflowTemplates.length === 0
1498
- ? ["- none"]
1499
- : context.workflowTemplates.map((item) => `- ${item.template.id} (score ${item.score}, missing evidence ${item.missingEvidence.join(", ") || "none"})`)),
1500
- "",
1501
- "## Skills",
1502
- ...(context.skills.selected.length === 0
1503
- ? ["- none"]
1504
- : context.skills.selected.map((item) => `- ${item.skill.id} (score ${item.score}): ${item.rationale.join("; ")}`)),
1505
- `- Source groups: ${context.skills.sourceGroups.join(", ") || "none"}`,
1506
- "",
1507
- renderMemoryPacket(context.memory),
1508
- "",
1509
- "## Decisions",
1510
- ...eventLines(context.decisions),
1511
- "",
1512
- "## Handoffs",
1513
- ...eventLines(context.handoffs),
1514
- "",
1515
- "## Reviews",
1516
- ...eventLines(context.reviews),
1517
- "",
1518
- "## Evidence",
1519
- ...eventLines(context.evidence),
1520
- "",
1521
- "## Gates",
1522
- ...eventLines(context.gates),
1523
- "",
1524
- "## Model Provenance",
1525
- ...(context.modelProvenance.length === 0
1526
- ? ["- none"]
1527
- : context.modelProvenance.map((record) => `- ${record.role}: ${record.provider}/${record.model} ${record.responseId}`)),
1528
- "",
1529
- ].join("\n");
1530
- }
1531
- function renderExecutionPlanMarkdown(plan) {
1532
- return [
1533
- `# Execution Plan: ${plan.taskId}`,
1534
- "",
1535
- ...plan.steps.map((step) => {
1536
- const gate = step.gate ? ` gate=${step.gate}` : "";
1537
- const dependsOn = step.dependsOn.length > 0
1538
- ? ` dependsOn=${step.dependsOn.join(",")}`
1539
- : "";
1540
- return `- ${step.id}: ${step.role}${gate}${dependsOn} - ${step.action}`;
1541
- }),
1542
- "",
1543
- ].join("\n");
1544
- }
1545
- function renderExecutionRunMarkdown(run) {
1546
- return [
1547
- `# Execution Run: ${run.taskId}`,
1548
- "",
1549
- `- Provider: ${run.provider}`,
1550
- `- Model: ${run.model}`,
1551
- "",
1552
- ...run.steps.map((step) => `- ${step.id}: ${step.role} ${step.status} ${step.responseId ?? step.error ?? ""}`.trim()),
1553
- "",
1554
- ].join("\n");
1555
- }
1556
- function renderPlaywrightPlanMarkdown(plan) {
1557
- return [
1558
- `# Playwright Test Plan: ${plan.taskId}`,
1559
- "",
1560
- `- Title: ${plan.title}`,
1561
- `- Target user: ${plan.targetUser}`,
1562
- "",
1563
- "## Scenarios",
1564
- ...plan.scenarios.flatMap((scenario) => [
1565
- `### ${scenario.name}`,
1566
- `- Source: ${scenario.source}`,
1567
- `- Page object: ${scenario.pageObject}`,
1568
- `- Selectors: ${scenario.selectors.join("; ")}`,
1569
- `- Assertions: ${scenario.assertions.join("; ")}`,
1570
- `- Evidence: ${scenario.evidence.join(", ")}`,
1571
- "",
1572
- ]),
1573
- "## Fixtures",
1574
- ...listOrNone(plan.fixtures),
1575
- "",
1576
- "## Notes",
1577
- ...listOrNone(plan.notes),
1578
- "",
1579
- ].join("\n");
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
- }
1602
460
  function eventLines(events) {
1603
461
  return events.length === 0
1604
462
  ? ["- none"]