@jterrats/open-orchestra 0.4.2-beta.2 → 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/README.md +29 -5
- package/dist/advisory-artifacts.d.ts +6 -0
- package/dist/advisory-artifacts.js +37 -1
- package/dist/advisory-artifacts.js.map +1 -1
- package/dist/assets/web-console.js +297 -4
- package/dist/cli.js +13 -118
- package/dist/cli.js.map +1 -1
- package/dist/command-manifest.d.ts +3 -0
- package/dist/command-manifest.js +132 -42
- 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 +6 -42
- package/dist/commands.js +204 -1372
- package/dist/commands.js.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- 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/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 +2 -5
- package/dist/runtime-commands.js.map +1 -1
- package/dist/setup-agents-import.js +1 -3
- package/dist/setup-agents-import.js.map +1 -1
- 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/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 +12 -833
- 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 +23 -45
- 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 +5 -17
- package/dist/workflow-services.js +26 -479
- 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/workspace.d.ts +18 -1
- package/dist/workspace.js +66 -4
- package/dist/workspace.js.map +1 -1
- 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 +15 -14
- package/docs/runtime-llm-flow.md +29 -28
- package/package.json +3 -2
|
@@ -1,28 +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";
|
|
21
17
|
import { applyContextBudget, DEFAULT_CONTEXT_TOKEN_BUDGET, } from "./context-budget.js";
|
|
22
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";
|
|
23
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";
|
|
24
25
|
export async function getWorkflowStatus(root = process.cwd()) {
|
|
25
26
|
const workspace = await loadWorkspace(root);
|
|
27
|
+
const config = await readJson(resolveWorkflowPath(root, FILES.config), {});
|
|
26
28
|
const counts = Object.create(null);
|
|
27
29
|
const blocked = [];
|
|
28
30
|
for (const task of workspace.tasks) {
|
|
@@ -36,6 +38,7 @@ export async function getWorkflowStatus(root = process.cwd()) {
|
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
return {
|
|
41
|
+
...(config.mode ? { mode: config.mode } : {}),
|
|
39
42
|
tasks: {
|
|
40
43
|
total: workspace.tasks.length,
|
|
41
44
|
byStatus: counts,
|
|
@@ -488,17 +491,7 @@ export async function checkReadiness(taskId, root = process.cwd()) {
|
|
|
488
491
|
return { task, report };
|
|
489
492
|
}
|
|
490
493
|
export async function evaluateWorkflowGate(gateId, taskId, root = process.cwd()) {
|
|
491
|
-
const
|
|
492
|
-
const task = workspace.tasks.find((candidate) => candidate.id === taskId);
|
|
493
|
-
if (!task) {
|
|
494
|
-
throw new Error(`unknown task: ${taskId}`);
|
|
495
|
-
}
|
|
496
|
-
const gate = getWorkflowGate(gateId);
|
|
497
|
-
const result = gate.evaluate({
|
|
498
|
-
task,
|
|
499
|
-
events: await readEvents(root),
|
|
500
|
-
locks: workspace.locks,
|
|
501
|
-
});
|
|
494
|
+
const result = await previewWorkflowGate(gateId, taskId, root);
|
|
502
495
|
await appendEvent(root, {
|
|
503
496
|
type: result.passed ? "GATE_PASSED" : "GATE_BLOCKED",
|
|
504
497
|
taskId,
|
|
@@ -513,6 +506,20 @@ export async function evaluateWorkflowGate(gateId, taskId, root = process.cwd())
|
|
|
513
506
|
});
|
|
514
507
|
return result;
|
|
515
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
|
+
}
|
|
516
523
|
export async function createHandoff(input, root = process.cwd()) {
|
|
517
524
|
const workspace = await loadWorkspace(root);
|
|
518
525
|
if (!workspace.roleIds.has(input.from) || !workspace.roleIds.has(input.to)) {
|
|
@@ -623,6 +630,7 @@ export async function getTaskContext(taskId, root = process.cwd(), options = {})
|
|
|
623
630
|
const memory = await queryMemory({
|
|
624
631
|
taskId,
|
|
625
632
|
hook: "before_implementation",
|
|
633
|
+
role: task.ownerRole,
|
|
626
634
|
root,
|
|
627
635
|
});
|
|
628
636
|
await recordMemoryEvent(root, memory, "MEMORY_QUERIED");
|
|
@@ -957,128 +965,6 @@ function pathsConflict(taskPath, lockPath) {
|
|
|
957
965
|
function normalizePath(value) {
|
|
958
966
|
return value.replaceAll("\\", "/").replace(/\/+$/, "");
|
|
959
967
|
}
|
|
960
|
-
export async function listApprovals(taskId, root = process.cwd()) {
|
|
961
|
-
const approvalDir = resolveWorkflowPath(root, "approvals");
|
|
962
|
-
const fileNames = (await exists(approvalDir))
|
|
963
|
-
? (await readdir(approvalDir)).filter((fileName) => fileName.endsWith(".md"))
|
|
964
|
-
: [];
|
|
965
|
-
const events = await readEvents(root);
|
|
966
|
-
const records = fileNames.map((fileName) => approvalRecordForArtifact(path.join(".agent-workflow", "approvals", fileName), events));
|
|
967
|
-
records.push(...gateApprovalRecords(events));
|
|
968
|
-
const filtered = taskId
|
|
969
|
-
? records.filter((record) => record.taskId === taskId)
|
|
970
|
-
: records;
|
|
971
|
-
return filtered.sort((left, right) => left.id.localeCompare(right.id));
|
|
972
|
-
}
|
|
973
|
-
export async function approveWorkflowGate(input, root = process.cwd()) {
|
|
974
|
-
const run = await readAutonomousRun(root, input.runId);
|
|
975
|
-
if (!run) {
|
|
976
|
-
throw new Error(`workflow run not found: ${input.runId}`);
|
|
977
|
-
}
|
|
978
|
-
const phaseIndex = run.phases.findLastIndex((phase) => phase.status === "gate_paused");
|
|
979
|
-
if (phaseIndex < 0) {
|
|
980
|
-
throw new Error(`workflow run ${input.runId} has no paused gate`);
|
|
981
|
-
}
|
|
982
|
-
const phase = run.phases[phaseIndex];
|
|
983
|
-
const gateId = phase.gateId ?? gateIdForPhase(run, phaseIndex);
|
|
984
|
-
if (input.gateId !== gateId) {
|
|
985
|
-
throw new Error(`wrong gate id: ${input.gateId}. Expected gate: ${gateId}`);
|
|
986
|
-
}
|
|
987
|
-
if (phase.approvedBy && phase.approvedAt) {
|
|
988
|
-
return {
|
|
989
|
-
run,
|
|
990
|
-
gateId,
|
|
991
|
-
approver: phase.approvedBy,
|
|
992
|
-
approvedAt: phase.approvedAt,
|
|
993
|
-
alreadyApproved: true,
|
|
994
|
-
};
|
|
995
|
-
}
|
|
996
|
-
const approvedAt = new Date().toISOString();
|
|
997
|
-
const approvedPhase = {
|
|
998
|
-
...phase,
|
|
999
|
-
gateId,
|
|
1000
|
-
approvedBy: input.approver,
|
|
1001
|
-
approvedAt,
|
|
1002
|
-
approvalRationale: input.rationale,
|
|
1003
|
-
};
|
|
1004
|
-
const updatedRun = {
|
|
1005
|
-
...run,
|
|
1006
|
-
phases: run.phases.map((candidate, index) => index === phaseIndex ? approvedPhase : candidate),
|
|
1007
|
-
updatedAt: approvedAt,
|
|
1008
|
-
};
|
|
1009
|
-
await persistRun(root, updatedRun);
|
|
1010
|
-
await appendEvent(root, removeUndefined({
|
|
1011
|
-
type: "GATE_APPROVED",
|
|
1012
|
-
taskId: run.taskId,
|
|
1013
|
-
actor: "parent",
|
|
1014
|
-
summary: `Gate approved: ${gateId}`,
|
|
1015
|
-
artifacts: phase.reviewArtifact ? [phase.reviewArtifact] : undefined,
|
|
1016
|
-
metadata: {
|
|
1017
|
-
runId: run.id,
|
|
1018
|
-
gateId,
|
|
1019
|
-
approver: input.approver,
|
|
1020
|
-
rationale: input.rationale,
|
|
1021
|
-
approvedAt,
|
|
1022
|
-
},
|
|
1023
|
-
}));
|
|
1024
|
-
return {
|
|
1025
|
-
run: updatedRun,
|
|
1026
|
-
gateId,
|
|
1027
|
-
approver: input.approver,
|
|
1028
|
-
approvedAt,
|
|
1029
|
-
alreadyApproved: false,
|
|
1030
|
-
};
|
|
1031
|
-
}
|
|
1032
|
-
export async function showApproval(id, root = process.cwd()) {
|
|
1033
|
-
const record = await getApprovalRecord(id, root);
|
|
1034
|
-
const content = await readFile(path.join(root, record.artifact), "utf8");
|
|
1035
|
-
return { ...record, content };
|
|
1036
|
-
}
|
|
1037
|
-
export async function approveApproval(input, root = process.cwd()) {
|
|
1038
|
-
return recordApprovalDecision(input, "approved", root);
|
|
1039
|
-
}
|
|
1040
|
-
export async function rejectApproval(input, root = process.cwd()) {
|
|
1041
|
-
return recordApprovalDecision(input, "rejected", root);
|
|
1042
|
-
}
|
|
1043
|
-
export async function recordReview(input, root = process.cwd(), notificationTransports = {}) {
|
|
1044
|
-
const workspace = await loadWorkspace(root);
|
|
1045
|
-
const reviewInput = removeUndefined({
|
|
1046
|
-
...input,
|
|
1047
|
-
role: normalizeReviewRole(input.role, workspace.roleIds) ?? input.role,
|
|
1048
|
-
});
|
|
1049
|
-
validateReviewInput(reviewInput, workspace.roleIds);
|
|
1050
|
-
const fileName = `${reviewInput.task}-${reviewInput.role}-review.md`;
|
|
1051
|
-
const content = [
|
|
1052
|
-
`# Review ${reviewInput.task}: ${reviewInput.role}`,
|
|
1053
|
-
"",
|
|
1054
|
-
`- Result: ${reviewInput.result}`,
|
|
1055
|
-
`- Severity: ${reviewInput.severity}`,
|
|
1056
|
-
`- Findings: ${reviewInput.findings}`,
|
|
1057
|
-
`- Recommendation: ${reviewInput.recommendation}`,
|
|
1058
|
-
"",
|
|
1059
|
-
].join("\n");
|
|
1060
|
-
const artifact = await writeArtifact(root, "reviews", fileName, content);
|
|
1061
|
-
await appendEvent(root, {
|
|
1062
|
-
type: "REVIEW_RECORDED",
|
|
1063
|
-
taskId: reviewInput.task,
|
|
1064
|
-
actor: reviewInput.role,
|
|
1065
|
-
summary: `Review recorded: ${reviewInput.result}`,
|
|
1066
|
-
artifacts: [artifact],
|
|
1067
|
-
metadata: { result: reviewInput.result, severity: reviewInput.severity },
|
|
1068
|
-
});
|
|
1069
|
-
if (reviewInput.result === "block" && reviewInput.severity === "critical") {
|
|
1070
|
-
await notifyWorkflowLifecycle({
|
|
1071
|
-
root,
|
|
1072
|
-
taskId: reviewInput.task,
|
|
1073
|
-
kind: "critical_blocking_review",
|
|
1074
|
-
actor: reviewInput.role,
|
|
1075
|
-
detail: reviewInput.findings,
|
|
1076
|
-
artifact,
|
|
1077
|
-
idempotencyKey: `critical_blocking_review:${reviewInput.task}:${artifact}`,
|
|
1078
|
-
}, notificationTransports);
|
|
1079
|
-
}
|
|
1080
|
-
return { artifact, content };
|
|
1081
|
-
}
|
|
1082
968
|
function riskReviewSteps(impactAreas) {
|
|
1083
969
|
const roleByImpact = {
|
|
1084
970
|
security: "security",
|
|
@@ -1147,117 +1033,6 @@ async function writeTaskGraphBatchArtifact(root, batch) {
|
|
|
1147
1033
|
"",
|
|
1148
1034
|
].join("\n"));
|
|
1149
1035
|
}
|
|
1150
|
-
export async function listReviews(taskId, root = process.cwd()) {
|
|
1151
|
-
return filterEvents("REVIEW_RECORDED", taskId, root);
|
|
1152
|
-
}
|
|
1153
|
-
export async function addEvidence(input, root = process.cwd()) {
|
|
1154
|
-
const evidenceInput = removeUndefined({
|
|
1155
|
-
...input,
|
|
1156
|
-
type: normalizeEvidenceType(input.type) ?? input.type,
|
|
1157
|
-
});
|
|
1158
|
-
const workspace = await loadWorkspace(root);
|
|
1159
|
-
validateEvidenceInput(evidenceInput, workspace.roleIds);
|
|
1160
|
-
if (typeof evidenceInput.path === "string" &&
|
|
1161
|
-
!(await exists(path.resolve(root, evidenceInput.path)))) {
|
|
1162
|
-
throw new Error(`evidence path does not exist: ${evidenceInput.path}`);
|
|
1163
|
-
}
|
|
1164
|
-
const fileName = `${evidenceInput.task}-${Date.now()}-${randomUUID()}-${evidenceInput.type}.md`;
|
|
1165
|
-
const content = [
|
|
1166
|
-
`# Evidence ${evidenceInput.task}: ${evidenceInput.type}`,
|
|
1167
|
-
"",
|
|
1168
|
-
`- Role: ${evidenceInput.role}`,
|
|
1169
|
-
`- Summary: ${evidenceInput.summary}`,
|
|
1170
|
-
`- Path: ${evidenceInput.path ?? "not applicable"}`,
|
|
1171
|
-
`- Command: ${evidenceInput.command ?? "not applicable"}`,
|
|
1172
|
-
`- Exit code: ${evidenceInput.exitCode ?? "not applicable"}`,
|
|
1173
|
-
"",
|
|
1174
|
-
].join("\n");
|
|
1175
|
-
const artifact = await writeArtifact(root, "evidence", fileName, content);
|
|
1176
|
-
await appendEvent(root, {
|
|
1177
|
-
type: "EVIDENCE_ADDED",
|
|
1178
|
-
taskId: evidenceInput.task,
|
|
1179
|
-
actor: evidenceInput.role,
|
|
1180
|
-
summary: evidenceInput.summary,
|
|
1181
|
-
artifacts: [artifact],
|
|
1182
|
-
metadata: { type: evidenceInput.type },
|
|
1183
|
-
});
|
|
1184
|
-
return { artifact, content };
|
|
1185
|
-
}
|
|
1186
|
-
export async function listEvidence(taskId, root = process.cwd()) {
|
|
1187
|
-
return filterEvents("EVIDENCE_ADDED", taskId, root);
|
|
1188
|
-
}
|
|
1189
|
-
export async function getWorkflowSummary(root = process.cwd()) {
|
|
1190
|
-
const workspace = await loadWorkspace(root);
|
|
1191
|
-
const events = await readEvents(root);
|
|
1192
|
-
return {
|
|
1193
|
-
tasks: workspace.tasks,
|
|
1194
|
-
locks: workspace.locks,
|
|
1195
|
-
reviews: events.filter((event) => event.type === "REVIEW_RECORDED"),
|
|
1196
|
-
evidence: events.filter((event) => event.type === "EVIDENCE_ADDED"),
|
|
1197
|
-
blockers: workspace.tasks.filter((task) => task.status === "blocked"),
|
|
1198
|
-
eventCount: events.length,
|
|
1199
|
-
};
|
|
1200
|
-
}
|
|
1201
|
-
export async function generatePullRequestSummary(taskId, root = process.cwd()) {
|
|
1202
|
-
const workspace = await loadWorkspace(root);
|
|
1203
|
-
const task = workspace.tasks.find((candidate) => candidate.id === taskId);
|
|
1204
|
-
if (!task) {
|
|
1205
|
-
throw new Error(`unknown task: ${taskId}`);
|
|
1206
|
-
}
|
|
1207
|
-
const events = await readEvents(root);
|
|
1208
|
-
const taskEvents = events.filter((event) => event.taskId === taskId);
|
|
1209
|
-
const handoffs = taskEvents.filter((event) => event.type === "HANDOFF_READY");
|
|
1210
|
-
const reviews = taskEvents.filter((event) => event.type === "REVIEW_RECORDED");
|
|
1211
|
-
const evidence = taskEvents.filter((event) => event.type === "EVIDENCE_ADDED");
|
|
1212
|
-
const gates = taskEvents.filter((event) => event.type === "GATE_PASSED" || event.type === "GATE_BLOCKED");
|
|
1213
|
-
const locks = workspace.locks.filter((lock) => lock.taskId === taskId);
|
|
1214
|
-
return {
|
|
1215
|
-
task,
|
|
1216
|
-
handoffs,
|
|
1217
|
-
reviews,
|
|
1218
|
-
evidence,
|
|
1219
|
-
gates,
|
|
1220
|
-
locks,
|
|
1221
|
-
risks: task.risks ?? [],
|
|
1222
|
-
rollout: "Use the project release process after required gates pass.",
|
|
1223
|
-
rollback: "Revert the related commit or disable the changed behavior.",
|
|
1224
|
-
review: analyzePullRequestReview({ task, reviews, evidence, gates, locks }),
|
|
1225
|
-
};
|
|
1226
|
-
}
|
|
1227
|
-
export async function generatePlaywrightTestPlan(taskId, root = process.cwd()) {
|
|
1228
|
-
const workspace = await loadWorkspace(root);
|
|
1229
|
-
const task = workspace.tasks.find((candidate) => candidate.id === taskId);
|
|
1230
|
-
if (!task) {
|
|
1231
|
-
throw new Error(`unknown task: ${taskId}`);
|
|
1232
|
-
}
|
|
1233
|
-
const criteria = task.acceptanceCriteria && task.acceptanceCriteria.length > 0
|
|
1234
|
-
? task.acceptanceCriteria
|
|
1235
|
-
: [task.goal ?? task.title];
|
|
1236
|
-
return {
|
|
1237
|
-
taskId: task.id,
|
|
1238
|
-
title: task.title,
|
|
1239
|
-
targetUser: "end user",
|
|
1240
|
-
scenarios: criteria.map((criterion, index) => ({
|
|
1241
|
-
name: `Scenario ${index + 1}: ${criterion}`,
|
|
1242
|
-
source: criterion,
|
|
1243
|
-
pageObject: `${toPascalCase(task.id)}Page`,
|
|
1244
|
-
selectors: [
|
|
1245
|
-
"Use role, label, and test-id selectors before CSS selectors",
|
|
1246
|
-
"Keep selectors in page object methods",
|
|
1247
|
-
],
|
|
1248
|
-
assertions: [
|
|
1249
|
-
`Verify user-visible outcome: ${criterion}`,
|
|
1250
|
-
"Verify no blocking error state is shown",
|
|
1251
|
-
],
|
|
1252
|
-
evidence: ["screenshot", "trace-on-failure"],
|
|
1253
|
-
})),
|
|
1254
|
-
fixtures: ["authenticated user when the flow requires identity"],
|
|
1255
|
-
notes: [
|
|
1256
|
-
"Prefer mobile-first viewport coverage before desktop expansion",
|
|
1257
|
-
"Attach trace, screenshot, or video when failures occur",
|
|
1258
|
-
],
|
|
1259
|
-
};
|
|
1260
|
-
}
|
|
1261
1036
|
export async function getWorkflowConfig(root = process.cwd()) {
|
|
1262
1037
|
return readJson(resolveWorkflowPath(root, FILES.config), {});
|
|
1263
1038
|
}
|
|
@@ -1491,224 +1266,6 @@ async function createBudgetFallbackProposal(taskId, budget, root) {
|
|
|
1491
1266
|
artifact,
|
|
1492
1267
|
};
|
|
1493
1268
|
}
|
|
1494
|
-
async function getApprovalRecord(id, root) {
|
|
1495
|
-
const artifact = approvalArtifactForId(id);
|
|
1496
|
-
if (!(await exists(path.join(root, artifact)))) {
|
|
1497
|
-
throw new Error(`unknown approval: ${id}`);
|
|
1498
|
-
}
|
|
1499
|
-
const events = await readEvents(root);
|
|
1500
|
-
return approvalRecordForArtifact(artifact, events);
|
|
1501
|
-
}
|
|
1502
|
-
async function findStoredApprovalForProposal(proposal, root) {
|
|
1503
|
-
if (!proposal.artifact) {
|
|
1504
|
-
return undefined;
|
|
1505
|
-
}
|
|
1506
|
-
const events = await readEvents(root);
|
|
1507
|
-
return approvalRecordForArtifact(proposal.artifact, events);
|
|
1508
|
-
}
|
|
1509
|
-
async function recordApprovalDecision(input, status, root) {
|
|
1510
|
-
const current = await getApprovalRecord(input.id, root);
|
|
1511
|
-
const requested = approvalEventsForArtifact(await readEvents(root), current.artifact).find((event) => event.type === "BUDGET_ESCALATION_REQUESTED");
|
|
1512
|
-
await appendEvent(root, removeUndefined({
|
|
1513
|
-
type: status === "approved"
|
|
1514
|
-
? "BUDGET_ESCALATION_APPROVED"
|
|
1515
|
-
: "BUDGET_ESCALATION_REJECTED",
|
|
1516
|
-
taskId: current.taskId,
|
|
1517
|
-
actor: "parent",
|
|
1518
|
-
summary: `Budget escalation ${status}: ${input.id}`,
|
|
1519
|
-
artifacts: [current.artifact],
|
|
1520
|
-
metadata: {
|
|
1521
|
-
approvalId: input.id,
|
|
1522
|
-
proposal: requested?.metadata.proposal,
|
|
1523
|
-
approver: input.approver,
|
|
1524
|
-
rationale: input.rationale,
|
|
1525
|
-
},
|
|
1526
|
-
}));
|
|
1527
|
-
return getApprovalRecord(input.id, root);
|
|
1528
|
-
}
|
|
1529
|
-
function approvalRecordForArtifact(artifact, events) {
|
|
1530
|
-
const id = approvalIdForArtifact(artifact);
|
|
1531
|
-
const related = approvalEventsForArtifact(events, artifact);
|
|
1532
|
-
const requested = related.find((event) => event.type === "BUDGET_ESCALATION_REQUESTED");
|
|
1533
|
-
const decision = [...related]
|
|
1534
|
-
.reverse()
|
|
1535
|
-
.find((event) => ["BUDGET_ESCALATION_APPROVED", "BUDGET_ESCALATION_REJECTED"].includes(event.type));
|
|
1536
|
-
const status = decisionStatus(decision);
|
|
1537
|
-
return removeUndefined({
|
|
1538
|
-
id,
|
|
1539
|
-
taskId: String(requested?.taskId ?? decision?.taskId ?? "") || undefined,
|
|
1540
|
-
artifact,
|
|
1541
|
-
status,
|
|
1542
|
-
summary: requested?.summary ?? `Approval proposal ${id}`,
|
|
1543
|
-
requestedAt: requested?.timestamp,
|
|
1544
|
-
decidedAt: decision?.timestamp,
|
|
1545
|
-
approver: typeof decision?.metadata.approver === "string"
|
|
1546
|
-
? decision.metadata.approver
|
|
1547
|
-
: undefined,
|
|
1548
|
-
rationale: typeof decision?.metadata.rationale === "string"
|
|
1549
|
-
? decision.metadata.rationale
|
|
1550
|
-
: undefined,
|
|
1551
|
-
});
|
|
1552
|
-
}
|
|
1553
|
-
function gateApprovalRecords(events) {
|
|
1554
|
-
return events
|
|
1555
|
-
.filter((event) => event.type === "GATE_APPROVED")
|
|
1556
|
-
.map((event) => {
|
|
1557
|
-
const runId = typeof event.metadata.runId === "string" ? event.metadata.runId : "run";
|
|
1558
|
-
const gateId = typeof event.metadata.gateId === "string"
|
|
1559
|
-
? event.metadata.gateId
|
|
1560
|
-
: "gate";
|
|
1561
|
-
return removeUndefined({
|
|
1562
|
-
id: `gate-${runId}-${gateId}`.replace(/[^a-zA-Z0-9._-]/g, "-"),
|
|
1563
|
-
taskId: event.taskId ?? undefined,
|
|
1564
|
-
artifact: event.artifacts?.[0] ?? "events.jsonl",
|
|
1565
|
-
status: "approved",
|
|
1566
|
-
summary: event.summary,
|
|
1567
|
-
requestedAt: event.timestamp,
|
|
1568
|
-
decidedAt: typeof event.metadata.approvedAt === "string"
|
|
1569
|
-
? event.metadata.approvedAt
|
|
1570
|
-
: event.timestamp,
|
|
1571
|
-
approver: typeof event.metadata.approver === "string"
|
|
1572
|
-
? event.metadata.approver
|
|
1573
|
-
: undefined,
|
|
1574
|
-
rationale: typeof event.metadata.rationale === "string"
|
|
1575
|
-
? event.metadata.rationale
|
|
1576
|
-
: undefined,
|
|
1577
|
-
});
|
|
1578
|
-
});
|
|
1579
|
-
}
|
|
1580
|
-
function gateIdForPhase(run, phaseIndex) {
|
|
1581
|
-
const phase = run.phases[phaseIndex]?.phase;
|
|
1582
|
-
const sequenceIndex = AUTONOMOUS_PHASE_SEQUENCE.findIndex((candidate) => candidate.phase === phase);
|
|
1583
|
-
const next = AUTONOMOUS_PHASE_SEQUENCE[sequenceIndex + 1]?.phase ?? "end";
|
|
1584
|
-
return `${phase}->${next}`;
|
|
1585
|
-
}
|
|
1586
|
-
function approvalEventsForArtifact(events, artifact) {
|
|
1587
|
-
return events.filter((event) => event.artifacts?.includes(artifact));
|
|
1588
|
-
}
|
|
1589
|
-
function decisionStatus(decision) {
|
|
1590
|
-
if (decision?.type === "BUDGET_ESCALATION_APPROVED") {
|
|
1591
|
-
return "approved";
|
|
1592
|
-
}
|
|
1593
|
-
if (decision?.type === "BUDGET_ESCALATION_REJECTED") {
|
|
1594
|
-
return "rejected";
|
|
1595
|
-
}
|
|
1596
|
-
return "pending";
|
|
1597
|
-
}
|
|
1598
|
-
function approvalArtifactForId(id) {
|
|
1599
|
-
if (!/^[a-zA-Z0-9._-]+$/.test(id)) {
|
|
1600
|
-
throw new Error(`invalid approval id: ${id}`);
|
|
1601
|
-
}
|
|
1602
|
-
return path.join(".agent-workflow", "approvals", `${id}.md`);
|
|
1603
|
-
}
|
|
1604
|
-
function approvalIdForArtifact(artifact) {
|
|
1605
|
-
return path.basename(artifact, ".md");
|
|
1606
|
-
}
|
|
1607
|
-
export async function addPlaywrightEvidence(input, root = process.cwd()) {
|
|
1608
|
-
return addEvidence(removeUndefined({
|
|
1609
|
-
task: input.task,
|
|
1610
|
-
role: "qa",
|
|
1611
|
-
type: input.kind,
|
|
1612
|
-
summary: input.summary,
|
|
1613
|
-
path: input.path,
|
|
1614
|
-
command: input.runId,
|
|
1615
|
-
}), root);
|
|
1616
|
-
}
|
|
1617
|
-
function aggregateUsage(key, records) {
|
|
1618
|
-
const inputTokens = records.reduce((sum, record) => sum + record.inputTokens, 0);
|
|
1619
|
-
const outputTokens = records.reduce((sum, record) => sum + record.outputTokens, 0);
|
|
1620
|
-
const estimatedCostUsd = records.reduce((sum, record) => sum + record.estimatedCostUsd, 0);
|
|
1621
|
-
return {
|
|
1622
|
-
key,
|
|
1623
|
-
requests: records.length,
|
|
1624
|
-
inputTokens,
|
|
1625
|
-
outputTokens,
|
|
1626
|
-
totalTokens: inputTokens + outputTokens,
|
|
1627
|
-
estimatedCostUsd,
|
|
1628
|
-
};
|
|
1629
|
-
}
|
|
1630
|
-
function aggregateUsageBy(records, keyFor) {
|
|
1631
|
-
const groups = new Map();
|
|
1632
|
-
for (const record of records) {
|
|
1633
|
-
const key = keyFor(record);
|
|
1634
|
-
groups.set(key, [...(groups.get(key) ?? []), record]);
|
|
1635
|
-
}
|
|
1636
|
-
return [...groups.entries()].map(([key, group]) => aggregateUsage(key, group));
|
|
1637
|
-
}
|
|
1638
|
-
function budgetViolations(scope, usage, budget) {
|
|
1639
|
-
return [
|
|
1640
|
-
budgetViolation(scope, "requests", usage.requests, budget.maxRequests),
|
|
1641
|
-
budgetViolation(scope, "inputTokens", usage.inputTokens, budget.maxInputTokens),
|
|
1642
|
-
budgetViolation(scope, "outputTokens", usage.outputTokens, budget.maxOutputTokens),
|
|
1643
|
-
budgetViolation(scope, "totalTokens", usage.totalTokens, budget.maxTotalTokens),
|
|
1644
|
-
budgetViolation(scope, "estimatedCostUsd", usage.estimatedCostUsd, budget.maxEstimatedCostUsd),
|
|
1645
|
-
].filter((violation) => Boolean(violation));
|
|
1646
|
-
}
|
|
1647
|
-
function budgetViolation(scope, metric, actual, limit) {
|
|
1648
|
-
if (limit === undefined || actual <= limit) {
|
|
1649
|
-
return undefined;
|
|
1650
|
-
}
|
|
1651
|
-
return {
|
|
1652
|
-
scope,
|
|
1653
|
-
metric,
|
|
1654
|
-
actual,
|
|
1655
|
-
limit,
|
|
1656
|
-
};
|
|
1657
|
-
}
|
|
1658
|
-
function projectUsageCost(usage, ownerRole, estimatedCostUsd) {
|
|
1659
|
-
const byRole = [...usage.byRole];
|
|
1660
|
-
const roleIndex = byRole.findIndex((entry) => entry.key === ownerRole);
|
|
1661
|
-
if (roleIndex >= 0) {
|
|
1662
|
-
const roleUsage = byRole[roleIndex] ?? emptyUsageBreakdown(ownerRole);
|
|
1663
|
-
byRole[roleIndex] = addEstimatedCost(roleUsage, estimatedCostUsd);
|
|
1664
|
-
}
|
|
1665
|
-
else {
|
|
1666
|
-
byRole.push(addEstimatedCost(emptyUsageBreakdown(ownerRole), estimatedCostUsd));
|
|
1667
|
-
}
|
|
1668
|
-
return {
|
|
1669
|
-
...usage,
|
|
1670
|
-
totals: addEstimatedCost(usage.totals, estimatedCostUsd),
|
|
1671
|
-
byRole,
|
|
1672
|
-
};
|
|
1673
|
-
}
|
|
1674
|
-
function addEstimatedCost(usage, estimatedCostUsd) {
|
|
1675
|
-
return {
|
|
1676
|
-
...usage,
|
|
1677
|
-
estimatedCostUsd: usage.estimatedCostUsd + estimatedCostUsd,
|
|
1678
|
-
};
|
|
1679
|
-
}
|
|
1680
|
-
function emptyUsageBreakdown(key) {
|
|
1681
|
-
return {
|
|
1682
|
-
key,
|
|
1683
|
-
requests: 0,
|
|
1684
|
-
inputTokens: 0,
|
|
1685
|
-
outputTokens: 0,
|
|
1686
|
-
totalTokens: 0,
|
|
1687
|
-
estimatedCostUsd: 0,
|
|
1688
|
-
};
|
|
1689
|
-
}
|
|
1690
|
-
function budgetEstimateWarningsForBudgets(taskId, ownerRole, usage, budgets) {
|
|
1691
|
-
const warnings = [];
|
|
1692
|
-
pushCostWarning(warnings, "defaults", usage.totals, budgets?.defaults);
|
|
1693
|
-
pushCostWarning(warnings, `task:${taskId}`, usage.totals, budgets?.byTask?.[taskId]);
|
|
1694
|
-
pushCostWarning(warnings, `role:${ownerRole}`, usage.byRole.find((roleUsage) => roleUsage.key === ownerRole) ??
|
|
1695
|
-
emptyUsageBreakdown(ownerRole), budgets?.byRole?.[ownerRole]);
|
|
1696
|
-
return warnings;
|
|
1697
|
-
}
|
|
1698
|
-
function pushCostWarning(warnings, scope, usage, budget) {
|
|
1699
|
-
const thresholdPercent = 80;
|
|
1700
|
-
const limit = budget?.maxEstimatedCostUsd;
|
|
1701
|
-
if (limit !== undefined &&
|
|
1702
|
-
usage.estimatedCostUsd >= limit * (thresholdPercent / 100)) {
|
|
1703
|
-
warnings.push({
|
|
1704
|
-
scope,
|
|
1705
|
-
metric: "estimatedCostUsd",
|
|
1706
|
-
actual: usage.estimatedCostUsd,
|
|
1707
|
-
limit,
|
|
1708
|
-
thresholdPercent,
|
|
1709
|
-
});
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
1269
|
async function writeBudgetEscalationProposal(root, taskId, proposal) {
|
|
1713
1270
|
const content = [
|
|
1714
1271
|
`# Budget Escalation Proposal: ${taskId}`,
|
|
@@ -1779,14 +1336,4 @@ function flowRequirementLines(requirements) {
|
|
|
1779
1336
|
function createLockId() {
|
|
1780
1337
|
return `lock-${Date.now()}-${randomUUID()}`;
|
|
1781
1338
|
}
|
|
1782
|
-
function removeUndefined(value) {
|
|
1783
|
-
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
1784
|
-
}
|
|
1785
|
-
function toPascalCase(value) {
|
|
1786
|
-
return value
|
|
1787
|
-
.split(/[^a-zA-Z0-9]+/)
|
|
1788
|
-
.filter(Boolean)
|
|
1789
|
-
.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
|
|
1790
|
-
.join("");
|
|
1791
|
-
}
|
|
1792
1339
|
//# sourceMappingURL=workflow-services.js.map
|