@jterrats/open-orchestra 0.1.0 → 0.3.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 (208) hide show
  1. package/AGENTS.md +90 -0
  2. package/CHANGELOG.md +104 -0
  3. package/CLAUDE.md +103 -0
  4. package/README.md +173 -22
  5. package/dist/assets/web-console.js +743 -0
  6. package/dist/autonomous-workflow.d.ts +45 -0
  7. package/dist/autonomous-workflow.js +386 -0
  8. package/dist/autonomous-workflow.js.map +1 -0
  9. package/dist/benchmark.d.ts +8 -0
  10. package/dist/benchmark.js +193 -0
  11. package/dist/benchmark.js.map +1 -0
  12. package/dist/burndown.d.ts +3 -0
  13. package/dist/burndown.js +141 -0
  14. package/dist/burndown.js.map +1 -0
  15. package/dist/clarification.d.ts +6 -0
  16. package/dist/clarification.js +88 -0
  17. package/dist/clarification.js.map +1 -0
  18. package/dist/cli.js +221 -4
  19. package/dist/cli.js.map +1 -1
  20. package/dist/collaboration-flows.d.ts +5 -0
  21. package/dist/collaboration-flows.js +256 -0
  22. package/dist/collaboration-flows.js.map +1 -0
  23. package/dist/command-manifest.d.ts +11 -0
  24. package/dist/command-manifest.js +52 -0
  25. package/dist/command-manifest.js.map +1 -0
  26. package/dist/commands.d.ts +39 -0
  27. package/dist/commands.js +1069 -2
  28. package/dist/commands.js.map +1 -1
  29. package/dist/constants.d.ts +4 -0
  30. package/dist/constants.js +22 -0
  31. package/dist/constants.js.map +1 -1
  32. package/dist/defaults.d.ts +7 -11
  33. package/dist/defaults.js +7 -625
  34. package/dist/defaults.js.map +1 -1
  35. package/dist/delegation-decision.d.ts +14 -0
  36. package/dist/delegation-decision.js +391 -0
  37. package/dist/delegation-decision.js.map +1 -0
  38. package/dist/detect-commands.d.ts +3 -0
  39. package/dist/detect-commands.js +28 -0
  40. package/dist/detect-commands.js.map +1 -0
  41. package/dist/diagram-validation.d.ts +36 -0
  42. package/dist/diagram-validation.js +118 -0
  43. package/dist/diagram-validation.js.map +1 -0
  44. package/dist/fs-utils.d.ts +2 -0
  45. package/dist/fs-utils.js +75 -6
  46. package/dist/fs-utils.js.map +1 -1
  47. package/dist/github.d.ts +11 -0
  48. package/dist/github.js +48 -0
  49. package/dist/github.js.map +1 -0
  50. package/dist/health-checks.d.ts +28 -0
  51. package/dist/health-checks.js +219 -0
  52. package/dist/health-checks.js.map +1 -0
  53. package/dist/health-commands.d.ts +2 -0
  54. package/dist/health-commands.js +18 -0
  55. package/dist/health-commands.js.map +1 -0
  56. package/dist/instruction-apply.d.ts +34 -0
  57. package/dist/instruction-apply.js +150 -0
  58. package/dist/instruction-apply.js.map +1 -0
  59. package/dist/instruction-blocks.d.ts +22 -0
  60. package/dist/instruction-blocks.js +120 -0
  61. package/dist/instruction-blocks.js.map +1 -0
  62. package/dist/instruction-imports.d.ts +12 -0
  63. package/dist/instruction-imports.js +45 -0
  64. package/dist/instruction-imports.js.map +1 -0
  65. package/dist/instruction-stale.d.ts +9 -0
  66. package/dist/instruction-stale.js +106 -0
  67. package/dist/instruction-stale.js.map +1 -0
  68. package/dist/instruction-types.d.ts +66 -0
  69. package/dist/instruction-types.js +2 -0
  70. package/dist/instruction-types.js.map +1 -0
  71. package/dist/instruction-updates.d.ts +4 -0
  72. package/dist/instruction-updates.js +5 -0
  73. package/dist/instruction-updates.js.map +1 -0
  74. package/dist/knowledge-base.d.ts +10 -0
  75. package/dist/knowledge-base.js +117 -0
  76. package/dist/knowledge-base.js.map +1 -0
  77. package/dist/mcp-oauth-proxy.d.ts +39 -0
  78. package/dist/mcp-oauth-proxy.js +80 -0
  79. package/dist/mcp-oauth-proxy.js.map +1 -0
  80. package/dist/pr-review.d.ts +20 -0
  81. package/dist/pr-review.js +142 -0
  82. package/dist/pr-review.js.map +1 -0
  83. package/dist/project-detection.d.ts +22 -0
  84. package/dist/project-detection.js +174 -0
  85. package/dist/project-detection.js.map +1 -0
  86. package/dist/prompt-registry.d.ts +56 -0
  87. package/dist/prompt-registry.js +163 -0
  88. package/dist/prompt-registry.js.map +1 -0
  89. package/dist/release-candidate.d.ts +41 -0
  90. package/dist/release-candidate.js +196 -0
  91. package/dist/release-candidate.js.map +1 -0
  92. package/dist/release-commands.d.ts +4 -0
  93. package/dist/release-commands.js +50 -0
  94. package/dist/release-commands.js.map +1 -0
  95. package/dist/roles/ai-support-roles.d.ts +11 -0
  96. package/dist/roles/ai-support-roles.js +67 -0
  97. package/dist/roles/ai-support-roles.js.map +1 -0
  98. package/dist/roles/core-roles.d.ts +11 -0
  99. package/dist/roles/core-roles.js +144 -0
  100. package/dist/roles/core-roles.js.map +1 -0
  101. package/dist/roles/engineering-roles.d.ts +11 -0
  102. package/dist/roles/engineering-roles.js +176 -0
  103. package/dist/roles/engineering-roles.js.map +1 -0
  104. package/dist/roles/governance-roles.d.ts +11 -0
  105. package/dist/roles/governance-roles.js +117 -0
  106. package/dist/roles/governance-roles.js.map +1 -0
  107. package/dist/roles/index.d.ts +11 -0
  108. package/dist/roles/index.js +17 -0
  109. package/dist/roles/index.js.map +1 -0
  110. package/dist/roles/platform-ops-roles.d.ts +11 -0
  111. package/dist/roles/platform-ops-roles.js +158 -0
  112. package/dist/roles/platform-ops-roles.js.map +1 -0
  113. package/dist/roles/qa-ux-roles.d.ts +11 -0
  114. package/dist/roles/qa-ux-roles.js +193 -0
  115. package/dist/roles/qa-ux-roles.js.map +1 -0
  116. package/dist/roles/release-ops-roles.d.ts +11 -0
  117. package/dist/roles/release-ops-roles.js +109 -0
  118. package/dist/roles/release-ops-roles.js.map +1 -0
  119. package/dist/runtime-adapters.d.ts +6 -0
  120. package/dist/runtime-adapters.js +88 -0
  121. package/dist/runtime-adapters.js.map +1 -0
  122. package/dist/runtime-bootstrap.d.ts +12 -0
  123. package/dist/runtime-bootstrap.js +136 -0
  124. package/dist/runtime-bootstrap.js.map +1 -0
  125. package/dist/skills.d.ts +36 -0
  126. package/dist/skills.js +665 -0
  127. package/dist/skills.js.map +1 -0
  128. package/dist/subagent-protocol.d.ts +41 -0
  129. package/dist/subagent-protocol.js +179 -0
  130. package/dist/subagent-protocol.js.map +1 -0
  131. package/dist/telemetry-consent.d.ts +24 -0
  132. package/dist/telemetry-consent.js +95 -0
  133. package/dist/telemetry-consent.js.map +1 -0
  134. package/dist/telemetry-export.d.ts +14 -0
  135. package/dist/telemetry-export.js +126 -0
  136. package/dist/telemetry-export.js.map +1 -0
  137. package/dist/telemetry-records.d.ts +3 -0
  138. package/dist/telemetry-records.js +96 -0
  139. package/dist/telemetry-records.js.map +1 -0
  140. package/dist/telemetry-redaction.d.ts +9 -0
  141. package/dist/telemetry-redaction.js +55 -0
  142. package/dist/telemetry-redaction.js.map +1 -0
  143. package/dist/telemetry-types.d.ts +52 -0
  144. package/dist/telemetry-types.js +2 -0
  145. package/dist/telemetry-types.js.map +1 -0
  146. package/dist/telemetry.d.ts +4 -0
  147. package/dist/telemetry.js +4 -0
  148. package/dist/telemetry.js.map +1 -0
  149. package/dist/types.d.ts +304 -1
  150. package/dist/types.js +1 -1
  151. package/dist/types.js.map +1 -1
  152. package/dist/validation.d.ts +3 -1
  153. package/dist/validation.js +28 -5
  154. package/dist/validation.js.map +1 -1
  155. package/dist/web-api.js +167 -3
  156. package/dist/web-api.js.map +1 -1
  157. package/dist/web-console.js +6 -160
  158. package/dist/web-console.js.map +1 -1
  159. package/dist/workflow-gates.js +4 -2
  160. package/dist/workflow-gates.js.map +1 -1
  161. package/dist/workflow-services.js +143 -67
  162. package/dist/workflow-services.js.map +1 -1
  163. package/dist/workflow-templates.d.ts +10 -0
  164. package/dist/workflow-templates.js +141 -0
  165. package/dist/workflow-templates.js.map +1 -0
  166. package/dist/workspace-classification.d.ts +5 -0
  167. package/dist/workspace-classification.js +127 -0
  168. package/dist/workspace-classification.js.map +1 -0
  169. package/dist/workspace-validator.js +11 -1
  170. package/dist/workspace-validator.js.map +1 -1
  171. package/dist/workspace.d.ts +8 -4
  172. package/dist/workspace.js +111 -4
  173. package/dist/workspace.js.map +1 -1
  174. package/docs/autonomous-workflow.md +165 -0
  175. package/docs/benchmark.md +219 -0
  176. package/docs/dev-team-specialist-role-profiles.md +171 -0
  177. package/docs/mcp-oauth-proxy-evaluation.md +44 -0
  178. package/docs/multi-agent-orchestrator-backlog.md +413 -1
  179. package/docs/open-orchestra-dogfooding-findings.md +66 -0
  180. package/docs/orchestra-mvp.md +161 -3
  181. package/docs/runtime-adapters.md +86 -0
  182. package/docs/runtime-llm-flow.md +124 -0
  183. package/docs/setup-agents-dogfooding-findings.md +101 -0
  184. package/docs/skill-loading-strategy.md +114 -0
  185. package/docs/source-of-truth-and-agent-learning.md +83 -0
  186. package/package.json +9 -5
  187. package/rules/agent-roles.mdc +30 -0
  188. package/rules/ai-assisted-development.mdc +22 -0
  189. package/skills/agent-learning/SKILL.md +24 -0
  190. package/skills/agent-learning/manifest.json +40 -0
  191. package/skills/backlog-sync/SKILL.md +24 -0
  192. package/skills/backlog-sync/manifest.json +41 -0
  193. package/skills/diagram-export/SKILL.md +35 -0
  194. package/skills/diagram-export/manifest.json +40 -0
  195. package/skills/model-evaluation/SKILL.md +25 -0
  196. package/skills/model-evaluation/manifest.json +41 -0
  197. package/skills/playwright-evidence/SKILL.md +28 -0
  198. package/skills/playwright-evidence/manifest.json +46 -0
  199. package/skills/pr-review/SKILL.md +23 -0
  200. package/skills/pr-review/manifest.json +43 -0
  201. package/skills/prompt-registry/SKILL.md +24 -0
  202. package/skills/prompt-registry/manifest.json +45 -0
  203. package/skills/release-readiness/SKILL.md +25 -0
  204. package/skills/release-readiness/manifest.json +45 -0
  205. package/skills/source-of-truth/SKILL.md +24 -0
  206. package/skills/source-of-truth/manifest.json +47 -0
  207. package/skills/static-analysis/SKILL.md +26 -0
  208. package/skills/static-analysis/manifest.json +46 -0
package/dist/commands.js CHANGED
@@ -1,11 +1,53 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
1
3
  import { initWorkspace } from "./workspace.js";
2
4
  import { requireArg } from "./args.js";
5
+ import { AUTONOMOUS_PHASE_SEQUENCE, autonomousRunsPath, checkArchitectSizing, closePhase, createAutonomousRun, initPhase, listAutonomousRuns, markRunDone, markRunFailed, readAutonomousRun, resumePhaseIndex, suspendPhaseForClarification, resumePhaseFromClarification, } from "./autonomous-workflow.js";
6
+ import { listClarifications, openClarification, answerClarification, } from "./clarification.js";
7
+ import { computeBenchmark, recordEstimate, summarizeBenchmark, } from "./benchmark.js";
8
+ import { computeSprintBurndown, renderBurndownAscii } from "./burndown.js";
9
+ import { SIZING_LABELS } from "./types.js";
10
+ import { resolveWorkspaceWritePath } from "./fs-utils.js";
3
11
  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";
4
12
  import { validateWorkspace } from "./workspace-validator.js";
13
+ import { addAgentLesson, listAgentLessons, listSkills, planSkillsForTask, promoteAgentLessons, readSourceOfTruth, recordSkillPlan, recordSkillRender, renderSkills, validateSkills, } from "./skills.js";
5
14
  import { getWebServerAddress, startWebApiServer } from "./web-api.js";
15
+ import { decideTaskDelegation } from "./delegation-decision.js";
16
+ import { lintMermaidDiagram, recordDiagramLintEvidence, } from "./diagram-validation.js";
17
+ import { evaluateMcpOAuthProxy, } from "./mcp-oauth-proxy.js";
18
+ import { detectStaleInstructionFilesFromManifest, resolveInstructionImportsFromFile, updateManagedInstructionFile, } from "./instruction-updates.js";
19
+ import { applyInstructionUpdatesFromManifest } from "./instruction-apply.js";
20
+ import { renderSubagentProtocol, upsertSubagentProtocolBlock, } from "./subagent-protocol.js";
21
+ import { listCollaborationFlows, recommendCollaborationFlow, } from "./collaboration-flows.js";
22
+ import { listWorkflowTemplates, renderWorkflowTemplates, selectWorkflowTemplates, validateWorkflowTemplates, } from "./workflow-templates.js";
23
+ import { listCommandManifest } from "./command-manifest.js";
24
+ import { listRuntimeAdapters, parseRuntimeTarget } from "./runtime-adapters.js";
25
+ import { renderRuntimeBootstrap, upsertRuntimeBootstrapBlock, } from "./runtime-bootstrap.js";
26
+ import { disableTelemetryConsent, enableTelemetryConsent, exportTelemetryDataset, exportTelemetryEvalDataset, getTelemetryConsent, parseTelemetryLevel, requiresSensitiveTelemetryOptIn, submitTelemetryDataset, } from "./telemetry.js";
27
+ import { buildPrBody, createPullRequest } from "./github.js";
6
28
  export async function initCommand(options, io) {
7
- const base = await initWorkspace({ force: Boolean(options.force) });
29
+ const root = stringOption(options["target-dir"]) ?? process.cwd();
30
+ const input = {
31
+ root,
32
+ force: Boolean(options.force),
33
+ advisory: Boolean(options.advisory),
34
+ confirmUnknown: Boolean(options["confirm-unknown"]),
35
+ };
36
+ const bootstrapTargetFile = stringOption(options["bootstrap-file"]);
37
+ const runtimeTargets = parseRuntimeTargetOptions(options);
38
+ const base = await initWorkspace({
39
+ ...input,
40
+ ...(bootstrapTargetFile ? { bootstrapTargetFile } : {}),
41
+ ...(runtimeTargets.length > 0 ? { runtimeTargets } : {}),
42
+ });
8
43
  io.log(`Initialized ${base}`);
44
+ if (options.advisory) {
45
+ io.log("Advisory mode enabled; root instruction files were not written.");
46
+ }
47
+ else {
48
+ io.log("Prompt registry scaffolded in .generated-prompts/");
49
+ }
50
+ io.log("Source catalog and agent lessons scaffolded in .agent-workflow/");
9
51
  io.log("Next: add tasks to .agent-workflow/tasks.json and run orchestra status");
10
52
  }
11
53
  export async function statusCommand(options, io) {
@@ -166,7 +208,7 @@ export async function lockListCommand(options, io) {
166
208
  }
167
209
  export async function lockClaimCommand(options, io) {
168
210
  const lock = await claimLock(removeUndefined({
169
- id: stringOption(options.id) ?? `lock-${Date.now()}`,
211
+ id: stringOption(options.id),
170
212
  taskId: requireArg(options, "task"),
171
213
  ownerRole: requireArg(options, "role"),
172
214
  path: requireArg(options, "path"),
@@ -363,6 +405,16 @@ export async function prSummaryCommand(options, io) {
363
405
  }
364
406
  io.log(renderPullRequestSummaryMarkdown(summary));
365
407
  }
408
+ export async function delegationDecideCommand(options, io) {
409
+ const decision = await decideTaskDelegation(requireArg(options, "task"), process.cwd(), {
410
+ record: !options["no-record"],
411
+ });
412
+ if (options.json) {
413
+ io.log(JSON.stringify(decision, null, 2));
414
+ return;
415
+ }
416
+ io.log(renderDelegationDecisionMarkdown(decision));
417
+ }
366
418
  export async function contextCommand(options, io) {
367
419
  const context = await getTaskContext(requireArg(options, "task"));
368
420
  if (options.json) {
@@ -485,6 +537,958 @@ export async function modelCompleteFakeCommand(options, io) {
485
537
  }
486
538
  io.log(`Fake completion used ${result.provider}/${result.model} fallback=${String(result.fallbackUsed)}`);
487
539
  }
540
+ export async function telemetryStatusCommand(options, io) {
541
+ const telemetry = await getTelemetryConsent();
542
+ if (options.json) {
543
+ io.log(JSON.stringify(telemetry, null, 2));
544
+ return;
545
+ }
546
+ io.log("Telemetry: " + (telemetry.enabled ? "enabled" : "off"));
547
+ io.log("Level: " + telemetry.level);
548
+ io.log("Policy: " + telemetry.policyVersion);
549
+ }
550
+ export async function telemetryEnableCommand(options, io) {
551
+ const level = parseTelemetryLevel(stringOption(options.level) ?? "metadata");
552
+ if (requiresSensitiveTelemetryOptIn(level) && !options["confirm-sensitive"]) {
553
+ throw new Error("prompt-sample and eval-dataset telemetry require --confirm-sensitive");
554
+ }
555
+ const telemetry = await enableTelemetryConsent({
556
+ level,
557
+ actor: stringOption(options.actor) ?? "user",
558
+ });
559
+ if (options.json) {
560
+ io.log(JSON.stringify(telemetry, null, 2));
561
+ return;
562
+ }
563
+ io.log("Telemetry enabled at level " + telemetry.level);
564
+ }
565
+ export async function telemetryExportCommand(options, io) {
566
+ const result = await exportTelemetryDataset({
567
+ dryRun: Boolean(options["dry-run"]),
568
+ });
569
+ if (options.json) {
570
+ io.log(JSON.stringify(result, null, 2));
571
+ return;
572
+ }
573
+ io.log((result.dryRun ? "Telemetry export dry run" : "Telemetry exported") +
574
+ ": " +
575
+ result.recordCount +
576
+ " record(s)");
577
+ if (result.file) {
578
+ io.log("File: " + result.file);
579
+ }
580
+ }
581
+ export async function telemetrySubmitCommand(options, io) {
582
+ const result = await submitTelemetryDataset({
583
+ file: requireArg(options, "file"),
584
+ endpoint: requireArg(options, "endpoint"),
585
+ });
586
+ if (options.json) {
587
+ io.log(JSON.stringify(result, null, 2));
588
+ return;
589
+ }
590
+ io.log("Telemetry submission recorded: " + result.fileHash);
591
+ }
592
+ export async function telemetryEvalDatasetCommand(options, io) {
593
+ const result = await exportTelemetryEvalDataset({
594
+ dryRun: Boolean(options["dry-run"]),
595
+ });
596
+ if (options.json) {
597
+ io.log(JSON.stringify(result, null, 2));
598
+ return;
599
+ }
600
+ io.log((result.dryRun ? "Eval dataset dry run" : "Eval dataset exported") +
601
+ ": " +
602
+ result.recordCount +
603
+ " record(s)");
604
+ if (result.file) {
605
+ io.log("File: " + result.file);
606
+ }
607
+ }
608
+ export async function telemetryDisableCommand(options, io) {
609
+ const telemetry = await disableTelemetryConsent({
610
+ actor: stringOption(options.actor) ?? "user",
611
+ });
612
+ if (options.json) {
613
+ io.log(JSON.stringify(telemetry, null, 2));
614
+ return;
615
+ }
616
+ io.log("Telemetry disabled");
617
+ }
618
+ export async function instructionsApplyCommand(options, io) {
619
+ const report = await applyInstructionUpdatesFromManifest({
620
+ manifestPath: requireArg(options, "manifest"),
621
+ check: Boolean(options.check),
622
+ dryRun: Boolean(options["dry-run"]),
623
+ force: Boolean(options.force),
624
+ });
625
+ if (options.json) {
626
+ io.log(JSON.stringify(report, null, 2));
627
+ return;
628
+ }
629
+ io.log("Instruction update " +
630
+ report.mode +
631
+ ": " +
632
+ report.totals.changed +
633
+ " changed, " +
634
+ report.totals.unchanged +
635
+ " unchanged, " +
636
+ report.totals.blocked +
637
+ " blocked");
638
+ for (const item of report.items) {
639
+ io.log(item.status +
640
+ " " +
641
+ item.filePath +
642
+ "#" +
643
+ item.blockId +
644
+ " - " +
645
+ item.reason);
646
+ }
647
+ }
648
+ export async function instructionsStaleCommand(options, io) {
649
+ const report = await detectStaleInstructionFilesFromManifest({
650
+ manifestPath: requireArg(options, "manifest"),
651
+ });
652
+ if (options.json) {
653
+ io.log(JSON.stringify(report, null, 2));
654
+ return;
655
+ }
656
+ io.log("Instruction file status:");
657
+ for (const item of report.items) {
658
+ io.log(item.status +
659
+ " " +
660
+ item.filePath +
661
+ "#" +
662
+ item.blockId +
663
+ " - " +
664
+ item.reason);
665
+ }
666
+ }
667
+ export async function instructionsBlockCommand(options, io) {
668
+ const result = await updateManagedInstructionFile({
669
+ filePath: resolveWorkspaceWritePath(process.cwd(), requireArg(options, "file")),
670
+ contentPath: resolveWorkspaceWritePath(process.cwd(), requireArg(options, "content-file")),
671
+ input: {
672
+ blockId: requireArg(options, "block"),
673
+ generator: stringOption(options.generator) ?? "open-orchestra",
674
+ version: stringOption(options.version) ?? "1",
675
+ target: parseInstructionTarget(stringOption(options.target) ?? "generic"),
676
+ sourceManifest: stringOption(options["source-manifest"]) ?? "local",
677
+ },
678
+ check: Boolean(options.check),
679
+ dryRun: Boolean(options["dry-run"]),
680
+ force: Boolean(options.force),
681
+ });
682
+ if (options.json) {
683
+ io.log(JSON.stringify(result, null, 2));
684
+ return;
685
+ }
686
+ if (result.drift) {
687
+ io.log("Drift detected for " + requireArg(options, "block"));
688
+ return;
689
+ }
690
+ io.log(result.changed
691
+ ? "Instruction block changed"
692
+ : "Instruction block unchanged");
693
+ }
694
+ export async function instructionsImportsCommand(options, io) {
695
+ const result = await resolveInstructionImportsFromFile({
696
+ registryPath: requireArg(options, "registry"),
697
+ entryId: requireArg(options, "entry"),
698
+ target: parseInstructionTarget(stringOption(options.target) ?? "generic"),
699
+ });
700
+ if (options.json) {
701
+ io.log(JSON.stringify(result, null, 2));
702
+ return;
703
+ }
704
+ io.log(result.content);
705
+ }
706
+ function parseInstructionTarget(value) {
707
+ return parseRuntimeTarget(value);
708
+ }
709
+ export async function collaborationFlowsCommand(options, io) {
710
+ const flows = listCollaborationFlows();
711
+ if (options.json) {
712
+ io.log(JSON.stringify(flows, null, 2));
713
+ return;
714
+ }
715
+ for (const flow of flows) {
716
+ io.log(flow.id + " - " + flow.name);
717
+ io.log(" roles: " + flow.roles.join(", "));
718
+ }
719
+ }
720
+ export async function collaborationRecommendCommand(options, io) {
721
+ const context = await getTaskContext(requireArg(options, "task"));
722
+ const recommendation = recommendCollaborationFlow(context.task, [
723
+ ...context.decisions,
724
+ ...context.handoffs,
725
+ ...context.reviews,
726
+ ...context.evidence,
727
+ ...context.gates,
728
+ ]);
729
+ if (options.json) {
730
+ io.log(JSON.stringify(recommendation, null, 2));
731
+ return;
732
+ }
733
+ if (!recommendation) {
734
+ io.log("No collaboration flow recommended");
735
+ return;
736
+ }
737
+ io.log(recommendation.flow.id + " - " + recommendation.flow.name);
738
+ io.log("Missing artifacts: " +
739
+ (recommendation.missingArtifacts.join(", ") || "none"));
740
+ io.log("Reviewers: " + (recommendation.optionalReviewers.join(", ") || "none"));
741
+ }
742
+ export async function workflowTemplatesCommand(options, io) {
743
+ const templates = listWorkflowTemplates();
744
+ const validationErrors = validateWorkflowTemplates();
745
+ if (options.json) {
746
+ io.log(JSON.stringify({ templates, validationErrors }, null, 2));
747
+ return;
748
+ }
749
+ for (const template of templates) {
750
+ io.log(template.id + " - " + template.name);
751
+ io.log(" roles: " + template.roles.join(", "));
752
+ }
753
+ if (validationErrors.length > 0) {
754
+ io.log("Validation errors: " + validationErrors.join("; "));
755
+ }
756
+ }
757
+ export async function workflowTemplateSelectCommand(options, io) {
758
+ const context = await getTaskContext(requireArg(options, "task"));
759
+ const events = [
760
+ ...context.decisions,
761
+ ...context.handoffs,
762
+ ...context.reviews,
763
+ ...context.evidence,
764
+ ...context.gates,
765
+ ];
766
+ const selection = selectWorkflowTemplates(context.task, events);
767
+ if (options.json) {
768
+ io.log(JSON.stringify(selection, null, 2));
769
+ return;
770
+ }
771
+ for (const item of selection) {
772
+ io.log(item.template.id + " (score " + item.score + ")");
773
+ io.log(" missing evidence: " + (item.missingEvidence.join(", ") || "none"));
774
+ }
775
+ }
776
+ export async function workflowTemplateRenderCommand(options, io) {
777
+ const context = await getTaskContext(requireArg(options, "task"));
778
+ const rendered = renderWorkflowTemplates({
779
+ task: context.task,
780
+ target: parseSkillRenderTarget(stringOption(options.target) ?? "generic"),
781
+ templates: context.workflowTemplates,
782
+ });
783
+ if (options.json) {
784
+ io.log(JSON.stringify(rendered, null, 2));
785
+ return;
786
+ }
787
+ io.log(rendered.content);
788
+ }
789
+ export async function commandsManifestCommand(options, io) {
790
+ const manifest = listCommandManifest();
791
+ if (options.json) {
792
+ io.log(JSON.stringify(manifest, null, 2));
793
+ return;
794
+ }
795
+ for (const command of manifest) {
796
+ io.log(command.command + " - " + command.summary);
797
+ }
798
+ }
799
+ export async function runtimeAdaptersCommand(options, io) {
800
+ const adapters = listRuntimeAdapters();
801
+ if (options.json) {
802
+ io.log(JSON.stringify(adapters, null, 2));
803
+ return;
804
+ }
805
+ for (const adapter of adapters) {
806
+ io.log(adapter.target +
807
+ " - " +
808
+ adapter.label +
809
+ " (" +
810
+ adapter.defaultInstructionFiles.join(", ") +
811
+ ")");
812
+ }
813
+ }
814
+ export async function runtimeBootstrapCommand(options, io) {
815
+ const target = parseSkillRenderTarget(stringOption(options.target) ?? "generic");
816
+ const bootstrap = renderRuntimeBootstrap(target);
817
+ const filePath = stringOption(options.file);
818
+ if (filePath) {
819
+ const resolvedFilePath = resolveWorkspaceWritePath(process.cwd(), path.normalize(filePath));
820
+ const existing = await readFile(resolvedFilePath, "utf8").catch(() => "");
821
+ const result = upsertRuntimeBootstrapBlock(existing, bootstrap, {
822
+ force: Boolean(options.force),
823
+ });
824
+ if (!options.check && !options["dry-run"] && !result.drift) {
825
+ await writeFile(resolvedFilePath, result.content);
826
+ }
827
+ if (options.json) {
828
+ io.log(JSON.stringify(result, null, 2));
829
+ return;
830
+ }
831
+ io.log(result.changed
832
+ ? "Runtime bootstrap changed"
833
+ : "Runtime bootstrap unchanged");
834
+ return;
835
+ }
836
+ if (options.json) {
837
+ io.log(JSON.stringify(bootstrap, null, 2));
838
+ return;
839
+ }
840
+ io.log(bootstrap.content);
841
+ }
842
+ export async function protocolRenderCommand(options, io) {
843
+ const protocol = await renderSubagentProtocol(removeUndefined({
844
+ target: parseSkillRenderTarget(stringOption(options.target) ?? "generic"),
845
+ taskId: stringOption(options.task),
846
+ }));
847
+ if (options.json) {
848
+ io.log(JSON.stringify(protocol, null, 2));
849
+ return;
850
+ }
851
+ io.log(protocol.content);
852
+ }
853
+ export async function protocolBlockCommand(options, io) {
854
+ const filePath = requireArg(options, "file");
855
+ const protocol = await renderSubagentProtocol(removeUndefined({
856
+ target: parseSkillRenderTarget(stringOption(options.target) ?? "generic"),
857
+ taskId: stringOption(options.task),
858
+ }));
859
+ const existing = await readFile(filePath, "utf8").catch(() => "");
860
+ const result = upsertSubagentProtocolBlock(existing, protocol, {
861
+ force: Boolean(options.force),
862
+ });
863
+ const shouldWrite = !options.check && !options["dry-run"] && !result.drift && result.changed;
864
+ if (shouldWrite) {
865
+ await writeFile(filePath, result.content);
866
+ }
867
+ if (options.json) {
868
+ io.log(JSON.stringify(result, null, 2));
869
+ return;
870
+ }
871
+ if (result.drift) {
872
+ io.log("Drift detected for subagent protocol block");
873
+ return;
874
+ }
875
+ io.log(result.changed
876
+ ? "Subagent protocol block changed"
877
+ : "Subagent protocol block unchanged");
878
+ }
879
+ export async function diagramsLintCommand(options, io) {
880
+ const result = await lintMermaidDiagram({
881
+ filePath: requireArg(options, "file"),
882
+ });
883
+ const evidence = stringOption(options.task)
884
+ ? await recordDiagramLintEvidence({
885
+ taskId: stringOption(options.task) ?? "",
886
+ result,
887
+ })
888
+ : undefined;
889
+ const output = removeUndefined({ result, evidence });
890
+ if (options.json) {
891
+ io.log(JSON.stringify(output, null, 2));
892
+ }
893
+ else {
894
+ io.log(result.valid ? "Mermaid diagram valid" : "Mermaid diagram invalid");
895
+ if (result.installHint) {
896
+ io.log(result.installHint);
897
+ }
898
+ if (evidence) {
899
+ io.log("Created " + evidence.artifact);
900
+ }
901
+ }
902
+ if (!result.valid) {
903
+ throw new Error("diagram lint failed");
904
+ }
905
+ }
906
+ export async function mcpOAuthProxyEvaluateCommand(options, io) {
907
+ const evaluation = evaluateMcpOAuthProxy(removeUndefined({
908
+ enabled: Boolean(options.enable),
909
+ mode: parseMcpProxyMode(stringOption(options.mode) ?? "stdio-proxy"),
910
+ serverUrl: requireArg(options, "server-url"),
911
+ tokenStorage: parseMcpSecretStorage(stringOption(options["token-storage"]) ?? "keychain"),
912
+ tokenPath: stringOption(options["token-path"]),
913
+ refreshWindowSeconds: Number(stringOption(options["refresh-window"]) ?? 300),
914
+ approvedBy: options.approve
915
+ ? (stringOption(options.approver) ?? "local-user")
916
+ : undefined,
917
+ }));
918
+ if (options.json) {
919
+ io.log(JSON.stringify(evaluation, null, 2));
920
+ return;
921
+ }
922
+ io.log(evaluation.approved
923
+ ? "MCP proxy plan approved"
924
+ : "MCP proxy plan has risks");
925
+ for (const risk of evaluation.risks) {
926
+ io.log("RISK: " + risk);
927
+ }
928
+ }
929
+ function parseMcpProxyMode(value) {
930
+ if (["stdio-proxy", "direct-http", "tool-native-oauth"].includes(value)) {
931
+ return value;
932
+ }
933
+ throw new Error("unknown MCP proxy mode: " + value);
934
+ }
935
+ function parseMcpSecretStorage(value) {
936
+ if (["keychain", "libsecret", "windows-credential", "secure-file"].includes(value)) {
937
+ return value;
938
+ }
939
+ throw new Error("unknown MCP secret storage: " + value);
940
+ }
941
+ export async function skillsValidateCommand(options, io) {
942
+ const report = await validateSkills();
943
+ if (options.json) {
944
+ io.log(JSON.stringify(report, null, 2));
945
+ }
946
+ else {
947
+ io.log(report.valid ? "Skills valid" : "Skills invalid");
948
+ for (const error of report.errors) {
949
+ io.log(`ERROR: ${error}`);
950
+ }
951
+ for (const warning of report.warnings) {
952
+ io.log(`WARN: ${warning}`);
953
+ }
954
+ }
955
+ if (!report.valid) {
956
+ throw new Error("skills validation failed");
957
+ }
958
+ }
959
+ export async function sourcesListCommand(options, io) {
960
+ const sources = await readSourceOfTruth();
961
+ if (options.json) {
962
+ io.log(JSON.stringify(sources, null, 2));
963
+ return;
964
+ }
965
+ for (const source of sources) {
966
+ io.log(`${source.id ?? "unknown"} - ${source.name ?? "Unnamed source"}`);
967
+ io.log(` ${(source.locations ?? []).join(", ")}`);
968
+ }
969
+ }
970
+ export async function lessonsListCommand(options, io) {
971
+ const lessons = await listAgentLessons();
972
+ if (options.json) {
973
+ io.log(JSON.stringify(lessons, null, 2));
974
+ return;
975
+ }
976
+ if (lessons.length === 0) {
977
+ io.log("No lessons");
978
+ return;
979
+ }
980
+ for (const lesson of lessons) {
981
+ io.log(`${lesson.timestamp} ${lesson.operation}: ${lesson.errorSignature}`);
982
+ }
983
+ }
984
+ export async function lessonsAddCommand(options, io) {
985
+ const lesson = await addAgentLesson(removeUndefined({
986
+ taskId: stringOption(options.task),
987
+ actor: stringOption(options.actor) ?? "parent",
988
+ operation: requireArg(options, "operation"),
989
+ failedAction: requireArg(options, "failed-action"),
990
+ errorSignature: requireArg(options, "error-signature"),
991
+ rootCause: requireArg(options, "root-cause"),
992
+ fix: requireArg(options, "fix"),
993
+ prevention: requireArg(options, "prevention"),
994
+ appliesTo: parseCsv(options["applies-to"]),
995
+ verifiedBy: parseCsv(options["verified-by"]),
996
+ }));
997
+ if (options.json) {
998
+ io.log(JSON.stringify(lesson, null, 2));
999
+ return;
1000
+ }
1001
+ io.log(`Recorded lesson ${lesson.errorSignature}`);
1002
+ }
1003
+ export async function lessonsPromoteCommand(options, io) {
1004
+ const result = await promoteAgentLessons(removeUndefined({
1005
+ to: parsePromotionTarget(stringOption(options.to) ?? "doc"),
1006
+ filter: stringOption(options.filter),
1007
+ }));
1008
+ if (options.json) {
1009
+ io.log(JSON.stringify(result, null, 2));
1010
+ return;
1011
+ }
1012
+ io.log(`Created ${result.artifact}`);
1013
+ io.log(`Promoted lessons: ${result.lessons.length}`);
1014
+ }
1015
+ function parsePromotionTarget(value) {
1016
+ if (["skill", "rule", "doc"].includes(value)) {
1017
+ return value;
1018
+ }
1019
+ throw new Error(`unknown promotion target: ${value}`);
1020
+ }
1021
+ export async function skillsListCommand(options, io) {
1022
+ const skills = listSkills();
1023
+ if (options.json) {
1024
+ io.log(JSON.stringify(skills, null, 2));
1025
+ return;
1026
+ }
1027
+ for (const skill of skills) {
1028
+ io.log(`${skill.id} - ${skill.name} (${skill.loadBudget})`);
1029
+ io.log(` ${skill.summary}`);
1030
+ io.log(` sources: ${skill.sourceGroups.join(", ")}`);
1031
+ }
1032
+ }
1033
+ export async function skillsPlanCommand(options, io) {
1034
+ const plan = await planSkillsForTask(requireArg(options, "task"));
1035
+ await recordSkillPlan(plan);
1036
+ if (options.json) {
1037
+ io.log(JSON.stringify(plan, null, 2));
1038
+ return;
1039
+ }
1040
+ io.log(`Skills for ${plan.taskId}:`);
1041
+ for (const item of plan.selected) {
1042
+ io.log(` ${item.skill.id} (score ${item.score})`);
1043
+ for (const reason of item.rationale) {
1044
+ io.log(` - ${reason}`);
1045
+ }
1046
+ }
1047
+ io.log(`Source groups: ${plan.sourceGroups.join(", ")}`);
1048
+ }
1049
+ export async function skillsRenderCommand(options, io) {
1050
+ const target = parseSkillRenderTarget(stringOption(options.target) ?? "generic");
1051
+ const rendered = await renderSkills({
1052
+ target,
1053
+ taskId: stringOption(options.task),
1054
+ skillIds: parseCsv(options.skills),
1055
+ });
1056
+ await recordSkillRender(rendered);
1057
+ if (options.json) {
1058
+ io.log(JSON.stringify(rendered, null, 2));
1059
+ return;
1060
+ }
1061
+ io.log(rendered.content);
1062
+ }
1063
+ function parseSkillRenderTarget(value) {
1064
+ return parseRuntimeTarget(value);
1065
+ }
1066
+ // --- Autonomous workflow commands ---
1067
+ const GATE_MODES = ["none", "phase", "all"];
1068
+ export async function workflowRunCommand(options, io) {
1069
+ const cwd = process.cwd();
1070
+ const taskId = requireArg(options, "task");
1071
+ const gates = (stringOption(options.gates) ?? "phase");
1072
+ const maxIterations = numberOption(options["max-iterations"], 5);
1073
+ const file = autonomousRunsPath(cwd);
1074
+ if (!GATE_MODES.includes(gates)) {
1075
+ throw new Error(`--gates must be one of: ${GATE_MODES.join(", ")}`);
1076
+ }
1077
+ if (options["dry-run"]) {
1078
+ return workflowDryRun(options, io, taskId, gates, maxIterations);
1079
+ }
1080
+ let run;
1081
+ let startIndex;
1082
+ if (options.resume) {
1083
+ const existing = await readAutonomousRun(cwd, String(options.resume));
1084
+ if (!existing)
1085
+ throw new Error(`workflow run not found: ${String(options.resume)}`);
1086
+ run = existing;
1087
+ startIndex = resumePhaseIndex(run);
1088
+ if (startIndex === -1) {
1089
+ io.log(`Workflow ${run.id} is already complete`);
1090
+ if (options.json)
1091
+ io.log(JSON.stringify({ run, file, cwd }, null, 2));
1092
+ return;
1093
+ }
1094
+ io.log(`Resuming run ${run.id} from phase ${AUTONOMOUS_PHASE_SEQUENCE[startIndex]?.phase}`);
1095
+ }
1096
+ else {
1097
+ run = await createAutonomousRun(cwd, { taskId, gates, maxIterations });
1098
+ startIndex = 0;
1099
+ io.log(`Started autonomous workflow ${run.id} for task ${taskId} [gates=${gates}]`);
1100
+ }
1101
+ run = await executePhases(cwd, run, startIndex, io);
1102
+ const result = { run, file, cwd };
1103
+ if (options.json) {
1104
+ io.log(JSON.stringify(result, null, 2));
1105
+ return;
1106
+ }
1107
+ io.log(run.status === "done"
1108
+ ? `Workflow complete [run=${run.id}]`
1109
+ : run.status === "paused"
1110
+ ? `Workflow paused — approve gate review then run: orchestra workflow run --task ${taskId} --resume ${run.id}`
1111
+ : `Workflow ${run.status} [run=${run.id}]`);
1112
+ }
1113
+ export async function workflowRunListCommand(options, io) {
1114
+ const cwd = process.cwd();
1115
+ const runs = await listAutonomousRuns(cwd);
1116
+ if (options.json) {
1117
+ io.log(JSON.stringify(runs, null, 2));
1118
+ return;
1119
+ }
1120
+ if (runs.length === 0) {
1121
+ io.log("No workflow runs");
1122
+ return;
1123
+ }
1124
+ for (const run of runs) {
1125
+ const phases = run.phases.map((p) => `${p.phase}:${p.status}`).join(" → ");
1126
+ io.log(`${run.id} [${run.status}] task=${run.taskId} gates=${run.gates}`);
1127
+ if (phases)
1128
+ io.log(` ${phases}`);
1129
+ }
1130
+ }
1131
+ const CLARIFICATION_TARGETS = ["po", "architect"];
1132
+ const CLARIFICATION_ALLOWED_PHASES = new Set(["developer", "qa"]);
1133
+ export async function workflowClarifyCommand(options, io) {
1134
+ const cwd = process.cwd();
1135
+ const runId = requireArg(options, "run");
1136
+ const from = requireArg(options, "from");
1137
+ const to = requireArg(options, "to");
1138
+ const question = requireArg(options, "question");
1139
+ if (!CLARIFICATION_TARGETS.includes(to)) {
1140
+ throw new Error(`--to must be one of: ${CLARIFICATION_TARGETS.join(", ")}`);
1141
+ }
1142
+ const run = await readAutonomousRun(cwd, runId);
1143
+ if (!run)
1144
+ throw new Error(`workflow run not found: ${runId}`);
1145
+ // Find the active phase (running or already awaiting_clarification)
1146
+ const activePhaseIdx = run.phases.findIndex((p) => p.role === from &&
1147
+ (p.status === "running" || p.status === "awaiting_clarification"));
1148
+ if (activePhaseIdx === -1) {
1149
+ throw new Error(`no active phase found for role ${from} in run ${runId}`);
1150
+ }
1151
+ const activePhase = run.phases[activePhaseIdx];
1152
+ if (!CLARIFICATION_ALLOWED_PHASES.has(activePhase.phase)) {
1153
+ throw new Error(`clarification is only allowed from developer or qa phases (active: ${activePhase.phase})`);
1154
+ }
1155
+ // Open clarification record
1156
+ const record = await openClarification(cwd, {
1157
+ runId,
1158
+ taskId: run.taskId,
1159
+ fromRole: from,
1160
+ to,
1161
+ question,
1162
+ });
1163
+ // Suspend the phase if it's still running
1164
+ if (activePhase.status === "running") {
1165
+ const phaseSeqIdx = AUTONOMOUS_PHASE_SEQUENCE.findIndex((d) => d.phase === activePhase.phase);
1166
+ await suspendPhaseForClarification(cwd, run, phaseSeqIdx);
1167
+ }
1168
+ if (options.json) {
1169
+ io.log(JSON.stringify(record, null, 2));
1170
+ return;
1171
+ }
1172
+ io.log(`Clarification ${record.id} opened — waiting for ${record.toRole}`);
1173
+ io.log(` Question: ${record.question}`);
1174
+ io.log(` Respond with:`);
1175
+ io.log(` orchestra workflow clarify-respond --run ${runId} --clarification ${record.id} --answer "<text>"`);
1176
+ }
1177
+ export async function workflowClarifyRespondCommand(options, io) {
1178
+ const cwd = process.cwd();
1179
+ const runId = requireArg(options, "run");
1180
+ const clarificationId = requireArg(options, "clarification");
1181
+ const answer = requireArg(options, "answer");
1182
+ const run = await readAutonomousRun(cwd, runId);
1183
+ if (!run)
1184
+ throw new Error(`workflow run not found: ${runId}`);
1185
+ const record = await answerClarification(cwd, {
1186
+ id: clarificationId,
1187
+ answer,
1188
+ });
1189
+ // Resume the suspended phase back to running
1190
+ const phaseSeqIdx = AUTONOMOUS_PHASE_SEQUENCE.findIndex((d) => d.role === record.fromRole);
1191
+ if (phaseSeqIdx !== -1) {
1192
+ const phase = run.phases.find((p) => p.role === record.fromRole);
1193
+ if (phase?.status === "awaiting_clarification") {
1194
+ await resumePhaseFromClarification(cwd, run, phaseSeqIdx);
1195
+ }
1196
+ }
1197
+ if (options.json) {
1198
+ io.log(JSON.stringify(record, null, 2));
1199
+ return;
1200
+ }
1201
+ io.log(`Clarification ${record.id} answered`);
1202
+ io.log(` Answer: ${record.answer}`);
1203
+ io.log(` Resume with:`);
1204
+ io.log(` orchestra workflow run --task ${run.taskId} --resume ${runId}`);
1205
+ }
1206
+ export async function workflowClarifyListCommand(options, io) {
1207
+ const cwd = process.cwd();
1208
+ const runId = requireArg(options, "run");
1209
+ const clarifications = await listClarifications(cwd, runId);
1210
+ if (options.json) {
1211
+ io.log(JSON.stringify(clarifications, null, 2));
1212
+ return;
1213
+ }
1214
+ if (clarifications.length === 0) {
1215
+ io.log(`No clarifications for run ${runId}`);
1216
+ return;
1217
+ }
1218
+ for (const c of clarifications) {
1219
+ const status = c.status === "answered" ? "✓" : "⏳";
1220
+ io.log(`${status} [${c.id}] ${c.fromRole} → ${c.toRole}`);
1221
+ io.log(` Q: ${c.question}`);
1222
+ if (c.answer)
1223
+ io.log(` A: ${c.answer}`);
1224
+ }
1225
+ }
1226
+ const DEV_PHASE_INDEX = AUTONOMOUS_PHASE_SEQUENCE.findIndex((d) => d.phase === "developer");
1227
+ async function executePhases(cwd, run, startIndex, io) {
1228
+ let current = run;
1229
+ let qaFailNotes;
1230
+ for (let i = startIndex; i < AUTONOMOUS_PHASE_SEQUENCE.length; i++) {
1231
+ const def = AUTONOMOUS_PHASE_SEQUENCE[i];
1232
+ // Init phase task (skip if already has a running/done/clarification record from resume)
1233
+ const existing = current.phases.find((p) => p.phase === def.phase && p.status !== "qa_failed");
1234
+ if (!existing || existing.status === "pending") {
1235
+ const retryContext = def.phase === "developer" && qaFailNotes ? qaFailNotes : undefined;
1236
+ const phaseRecord = await initPhase(cwd, current, i, retryContext);
1237
+ current = { ...current, phases: [...current.phases, phaseRecord] };
1238
+ if (retryContext) {
1239
+ io.log(`↺ ${def.phase} (${def.role}) retry — QA findings: ${retryContext.slice(0, 80)}`);
1240
+ }
1241
+ else {
1242
+ io.log(`→ ${def.phase} (${def.role}) task=${phaseRecord.taskId}`);
1243
+ }
1244
+ }
1245
+ else if (existing.status === "awaiting_clarification") {
1246
+ io.log(`↺ ${def.phase} (${def.role}) resuming after clarification`);
1247
+ }
1248
+ // Architect sizing gate — always enforced regardless of --gates mode
1249
+ if (def.phase === "architect") {
1250
+ const sizing = await checkArchitectSizing(cwd, current.taskId);
1251
+ if (!sizing.found) {
1252
+ io.log(`⚠ Architect phase requires a sizing decision before handoff to developer.`);
1253
+ io.log(` Record one with:`);
1254
+ 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`);
1255
+ io.log(` Then resume: orchestra workflow run --task ${current.taskId} --resume ${current.id}`);
1256
+ current = await markRunFailed(cwd, current, "Architect sizing decision required before developer handoff");
1257
+ return current;
1258
+ }
1259
+ io.log(` ✓ sizing=${sizing.sizing}${sizing.points !== undefined ? ` (${sizing.points} pts)` : ""}`);
1260
+ }
1261
+ // QA loop guard
1262
+ if (def.phase === "qa" && current.qaIterations >= current.maxIterations) {
1263
+ io.log(`✗ QA failed after ${current.maxIterations} iterations — workflow blocked`);
1264
+ current = await markRunFailed(cwd, current, `Max QA iterations (${current.maxIterations}) reached`);
1265
+ return current;
1266
+ }
1267
+ // Determine outcome — QA phase can fail and route back to developer
1268
+ // When LLM execution lands (ORCH-019), this will be the actual QA verdict.
1269
+ // For now the outcome is always done; qa_fail is exercised via tests/simulation.
1270
+ const outcome = {
1271
+ kind: "done",
1272
+ notes: def.summary,
1273
+ };
1274
+ const result = await closePhase(cwd, current, i, outcome);
1275
+ current = result.run;
1276
+ if (result.handoffArtifact) {
1277
+ io.log(` ✓ handoff → ${AUTONOMOUS_PHASE_SEQUENCE[i + 1]?.role ?? "end"} (${result.handoffArtifact})`);
1278
+ }
1279
+ if (result.reviewArtifact) {
1280
+ io.log(` ⏸ gate ${def.phase}→${AUTONOMOUS_PHASE_SEQUENCE[i + 1]?.phase} — review: ${result.reviewArtifact}`);
1281
+ io.log(` Approve: orchestra workflow run --task ${current.taskId} --resume ${current.id}`);
1282
+ return current;
1283
+ }
1284
+ // QA failure — loop back to developer with findings as context
1285
+ const closedPhase = current.phases.find((p) => p.phase === def.phase && p.status === "qa_failed");
1286
+ if (closedPhase) {
1287
+ qaFailNotes = closedPhase.notes ?? "QA findings — see QA phase artifact";
1288
+ io.log(` ✗ qa failed (iteration ${current.qaIterations}/${current.maxIterations}) — routing back to developer`);
1289
+ i = DEV_PHASE_INDEX - 1; // will be incremented to DEV_PHASE_INDEX at top of loop
1290
+ continue;
1291
+ }
1292
+ // Release phase: auto-create PR if configured
1293
+ if (def.phase === "release") {
1294
+ const config = await getWorkflowConfig(cwd);
1295
+ if (config.github?.autoCreatePr) {
1296
+ try {
1297
+ const prSummary = await generatePullRequestSummary(current.taskId, cwd);
1298
+ const prBody = buildPrBody(prSummary, current.qaIterations);
1299
+ const prTitle = `${current.taskId}: ${prSummary.task.title}`;
1300
+ const prResult = await createPullRequest({
1301
+ title: prTitle,
1302
+ body: prBody,
1303
+ ...(config.github.baseBranch
1304
+ ? { baseBranch: config.github.baseBranch }
1305
+ : {}),
1306
+ });
1307
+ await addEvidence({
1308
+ task: current.taskId,
1309
+ role: "release_manager",
1310
+ type: "report",
1311
+ summary: `PR created: ${prResult.url}`,
1312
+ }, cwd);
1313
+ io.log(` ✓ PR created: ${prResult.url}`);
1314
+ }
1315
+ catch (err) {
1316
+ io.log(` ⚠ PR creation failed: ${err instanceof Error ? err.message : String(err)}`);
1317
+ }
1318
+ }
1319
+ }
1320
+ }
1321
+ current = await markRunDone(cwd, current);
1322
+ return current;
1323
+ }
1324
+ async function workflowDryRun(options, io, taskId, gates, maxIterations) {
1325
+ const gateTransitions = new Set(["po→architect", "qa→release"]);
1326
+ io.log(`Dry run — no records will be created`);
1327
+ io.log(`Task: ${taskId} gates: ${gates} max-iterations: ${maxIterations}`);
1328
+ io.log(``);
1329
+ for (let i = 0; i < AUTONOMOUS_PHASE_SEQUENCE.length; i++) {
1330
+ const def = AUTONOMOUS_PHASE_SEQUENCE[i];
1331
+ const next = AUTONOMOUS_PHASE_SEQUENCE[i + 1];
1332
+ const transitionKey = next ? `${def.phase}→${next.phase}` : "";
1333
+ const gateLabel = gates === "all"
1334
+ ? "gate=yes"
1335
+ : gates === "phase" && gateTransitions.has(transitionKey)
1336
+ ? "gate=yes"
1337
+ : "gate=no";
1338
+ io.log(` ${def.phase} (${def.role}) ${gateLabel}`);
1339
+ }
1340
+ if (options.json) {
1341
+ io.log(JSON.stringify({
1342
+ dryRun: true,
1343
+ taskId,
1344
+ gates,
1345
+ maxIterations,
1346
+ phases: AUTONOMOUS_PHASE_SEQUENCE,
1347
+ }, null, 2));
1348
+ }
1349
+ }
1350
+ // --- Estimate & Benchmark commands ---
1351
+ const CONFIDENCE_LEVELS = ["low", "medium", "high"];
1352
+ export async function estimateCommand(options, io) {
1353
+ const cwd = process.cwd();
1354
+ const taskId = requireArg(options, "task");
1355
+ const sizingRaw = requireArg(options, "sizing");
1356
+ const soloDays = numberOption(options["solo-days"], 0);
1357
+ const aiDays = numberOption(options["ai-unguided-days"], 0);
1358
+ const confidence = (stringOption(options.confidence) ??
1359
+ "medium");
1360
+ const declaredBy = stringOption(options["declared-by"]) ?? "pm";
1361
+ if (!SIZING_LABELS.includes(sizingRaw)) {
1362
+ throw new Error(`--sizing must be one of: ${SIZING_LABELS.join(", ")}`);
1363
+ }
1364
+ if (!CONFIDENCE_LEVELS.includes(confidence)) {
1365
+ throw new Error(`--confidence must be one of: ${CONFIDENCE_LEVELS.join(", ")}`);
1366
+ }
1367
+ if (soloDays <= 0)
1368
+ throw new Error(`--solo-days must be > 0`);
1369
+ if (aiDays <= 0)
1370
+ throw new Error(`--ai-unguided-days must be > 0`);
1371
+ const record = await recordEstimate(cwd, {
1372
+ taskId,
1373
+ sizingLabel: sizingRaw,
1374
+ soloEstimateDays: soloDays,
1375
+ aiUnguidedEstimateDays: aiDays,
1376
+ confidence,
1377
+ declaredBy,
1378
+ });
1379
+ if (options.json) {
1380
+ io.log(JSON.stringify(record, null, 2));
1381
+ return;
1382
+ }
1383
+ io.log(`Estimate recorded for ${taskId} [${record.id}]`);
1384
+ io.log(` Sizing: ${record.sizingLabel}`);
1385
+ io.log(` Solo: ${record.soloEstimateDays}d`);
1386
+ io.log(` AI-unguided: ${record.aiUnguidedEstimateDays}d`);
1387
+ io.log(` Confidence: ${record.confidence}`);
1388
+ io.log(` Declared by: ${record.declaredBy}`);
1389
+ }
1390
+ export async function benchmarkCommand(options, io) {
1391
+ const cwd = process.cwd();
1392
+ if (options.summary) {
1393
+ const summary = await summarizeBenchmark(cwd);
1394
+ if (options.json) {
1395
+ io.log(JSON.stringify(summary, null, 2));
1396
+ return;
1397
+ }
1398
+ if (summary.stories.length === 0) {
1399
+ io.log("No estimates recorded yet. Use: orchestra estimate --task <id> ...");
1400
+ return;
1401
+ }
1402
+ 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"}`;
1403
+ io.log(header);
1404
+ io.log("─".repeat(header.length));
1405
+ for (const s of summary.stories) {
1406
+ const actual = s.actualDays !== null ? `${s.actualDays}d` : "pending";
1407
+ const vsSolo = s.vsSoloPct !== null
1408
+ ? `${s.vsSoloPct > 0 ? "+" : ""}${s.vsSoloPct}%`
1409
+ : "—";
1410
+ const vsAi = s.vsAiUnguidedPct !== null
1411
+ ? `${s.vsAiUnguidedPct > 0 ? "+" : ""}${s.vsAiUnguidedPct}%`
1412
+ : "—";
1413
+ 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}`);
1414
+ }
1415
+ if (summary.totalWithActuals > 0) {
1416
+ io.log("");
1417
+ io.log(`Avg savings vs solo: ${summary.avgVsSoloPct}%`);
1418
+ io.log(`Avg savings vs AI-unguided: ${summary.avgVsAiUnguidedPct}%`);
1419
+ io.log(`Stories with actuals: ${summary.totalWithActuals}/${summary.stories.length}`);
1420
+ }
1421
+ return;
1422
+ }
1423
+ const taskId = requireArg(options, "task");
1424
+ const result = await computeBenchmark(cwd, taskId);
1425
+ if (options.json) {
1426
+ io.log(JSON.stringify(result, null, 2));
1427
+ return;
1428
+ }
1429
+ io.log(`Benchmark: ${taskId} [${result.status}]`);
1430
+ io.log(` Sizing: ${result.sizingLabel}`);
1431
+ io.log(` Solo: ${result.soloEstimateDays}d (declared)`);
1432
+ io.log(` AI-unguided: ${result.aiUnguidedEstimateDays}d (declared)`);
1433
+ io.log(` Actual: ${result.actualDays !== null ? `${result.actualDays}d` : "pending — run not complete"}`);
1434
+ if (result.vsSoloPct !== null) {
1435
+ const sign = result.vsSoloPct > 0 ? "+" : "";
1436
+ io.log(` vs Solo: ${sign}${result.vsSoloPct}%`);
1437
+ io.log(` vs AI: ${result.vsAiUnguidedPct !== null ? `${result.vsAiUnguidedPct > 0 ? "+" : ""}${result.vsAiUnguidedPct}%` : "—"}`);
1438
+ }
1439
+ io.log(` QA loops: ${result.qaIterations}`);
1440
+ io.log(` Reviews: ${result.quality.reviewCount} (${result.quality.blockingReviews} blocking)`);
1441
+ io.log(` Evidence: ${result.quality.evidenceCount} artifacts`);
1442
+ io.log(` Gate blocks: ${result.quality.gateBlockCount}`);
1443
+ io.log(` Lessons: ${result.quality.lessonCount}`);
1444
+ if (result.quality.totalInputTokens > 0 ||
1445
+ result.quality.totalOutputTokens > 0) {
1446
+ io.log(` Tokens: ${result.quality.totalInputTokens}in / ${result.quality.totalOutputTokens}out`);
1447
+ io.log(` Cost: $${result.quality.estimatedCostUsd}`);
1448
+ }
1449
+ }
1450
+ // --- Burndown command ---
1451
+ export async function burndownCommand(options, io) {
1452
+ const cwd = process.cwd();
1453
+ const sprintCsv = requireArg(options, "sprint");
1454
+ const sprintTaskIds = sprintCsv
1455
+ .split(",")
1456
+ .map((s) => s.trim())
1457
+ .filter(Boolean);
1458
+ if (sprintTaskIds.length === 0)
1459
+ throw new Error(`--sprint requires at least one task id`);
1460
+ const series = await computeSprintBurndown(cwd, sprintTaskIds);
1461
+ if (options.json) {
1462
+ io.log(JSON.stringify(series, null, 2));
1463
+ return;
1464
+ }
1465
+ for (const w of series.warnings)
1466
+ io.log(`⚠ ${w}`);
1467
+ if (series.totalPoints === 0) {
1468
+ io.log("No points to chart — record estimates first with: orchestra estimate --task <id> ...");
1469
+ return;
1470
+ }
1471
+ io.log(`Sprint burndown total=${series.totalPoints} pts tasks=${series.sprintTaskIds.length}`);
1472
+ io.log("");
1473
+ io.log(renderBurndownAscii(series));
1474
+ io.log("");
1475
+ io.log("Task breakdown:");
1476
+ for (const t of series.taskBreakdown) {
1477
+ const arch = t.architectPoints !== null ? `arch=${t.architectPoints}` : "arch=—";
1478
+ const dev = t.developerPoints !== null ? `dev=${t.developerPoints}` : "dev=—";
1479
+ const done = t.completedAt
1480
+ ? `done ${t.completedAt.slice(0, 10)}`
1481
+ : "pending";
1482
+ io.log(` ${t.taskId.padEnd(14)} ${arch.padEnd(10)} ${dev.padEnd(10)} resolved=${t.resolvedPoints} ${done}`);
1483
+ }
1484
+ }
1485
+ function parseRuntimeTargetOptions(options) {
1486
+ const targetValues = [
1487
+ ...parseCsv(options.target),
1488
+ ...parseCsv(options.targets),
1489
+ ];
1490
+ return [...new Set(targetValues)].map((target) => parseRuntimeTarget(target));
1491
+ }
488
1492
  function parseCsv(value) {
489
1493
  if (typeof value !== "string" || value.trim() === "") {
490
1494
  return [];
@@ -759,6 +1763,20 @@ function renderPullRequestSummaryMarkdown(summary) {
759
1763
  "## Risks",
760
1764
  ...listOrNone(summary.risks),
761
1765
  "",
1766
+ "## Review Routing",
1767
+ `- Merge blocked: ${summary.review.mergeBlocked}`,
1768
+ `- Required reviewers: ${summary.review.requiredReviewers.join(", ") || "none"}`,
1769
+ `- Missing reviewers: ${summary.review.missingReviewers.join(", ") || "none"}`,
1770
+ "",
1771
+ "## Review Checklist",
1772
+ ...listOrNone(summary.review.checklist),
1773
+ "",
1774
+ "## Evidence Gaps",
1775
+ ...listOrNone(summary.review.evidenceGaps),
1776
+ "",
1777
+ "## Merge Blockers",
1778
+ ...listOrNone(summary.review.mergeBlockers),
1779
+ "",
762
1780
  "## Gates",
763
1781
  ...eventLines(summary.gates),
764
1782
  "",
@@ -784,6 +1802,34 @@ function renderPullRequestSummaryMarkdown(summary) {
784
1802
  "",
785
1803
  ].join("\n");
786
1804
  }
1805
+ function renderDelegationDecisionMarkdown(decision) {
1806
+ return [
1807
+ `# Delegation Decision: ${decision.taskId}`,
1808
+ "",
1809
+ `- Recommendation: ${decision.recommendation}`,
1810
+ `- Complexity score: ${decision.complexityScore}`,
1811
+ `- Urgency: ${decision.urgency}`,
1812
+ `- Disjoint write scopes: ${String(decision.disjointWriteScopes)}`,
1813
+ "",
1814
+ "## Rationale",
1815
+ ...listOrNone(decision.rationale),
1816
+ "",
1817
+ "## Blocking Conditions",
1818
+ ...listOrNone(decision.blockingConditions),
1819
+ "",
1820
+ "## Context Bundle",
1821
+ ...listOrNone(decision.contextBundle),
1822
+ "",
1823
+ "## Delegates",
1824
+ ...(decision.delegates.length === 0
1825
+ ? ["- none"]
1826
+ : decision.delegates.map((delegate) => `- ${delegate.role} [${delegate.mode}] scopes=${delegate.writeScopes.join(",") || "none"}`)),
1827
+ "",
1828
+ "## Expected Outputs",
1829
+ ...listOrNone(decision.expectedOutputs),
1830
+ "",
1831
+ ].join("\n");
1832
+ }
787
1833
  function renderTaskContextMarkdown(context) {
788
1834
  return [
789
1835
  `# Task Context: ${context.task.id}`,
@@ -794,6 +1840,27 @@ function renderTaskContextMarkdown(context) {
794
1840
  `- Dependencies: ${context.dependencies.isSatisfied ? "satisfied" : "blocked"}`,
795
1841
  `- Locks: ${context.locks.length}`,
796
1842
  `- Risks: ${context.risks.length}`,
1843
+ `- Delegation: ${context.delegation?.recommendation ?? "not decided"}`,
1844
+ "",
1845
+ "## Collaboration Flow",
1846
+ ...(context.collaborationFlow
1847
+ ? [
1848
+ `- ${context.collaborationFlow.flow.id}: ${context.collaborationFlow.flow.name}`,
1849
+ `- Missing artifacts: ${context.collaborationFlow.missingArtifacts.join(", ") || "none"}`,
1850
+ `- Reviewers: ${context.collaborationFlow.optionalReviewers.join(", ") || "none"}`,
1851
+ ]
1852
+ : ["- none"]),
1853
+ "",
1854
+ "## Workflow Templates",
1855
+ ...(context.workflowTemplates.length === 0
1856
+ ? ["- none"]
1857
+ : context.workflowTemplates.map((item) => `- ${item.template.id} (score ${item.score}, missing evidence ${item.missingEvidence.join(", ") || "none"})`)),
1858
+ "",
1859
+ "## Skills",
1860
+ ...(context.skills.selected.length === 0
1861
+ ? ["- none"]
1862
+ : context.skills.selected.map((item) => `- ${item.skill.id} (score ${item.score}): ${item.rationale.join("; ")}`)),
1863
+ `- Source groups: ${context.skills.sourceGroups.join(", ") || "none"}`,
797
1864
  "",
798
1865
  "## Decisions",
799
1866
  ...eventLines(context.decisions),