@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.
- package/AGENTS.md +90 -0
- package/CHANGELOG.md +104 -0
- package/CLAUDE.md +103 -0
- package/README.md +173 -22
- package/dist/assets/web-console.js +743 -0
- package/dist/autonomous-workflow.d.ts +45 -0
- package/dist/autonomous-workflow.js +386 -0
- package/dist/autonomous-workflow.js.map +1 -0
- package/dist/benchmark.d.ts +8 -0
- package/dist/benchmark.js +193 -0
- package/dist/benchmark.js.map +1 -0
- package/dist/burndown.d.ts +3 -0
- package/dist/burndown.js +141 -0
- package/dist/burndown.js.map +1 -0
- package/dist/clarification.d.ts +6 -0
- package/dist/clarification.js +88 -0
- package/dist/clarification.js.map +1 -0
- package/dist/cli.js +221 -4
- package/dist/cli.js.map +1 -1
- package/dist/collaboration-flows.d.ts +5 -0
- package/dist/collaboration-flows.js +256 -0
- package/dist/collaboration-flows.js.map +1 -0
- package/dist/command-manifest.d.ts +11 -0
- package/dist/command-manifest.js +52 -0
- package/dist/command-manifest.js.map +1 -0
- package/dist/commands.d.ts +39 -0
- package/dist/commands.js +1069 -2
- package/dist/commands.js.map +1 -1
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +22 -0
- package/dist/constants.js.map +1 -1
- package/dist/defaults.d.ts +7 -11
- package/dist/defaults.js +7 -625
- package/dist/defaults.js.map +1 -1
- package/dist/delegation-decision.d.ts +14 -0
- package/dist/delegation-decision.js +391 -0
- package/dist/delegation-decision.js.map +1 -0
- package/dist/detect-commands.d.ts +3 -0
- package/dist/detect-commands.js +28 -0
- package/dist/detect-commands.js.map +1 -0
- package/dist/diagram-validation.d.ts +36 -0
- package/dist/diagram-validation.js +118 -0
- package/dist/diagram-validation.js.map +1 -0
- package/dist/fs-utils.d.ts +2 -0
- package/dist/fs-utils.js +75 -6
- package/dist/fs-utils.js.map +1 -1
- package/dist/github.d.ts +11 -0
- package/dist/github.js +48 -0
- package/dist/github.js.map +1 -0
- package/dist/health-checks.d.ts +28 -0
- package/dist/health-checks.js +219 -0
- package/dist/health-checks.js.map +1 -0
- package/dist/health-commands.d.ts +2 -0
- package/dist/health-commands.js +18 -0
- package/dist/health-commands.js.map +1 -0
- package/dist/instruction-apply.d.ts +34 -0
- package/dist/instruction-apply.js +150 -0
- package/dist/instruction-apply.js.map +1 -0
- package/dist/instruction-blocks.d.ts +22 -0
- package/dist/instruction-blocks.js +120 -0
- package/dist/instruction-blocks.js.map +1 -0
- package/dist/instruction-imports.d.ts +12 -0
- package/dist/instruction-imports.js +45 -0
- package/dist/instruction-imports.js.map +1 -0
- package/dist/instruction-stale.d.ts +9 -0
- package/dist/instruction-stale.js +106 -0
- package/dist/instruction-stale.js.map +1 -0
- package/dist/instruction-types.d.ts +66 -0
- package/dist/instruction-types.js +2 -0
- package/dist/instruction-types.js.map +1 -0
- package/dist/instruction-updates.d.ts +4 -0
- package/dist/instruction-updates.js +5 -0
- package/dist/instruction-updates.js.map +1 -0
- package/dist/knowledge-base.d.ts +10 -0
- package/dist/knowledge-base.js +117 -0
- package/dist/knowledge-base.js.map +1 -0
- package/dist/mcp-oauth-proxy.d.ts +39 -0
- package/dist/mcp-oauth-proxy.js +80 -0
- package/dist/mcp-oauth-proxy.js.map +1 -0
- package/dist/pr-review.d.ts +20 -0
- package/dist/pr-review.js +142 -0
- package/dist/pr-review.js.map +1 -0
- package/dist/project-detection.d.ts +22 -0
- package/dist/project-detection.js +174 -0
- package/dist/project-detection.js.map +1 -0
- package/dist/prompt-registry.d.ts +56 -0
- package/dist/prompt-registry.js +163 -0
- package/dist/prompt-registry.js.map +1 -0
- package/dist/release-candidate.d.ts +41 -0
- package/dist/release-candidate.js +196 -0
- package/dist/release-candidate.js.map +1 -0
- package/dist/release-commands.d.ts +4 -0
- package/dist/release-commands.js +50 -0
- package/dist/release-commands.js.map +1 -0
- package/dist/roles/ai-support-roles.d.ts +11 -0
- package/dist/roles/ai-support-roles.js +67 -0
- package/dist/roles/ai-support-roles.js.map +1 -0
- package/dist/roles/core-roles.d.ts +11 -0
- package/dist/roles/core-roles.js +144 -0
- package/dist/roles/core-roles.js.map +1 -0
- package/dist/roles/engineering-roles.d.ts +11 -0
- package/dist/roles/engineering-roles.js +176 -0
- package/dist/roles/engineering-roles.js.map +1 -0
- package/dist/roles/governance-roles.d.ts +11 -0
- package/dist/roles/governance-roles.js +117 -0
- package/dist/roles/governance-roles.js.map +1 -0
- package/dist/roles/index.d.ts +11 -0
- package/dist/roles/index.js +17 -0
- package/dist/roles/index.js.map +1 -0
- package/dist/roles/platform-ops-roles.d.ts +11 -0
- package/dist/roles/platform-ops-roles.js +158 -0
- package/dist/roles/platform-ops-roles.js.map +1 -0
- package/dist/roles/qa-ux-roles.d.ts +11 -0
- package/dist/roles/qa-ux-roles.js +193 -0
- package/dist/roles/qa-ux-roles.js.map +1 -0
- package/dist/roles/release-ops-roles.d.ts +11 -0
- package/dist/roles/release-ops-roles.js +109 -0
- package/dist/roles/release-ops-roles.js.map +1 -0
- package/dist/runtime-adapters.d.ts +6 -0
- package/dist/runtime-adapters.js +88 -0
- package/dist/runtime-adapters.js.map +1 -0
- package/dist/runtime-bootstrap.d.ts +12 -0
- package/dist/runtime-bootstrap.js +136 -0
- package/dist/runtime-bootstrap.js.map +1 -0
- package/dist/skills.d.ts +36 -0
- package/dist/skills.js +665 -0
- package/dist/skills.js.map +1 -0
- package/dist/subagent-protocol.d.ts +41 -0
- package/dist/subagent-protocol.js +179 -0
- package/dist/subagent-protocol.js.map +1 -0
- package/dist/telemetry-consent.d.ts +24 -0
- package/dist/telemetry-consent.js +95 -0
- package/dist/telemetry-consent.js.map +1 -0
- package/dist/telemetry-export.d.ts +14 -0
- package/dist/telemetry-export.js +126 -0
- package/dist/telemetry-export.js.map +1 -0
- package/dist/telemetry-records.d.ts +3 -0
- package/dist/telemetry-records.js +96 -0
- package/dist/telemetry-records.js.map +1 -0
- package/dist/telemetry-redaction.d.ts +9 -0
- package/dist/telemetry-redaction.js +55 -0
- package/dist/telemetry-redaction.js.map +1 -0
- package/dist/telemetry-types.d.ts +52 -0
- package/dist/telemetry-types.js +2 -0
- package/dist/telemetry-types.js.map +1 -0
- package/dist/telemetry.d.ts +4 -0
- package/dist/telemetry.js +4 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/types.d.ts +304 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validation.d.ts +3 -1
- package/dist/validation.js +28 -5
- package/dist/validation.js.map +1 -1
- package/dist/web-api.js +167 -3
- package/dist/web-api.js.map +1 -1
- package/dist/web-console.js +6 -160
- package/dist/web-console.js.map +1 -1
- package/dist/workflow-gates.js +4 -2
- package/dist/workflow-gates.js.map +1 -1
- package/dist/workflow-services.js +143 -67
- package/dist/workflow-services.js.map +1 -1
- package/dist/workflow-templates.d.ts +10 -0
- package/dist/workflow-templates.js +141 -0
- package/dist/workflow-templates.js.map +1 -0
- package/dist/workspace-classification.d.ts +5 -0
- package/dist/workspace-classification.js +127 -0
- package/dist/workspace-classification.js.map +1 -0
- package/dist/workspace-validator.js +11 -1
- package/dist/workspace-validator.js.map +1 -1
- package/dist/workspace.d.ts +8 -4
- package/dist/workspace.js +111 -4
- package/dist/workspace.js.map +1 -1
- package/docs/autonomous-workflow.md +165 -0
- package/docs/benchmark.md +219 -0
- package/docs/dev-team-specialist-role-profiles.md +171 -0
- package/docs/mcp-oauth-proxy-evaluation.md +44 -0
- package/docs/multi-agent-orchestrator-backlog.md +413 -1
- package/docs/open-orchestra-dogfooding-findings.md +66 -0
- package/docs/orchestra-mvp.md +161 -3
- package/docs/runtime-adapters.md +86 -0
- package/docs/runtime-llm-flow.md +124 -0
- package/docs/setup-agents-dogfooding-findings.md +101 -0
- package/docs/skill-loading-strategy.md +114 -0
- package/docs/source-of-truth-and-agent-learning.md +83 -0
- package/package.json +9 -5
- package/rules/agent-roles.mdc +30 -0
- package/rules/ai-assisted-development.mdc +22 -0
- package/skills/agent-learning/SKILL.md +24 -0
- package/skills/agent-learning/manifest.json +40 -0
- package/skills/backlog-sync/SKILL.md +24 -0
- package/skills/backlog-sync/manifest.json +41 -0
- package/skills/diagram-export/SKILL.md +35 -0
- package/skills/diagram-export/manifest.json +40 -0
- package/skills/model-evaluation/SKILL.md +25 -0
- package/skills/model-evaluation/manifest.json +41 -0
- package/skills/playwright-evidence/SKILL.md +28 -0
- package/skills/playwright-evidence/manifest.json +46 -0
- package/skills/pr-review/SKILL.md +23 -0
- package/skills/pr-review/manifest.json +43 -0
- package/skills/prompt-registry/SKILL.md +24 -0
- package/skills/prompt-registry/manifest.json +45 -0
- package/skills/release-readiness/SKILL.md +25 -0
- package/skills/release-readiness/manifest.json +45 -0
- package/skills/source-of-truth/SKILL.md +24 -0
- package/skills/source-of-truth/manifest.json +47 -0
- package/skills/static-analysis/SKILL.md +26 -0
- 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
|
|
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)
|
|
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),
|