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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/AGENTS.md +5 -3
  2. package/README.md +29 -5
  3. package/dist/advisory-artifacts.d.ts +20 -0
  4. package/dist/advisory-artifacts.js +136 -0
  5. package/dist/advisory-artifacts.js.map +1 -0
  6. package/dist/assets/web-console.js +436 -4
  7. package/dist/cli.js +16 -117
  8. package/dist/cli.js.map +1 -1
  9. package/dist/command-manifest.d.ts +6 -0
  10. package/dist/command-manifest.js +141 -43
  11. package/dist/command-manifest.js.map +1 -1
  12. package/dist/command-utils.d.ts +5 -0
  13. package/dist/command-utils.js +23 -0
  14. package/dist/command-utils.js.map +1 -1
  15. package/dist/commands.d.ts +7 -42
  16. package/dist/commands.js +214 -1356
  17. package/dist/commands.js.map +1 -1
  18. package/dist/constants.js +3 -0
  19. package/dist/constants.js.map +1 -1
  20. package/dist/context-budget.d.ts +4 -0
  21. package/dist/context-budget.js +119 -0
  22. package/dist/context-budget.js.map +1 -0
  23. package/dist/delivery-commands.d.ts +10 -0
  24. package/dist/delivery-commands.js +152 -0
  25. package/dist/delivery-commands.js.map +1 -0
  26. package/dist/github.d.ts +50 -1
  27. package/dist/github.js +234 -0
  28. package/dist/github.js.map +1 -1
  29. package/dist/health-checks.d.ts +1 -0
  30. package/dist/health-checks.js +11 -1
  31. package/dist/health-checks.js.map +1 -1
  32. package/dist/health-commands.js +2 -0
  33. package/dist/health-commands.js.map +1 -1
  34. package/dist/mcp-oauth-proxy.d.ts +32 -0
  35. package/dist/mcp-oauth-proxy.js +120 -0
  36. package/dist/mcp-oauth-proxy.js.map +1 -1
  37. package/dist/memory.d.ts +2 -1
  38. package/dist/memory.js +71 -10
  39. package/dist/memory.js.map +1 -1
  40. package/dist/package-update-check.d.ts +5 -1
  41. package/dist/package-update-check.js +20 -8
  42. package/dist/package-update-check.js.map +1 -1
  43. package/dist/planning-commands.d.ts +14 -0
  44. package/dist/planning-commands.js +372 -0
  45. package/dist/planning-commands.js.map +1 -0
  46. package/dist/release-candidate.d.ts +2 -0
  47. package/dist/release-candidate.js +9 -14
  48. package/dist/release-candidate.js.map +1 -1
  49. package/dist/release-commands.d.ts +2 -0
  50. package/dist/release-commands.js +58 -6
  51. package/dist/release-commands.js.map +1 -1
  52. package/dist/release-readiness.d.ts +49 -0
  53. package/dist/release-readiness.js +172 -0
  54. package/dist/release-readiness.js.map +1 -0
  55. package/dist/runtime-commands.js +11 -4
  56. package/dist/runtime-commands.js.map +1 -1
  57. package/dist/runtime-execution-renderer.js +2 -0
  58. package/dist/runtime-execution-renderer.js.map +1 -1
  59. package/dist/runtime-execution.d.ts +4 -2
  60. package/dist/runtime-execution.js +11 -4
  61. package/dist/runtime-execution.js.map +1 -1
  62. package/dist/setup-agents-import.d.ts +42 -0
  63. package/dist/setup-agents-import.js +335 -0
  64. package/dist/setup-agents-import.js.map +1 -0
  65. package/dist/skills-catalog-service.d.ts +2 -0
  66. package/dist/skills-catalog-service.js +8 -0
  67. package/dist/skills-catalog-service.js.map +1 -0
  68. package/dist/skills-catalog.d.ts +2 -0
  69. package/dist/skills-catalog.js +389 -0
  70. package/dist/skills-catalog.js.map +1 -0
  71. package/dist/skills-commands.js +1 -11
  72. package/dist/skills-commands.js.map +1 -1
  73. package/dist/skills-events.d.ts +9 -0
  74. package/dist/skills-events.js +50 -0
  75. package/dist/skills-events.js.map +1 -0
  76. package/dist/skills-memory.d.ts +18 -0
  77. package/dist/skills-memory.js +127 -0
  78. package/dist/skills-memory.js.map +1 -0
  79. package/dist/skills-planning.d.ts +2 -0
  80. package/dist/skills-planning.js +87 -0
  81. package/dist/skills-planning.js.map +1 -0
  82. package/dist/skills-render.d.ts +14 -0
  83. package/dist/skills-render.js +83 -0
  84. package/dist/skills-render.js.map +1 -0
  85. package/dist/skills-validation.d.ts +2 -0
  86. package/dist/skills-validation.js +49 -0
  87. package/dist/skills-validation.js.map +1 -0
  88. package/dist/skills.d.ts +6 -42
  89. package/dist/skills.js +6 -773
  90. package/dist/skills.js.map +1 -1
  91. package/dist/task-graph-commands.d.ts +14 -0
  92. package/dist/task-graph-commands.js +367 -0
  93. package/dist/task-graph-commands.js.map +1 -0
  94. package/dist/tool-commands.js +8 -0
  95. package/dist/tool-commands.js.map +1 -1
  96. package/dist/types/context.d.ts +12 -0
  97. package/dist/types/context.js +2 -0
  98. package/dist/types/context.js.map +1 -0
  99. package/dist/types/metrics.d.ts +114 -0
  100. package/dist/types/metrics.js +2 -0
  101. package/dist/types/metrics.js.map +1 -0
  102. package/dist/types/model-config.d.ts +212 -0
  103. package/dist/types/model-config.js +2 -0
  104. package/dist/types/model-config.js.map +1 -0
  105. package/dist/types/runtime.d.ts +93 -0
  106. package/dist/types/runtime.js +2 -0
  107. package/dist/types/runtime.js.map +1 -0
  108. package/dist/types/skills.d.ts +147 -0
  109. package/dist/types/skills.js +2 -0
  110. package/dist/types/skills.js.map +1 -0
  111. package/dist/types/tasks.d.ts +171 -0
  112. package/dist/types/tasks.js +2 -0
  113. package/dist/types/tasks.js.map +1 -0
  114. package/dist/types/workflow-run.d.ts +79 -0
  115. package/dist/types/workflow-run.js +2 -0
  116. package/dist/types/workflow-run.js.map +1 -0
  117. package/dist/types.d.ts +13 -798
  118. package/dist/types.js +1 -1
  119. package/dist/types.js.map +1 -1
  120. package/dist/upgrade-commands.d.ts +2 -0
  121. package/dist/upgrade-commands.js +65 -0
  122. package/dist/upgrade-commands.js.map +1 -0
  123. package/dist/web-api-read-routes.d.ts +5 -0
  124. package/dist/web-api-read-routes.js +37 -0
  125. package/dist/web-api-read-routes.js.map +1 -0
  126. package/dist/web-api.d.ts +1 -3
  127. package/dist/web-api.js +145 -44
  128. package/dist/web-api.js.map +1 -1
  129. package/dist/web-console-sections.d.ts +2 -0
  130. package/dist/web-console-sections.js +7 -0
  131. package/dist/web-console-sections.js.map +1 -0
  132. package/dist/web-console.js +23 -3
  133. package/dist/web-console.js.map +1 -1
  134. package/dist/workflow-approval-service.d.ts +9 -0
  135. package/dist/workflow-approval-service.js +126 -0
  136. package/dist/workflow-approval-service.js.map +1 -0
  137. package/dist/workflow-approval-utils.d.ts +10 -0
  138. package/dist/workflow-approval-utils.js +82 -0
  139. package/dist/workflow-approval-utils.js.map +1 -0
  140. package/dist/workflow-budget-utils.d.ts +7 -0
  141. package/dist/workflow-budget-utils.js +96 -0
  142. package/dist/workflow-budget-utils.js.map +1 -0
  143. package/dist/workflow-evidence-service.d.ts +7 -0
  144. package/dist/workflow-evidence-service.js +100 -0
  145. package/dist/workflow-evidence-service.js.map +1 -0
  146. package/dist/workflow-run-commands.d.ts +8 -0
  147. package/dist/workflow-run-commands.js +479 -0
  148. package/dist/workflow-run-commands.js.map +1 -0
  149. package/dist/workflow-services.d.ts +8 -18
  150. package/dist/workflow-services.js +30 -481
  151. package/dist/workflow-services.js.map +1 -1
  152. package/dist/workflow-summary-service.d.ts +4 -0
  153. package/dist/workflow-summary-service.js +82 -0
  154. package/dist/workflow-summary-service.js.map +1 -0
  155. package/dist/workflow-templates.d.ts +1 -0
  156. package/dist/workflow-templates.js +1 -0
  157. package/dist/workflow-templates.js.map +1 -1
  158. package/dist/workspace.d.ts +18 -1
  159. package/dist/workspace.js +72 -4
  160. package/dist/workspace.js.map +1 -1
  161. package/docs/command-contracts.md +22 -0
  162. package/docs/mcp-oauth-proxy-evaluation.md +14 -0
  163. package/docs/orchestra-mvp.md +158 -114
  164. package/docs/package-naming.md +20 -0
  165. package/docs/persona-workflows.md +209 -0
  166. package/docs/runtime-adapters.md +19 -14
  167. package/docs/runtime-llm-flow.md +29 -28
  168. package/docs/setup-agents-bridge.md +61 -0
  169. package/docs/traceability-flow.md +89 -0
  170. package/package.json +9 -7
@@ -1,27 +1,30 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import path from "node:path";
3
- import { readdir, readFile } from "node:fs/promises";
4
3
  import { FILES } from "./constants.js";
5
- import { exists, ensureDir, readJson, resolveWorkflowPath, updateJsonFile, writeJson, } from "./fs-utils.js";
4
+ import { removeUndefined } from "./command-utils.js";
5
+ import { ensureDir, readJson, resolveWorkflowPath, updateJsonFile, writeJson, } from "./fs-utils.js";
6
6
  import { assertProviderAllowed, assertVendorFallbackAllowed, createModelProvider, FakeModelProvider, InMemoryModelProviderRegistry, summarizeConfiguredProviders, } from "./model-providers.js";
7
7
  import { appendEvent, loadWorkspace, readEvents, writeArtifact, } from "./workspace.js";
8
- import { normalizeEvidenceType, normalizeReviewRole, validateEvidenceInput, validateReadiness, validateReviewInput, } from "./validation.js";
8
+ import { validateReadiness } from "./validation.js";
9
9
  import { getWorkflowGate } from "./workflow-gates.js";
10
- import { analyzePullRequestReview } from "./pr-review.js";
11
10
  import { planSkillsForTask } from "./skills.js";
12
11
  import { getTelemetryConsent } from "./telemetry.js";
13
12
  import { latestDelegationDecision } from "./delegation-decision.js";
14
13
  import { listAutonomousRuns } from "./autonomous-workflow.js";
15
- import { persistRun, readAutonomousRun } from "./autonomous-run-store.js";
16
- import { AUTONOMOUS_PHASE_SEQUENCE } from "./autonomous-workflow-constants.js";
17
14
  import { readEstimate } from "./benchmark.js";
18
- import { notifyWorkflowLifecycle, } from "./notifications.js";
19
15
  import { handoffFlowRequirements, recommendCollaborationFlow, } from "./collaboration-flows.js";
20
16
  import { queryMemory, recordMemoryEvent } from "./memory.js";
17
+ import { applyContextBudget, DEFAULT_CONTEXT_TOKEN_BUDGET, } from "./context-budget.js";
21
18
  import { selectWorkflowTemplates } from "./workflow-templates.js";
19
+ import { findStoredApprovalForProposal } from "./workflow-approval-service.js";
20
+ import { aggregateUsage, aggregateUsageBy, budgetEstimateWarningsForBudgets, budgetViolations, emptyUsageBreakdown, projectUsageCost, } from "./workflow-budget-utils.js";
22
21
  import { SIZING_LABELS } from "./types.js";
22
+ export { addEvidence, addPlaywrightEvidence, listEvidence, listReviews, recordReview, } from "./workflow-evidence-service.js";
23
+ export { generatePlaywrightTestPlan, generatePullRequestSummary, getWorkflowSummary, } from "./workflow-summary-service.js";
24
+ export { approveApproval, approveWorkflowGate, listApprovals, rejectApproval, showApproval, } from "./workflow-approval-service.js";
23
25
  export async function getWorkflowStatus(root = process.cwd()) {
24
26
  const workspace = await loadWorkspace(root);
27
+ const config = await readJson(resolveWorkflowPath(root, FILES.config), {});
25
28
  const counts = Object.create(null);
26
29
  const blocked = [];
27
30
  for (const task of workspace.tasks) {
@@ -35,6 +38,7 @@ export async function getWorkflowStatus(root = process.cwd()) {
35
38
  }
36
39
  }
37
40
  return {
41
+ ...(config.mode ? { mode: config.mode } : {}),
38
42
  tasks: {
39
43
  total: workspace.tasks.length,
40
44
  byStatus: counts,
@@ -487,17 +491,7 @@ export async function checkReadiness(taskId, root = process.cwd()) {
487
491
  return { task, report };
488
492
  }
489
493
  export async function evaluateWorkflowGate(gateId, taskId, root = process.cwd()) {
490
- const workspace = await loadWorkspace(root);
491
- const task = workspace.tasks.find((candidate) => candidate.id === taskId);
492
- if (!task) {
493
- throw new Error(`unknown task: ${taskId}`);
494
- }
495
- const gate = getWorkflowGate(gateId);
496
- const result = gate.evaluate({
497
- task,
498
- events: await readEvents(root),
499
- locks: workspace.locks,
500
- });
494
+ const result = await previewWorkflowGate(gateId, taskId, root);
501
495
  await appendEvent(root, {
502
496
  type: result.passed ? "GATE_PASSED" : "GATE_BLOCKED",
503
497
  taskId,
@@ -512,6 +506,20 @@ export async function evaluateWorkflowGate(gateId, taskId, root = process.cwd())
512
506
  });
513
507
  return result;
514
508
  }
509
+ export async function previewWorkflowGate(gateId, taskId, root = process.cwd()) {
510
+ const workspace = await loadWorkspace(root);
511
+ const task = workspace.tasks.find((candidate) => candidate.id === taskId);
512
+ if (!task) {
513
+ throw new Error(`unknown task: ${taskId}`);
514
+ }
515
+ const gate = getWorkflowGate(gateId);
516
+ const result = gate.evaluate({
517
+ task,
518
+ events: await readEvents(root),
519
+ locks: workspace.locks,
520
+ });
521
+ return result;
522
+ }
515
523
  export async function createHandoff(input, root = process.cwd()) {
516
524
  const workspace = await loadWorkspace(root);
517
525
  if (!workspace.roleIds.has(input.from) || !workspace.roleIds.has(input.to)) {
@@ -611,7 +619,7 @@ export async function recordDecision(input, root = process.cwd()) {
611
619
  export async function listDecisions(taskId, root = process.cwd()) {
612
620
  return filterEvents("DECISION_RECORDED", taskId, root);
613
621
  }
614
- export async function getTaskContext(taskId, root = process.cwd()) {
622
+ export async function getTaskContext(taskId, root = process.cwd(), options = {}) {
615
623
  const workspace = await loadWorkspace(root);
616
624
  const task = workspace.tasks.find((candidate) => candidate.id === taskId);
617
625
  if (!task) {
@@ -622,10 +630,11 @@ export async function getTaskContext(taskId, root = process.cwd()) {
622
630
  const memory = await queryMemory({
623
631
  taskId,
624
632
  hook: "before_implementation",
633
+ role: task.ownerRole,
625
634
  root,
626
635
  });
627
636
  await recordMemoryEvent(root, memory, "MEMORY_QUERIED");
628
- return removeUndefined({
637
+ const context = removeUndefined({
629
638
  task,
630
639
  dependencies: await checkTaskDependencies(taskId, root),
631
640
  locks: workspace.locks.filter((lock) => lock.taskId === taskId),
@@ -643,6 +652,7 @@ export async function getTaskContext(taskId, root = process.cwd()) {
643
652
  collaborationFlow: recommendCollaborationFlow(task, taskEvents),
644
653
  workflowTemplates: selectWorkflowTemplates(task, taskEvents),
645
654
  });
655
+ return applyContextBudget(context, options.tokenBudget ?? DEFAULT_CONTEXT_TOKEN_BUDGET);
646
656
  }
647
657
  export async function generateExecutionPlan(taskId, root = process.cwd()) {
648
658
  const context = await getTaskContext(taskId, root);
@@ -955,128 +965,6 @@ function pathsConflict(taskPath, lockPath) {
955
965
  function normalizePath(value) {
956
966
  return value.replaceAll("\\", "/").replace(/\/+$/, "");
957
967
  }
958
- export async function listApprovals(taskId, root = process.cwd()) {
959
- const approvalDir = resolveWorkflowPath(root, "approvals");
960
- const fileNames = (await exists(approvalDir))
961
- ? (await readdir(approvalDir)).filter((fileName) => fileName.endsWith(".md"))
962
- : [];
963
- const events = await readEvents(root);
964
- const records = fileNames.map((fileName) => approvalRecordForArtifact(path.join(".agent-workflow", "approvals", fileName), events));
965
- records.push(...gateApprovalRecords(events));
966
- const filtered = taskId
967
- ? records.filter((record) => record.taskId === taskId)
968
- : records;
969
- return filtered.sort((left, right) => left.id.localeCompare(right.id));
970
- }
971
- export async function approveWorkflowGate(input, root = process.cwd()) {
972
- const run = await readAutonomousRun(root, input.runId);
973
- if (!run) {
974
- throw new Error(`workflow run not found: ${input.runId}`);
975
- }
976
- const phaseIndex = run.phases.findLastIndex((phase) => phase.status === "gate_paused");
977
- if (phaseIndex < 0) {
978
- throw new Error(`workflow run ${input.runId} has no paused gate`);
979
- }
980
- const phase = run.phases[phaseIndex];
981
- const gateId = phase.gateId ?? gateIdForPhase(run, phaseIndex);
982
- if (input.gateId !== gateId) {
983
- throw new Error(`wrong gate id: ${input.gateId}. Expected gate: ${gateId}`);
984
- }
985
- if (phase.approvedBy && phase.approvedAt) {
986
- return {
987
- run,
988
- gateId,
989
- approver: phase.approvedBy,
990
- approvedAt: phase.approvedAt,
991
- alreadyApproved: true,
992
- };
993
- }
994
- const approvedAt = new Date().toISOString();
995
- const approvedPhase = {
996
- ...phase,
997
- gateId,
998
- approvedBy: input.approver,
999
- approvedAt,
1000
- approvalRationale: input.rationale,
1001
- };
1002
- const updatedRun = {
1003
- ...run,
1004
- phases: run.phases.map((candidate, index) => index === phaseIndex ? approvedPhase : candidate),
1005
- updatedAt: approvedAt,
1006
- };
1007
- await persistRun(root, updatedRun);
1008
- await appendEvent(root, removeUndefined({
1009
- type: "GATE_APPROVED",
1010
- taskId: run.taskId,
1011
- actor: "parent",
1012
- summary: `Gate approved: ${gateId}`,
1013
- artifacts: phase.reviewArtifact ? [phase.reviewArtifact] : undefined,
1014
- metadata: {
1015
- runId: run.id,
1016
- gateId,
1017
- approver: input.approver,
1018
- rationale: input.rationale,
1019
- approvedAt,
1020
- },
1021
- }));
1022
- return {
1023
- run: updatedRun,
1024
- gateId,
1025
- approver: input.approver,
1026
- approvedAt,
1027
- alreadyApproved: false,
1028
- };
1029
- }
1030
- export async function showApproval(id, root = process.cwd()) {
1031
- const record = await getApprovalRecord(id, root);
1032
- const content = await readFile(path.join(root, record.artifact), "utf8");
1033
- return { ...record, content };
1034
- }
1035
- export async function approveApproval(input, root = process.cwd()) {
1036
- return recordApprovalDecision(input, "approved", root);
1037
- }
1038
- export async function rejectApproval(input, root = process.cwd()) {
1039
- return recordApprovalDecision(input, "rejected", root);
1040
- }
1041
- export async function recordReview(input, root = process.cwd(), notificationTransports = {}) {
1042
- const workspace = await loadWorkspace(root);
1043
- const reviewInput = removeUndefined({
1044
- ...input,
1045
- role: normalizeReviewRole(input.role, workspace.roleIds) ?? input.role,
1046
- });
1047
- validateReviewInput(reviewInput, workspace.roleIds);
1048
- const fileName = `${reviewInput.task}-${reviewInput.role}-review.md`;
1049
- const content = [
1050
- `# Review ${reviewInput.task}: ${reviewInput.role}`,
1051
- "",
1052
- `- Result: ${reviewInput.result}`,
1053
- `- Severity: ${reviewInput.severity}`,
1054
- `- Findings: ${reviewInput.findings}`,
1055
- `- Recommendation: ${reviewInput.recommendation}`,
1056
- "",
1057
- ].join("\n");
1058
- const artifact = await writeArtifact(root, "reviews", fileName, content);
1059
- await appendEvent(root, {
1060
- type: "REVIEW_RECORDED",
1061
- taskId: reviewInput.task,
1062
- actor: reviewInput.role,
1063
- summary: `Review recorded: ${reviewInput.result}`,
1064
- artifacts: [artifact],
1065
- metadata: { result: reviewInput.result, severity: reviewInput.severity },
1066
- });
1067
- if (reviewInput.result === "block" && reviewInput.severity === "critical") {
1068
- await notifyWorkflowLifecycle({
1069
- root,
1070
- taskId: reviewInput.task,
1071
- kind: "critical_blocking_review",
1072
- actor: reviewInput.role,
1073
- detail: reviewInput.findings,
1074
- artifact,
1075
- idempotencyKey: `critical_blocking_review:${reviewInput.task}:${artifact}`,
1076
- }, notificationTransports);
1077
- }
1078
- return { artifact, content };
1079
- }
1080
968
  function riskReviewSteps(impactAreas) {
1081
969
  const roleByImpact = {
1082
970
  security: "security",
@@ -1145,117 +1033,6 @@ async function writeTaskGraphBatchArtifact(root, batch) {
1145
1033
  "",
1146
1034
  ].join("\n"));
1147
1035
  }
1148
- export async function listReviews(taskId, root = process.cwd()) {
1149
- return filterEvents("REVIEW_RECORDED", taskId, root);
1150
- }
1151
- export async function addEvidence(input, root = process.cwd()) {
1152
- const evidenceInput = removeUndefined({
1153
- ...input,
1154
- type: normalizeEvidenceType(input.type) ?? input.type,
1155
- });
1156
- const workspace = await loadWorkspace(root);
1157
- validateEvidenceInput(evidenceInput, workspace.roleIds);
1158
- if (typeof evidenceInput.path === "string" &&
1159
- !(await exists(path.resolve(root, evidenceInput.path)))) {
1160
- throw new Error(`evidence path does not exist: ${evidenceInput.path}`);
1161
- }
1162
- const fileName = `${evidenceInput.task}-${Date.now()}-${randomUUID()}-${evidenceInput.type}.md`;
1163
- const content = [
1164
- `# Evidence ${evidenceInput.task}: ${evidenceInput.type}`,
1165
- "",
1166
- `- Role: ${evidenceInput.role}`,
1167
- `- Summary: ${evidenceInput.summary}`,
1168
- `- Path: ${evidenceInput.path ?? "not applicable"}`,
1169
- `- Command: ${evidenceInput.command ?? "not applicable"}`,
1170
- `- Exit code: ${evidenceInput.exitCode ?? "not applicable"}`,
1171
- "",
1172
- ].join("\n");
1173
- const artifact = await writeArtifact(root, "evidence", fileName, content);
1174
- await appendEvent(root, {
1175
- type: "EVIDENCE_ADDED",
1176
- taskId: evidenceInput.task,
1177
- actor: evidenceInput.role,
1178
- summary: evidenceInput.summary,
1179
- artifacts: [artifact],
1180
- metadata: { type: evidenceInput.type },
1181
- });
1182
- return { artifact, content };
1183
- }
1184
- export async function listEvidence(taskId, root = process.cwd()) {
1185
- return filterEvents("EVIDENCE_ADDED", taskId, root);
1186
- }
1187
- export async function getWorkflowSummary(root = process.cwd()) {
1188
- const workspace = await loadWorkspace(root);
1189
- const events = await readEvents(root);
1190
- return {
1191
- tasks: workspace.tasks,
1192
- locks: workspace.locks,
1193
- reviews: events.filter((event) => event.type === "REVIEW_RECORDED"),
1194
- evidence: events.filter((event) => event.type === "EVIDENCE_ADDED"),
1195
- blockers: workspace.tasks.filter((task) => task.status === "blocked"),
1196
- eventCount: events.length,
1197
- };
1198
- }
1199
- export async function generatePullRequestSummary(taskId, root = process.cwd()) {
1200
- const workspace = await loadWorkspace(root);
1201
- const task = workspace.tasks.find((candidate) => candidate.id === taskId);
1202
- if (!task) {
1203
- throw new Error(`unknown task: ${taskId}`);
1204
- }
1205
- const events = await readEvents(root);
1206
- const taskEvents = events.filter((event) => event.taskId === taskId);
1207
- const handoffs = taskEvents.filter((event) => event.type === "HANDOFF_READY");
1208
- const reviews = taskEvents.filter((event) => event.type === "REVIEW_RECORDED");
1209
- const evidence = taskEvents.filter((event) => event.type === "EVIDENCE_ADDED");
1210
- const gates = taskEvents.filter((event) => event.type === "GATE_PASSED" || event.type === "GATE_BLOCKED");
1211
- const locks = workspace.locks.filter((lock) => lock.taskId === taskId);
1212
- return {
1213
- task,
1214
- handoffs,
1215
- reviews,
1216
- evidence,
1217
- gates,
1218
- locks,
1219
- risks: task.risks ?? [],
1220
- rollout: "Use the project release process after required gates pass.",
1221
- rollback: "Revert the related commit or disable the changed behavior.",
1222
- review: analyzePullRequestReview({ task, reviews, evidence, gates, locks }),
1223
- };
1224
- }
1225
- export async function generatePlaywrightTestPlan(taskId, root = process.cwd()) {
1226
- const workspace = await loadWorkspace(root);
1227
- const task = workspace.tasks.find((candidate) => candidate.id === taskId);
1228
- if (!task) {
1229
- throw new Error(`unknown task: ${taskId}`);
1230
- }
1231
- const criteria = task.acceptanceCriteria && task.acceptanceCriteria.length > 0
1232
- ? task.acceptanceCriteria
1233
- : [task.goal ?? task.title];
1234
- return {
1235
- taskId: task.id,
1236
- title: task.title,
1237
- targetUser: "end user",
1238
- scenarios: criteria.map((criterion, index) => ({
1239
- name: `Scenario ${index + 1}: ${criterion}`,
1240
- source: criterion,
1241
- pageObject: `${toPascalCase(task.id)}Page`,
1242
- selectors: [
1243
- "Use role, label, and test-id selectors before CSS selectors",
1244
- "Keep selectors in page object methods",
1245
- ],
1246
- assertions: [
1247
- `Verify user-visible outcome: ${criterion}`,
1248
- "Verify no blocking error state is shown",
1249
- ],
1250
- evidence: ["screenshot", "trace-on-failure"],
1251
- })),
1252
- fixtures: ["authenticated user when the flow requires identity"],
1253
- notes: [
1254
- "Prefer mobile-first viewport coverage before desktop expansion",
1255
- "Attach trace, screenshot, or video when failures occur",
1256
- ],
1257
- };
1258
- }
1259
1036
  export async function getWorkflowConfig(root = process.cwd()) {
1260
1037
  return readJson(resolveWorkflowPath(root, FILES.config), {});
1261
1038
  }
@@ -1489,224 +1266,6 @@ async function createBudgetFallbackProposal(taskId, budget, root) {
1489
1266
  artifact,
1490
1267
  };
1491
1268
  }
1492
- async function getApprovalRecord(id, root) {
1493
- const artifact = approvalArtifactForId(id);
1494
- if (!(await exists(path.join(root, artifact)))) {
1495
- throw new Error(`unknown approval: ${id}`);
1496
- }
1497
- const events = await readEvents(root);
1498
- return approvalRecordForArtifact(artifact, events);
1499
- }
1500
- async function findStoredApprovalForProposal(proposal, root) {
1501
- if (!proposal.artifact) {
1502
- return undefined;
1503
- }
1504
- const events = await readEvents(root);
1505
- return approvalRecordForArtifact(proposal.artifact, events);
1506
- }
1507
- async function recordApprovalDecision(input, status, root) {
1508
- const current = await getApprovalRecord(input.id, root);
1509
- const requested = approvalEventsForArtifact(await readEvents(root), current.artifact).find((event) => event.type === "BUDGET_ESCALATION_REQUESTED");
1510
- await appendEvent(root, removeUndefined({
1511
- type: status === "approved"
1512
- ? "BUDGET_ESCALATION_APPROVED"
1513
- : "BUDGET_ESCALATION_REJECTED",
1514
- taskId: current.taskId,
1515
- actor: "parent",
1516
- summary: `Budget escalation ${status}: ${input.id}`,
1517
- artifacts: [current.artifact],
1518
- metadata: {
1519
- approvalId: input.id,
1520
- proposal: requested?.metadata.proposal,
1521
- approver: input.approver,
1522
- rationale: input.rationale,
1523
- },
1524
- }));
1525
- return getApprovalRecord(input.id, root);
1526
- }
1527
- function approvalRecordForArtifact(artifact, events) {
1528
- const id = approvalIdForArtifact(artifact);
1529
- const related = approvalEventsForArtifact(events, artifact);
1530
- const requested = related.find((event) => event.type === "BUDGET_ESCALATION_REQUESTED");
1531
- const decision = [...related]
1532
- .reverse()
1533
- .find((event) => ["BUDGET_ESCALATION_APPROVED", "BUDGET_ESCALATION_REJECTED"].includes(event.type));
1534
- const status = decisionStatus(decision);
1535
- return removeUndefined({
1536
- id,
1537
- taskId: String(requested?.taskId ?? decision?.taskId ?? "") || undefined,
1538
- artifact,
1539
- status,
1540
- summary: requested?.summary ?? `Approval proposal ${id}`,
1541
- requestedAt: requested?.timestamp,
1542
- decidedAt: decision?.timestamp,
1543
- approver: typeof decision?.metadata.approver === "string"
1544
- ? decision.metadata.approver
1545
- : undefined,
1546
- rationale: typeof decision?.metadata.rationale === "string"
1547
- ? decision.metadata.rationale
1548
- : undefined,
1549
- });
1550
- }
1551
- function gateApprovalRecords(events) {
1552
- return events
1553
- .filter((event) => event.type === "GATE_APPROVED")
1554
- .map((event) => {
1555
- const runId = typeof event.metadata.runId === "string" ? event.metadata.runId : "run";
1556
- const gateId = typeof event.metadata.gateId === "string"
1557
- ? event.metadata.gateId
1558
- : "gate";
1559
- return removeUndefined({
1560
- id: `gate-${runId}-${gateId}`.replace(/[^a-zA-Z0-9._-]/g, "-"),
1561
- taskId: event.taskId ?? undefined,
1562
- artifact: event.artifacts?.[0] ?? "events.jsonl",
1563
- status: "approved",
1564
- summary: event.summary,
1565
- requestedAt: event.timestamp,
1566
- decidedAt: typeof event.metadata.approvedAt === "string"
1567
- ? event.metadata.approvedAt
1568
- : event.timestamp,
1569
- approver: typeof event.metadata.approver === "string"
1570
- ? event.metadata.approver
1571
- : undefined,
1572
- rationale: typeof event.metadata.rationale === "string"
1573
- ? event.metadata.rationale
1574
- : undefined,
1575
- });
1576
- });
1577
- }
1578
- function gateIdForPhase(run, phaseIndex) {
1579
- const phase = run.phases[phaseIndex]?.phase;
1580
- const sequenceIndex = AUTONOMOUS_PHASE_SEQUENCE.findIndex((candidate) => candidate.phase === phase);
1581
- const next = AUTONOMOUS_PHASE_SEQUENCE[sequenceIndex + 1]?.phase ?? "end";
1582
- return `${phase}->${next}`;
1583
- }
1584
- function approvalEventsForArtifact(events, artifact) {
1585
- return events.filter((event) => event.artifacts?.includes(artifact));
1586
- }
1587
- function decisionStatus(decision) {
1588
- if (decision?.type === "BUDGET_ESCALATION_APPROVED") {
1589
- return "approved";
1590
- }
1591
- if (decision?.type === "BUDGET_ESCALATION_REJECTED") {
1592
- return "rejected";
1593
- }
1594
- return "pending";
1595
- }
1596
- function approvalArtifactForId(id) {
1597
- if (!/^[a-zA-Z0-9._-]+$/.test(id)) {
1598
- throw new Error(`invalid approval id: ${id}`);
1599
- }
1600
- return path.join(".agent-workflow", "approvals", `${id}.md`);
1601
- }
1602
- function approvalIdForArtifact(artifact) {
1603
- return path.basename(artifact, ".md");
1604
- }
1605
- export async function addPlaywrightEvidence(input, root = process.cwd()) {
1606
- return addEvidence(removeUndefined({
1607
- task: input.task,
1608
- role: "qa",
1609
- type: input.kind,
1610
- summary: input.summary,
1611
- path: input.path,
1612
- command: input.runId,
1613
- }), root);
1614
- }
1615
- function aggregateUsage(key, records) {
1616
- const inputTokens = records.reduce((sum, record) => sum + record.inputTokens, 0);
1617
- const outputTokens = records.reduce((sum, record) => sum + record.outputTokens, 0);
1618
- const estimatedCostUsd = records.reduce((sum, record) => sum + record.estimatedCostUsd, 0);
1619
- return {
1620
- key,
1621
- requests: records.length,
1622
- inputTokens,
1623
- outputTokens,
1624
- totalTokens: inputTokens + outputTokens,
1625
- estimatedCostUsd,
1626
- };
1627
- }
1628
- function aggregateUsageBy(records, keyFor) {
1629
- const groups = new Map();
1630
- for (const record of records) {
1631
- const key = keyFor(record);
1632
- groups.set(key, [...(groups.get(key) ?? []), record]);
1633
- }
1634
- return [...groups.entries()].map(([key, group]) => aggregateUsage(key, group));
1635
- }
1636
- function budgetViolations(scope, usage, budget) {
1637
- return [
1638
- budgetViolation(scope, "requests", usage.requests, budget.maxRequests),
1639
- budgetViolation(scope, "inputTokens", usage.inputTokens, budget.maxInputTokens),
1640
- budgetViolation(scope, "outputTokens", usage.outputTokens, budget.maxOutputTokens),
1641
- budgetViolation(scope, "totalTokens", usage.totalTokens, budget.maxTotalTokens),
1642
- budgetViolation(scope, "estimatedCostUsd", usage.estimatedCostUsd, budget.maxEstimatedCostUsd),
1643
- ].filter((violation) => Boolean(violation));
1644
- }
1645
- function budgetViolation(scope, metric, actual, limit) {
1646
- if (limit === undefined || actual <= limit) {
1647
- return undefined;
1648
- }
1649
- return {
1650
- scope,
1651
- metric,
1652
- actual,
1653
- limit,
1654
- };
1655
- }
1656
- function projectUsageCost(usage, ownerRole, estimatedCostUsd) {
1657
- const byRole = [...usage.byRole];
1658
- const roleIndex = byRole.findIndex((entry) => entry.key === ownerRole);
1659
- if (roleIndex >= 0) {
1660
- const roleUsage = byRole[roleIndex] ?? emptyUsageBreakdown(ownerRole);
1661
- byRole[roleIndex] = addEstimatedCost(roleUsage, estimatedCostUsd);
1662
- }
1663
- else {
1664
- byRole.push(addEstimatedCost(emptyUsageBreakdown(ownerRole), estimatedCostUsd));
1665
- }
1666
- return {
1667
- ...usage,
1668
- totals: addEstimatedCost(usage.totals, estimatedCostUsd),
1669
- byRole,
1670
- };
1671
- }
1672
- function addEstimatedCost(usage, estimatedCostUsd) {
1673
- return {
1674
- ...usage,
1675
- estimatedCostUsd: usage.estimatedCostUsd + estimatedCostUsd,
1676
- };
1677
- }
1678
- function emptyUsageBreakdown(key) {
1679
- return {
1680
- key,
1681
- requests: 0,
1682
- inputTokens: 0,
1683
- outputTokens: 0,
1684
- totalTokens: 0,
1685
- estimatedCostUsd: 0,
1686
- };
1687
- }
1688
- function budgetEstimateWarningsForBudgets(taskId, ownerRole, usage, budgets) {
1689
- const warnings = [];
1690
- pushCostWarning(warnings, "defaults", usage.totals, budgets?.defaults);
1691
- pushCostWarning(warnings, `task:${taskId}`, usage.totals, budgets?.byTask?.[taskId]);
1692
- pushCostWarning(warnings, `role:${ownerRole}`, usage.byRole.find((roleUsage) => roleUsage.key === ownerRole) ??
1693
- emptyUsageBreakdown(ownerRole), budgets?.byRole?.[ownerRole]);
1694
- return warnings;
1695
- }
1696
- function pushCostWarning(warnings, scope, usage, budget) {
1697
- const thresholdPercent = 80;
1698
- const limit = budget?.maxEstimatedCostUsd;
1699
- if (limit !== undefined &&
1700
- usage.estimatedCostUsd >= limit * (thresholdPercent / 100)) {
1701
- warnings.push({
1702
- scope,
1703
- metric: "estimatedCostUsd",
1704
- actual: usage.estimatedCostUsd,
1705
- limit,
1706
- thresholdPercent,
1707
- });
1708
- }
1709
- }
1710
1269
  async function writeBudgetEscalationProposal(root, taskId, proposal) {
1711
1270
  const content = [
1712
1271
  `# Budget Escalation Proposal: ${taskId}`,
@@ -1777,14 +1336,4 @@ function flowRequirementLines(requirements) {
1777
1336
  function createLockId() {
1778
1337
  return `lock-${Date.now()}-${randomUUID()}`;
1779
1338
  }
1780
- function removeUndefined(value) {
1781
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
1782
- }
1783
- function toPascalCase(value) {
1784
- return value
1785
- .split(/[^a-zA-Z0-9]+/)
1786
- .filter(Boolean)
1787
- .map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
1788
- .join("");
1789
- }
1790
1339
  //# sourceMappingURL=workflow-services.js.map