@jterrats/open-orchestra 0.5.5 → 1.0.1
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 +9 -8
- package/CLAUDE.md +13 -11
- package/README.md +78 -11
- package/dist/assets/web-console.js +203 -36
- package/dist/automation-evidence.d.ts +23 -0
- package/dist/automation-evidence.js +97 -0
- package/dist/automation-evidence.js.map +1 -0
- package/dist/autonomous-run-state.d.ts +4 -1
- package/dist/autonomous-run-state.js +8 -2
- package/dist/autonomous-run-state.js.map +1 -1
- package/dist/autonomous-run-store.d.ts +3 -1
- package/dist/autonomous-run-store.js +9 -3
- package/dist/autonomous-run-store.js.map +1 -1
- package/dist/autonomous-workflow-constants.js +5 -1
- package/dist/autonomous-workflow-constants.js.map +1 -1
- package/dist/benchmark.d.ts +4 -1
- package/dist/benchmark.js +140 -19
- package/dist/benchmark.js.map +1 -1
- package/dist/cli.js +88 -2
- package/dist/cli.js.map +1 -1
- package/dist/collaboration-flows.js +5 -19
- package/dist/collaboration-flows.js.map +1 -1
- package/dist/collection-utils.d.ts +3 -0
- package/dist/collection-utils.js +10 -0
- package/dist/collection-utils.js.map +1 -0
- package/dist/command-manifest.d.ts +12 -1
- package/dist/command-manifest.js +218 -10
- package/dist/command-manifest.js.map +1 -1
- package/dist/commands.d.ts +14 -6
- package/dist/commands.js +78 -28
- package/dist/commands.js.map +1 -1
- package/dist/config-migrations.d.ts +24 -0
- package/dist/config-migrations.js +102 -0
- package/dist/config-migrations.js.map +1 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +26 -0
- package/dist/constants.js.map +1 -1
- package/dist/cursor-canvas.d.ts +20 -0
- package/dist/cursor-canvas.js +119 -0
- package/dist/cursor-canvas.js.map +1 -0
- package/dist/dashboard-commands.d.ts +2 -0
- package/dist/dashboard-commands.js +14 -0
- package/dist/dashboard-commands.js.map +1 -0
- package/dist/defaults.d.ts +13 -0
- package/dist/defaults.js +13 -0
- package/dist/defaults.js.map +1 -1
- package/dist/delegation-decision.js +23 -8
- package/dist/delegation-decision.js.map +1 -1
- package/dist/delivery-commands.js +5 -0
- package/dist/delivery-commands.js.map +1 -1
- package/dist/delivery-dashboard-charts.d.ts +4 -0
- package/dist/delivery-dashboard-charts.js +156 -0
- package/dist/delivery-dashboard-charts.js.map +1 -0
- package/dist/delivery-dashboard-html.d.ts +2 -0
- package/dist/delivery-dashboard-html.js +115 -0
- package/dist/delivery-dashboard-html.js.map +1 -0
- package/dist/delivery-dashboard-types.d.ts +78 -0
- package/dist/delivery-dashboard-types.js +2 -0
- package/dist/delivery-dashboard-types.js.map +1 -0
- package/dist/delivery-dashboard.d.ts +8 -0
- package/dist/delivery-dashboard.js +124 -0
- package/dist/delivery-dashboard.js.map +1 -0
- package/dist/doc-sync.d.ts +25 -0
- package/dist/doc-sync.js +79 -0
- package/dist/doc-sync.js.map +1 -0
- package/dist/effort-classification.d.ts +7 -0
- package/dist/effort-classification.js +72 -0
- package/dist/effort-classification.js.map +1 -0
- package/dist/extension-commands.d.ts +3 -0
- package/dist/extension-commands.js +40 -0
- package/dist/extension-commands.js.map +1 -0
- package/dist/extensions.d.ts +22 -0
- package/dist/extensions.js +126 -0
- package/dist/extensions.js.map +1 -0
- package/dist/gemini-provider.d.ts +3 -6
- package/dist/gemini-provider.js +8 -17
- package/dist/gemini-provider.js.map +1 -1
- package/dist/github.d.ts +2 -0
- package/dist/github.js +15 -3
- package/dist/github.js.map +1 -1
- package/dist/health-checks.js +51 -0
- package/dist/health-checks.js.map +1 -1
- package/dist/lucid-story-map.d.ts +73 -0
- package/dist/lucid-story-map.js +112 -0
- package/dist/lucid-story-map.js.map +1 -0
- package/dist/mcp-integrations.d.ts +19 -0
- package/dist/mcp-integrations.js +58 -0
- package/dist/mcp-integrations.js.map +1 -0
- package/dist/mcp-tool-adapter.d.ts +21 -0
- package/dist/mcp-tool-adapter.js +56 -0
- package/dist/mcp-tool-adapter.js.map +1 -0
- package/dist/metrics-commands.js +47 -13
- package/dist/metrics-commands.js.map +1 -1
- package/dist/model-commands.d.ts +5 -0
- package/dist/model-commands.js +95 -1
- package/dist/model-commands.js.map +1 -1
- package/dist/model-providers.d.ts +5 -12
- package/dist/model-providers.js +30 -43
- package/dist/model-providers.js.map +1 -1
- package/dist/network-policy.d.ts +2 -0
- package/dist/network-policy.js +6 -0
- package/dist/network-policy.js.map +1 -0
- package/dist/ollama-provider.d.ts +3 -6
- package/dist/ollama-provider.js +7 -16
- package/dist/ollama-provider.js.map +1 -1
- package/dist/package-update-check.d.ts +19 -0
- package/dist/package-update-check.js +24 -0
- package/dist/package-update-check.js.map +1 -1
- package/dist/phase-executor.d.ts +1 -0
- package/dist/phase-executor.js +401 -9
- package/dist/phase-executor.js.map +1 -1
- package/dist/phase-playbooks.d.ts +18 -1
- package/dist/phase-playbooks.js +146 -2
- package/dist/phase-playbooks.js.map +1 -1
- package/dist/planning-commands.d.ts +1 -0
- package/dist/planning-commands.js +36 -36
- package/dist/planning-commands.js.map +1 -1
- package/dist/policy-commands.d.ts +2 -0
- package/dist/policy-commands.js +29 -0
- package/dist/policy-commands.js.map +1 -0
- package/dist/policy-defaults.d.ts +2 -0
- package/dist/policy-defaults.js +42 -0
- package/dist/policy-defaults.js.map +1 -0
- package/dist/policy.d.ts +20 -0
- package/dist/policy.js +155 -0
- package/dist/policy.js.map +1 -0
- package/dist/project-detection.js +9 -7
- package/dist/project-detection.js.map +1 -1
- package/dist/prompt-registry-update.d.ts +2 -0
- package/dist/prompt-registry-update.js +5 -1
- package/dist/prompt-registry-update.js.map +1 -1
- package/dist/prompt-registry-validation.d.ts +3 -0
- package/dist/prompt-registry-validation.js +61 -21
- package/dist/prompt-registry-validation.js.map +1 -1
- package/dist/provider-utils.d.ts +11 -0
- package/dist/provider-utils.js +14 -0
- package/dist/provider-utils.js.map +1 -1
- package/dist/qa-commands.d.ts +2 -0
- package/dist/qa-commands.js +18 -0
- package/dist/qa-commands.js.map +1 -0
- package/dist/qa-coverage.d.ts +24 -0
- package/dist/qa-coverage.js +189 -0
- package/dist/qa-coverage.js.map +1 -0
- package/dist/qa-readiness.d.ts +5 -0
- package/dist/qa-readiness.js +26 -0
- package/dist/qa-readiness.js.map +1 -0
- package/dist/refresh-generated.d.ts +32 -0
- package/dist/refresh-generated.js +180 -0
- package/dist/refresh-generated.js.map +1 -0
- package/dist/release-candidate.d.ts +9 -1
- package/dist/release-candidate.js +52 -1
- package/dist/release-candidate.js.map +1 -1
- package/dist/release-commands.js +161 -8
- package/dist/release-commands.js.map +1 -1
- package/dist/release-readiness.d.ts +33 -0
- package/dist/release-readiness.js +187 -3
- package/dist/release-readiness.js.map +1 -1
- package/dist/runtime-adapters.d.ts +2 -1
- package/dist/runtime-adapters.js +16 -0
- package/dist/runtime-adapters.js.map +1 -1
- package/dist/runtime-bootstrap.js +1 -1
- package/dist/runtime-bootstrap.js.map +1 -1
- package/dist/runtime-commands.d.ts +2 -0
- package/dist/runtime-commands.js +85 -3
- package/dist/runtime-commands.js.map +1 -1
- package/dist/runtime-execution-adapters.js +40 -0
- package/dist/runtime-execution-adapters.js.map +1 -1
- package/dist/runtime-execution-renderer.d.ts +3 -2
- package/dist/runtime-execution-renderer.js +46 -8
- package/dist/runtime-execution-renderer.js.map +1 -1
- package/dist/runtime-execution.d.ts +8 -2
- package/dist/runtime-execution.js +109 -11
- package/dist/runtime-execution.js.map +1 -1
- package/dist/runtime-guardrails.d.ts +26 -0
- package/dist/runtime-guardrails.js +168 -0
- package/dist/runtime-guardrails.js.map +1 -0
- package/dist/setup-agents-import.js +5 -3
- package/dist/setup-agents-import.js.map +1 -1
- package/dist/skills-catalog.js +1 -0
- package/dist/skills-catalog.js.map +1 -1
- package/dist/skills-commands.d.ts +5 -0
- package/dist/skills-commands.js +79 -2
- package/dist/skills-commands.js.map +1 -1
- package/dist/skills-memory.d.ts +36 -2
- package/dist/skills-memory.js +165 -6
- package/dist/skills-memory.js.map +1 -1
- package/dist/skills-planning.js +9 -22
- package/dist/skills-planning.js.map +1 -1
- package/dist/skills-render.js +2 -4
- package/dist/skills-render.js.map +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +1 -1
- package/dist/skills.js.map +1 -1
- package/dist/sprint-commands.js +2 -1
- package/dist/sprint-commands.js.map +1 -1
- package/dist/subagent-protocol.js +3 -5
- package/dist/subagent-protocol.js.map +1 -1
- package/dist/support-commands.d.ts +2 -0
- package/dist/support-commands.js +18 -0
- package/dist/support-commands.js.map +1 -0
- package/dist/support-diagnostics.d.ts +49 -0
- package/dist/support-diagnostics.js +86 -0
- package/dist/support-diagnostics.js.map +1 -0
- package/dist/task-graph-commands.js +6 -14
- package/dist/task-graph-commands.js.map +1 -1
- package/dist/task-text.d.ts +8 -0
- package/dist/task-text.js +18 -0
- package/dist/task-text.js.map +1 -0
- package/dist/telemetry-redaction.js +8 -1
- package/dist/telemetry-redaction.js.map +1 -1
- package/dist/tool-commands.d.ts +3 -0
- package/dist/tool-commands.js +62 -0
- package/dist/tool-commands.js.map +1 -1
- package/dist/tracker-adapters.d.ts +71 -0
- package/dist/tracker-adapters.js +186 -0
- package/dist/tracker-adapters.js.map +1 -0
- package/dist/tracker-commands.d.ts +2 -0
- package/dist/tracker-commands.js +119 -0
- package/dist/tracker-commands.js.map +1 -0
- package/dist/types/metrics.d.ts +25 -1
- package/dist/types/model-config.d.ts +51 -4
- package/dist/types/runtime.d.ts +83 -0
- package/dist/types/skills.d.ts +2 -0
- package/dist/types/tasks.d.ts +10 -0
- package/dist/types/workflow-run.d.ts +35 -0
- package/dist/types.d.ts +12 -4
- package/dist/types.js.map +1 -1
- package/dist/upgrade-commands.js +13 -4
- package/dist/upgrade-commands.js.map +1 -1
- package/dist/validation.js +2 -2
- package/dist/validation.js.map +1 -1
- package/dist/visual-validation.d.ts +81 -0
- package/dist/visual-validation.js +290 -0
- package/dist/visual-validation.js.map +1 -0
- package/dist/web-action-security.d.ts +11 -0
- package/dist/web-action-security.js +45 -0
- package/dist/web-action-security.js.map +1 -0
- package/dist/web-api-read-routes.js +115 -3
- package/dist/web-api-read-routes.js.map +1 -1
- package/dist/web-api.js +507 -5
- package/dist/web-api.js.map +1 -1
- package/dist/web-artifacts.d.ts +55 -0
- package/dist/web-artifacts.js +222 -0
- package/dist/web-artifacts.js.map +1 -0
- package/dist/web-console/assets/index-C9lx-V42.css +1 -0
- package/dist/web-console/assets/index-M3S0g1GK.js +11 -0
- package/dist/web-console/index.html +13 -0
- package/dist/web-console.js +9 -3
- package/dist/web-console.js.map +1 -1
- package/dist/web-recovery.d.ts +30 -0
- package/dist/web-recovery.js +163 -0
- package/dist/web-recovery.js.map +1 -0
- package/dist/web-workflow-progress.d.ts +41 -0
- package/dist/web-workflow-progress.js +114 -0
- package/dist/web-workflow-progress.js.map +1 -0
- package/dist/workflow-approval-service.d.ts +2 -1
- package/dist/workflow-approval-service.js +83 -4
- package/dist/workflow-approval-service.js.map +1 -1
- package/dist/workflow-approval-utils.js +13 -3
- package/dist/workflow-approval-utils.js.map +1 -1
- package/dist/workflow-event-query.d.ts +2 -0
- package/dist/workflow-event-query.js +6 -0
- package/dist/workflow-event-query.js.map +1 -0
- package/dist/workflow-evidence-service.js +18 -9
- package/dist/workflow-evidence-service.js.map +1 -1
- package/dist/workflow-gates.d.ts +2 -0
- package/dist/workflow-gates.js +103 -0
- package/dist/workflow-gates.js.map +1 -1
- package/dist/workflow-markdown.d.ts +6 -0
- package/dist/workflow-markdown.js +25 -0
- package/dist/workflow-markdown.js.map +1 -0
- package/dist/workflow-phase-planner.d.ts +19 -0
- package/dist/workflow-phase-planner.js +133 -0
- package/dist/workflow-phase-planner.js.map +1 -0
- package/dist/workflow-run-commands.d.ts +1 -0
- package/dist/workflow-run-commands.js +247 -20
- package/dist/workflow-run-commands.js.map +1 -1
- package/dist/workflow-services.d.ts +21 -12
- package/dist/workflow-services.js +376 -260
- package/dist/workflow-services.js.map +1 -1
- package/dist/workflow-task-service.d.ts +11 -0
- package/dist/workflow-task-service.js +242 -0
- package/dist/workflow-task-service.js.map +1 -0
- package/dist/workflow-templates.js +2 -14
- package/dist/workflow-templates.js.map +1 -1
- package/dist/workspace-validator.js +133 -5
- package/dist/workspace-validator.js.map +1 -1
- package/dist/workspace.js +10 -2
- package/dist/workspace.js.map +1 -1
- package/docs/adoption-guide.md +147 -0
- package/docs/autonomous-workflow.md +146 -28
- package/docs/benchmark.md +17 -9
- package/docs/command-contracts.md +18 -1
- package/docs/core-command-surface.md +62 -13
- package/docs/end-to-end-demo.md +1 -0
- package/docs/extension-contracts.md +83 -0
- package/docs/orchestra-mvp.md +86 -3
- package/docs/persona-workflows.md +32 -0
- package/docs/release-test-matrix.md +42 -0
- package/docs/runtime-adapters.md +113 -0
- package/docs/runtime-llm-flow.md +13 -0
- package/docs/setup-agents-applicability-review.md +173 -0
- package/docs/skill-loading-strategy.md +1 -0
- package/docs/source-of-truth-and-agent-learning.md +14 -0
- package/docs/traceability-flow.md +5 -1
- package/docs/tracker-adapter-contract.md +10 -1
- package/docs/web-console-qa.md +35 -0
- package/package.json +12 -6
- package/rules/development-engineering.mdc +66 -0
- package/skills/doc-sync/SKILL.md +2 -0
package/dist/web-api.js
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import http from "node:http";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
4
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
|
-
import { listAutonomousRuns } from "./autonomous-workflow.js";
|
|
7
|
+
import { cancelRun, listAutonomousRuns } from "./autonomous-workflow.js";
|
|
8
8
|
import { parsePromotionTarget, parseSkillRenderTarget, removeUndefined, } from "./command-utils.js";
|
|
9
|
-
import { addEvidence, addTask, approveWorkflowGate, getTaskContext, listTasks, previewWorkflowGate, recordReview, updateTask, } from "./workflow-services.js";
|
|
9
|
+
import { addEvidence, addTask, approveWorkflowGate, getWorkflowConfig, getTaskContext, listTasks, previewWorkflowGate, recordWorkflowGateDecision, recordReview, setRoleModelProvider, updateTask, } from "./workflow-services.js";
|
|
10
10
|
import { listWebEvidence, readWorkspaceArtifact } from "./web-evidence.js";
|
|
11
|
+
import { attachArtifactEvidence, previewWebArtifact } from "./web-artifacts.js";
|
|
11
12
|
import { attachWebPlaywrightEvidence, getWebPlaywrightPlan, } from "./web-playwright.js";
|
|
13
|
+
import { authorizeWebAction } from "./web-action-security.js";
|
|
12
14
|
import { getWebRoleActivation } from "./web-roles.js";
|
|
15
|
+
import { generateQaAutomationCoverage } from "./qa-coverage.js";
|
|
16
|
+
import { repairRecoveryItem } from "./web-recovery.js";
|
|
13
17
|
import { renderWebConsoleHtml } from "./web-console.js";
|
|
14
18
|
import { renderSubagentProtocol } from "./subagent-protocol.js";
|
|
15
19
|
import { recommendCollaborationFlow } from "./collaboration-flows.js";
|
|
16
20
|
import { renderWorkflowTemplates, selectWorkflowTemplates, } from "./workflow-templates.js";
|
|
21
|
+
import { listWorkflowEvents } from "./web-workflow-progress.js";
|
|
17
22
|
import { loadPhasePlaybook } from "./phase-playbooks.js";
|
|
18
23
|
import { decideTaskDelegation } from "./delegation-decision.js";
|
|
19
24
|
import { addAgentLesson, planSkillsForTask, promoteAgentLessons, renderSkills, } from "./skills.js";
|
|
@@ -25,6 +30,8 @@ const DEFAULT_WEB_PORT = 3717;
|
|
|
25
30
|
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
26
31
|
const CHART_JS_PATH = join(PACKAGE_ROOT, "node_modules/chart.js/dist/chart.umd.js");
|
|
27
32
|
const WEB_CONSOLE_BUNDLE_PATH = join(PACKAGE_ROOT, "dist/assets/web-console.js");
|
|
33
|
+
const REACT_WEB_CONSOLE_DIR = join(PACKAGE_ROOT, "dist/web-console");
|
|
34
|
+
const REACT_WEB_CONSOLE_INDEX_PATH = join(REACT_WEB_CONSOLE_DIR, "index.html");
|
|
28
35
|
const ORCHESTRA_CLI_PATH = join(PACKAGE_ROOT, "bin/orchestra.js");
|
|
29
36
|
const execFileAsync = promisify(execFile);
|
|
30
37
|
const routes = createWebReadRoutes();
|
|
@@ -52,6 +59,10 @@ const requestRoutes = {
|
|
|
52
59
|
status: 200,
|
|
53
60
|
body: await getWebPlaywrightPlan(root, requiredParam(url, "task")),
|
|
54
61
|
}),
|
|
62
|
+
"/api/qa/coverage": async ({ root, url }) => ({
|
|
63
|
+
status: 200,
|
|
64
|
+
body: await generateQaAutomationCoverage(requiredParam(url, "task"), root),
|
|
65
|
+
}),
|
|
55
66
|
"/api/delegation/decide": async ({ root, url }) => ({
|
|
56
67
|
status: 200,
|
|
57
68
|
body: await decideTaskDelegation(requiredParam(url, "task"), root, {
|
|
@@ -98,6 +109,15 @@ const requestRoutes = {
|
|
|
98
109
|
}
|
|
99
110
|
return { status: 200, body: selectWorkflowTemplates(task) };
|
|
100
111
|
},
|
|
112
|
+
"/api/workflow/events": async ({ root, url }) => ({
|
|
113
|
+
status: 200,
|
|
114
|
+
body: await listWorkflowEvents(removeUndefined({
|
|
115
|
+
root,
|
|
116
|
+
task: stringParam(url, "task"),
|
|
117
|
+
run: stringParam(url, "run"),
|
|
118
|
+
limit: positiveIntegerParam(url, "limit") ?? 20,
|
|
119
|
+
})),
|
|
120
|
+
}),
|
|
101
121
|
"/api/workflow/render": async ({ root, url }) => {
|
|
102
122
|
const taskId = requiredParam(url, "task");
|
|
103
123
|
const task = (await listTasks(root)).find((candidate) => candidate.id === taskId);
|
|
@@ -117,6 +137,10 @@ const requestRoutes = {
|
|
|
117
137
|
}),
|
|
118
138
|
};
|
|
119
139
|
},
|
|
140
|
+
"/api/release/readiness": async ({ root }) => ({
|
|
141
|
+
status: 200,
|
|
142
|
+
body: await releaseReadinessView(root),
|
|
143
|
+
}),
|
|
120
144
|
};
|
|
121
145
|
export async function handleWebApiRequest(pathname, context) {
|
|
122
146
|
const handler = routes[pathname];
|
|
@@ -163,11 +187,28 @@ export async function startWebApiServer(options = {}) {
|
|
|
163
187
|
writeJson(response, result.status, result.body);
|
|
164
188
|
return;
|
|
165
189
|
}
|
|
190
|
+
if (request.method === "POST" &&
|
|
191
|
+
url.pathname === "/api/artifacts/evidence") {
|
|
192
|
+
const result = await handleArtifactEvidencePost(request, root);
|
|
193
|
+
writeJson(response, result.status, result.body);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
166
196
|
if (request.method === "POST" && url.pathname === "/api/reviews/add") {
|
|
167
197
|
const result = await handleReviewAddPost(request, root);
|
|
168
198
|
writeJson(response, result.status, result.body);
|
|
169
199
|
return;
|
|
170
200
|
}
|
|
201
|
+
if (request.method === "POST" &&
|
|
202
|
+
url.pathname === "/api/provider/set-role") {
|
|
203
|
+
const result = await handleProviderSetRolePost(request, root);
|
|
204
|
+
writeJson(response, result.status, result.body);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (request.method === "POST" && url.pathname === "/api/recovery/repair") {
|
|
208
|
+
const result = await handleRecoveryRepairPost(request, root);
|
|
209
|
+
writeJson(response, result.status, result.body);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
171
212
|
if (request.method === "POST" && url.pathname === "/api/workflow/start") {
|
|
172
213
|
const result = await handleWorkflowStartPost(request, root);
|
|
173
214
|
writeJson(response, result.status, result.body);
|
|
@@ -178,12 +219,23 @@ export async function startWebApiServer(options = {}) {
|
|
|
178
219
|
writeJson(response, result.status, result.body);
|
|
179
220
|
return;
|
|
180
221
|
}
|
|
222
|
+
if (request.method === "POST" && url.pathname === "/api/workflow/cancel") {
|
|
223
|
+
const result = await handleWorkflowCancelPost(request, root);
|
|
224
|
+
writeJson(response, result.status, result.body);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
181
227
|
if (request.method === "POST" &&
|
|
182
228
|
url.pathname === "/api/workflow/gate-approve") {
|
|
183
229
|
const result = await handleWorkflowGateApprovePost(request, root);
|
|
184
230
|
writeJson(response, result.status, result.body);
|
|
185
231
|
return;
|
|
186
232
|
}
|
|
233
|
+
if (request.method === "POST" &&
|
|
234
|
+
url.pathname === "/api/workflow/gate-decision") {
|
|
235
|
+
const result = await handleWorkflowGateDecisionPost(request, root);
|
|
236
|
+
writeJson(response, result.status, result.body);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
187
239
|
if (request.method === "POST" && url.pathname === "/api/lessons/add") {
|
|
188
240
|
const result = await handleLessonAddPost(request, root);
|
|
189
241
|
writeJson(response, result.status, result.body);
|
|
@@ -202,7 +254,7 @@ export async function startWebApiServer(options = {}) {
|
|
|
202
254
|
return;
|
|
203
255
|
}
|
|
204
256
|
if (url.pathname === "/") {
|
|
205
|
-
|
|
257
|
+
await writeConsoleIndex(response);
|
|
206
258
|
return;
|
|
207
259
|
}
|
|
208
260
|
if (url.pathname === "/vendor/chart.umd.js") {
|
|
@@ -213,10 +265,22 @@ export async function startWebApiServer(options = {}) {
|
|
|
213
265
|
await writeJavaScript(response, WEB_CONSOLE_BUNDLE_PATH);
|
|
214
266
|
return;
|
|
215
267
|
}
|
|
268
|
+
if (url.pathname.startsWith("/assets/")) {
|
|
269
|
+
await writeReactConsoleAsset(response, url.pathname);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
216
272
|
if (url.pathname === "/api/artifacts") {
|
|
217
273
|
await writeArtifactResponse(response, root, requiredParam(url, "path"));
|
|
218
274
|
return;
|
|
219
275
|
}
|
|
276
|
+
if (url.pathname === "/api/artifacts/preview") {
|
|
277
|
+
const result = await safeRequestRoute(async ({ root: requestRoot, url: requestUrl }) => ({
|
|
278
|
+
status: 200,
|
|
279
|
+
body: await previewWebArtifact(requestRoot, requiredParam(requestUrl, "path")),
|
|
280
|
+
}), { root, url, request });
|
|
281
|
+
writeJson(response, result.status, result.body);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
220
284
|
const requestHandler = requestRoutes[url.pathname];
|
|
221
285
|
const result = requestHandler
|
|
222
286
|
? await safeRequestRoute(requestHandler, { root, url, request })
|
|
@@ -236,6 +300,13 @@ async function handleTaskAddPost(request, root) {
|
|
|
236
300
|
try {
|
|
237
301
|
const input = (await readJsonBody(request));
|
|
238
302
|
validateTaskAddInput(input);
|
|
303
|
+
await authorizeWebAction({
|
|
304
|
+
root,
|
|
305
|
+
taskId: input.id,
|
|
306
|
+
actor: "web-console",
|
|
307
|
+
action: "web task add",
|
|
308
|
+
paths: input.paths ?? [],
|
|
309
|
+
});
|
|
239
310
|
return { status: 200, body: await addTask(input, root) };
|
|
240
311
|
}
|
|
241
312
|
catch (error) {
|
|
@@ -248,6 +319,13 @@ async function handleTaskUpdatePost(request, root) {
|
|
|
248
319
|
if (!input.id) {
|
|
249
320
|
throw new Error("id is required");
|
|
250
321
|
}
|
|
322
|
+
await authorizeWebAction({
|
|
323
|
+
root,
|
|
324
|
+
taskId: input.id,
|
|
325
|
+
actor: "web-console",
|
|
326
|
+
action: "web task update",
|
|
327
|
+
paths: input.paths ?? [],
|
|
328
|
+
});
|
|
251
329
|
return {
|
|
252
330
|
status: 200,
|
|
253
331
|
body: await updateTask({ ...input, id: input.id }, root),
|
|
@@ -261,22 +339,114 @@ async function handleEvidenceAddPost(request, root) {
|
|
|
261
339
|
try {
|
|
262
340
|
const input = (await readJsonBody(request));
|
|
263
341
|
validateEvidencePostInput(input);
|
|
342
|
+
await authorizeWebAction(removeUndefined({
|
|
343
|
+
root,
|
|
344
|
+
taskId: input.task,
|
|
345
|
+
actor: input.role,
|
|
346
|
+
action: "web evidence add",
|
|
347
|
+
command: stringOrUndefined(input.command),
|
|
348
|
+
paths: stringArray(input.path),
|
|
349
|
+
}));
|
|
264
350
|
return { status: 200, body: await addEvidence(input, root) };
|
|
265
351
|
}
|
|
266
352
|
catch (error) {
|
|
267
353
|
return errorResult(error);
|
|
268
354
|
}
|
|
269
355
|
}
|
|
356
|
+
async function handleArtifactEvidencePost(request, root) {
|
|
357
|
+
try {
|
|
358
|
+
const input = (await readJsonBody(request));
|
|
359
|
+
validateArtifactEvidenceInput(input);
|
|
360
|
+
await authorizeWebAction({
|
|
361
|
+
root,
|
|
362
|
+
taskId: input.task,
|
|
363
|
+
actor: input.role,
|
|
364
|
+
action: "web artifact evidence attach",
|
|
365
|
+
paths: [input.path],
|
|
366
|
+
});
|
|
367
|
+
return {
|
|
368
|
+
status: 200,
|
|
369
|
+
body: await attachArtifactEvidence(root, removeUndefined({
|
|
370
|
+
task: input.task,
|
|
371
|
+
role: input.role,
|
|
372
|
+
type: input.type,
|
|
373
|
+
path: input.path,
|
|
374
|
+
summary: input.summary,
|
|
375
|
+
runId: input.runId,
|
|
376
|
+
})),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
return errorResult(error);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
270
383
|
async function handleReviewAddPost(request, root) {
|
|
271
384
|
try {
|
|
272
385
|
const input = (await readJsonBody(request));
|
|
273
386
|
validateReviewPostInput(input);
|
|
387
|
+
await authorizeWebAction({
|
|
388
|
+
root,
|
|
389
|
+
taskId: input.task,
|
|
390
|
+
actor: input.role,
|
|
391
|
+
action: "web review add",
|
|
392
|
+
});
|
|
274
393
|
return { status: 200, body: await recordReview(input, root) };
|
|
275
394
|
}
|
|
276
395
|
catch (error) {
|
|
277
396
|
return errorResult(error);
|
|
278
397
|
}
|
|
279
398
|
}
|
|
399
|
+
async function handleProviderSetRolePost(request, root) {
|
|
400
|
+
try {
|
|
401
|
+
const input = (await readJsonBody(request));
|
|
402
|
+
validateProviderSetRoleInput(input);
|
|
403
|
+
await authorizeWebAction({
|
|
404
|
+
root,
|
|
405
|
+
actor: "web-console",
|
|
406
|
+
action: "web provider role routing update",
|
|
407
|
+
command: `${input.role} ${input.provider}/${input.model}`,
|
|
408
|
+
});
|
|
409
|
+
const config = await getWorkflowConfig(root);
|
|
410
|
+
const current = config.providers?.byRole?.[input.role] ?? config.providers?.defaults;
|
|
411
|
+
const routing = {
|
|
412
|
+
provider: input.provider,
|
|
413
|
+
model: input.model,
|
|
414
|
+
fallbacks: input.fallbacks ?? current?.fallbacks ?? [],
|
|
415
|
+
maxTokens: input.maxTokens ?? current?.maxTokens ?? 8000,
|
|
416
|
+
maxCostUsd: input.maxCostUsd ?? current?.maxCostUsd ?? 1,
|
|
417
|
+
timeoutMs: input.timeoutMs ?? current?.timeoutMs ?? 120000,
|
|
418
|
+
retries: input.retries ?? current?.retries ?? 0,
|
|
419
|
+
requiredCapabilities: input.requiredCapabilities ?? current?.requiredCapabilities ?? [],
|
|
420
|
+
};
|
|
421
|
+
return {
|
|
422
|
+
status: 200,
|
|
423
|
+
body: await setRoleModelProvider(input.role, routing, root),
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
catch (error) {
|
|
427
|
+
return errorResult(error);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
async function handleRecoveryRepairPost(request, root) {
|
|
431
|
+
try {
|
|
432
|
+
const input = (await readJsonBody(request));
|
|
433
|
+
validateRecoveryRepairInput(input);
|
|
434
|
+
await authorizeWebAction({
|
|
435
|
+
root,
|
|
436
|
+
actor: "web-console",
|
|
437
|
+
action: `web recovery ${input.action}`,
|
|
438
|
+
command: input.target,
|
|
439
|
+
confirmed: input.confirmed,
|
|
440
|
+
});
|
|
441
|
+
return {
|
|
442
|
+
status: 200,
|
|
443
|
+
body: await repairRecoveryItem(input, root),
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
return errorResult(error);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
280
450
|
async function handleWorkflowStartPost(request, root) {
|
|
281
451
|
try {
|
|
282
452
|
const input = (await readJsonBody(request));
|
|
@@ -284,6 +454,12 @@ async function handleWorkflowStartPost(request, root) {
|
|
|
284
454
|
throw new Error("task is required");
|
|
285
455
|
}
|
|
286
456
|
const gates = parseWorkflowGates(input.gates);
|
|
457
|
+
await authorizeWebAction({
|
|
458
|
+
root,
|
|
459
|
+
taskId: input.task,
|
|
460
|
+
actor: "web-console",
|
|
461
|
+
action: "web workflow start",
|
|
462
|
+
});
|
|
287
463
|
const execution = await runOrchestraCli(root, [
|
|
288
464
|
"workflow",
|
|
289
465
|
"run",
|
|
@@ -310,6 +486,12 @@ async function handleWorkflowResumePost(request, root) {
|
|
|
310
486
|
if (!input.task || !input.run) {
|
|
311
487
|
throw new Error("task and run are required");
|
|
312
488
|
}
|
|
489
|
+
await authorizeWebAction({
|
|
490
|
+
root,
|
|
491
|
+
taskId: input.task,
|
|
492
|
+
actor: "web-console",
|
|
493
|
+
action: "web workflow resume",
|
|
494
|
+
});
|
|
313
495
|
const execution = await runOrchestraCli(root, [
|
|
314
496
|
"workflow",
|
|
315
497
|
"run",
|
|
@@ -330,12 +512,38 @@ async function handleWorkflowResumePost(request, root) {
|
|
|
330
512
|
return errorResult(error);
|
|
331
513
|
}
|
|
332
514
|
}
|
|
515
|
+
async function handleWorkflowCancelPost(request, root) {
|
|
516
|
+
try {
|
|
517
|
+
const input = (await readJsonBody(request));
|
|
518
|
+
if (!input.run) {
|
|
519
|
+
throw new Error("run is required");
|
|
520
|
+
}
|
|
521
|
+
await authorizeWebAction(removeUndefined({
|
|
522
|
+
root,
|
|
523
|
+
actor: "web-console",
|
|
524
|
+
action: "web workflow cancel",
|
|
525
|
+
command: input.reason,
|
|
526
|
+
}));
|
|
527
|
+
return {
|
|
528
|
+
status: 200,
|
|
529
|
+
body: await cancelRun(root, input.run, input.reason ?? "Canceled from web console"),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
catch (error) {
|
|
533
|
+
return errorResult(error);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
333
536
|
async function handleWorkflowGateApprovePost(request, root) {
|
|
334
537
|
try {
|
|
335
538
|
const input = (await readJsonBody(request));
|
|
336
539
|
if (!input.run || !input.gate || !input.approver || !input.rationale) {
|
|
337
540
|
throw new Error("run, gate, approver, and rationale are required");
|
|
338
541
|
}
|
|
542
|
+
await authorizeWebAction({
|
|
543
|
+
root,
|
|
544
|
+
actor: input.approver,
|
|
545
|
+
action: "web workflow gate approve",
|
|
546
|
+
});
|
|
339
547
|
return {
|
|
340
548
|
status: 200,
|
|
341
549
|
body: await approveWorkflowGate({
|
|
@@ -350,6 +558,39 @@ async function handleWorkflowGateApprovePost(request, root) {
|
|
|
350
558
|
return errorResult(error);
|
|
351
559
|
}
|
|
352
560
|
}
|
|
561
|
+
async function handleWorkflowGateDecisionPost(request, root) {
|
|
562
|
+
try {
|
|
563
|
+
const input = (await readJsonBody(request));
|
|
564
|
+
if (!input.run || !input.gate || !input.reviewer || !input.rationale) {
|
|
565
|
+
throw new Error("run, gate, reviewer, and rationale are required");
|
|
566
|
+
}
|
|
567
|
+
if (!isWorkflowGateDecisionResult(input.result)) {
|
|
568
|
+
throw new Error("result must be one of: approve, block, changes");
|
|
569
|
+
}
|
|
570
|
+
await authorizeWebAction({
|
|
571
|
+
root,
|
|
572
|
+
actor: input.reviewer,
|
|
573
|
+
action: `web workflow gate ${input.result}`,
|
|
574
|
+
command: input.rationale,
|
|
575
|
+
});
|
|
576
|
+
return {
|
|
577
|
+
status: 200,
|
|
578
|
+
body: await recordWorkflowGateDecision({
|
|
579
|
+
runId: input.run,
|
|
580
|
+
gateId: input.gate,
|
|
581
|
+
reviewer: input.reviewer,
|
|
582
|
+
rationale: input.rationale,
|
|
583
|
+
result: input.result,
|
|
584
|
+
}, root),
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
catch (error) {
|
|
588
|
+
return errorResult(error);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function isWorkflowGateDecisionResult(result) {
|
|
592
|
+
return result === "approve" || result === "block" || result === "changes";
|
|
593
|
+
}
|
|
353
594
|
export function getWebServerAddress(server) {
|
|
354
595
|
const address = server.address();
|
|
355
596
|
if (!address) {
|
|
@@ -376,6 +617,13 @@ function writeHtml(response, body) {
|
|
|
376
617
|
});
|
|
377
618
|
response.end(body);
|
|
378
619
|
}
|
|
620
|
+
async function writeConsoleIndex(response) {
|
|
621
|
+
if (await fileExists(REACT_WEB_CONSOLE_INDEX_PATH)) {
|
|
622
|
+
writeHtml(response, await readFile(REACT_WEB_CONSOLE_INDEX_PATH, "utf8"));
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
writeHtml(response, renderWebConsoleHtml());
|
|
626
|
+
}
|
|
379
627
|
async function writeJavaScript(response, filePath) {
|
|
380
628
|
const bundle = await readFile(filePath, "utf8");
|
|
381
629
|
response.writeHead(200, {
|
|
@@ -384,6 +632,52 @@ async function writeJavaScript(response, filePath) {
|
|
|
384
632
|
});
|
|
385
633
|
response.end(bundle);
|
|
386
634
|
}
|
|
635
|
+
async function writeReactConsoleAsset(response, pathname) {
|
|
636
|
+
const assetPath = resolve(REACT_WEB_CONSOLE_DIR, pathname.replace(/^\//, ""));
|
|
637
|
+
if (isOutsideDirectory(REACT_WEB_CONSOLE_DIR, assetPath)) {
|
|
638
|
+
writeJson(response, 400, {
|
|
639
|
+
error: "invalid_asset_path",
|
|
640
|
+
message: "asset path must stay inside the web console build directory",
|
|
641
|
+
});
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (!(await fileExists(assetPath))) {
|
|
645
|
+
writeJson(response, 404, {
|
|
646
|
+
error: "not_found",
|
|
647
|
+
message: `unknown asset: ${pathname}`,
|
|
648
|
+
});
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
const body = await readFile(assetPath);
|
|
652
|
+
response.writeHead(200, {
|
|
653
|
+
"content-type": contentTypeForPath(assetPath),
|
|
654
|
+
"cache-control": "no-store",
|
|
655
|
+
});
|
|
656
|
+
response.end(body);
|
|
657
|
+
}
|
|
658
|
+
function isOutsideDirectory(root, candidate) {
|
|
659
|
+
const pathFromRoot = relative(root, candidate);
|
|
660
|
+
return pathFromRoot.startsWith("..") || pathFromRoot === "";
|
|
661
|
+
}
|
|
662
|
+
async function fileExists(filePath) {
|
|
663
|
+
try {
|
|
664
|
+
return (await stat(filePath)).isFile();
|
|
665
|
+
}
|
|
666
|
+
catch {
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function contentTypeForPath(filePath) {
|
|
671
|
+
if (filePath.endsWith(".js"))
|
|
672
|
+
return "text/javascript; charset=utf-8";
|
|
673
|
+
if (filePath.endsWith(".css"))
|
|
674
|
+
return "text/css; charset=utf-8";
|
|
675
|
+
if (filePath.endsWith(".svg"))
|
|
676
|
+
return "image/svg+xml";
|
|
677
|
+
if (filePath.endsWith(".json"))
|
|
678
|
+
return "application/json; charset=utf-8";
|
|
679
|
+
return "application/octet-stream";
|
|
680
|
+
}
|
|
387
681
|
async function safeRequestRoute(handler, context) {
|
|
388
682
|
try {
|
|
389
683
|
return await handler(context);
|
|
@@ -396,6 +690,13 @@ async function handlePlaywrightEvidencePost(request, root) {
|
|
|
396
690
|
try {
|
|
397
691
|
const input = (await readJsonBody(request));
|
|
398
692
|
validatePlaywrightEvidenceInput(input);
|
|
693
|
+
await authorizeWebAction({
|
|
694
|
+
root,
|
|
695
|
+
taskId: input.task,
|
|
696
|
+
actor: "qa",
|
|
697
|
+
action: "web playwright evidence attach",
|
|
698
|
+
paths: [input.path],
|
|
699
|
+
});
|
|
399
700
|
return {
|
|
400
701
|
status: 200,
|
|
401
702
|
body: await attachWebPlaywrightEvidence(root, input),
|
|
@@ -434,6 +735,26 @@ function validateEvidencePostInput(input) {
|
|
|
434
735
|
throw new Error("task, role, type, and summary are required");
|
|
435
736
|
}
|
|
436
737
|
}
|
|
738
|
+
function validateArtifactEvidenceInput(input) {
|
|
739
|
+
if (!input.task ||
|
|
740
|
+
!input.role ||
|
|
741
|
+
!input.type ||
|
|
742
|
+
!input.path ||
|
|
743
|
+
!input.summary) {
|
|
744
|
+
throw new Error("task, role, type, path, and summary are required");
|
|
745
|
+
}
|
|
746
|
+
if (![
|
|
747
|
+
"command",
|
|
748
|
+
"file",
|
|
749
|
+
"screenshot",
|
|
750
|
+
"trace",
|
|
751
|
+
"video",
|
|
752
|
+
"log",
|
|
753
|
+
"report",
|
|
754
|
+
].includes(input.type)) {
|
|
755
|
+
throw new Error("invalid evidence type: " + input.type);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
437
758
|
function validateReviewPostInput(input) {
|
|
438
759
|
if (!input.task ||
|
|
439
760
|
!input.role ||
|
|
@@ -446,6 +767,46 @@ function validateReviewPostInput(input) {
|
|
|
446
767
|
input.severity = "low";
|
|
447
768
|
}
|
|
448
769
|
}
|
|
770
|
+
function validateProviderSetRoleInput(input) {
|
|
771
|
+
if (!input.role || !input.provider || !input.model) {
|
|
772
|
+
throw new Error("role, provider, and model are required");
|
|
773
|
+
}
|
|
774
|
+
if (input.fallbacks && !Array.isArray(input.fallbacks)) {
|
|
775
|
+
throw new Error("fallbacks must be an array");
|
|
776
|
+
}
|
|
777
|
+
if (input.requiredCapabilities &&
|
|
778
|
+
!Array.isArray(input.requiredCapabilities)) {
|
|
779
|
+
throw new Error("requiredCapabilities must be an array");
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function validateRecoveryRepairInput(input) {
|
|
783
|
+
if (input.action !== "release-lock" &&
|
|
784
|
+
input.action !== "remove-temp-file" &&
|
|
785
|
+
input.action !== "remove-file-lock") {
|
|
786
|
+
throw new Error("action must be one of: release-lock, remove-temp-file, remove-file-lock");
|
|
787
|
+
}
|
|
788
|
+
if (!input.target) {
|
|
789
|
+
throw new Error("target is required");
|
|
790
|
+
}
|
|
791
|
+
if (!input.confirmed) {
|
|
792
|
+
throw new Error("confirmed is required");
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
function stringOrUndefined(value) {
|
|
796
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
797
|
+
return value;
|
|
798
|
+
}
|
|
799
|
+
return undefined;
|
|
800
|
+
}
|
|
801
|
+
function stringArray(value) {
|
|
802
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
803
|
+
return [value];
|
|
804
|
+
}
|
|
805
|
+
if (Array.isArray(value)) {
|
|
806
|
+
return value.filter((item) => typeof item === "string");
|
|
807
|
+
}
|
|
808
|
+
return [];
|
|
809
|
+
}
|
|
449
810
|
function parseWorkflowGates(value) {
|
|
450
811
|
const gates = value ?? "phase";
|
|
451
812
|
if (!["none", "phase", "all"].includes(gates)) {
|
|
@@ -551,6 +912,14 @@ function stringParam(url, name) {
|
|
|
551
912
|
const value = url.searchParams.get(name);
|
|
552
913
|
return value && value.trim() !== "" ? value : undefined;
|
|
553
914
|
}
|
|
915
|
+
function positiveIntegerParam(url, name) {
|
|
916
|
+
const value = stringParam(url, name);
|
|
917
|
+
if (!value) {
|
|
918
|
+
return undefined;
|
|
919
|
+
}
|
|
920
|
+
const parsed = Number.parseInt(value, 10);
|
|
921
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
922
|
+
}
|
|
554
923
|
function webEvidenceFilters(url) {
|
|
555
924
|
const filters = {};
|
|
556
925
|
const task = stringParam(url, "task");
|
|
@@ -567,6 +936,139 @@ async function taskReleaseReadinessReport(root, taskId) {
|
|
|
567
936
|
const report = await generateReleaseReadinessReport(root);
|
|
568
937
|
return report.tasks.find((task) => task.taskId === taskId) ?? null;
|
|
569
938
|
}
|
|
939
|
+
async function releaseReadinessView(root) {
|
|
940
|
+
const [report, tasks, packageInfo, commits] = await Promise.all([
|
|
941
|
+
generateReleaseReadinessReport(root),
|
|
942
|
+
listTasks(root),
|
|
943
|
+
readPackageInfo(),
|
|
944
|
+
recentCommits(root),
|
|
945
|
+
]);
|
|
946
|
+
const closedTasks = tasks
|
|
947
|
+
.filter((task) => ["approved", "done"].includes(task.status))
|
|
948
|
+
.map((task) => ({
|
|
949
|
+
id: task.id,
|
|
950
|
+
title: task.title,
|
|
951
|
+
status: task.status,
|
|
952
|
+
ownerRole: task.ownerRole,
|
|
953
|
+
}));
|
|
954
|
+
const pendingTasks = tasks
|
|
955
|
+
.filter((task) => ["pending", "ready", "in_progress", "blocked", "review"].includes(task.status))
|
|
956
|
+
.map((task) => ({
|
|
957
|
+
id: task.id,
|
|
958
|
+
title: task.title,
|
|
959
|
+
status: task.status,
|
|
960
|
+
}));
|
|
961
|
+
const checks = releaseChecks(report, pendingTasks.length);
|
|
962
|
+
return {
|
|
963
|
+
version: String(packageInfo.version ?? "unknown"),
|
|
964
|
+
commits,
|
|
965
|
+
closedTasks,
|
|
966
|
+
pendingTasks,
|
|
967
|
+
checks,
|
|
968
|
+
criticalBlockers: checks
|
|
969
|
+
.filter((check) => check.blocking && !check.passed)
|
|
970
|
+
.map((check) => check.label),
|
|
971
|
+
acceptedRisks: report.acceptedRisks,
|
|
972
|
+
summary: renderReleaseSummary({
|
|
973
|
+
version: String(packageInfo.version ?? "unknown"),
|
|
974
|
+
report,
|
|
975
|
+
closedTaskCount: closedTasks.length,
|
|
976
|
+
pendingTaskCount: pendingTasks.length,
|
|
977
|
+
checks,
|
|
978
|
+
}),
|
|
979
|
+
report,
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
async function readPackageInfo() {
|
|
983
|
+
return JSON.parse(await readFile(join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
984
|
+
}
|
|
985
|
+
async function recentCommits(root) {
|
|
986
|
+
try {
|
|
987
|
+
const { stdout } = await execFileAsync("git", ["log", "-5", "--pretty=format:%h %s"], { cwd: root });
|
|
988
|
+
return stdout.split("\n").filter(Boolean);
|
|
989
|
+
}
|
|
990
|
+
catch {
|
|
991
|
+
return [];
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
function releaseChecks(report, pendingTaskCount) {
|
|
995
|
+
return [
|
|
996
|
+
{
|
|
997
|
+
id: "tests",
|
|
998
|
+
label: "Tests evidence",
|
|
999
|
+
passed: !report.knownGaps.some((gap) => /test|QA automation/i.test(gap)),
|
|
1000
|
+
blocking: true,
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
id: "e2e",
|
|
1004
|
+
label: "E2E evidence",
|
|
1005
|
+
passed: report.tasks.some((task) => [
|
|
1006
|
+
...task.qaAutomationCoverage.commands,
|
|
1007
|
+
...task.qaAutomationCoverage.pageObjects,
|
|
1008
|
+
].some((item) => /e2e|playwright/i.test(item))),
|
|
1009
|
+
blocking: false,
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
id: "docs",
|
|
1013
|
+
label: "Docs or changelog evidence",
|
|
1014
|
+
passed: !report.missingDocumentationEvidence,
|
|
1015
|
+
blocking: false,
|
|
1016
|
+
},
|
|
1017
|
+
{
|
|
1018
|
+
id: "site",
|
|
1019
|
+
label: "Site or public documentation evidence",
|
|
1020
|
+
passed: !report.missingDocumentationEvidence,
|
|
1021
|
+
blocking: false,
|
|
1022
|
+
},
|
|
1023
|
+
{
|
|
1024
|
+
id: "extension",
|
|
1025
|
+
label: "Extension compatibility evidence",
|
|
1026
|
+
passed: !report.knownGaps.some((gap) => /extension/i.test(gap)),
|
|
1027
|
+
blocking: false,
|
|
1028
|
+
},
|
|
1029
|
+
{
|
|
1030
|
+
id: "security",
|
|
1031
|
+
label: "Security evidence",
|
|
1032
|
+
passed: report.gaReadiness.criteria.some((criterion) => criterion.id === "security-evidence" && criterion.passed),
|
|
1033
|
+
blocking: true,
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
id: "pending-issues",
|
|
1037
|
+
label: "Pending local work",
|
|
1038
|
+
passed: pendingTaskCount === 0,
|
|
1039
|
+
blocking: false,
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
id: "critical-gates",
|
|
1043
|
+
label: "Critical release gates",
|
|
1044
|
+
passed: report.gaReadiness.blockers.length === 0 && report.passed,
|
|
1045
|
+
blocking: true,
|
|
1046
|
+
},
|
|
1047
|
+
];
|
|
1048
|
+
}
|
|
1049
|
+
function renderReleaseSummary({ version, report, closedTaskCount, pendingTaskCount, checks, }) {
|
|
1050
|
+
return [
|
|
1051
|
+
"# Release Readiness Summary",
|
|
1052
|
+
"",
|
|
1053
|
+
"- Version: " + version,
|
|
1054
|
+
"- Decision: " + report.gaReadiness.decision,
|
|
1055
|
+
"- Closed tasks: " + closedTaskCount,
|
|
1056
|
+
"- Pending local work: " + pendingTaskCount,
|
|
1057
|
+
"- Accepted risks: " + report.acceptedRisks.length,
|
|
1058
|
+
"",
|
|
1059
|
+
"## Checks",
|
|
1060
|
+
...checks.map((check) => "- " +
|
|
1061
|
+
(check.passed ? "PASS" : "BLOCKED") +
|
|
1062
|
+
" " +
|
|
1063
|
+
check.label +
|
|
1064
|
+
(check.blocking ? " (blocking)" : "")),
|
|
1065
|
+
"",
|
|
1066
|
+
"## Known Gaps",
|
|
1067
|
+
...(report.knownGaps.length > 0
|
|
1068
|
+
? report.knownGaps.slice(0, 20).map((gap) => "- " + gap)
|
|
1069
|
+
: ["- none"]),
|
|
1070
|
+
].join("\n");
|
|
1071
|
+
}
|
|
570
1072
|
async function writeArtifactResponse(response, root, artifactPath) {
|
|
571
1073
|
try {
|
|
572
1074
|
const artifact = await readWorkspaceArtifact(root, artifactPath);
|