@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.
- package/AGENTS.md +5 -3
- package/README.md +29 -5
- package/dist/advisory-artifacts.d.ts +20 -0
- package/dist/advisory-artifacts.js +136 -0
- package/dist/advisory-artifacts.js.map +1 -0
- package/dist/assets/web-console.js +436 -4
- package/dist/cli.js +16 -117
- package/dist/cli.js.map +1 -1
- package/dist/command-manifest.d.ts +6 -0
- package/dist/command-manifest.js +141 -43
- package/dist/command-manifest.js.map +1 -1
- package/dist/command-utils.d.ts +5 -0
- package/dist/command-utils.js +23 -0
- package/dist/command-utils.js.map +1 -1
- package/dist/commands.d.ts +7 -42
- package/dist/commands.js +214 -1356
- package/dist/commands.js.map +1 -1
- package/dist/constants.js +3 -0
- package/dist/constants.js.map +1 -1
- package/dist/context-budget.d.ts +4 -0
- package/dist/context-budget.js +119 -0
- package/dist/context-budget.js.map +1 -0
- package/dist/delivery-commands.d.ts +10 -0
- package/dist/delivery-commands.js +152 -0
- package/dist/delivery-commands.js.map +1 -0
- package/dist/github.d.ts +50 -1
- package/dist/github.js +234 -0
- package/dist/github.js.map +1 -1
- package/dist/health-checks.d.ts +1 -0
- package/dist/health-checks.js +11 -1
- package/dist/health-checks.js.map +1 -1
- package/dist/health-commands.js +2 -0
- package/dist/health-commands.js.map +1 -1
- package/dist/mcp-oauth-proxy.d.ts +32 -0
- package/dist/mcp-oauth-proxy.js +120 -0
- package/dist/mcp-oauth-proxy.js.map +1 -1
- package/dist/memory.d.ts +2 -1
- package/dist/memory.js +71 -10
- package/dist/memory.js.map +1 -1
- package/dist/package-update-check.d.ts +5 -1
- package/dist/package-update-check.js +20 -8
- package/dist/package-update-check.js.map +1 -1
- package/dist/planning-commands.d.ts +14 -0
- package/dist/planning-commands.js +372 -0
- package/dist/planning-commands.js.map +1 -0
- package/dist/release-candidate.d.ts +2 -0
- package/dist/release-candidate.js +9 -14
- package/dist/release-candidate.js.map +1 -1
- package/dist/release-commands.d.ts +2 -0
- package/dist/release-commands.js +58 -6
- package/dist/release-commands.js.map +1 -1
- package/dist/release-readiness.d.ts +49 -0
- package/dist/release-readiness.js +172 -0
- package/dist/release-readiness.js.map +1 -0
- package/dist/runtime-commands.js +11 -4
- package/dist/runtime-commands.js.map +1 -1
- package/dist/runtime-execution-renderer.js +2 -0
- package/dist/runtime-execution-renderer.js.map +1 -1
- package/dist/runtime-execution.d.ts +4 -2
- package/dist/runtime-execution.js +11 -4
- package/dist/runtime-execution.js.map +1 -1
- package/dist/setup-agents-import.d.ts +42 -0
- package/dist/setup-agents-import.js +335 -0
- package/dist/setup-agents-import.js.map +1 -0
- package/dist/skills-catalog-service.d.ts +2 -0
- package/dist/skills-catalog-service.js +8 -0
- package/dist/skills-catalog-service.js.map +1 -0
- package/dist/skills-catalog.d.ts +2 -0
- package/dist/skills-catalog.js +389 -0
- package/dist/skills-catalog.js.map +1 -0
- package/dist/skills-commands.js +1 -11
- package/dist/skills-commands.js.map +1 -1
- package/dist/skills-events.d.ts +9 -0
- package/dist/skills-events.js +50 -0
- package/dist/skills-events.js.map +1 -0
- package/dist/skills-memory.d.ts +18 -0
- package/dist/skills-memory.js +127 -0
- package/dist/skills-memory.js.map +1 -0
- package/dist/skills-planning.d.ts +2 -0
- package/dist/skills-planning.js +87 -0
- package/dist/skills-planning.js.map +1 -0
- package/dist/skills-render.d.ts +14 -0
- package/dist/skills-render.js +83 -0
- package/dist/skills-render.js.map +1 -0
- package/dist/skills-validation.d.ts +2 -0
- package/dist/skills-validation.js +49 -0
- package/dist/skills-validation.js.map +1 -0
- package/dist/skills.d.ts +6 -42
- package/dist/skills.js +6 -773
- package/dist/skills.js.map +1 -1
- package/dist/task-graph-commands.d.ts +14 -0
- package/dist/task-graph-commands.js +367 -0
- package/dist/task-graph-commands.js.map +1 -0
- package/dist/tool-commands.js +8 -0
- package/dist/tool-commands.js.map +1 -1
- package/dist/types/context.d.ts +12 -0
- package/dist/types/context.js +2 -0
- package/dist/types/context.js.map +1 -0
- package/dist/types/metrics.d.ts +114 -0
- package/dist/types/metrics.js +2 -0
- package/dist/types/metrics.js.map +1 -0
- package/dist/types/model-config.d.ts +212 -0
- package/dist/types/model-config.js +2 -0
- package/dist/types/model-config.js.map +1 -0
- package/dist/types/runtime.d.ts +93 -0
- package/dist/types/runtime.js +2 -0
- package/dist/types/runtime.js.map +1 -0
- package/dist/types/skills.d.ts +147 -0
- package/dist/types/skills.js +2 -0
- package/dist/types/skills.js.map +1 -0
- package/dist/types/tasks.d.ts +171 -0
- package/dist/types/tasks.js +2 -0
- package/dist/types/tasks.js.map +1 -0
- package/dist/types/workflow-run.d.ts +79 -0
- package/dist/types/workflow-run.js +2 -0
- package/dist/types/workflow-run.js.map +1 -0
- package/dist/types.d.ts +13 -798
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/upgrade-commands.d.ts +2 -0
- package/dist/upgrade-commands.js +65 -0
- package/dist/upgrade-commands.js.map +1 -0
- package/dist/web-api-read-routes.d.ts +5 -0
- package/dist/web-api-read-routes.js +37 -0
- package/dist/web-api-read-routes.js.map +1 -0
- package/dist/web-api.d.ts +1 -3
- package/dist/web-api.js +145 -44
- package/dist/web-api.js.map +1 -1
- package/dist/web-console-sections.d.ts +2 -0
- package/dist/web-console-sections.js +7 -0
- package/dist/web-console-sections.js.map +1 -0
- package/dist/web-console.js +23 -3
- package/dist/web-console.js.map +1 -1
- package/dist/workflow-approval-service.d.ts +9 -0
- package/dist/workflow-approval-service.js +126 -0
- package/dist/workflow-approval-service.js.map +1 -0
- package/dist/workflow-approval-utils.d.ts +10 -0
- package/dist/workflow-approval-utils.js +82 -0
- package/dist/workflow-approval-utils.js.map +1 -0
- package/dist/workflow-budget-utils.d.ts +7 -0
- package/dist/workflow-budget-utils.js +96 -0
- package/dist/workflow-budget-utils.js.map +1 -0
- package/dist/workflow-evidence-service.d.ts +7 -0
- package/dist/workflow-evidence-service.js +100 -0
- package/dist/workflow-evidence-service.js.map +1 -0
- package/dist/workflow-run-commands.d.ts +8 -0
- package/dist/workflow-run-commands.js +479 -0
- package/dist/workflow-run-commands.js.map +1 -0
- package/dist/workflow-services.d.ts +8 -18
- package/dist/workflow-services.js +30 -481
- package/dist/workflow-services.js.map +1 -1
- package/dist/workflow-summary-service.d.ts +4 -0
- package/dist/workflow-summary-service.js +82 -0
- package/dist/workflow-summary-service.js.map +1 -0
- package/dist/workflow-templates.d.ts +1 -0
- package/dist/workflow-templates.js +1 -0
- package/dist/workflow-templates.js.map +1 -1
- package/dist/workspace.d.ts +18 -1
- package/dist/workspace.js +72 -4
- package/dist/workspace.js.map +1 -1
- package/docs/command-contracts.md +22 -0
- package/docs/mcp-oauth-proxy-evaluation.md +14 -0
- package/docs/orchestra-mvp.md +158 -114
- package/docs/package-naming.md +20 -0
- package/docs/persona-workflows.md +209 -0
- package/docs/runtime-adapters.md +19 -14
- package/docs/runtime-llm-flow.md +29 -28
- package/docs/setup-agents-bridge.md +61 -0
- package/docs/traceability-flow.md +89 -0
- 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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|