@tea-agent/loop-agent 0.39.0-beta.5 → 0.39.0-beta.7
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/CHANGELOG.md +13 -1
- package/dist/build-stamp.json +3 -3
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +138 -36
- package/dist/executors/pi-executor.js +7 -0
- package/dist/executors/pi-playwright-cli-tool.js +23 -16
- package/dist/executors/shell-executor.js +26 -9
- package/dist/shared/dag-failure-category.js +150 -0
- package/dist/task/config-types.js +6 -0
- package/dist/task/frontend-project-capability.js +19 -0
- package/dist/task/source-references.js +22 -1
- package/dist/worker/console/chat/operation-card.js +8 -1
- package/dist/worker/console/chat/routes.js +5 -3
- package/dist/worker/console/inspect-split.js +18 -0
- package/dist/worker/console/operation-run-facts.js +19 -3
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-zsNyvGaH.js → abnfDiagram-N423BO3Z-bPer1waP.js} +1 -1
- package/dist/worker/console/static/assets/{arc-BDjZ5kE1.js → arc-BLdkwucW.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BkgaujnN.js → architectureDiagram-T3A2C74G-Bw4wo4I1.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CqxOJi0j.js → blockDiagram-VBNYF7ZC-DUUkc0gr.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-C2JHLdRi.js → c4Diagram-5PPSVZJV-BC3O0nob.js} +1 -1
- package/dist/worker/console/static/assets/channel-AbFtVnFm.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DF_YSvOS.js → chunk-2GRJ4B5K-Dd4g2nti.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D3Ort5Vo.js → chunk-2Q5K7J3B-s2EDm7-n.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-XfAfZeUC.js → chunk-5RXB4S5H-GqtHM2yQ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-SLBibhf5.js → chunk-5VM5RSS4-BzRFrN8j.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Dz29Ye_f.js → chunk-6Q2QTUOP-Bw9nFjCJ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-DB2G4H8V.js → chunk-GF5L2VYU-B4n-03Qm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CIoGkxdu.js → chunk-JWPE2WC7-Dt75ZdBD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CG78FGkn.js → chunk-KBJHAD2P-B_afMhHB.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-ZDHAvXPd.js → chunk-RYQCIY6F-Bd8Rqqg9.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BKkkC4VA.js → chunk-XXDRQBXY-9boOMji4.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-n1yf2Jcv.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-n1yf2Jcv.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-SposUZsO.js → cose-bilkent-JH36ORCC-BtCycfCc.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BSCtQpD7.js → cynefin-VYW2F7L2-eTOts9L1.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DgFHrw9l.js → cynefinDiagram-MW4NZA55-Cibd2mjg.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BVBasYbj.js → dagre-VZM6K2ZE-CDjDZtDV.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-6r_4rN_c.js → diagram-7IWD3JNH-Btexn-JJ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-JmuaB41P.js → diagram-B4RE2ZJO-Cyl8sdpM.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-xXvAY3sk.js → diagram-LBJQPF4R-Ev-83E1t.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D2B3lY2o.js → diagram-Q27KOJAE-41LqE0Mc.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-tK54tMEX.js → diagram-UB23O5K3-BC39T5R3.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CNtELzV-.js → ebnfDiagram-BXEA7PRR-DzcNRuri.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-C1xKMKcW.js → erDiagram-JOGREHBK-B5Frvvnz.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CZ7dlPMz.js → flowDiagram-UKHOOZJN-BapxROIM.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BqGb42Xe.js → ganttDiagram-PKOTCBZU-Cd4d4wQ0.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-D_BlXEtt.js → gitGraphDiagram-DS77QQ5N-CrL6buFW.js} +1 -1
- package/dist/worker/console/static/assets/index-DBh_x_Ek.js +406 -0
- package/dist/worker/console/static/assets/index-DwOGauxU.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Vce2vmIe.js → infoDiagram-6WML65LV-BrSiNxyd.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DDwVBAvI.js → ishikawaDiagram-WSZJBQD7-gvlvY-n9.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-y_Ew-wv4.js → journeyDiagram-NVQOT4AX-D-YaPzRQ.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CjXJFp1K.js → kanban-definition-27J2QSJJ-BsmDv9E_.js} +1 -1
- package/dist/worker/console/static/assets/{linear-1itMst1Q.js → linear-UWaoDN3h.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-C-D3ZRUa.js → mermaid.core-BlvpGVG8.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BiYywmZc.js → mindmap-definition-FAOFIHXS-CUCIP5CP.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-l2fydNWA.js → pegDiagram-VL7TDLO6-CB1aQUAK.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-Dz0hNbYW.js → pieDiagram-7S7Q4E2Y-BwehHxhf.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-BujI_mLZ.js → quadrantDiagram-CIZ2JOQS-CFz30APB.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-pWe84beb.js → railroadDiagram-AXF67PYL-BwNmw1i6.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-ByYu2frg.js → requirementDiagram-LRYGKXZP-jg-tPXlI.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CoPnilFP.js → sankeyDiagram-W5VNT64P-BlyReDKl.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-1Md2LnS0.js → sequenceDiagram-SI44F4Z6-DBUzW1f3.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DqqqAoSH.js → sizeCapture-X5ZJPWSS-BYcI2RAr.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-C4sGkf--.js → stateDiagram-OKZ733FA-D7AiyJdo.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BBSH5c8m.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-ByxJrUzW.js → swimlanes-SLNWSIFB-BHGt7Kkv.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D2F7-U4H.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-D8MLgM0O.js → timeline-definition-Z64GVDOM-OPjooWcX.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DnBis-wZ.js → vennDiagram-T6HMQDX7-ggsWDvrO.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-uuyYT7UO.js → wardleyDiagram-T6FBY63Y-DjNe-BjZ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-BjgabhwK.js → xychartDiagram-ELKLHX3M-Byp63Hei.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +38 -2
- package/dist/worker/console/static-src/app/useRunProgress.js +46 -0
- package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
- package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +13 -1
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +29 -9
- package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
- package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
- package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
- package/dist/worker/materialize/harness-task-lineage.js +5 -2
- package/dist/worker/observability/read-model.js +22 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +29 -1
- package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +87 -4
- package/dist/workflows/dag/backend-test-writer-completeness.js +10 -5
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -1
- package/dist/workflows/dag/failure-category.js +7 -128
- package/dist/workflows/dag/frontend-design-policy.js +31 -7
- package/dist/workflows/dag/frontend-implementation-contract.js +78 -3
- package/dist/workflows/dag/frontend-review-findings.js +238 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +24 -10
- package/dist/workflows/dag/frontend-shape.js +5 -3
- package/dist/workflows/dag/frontend-test-case-checklist.js +4 -2
- package/dist/workflows/dag/frontend-test-case-manifest.js +6 -4
- package/dist/workflows/dag/frontend-test-case-quality.js +6 -3
- package/dist/workflows/dag/frontend-test-environment-probe.js +10 -7
- package/dist/workflows/dag/frontend-test-html-report.js +3 -1
- package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
- package/dist/workflows/dag/frontend-test-layout.js +159 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +28 -14
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +3 -1
- package/dist/workflows/dag/init-hybrid.js +149 -15
- package/dist/workflows/dag/node-execution.js +18 -1
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +101 -1
- package/dist/workflows/dag/retry-policy.js +31 -3
- package/dist/workflows/dag/runner.js +46 -1
- package/dist/workflows/dag/types.js +12 -0
- package/dist/workflows/dag/validate.js +32 -22
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +1 -1
- package/docs/templates/frontend-test-case-checklist.md +2 -2
- package/package.json +1 -1
- package/skills/fe-test-ui-scout/SKILL.md +4 -4
- package/skills/fe-test-ui-scout/references/ledger-schema.md +1 -1
- package/skills/playwright-cli/SKILL.md +1 -1
- package/skills/playwright-cli-case-generator/SKILL.md +10 -8
- package/dist/worker/console/static/assets/channel-BuqaGKAT.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DNdSHjH9.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DNdSHjH9.js +0 -1
- package/dist/worker/console/static/assets/index-D5zGX6fG.js +0 -404
- package/dist/worker/console/static/assets/index-DBbhESQ_.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8rmop_9.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B0ky-kBM.js +0 -8
|
@@ -125,6 +125,8 @@ const dagNodeArtifactsReportSchema = z
|
|
|
125
125
|
humanEscalationMd: dagArtifactRefSchema,
|
|
126
126
|
humanApproval: dagArtifactRefSchema,
|
|
127
127
|
humanRejection: dagArtifactRefSchema,
|
|
128
|
+
changeManifest: dagArtifactRefSchema,
|
|
129
|
+
changeManifestChangedFiles: z.array(z.string()).optional(),
|
|
128
130
|
})
|
|
129
131
|
.strict();
|
|
130
132
|
const dagNodeReportRowSchema = z
|
|
@@ -259,6 +261,20 @@ export async function probeArtifact(filePath) {
|
|
|
259
261
|
export async function buildNodeArtifactsReport(runDir, nodeId) {
|
|
260
262
|
const nodeDir = path.join(runDir, nodeId);
|
|
261
263
|
const artifact = (name) => probeArtifact(path.join(nodeDir, name));
|
|
264
|
+
const changeManifestRef = await artifact("change-manifest.json");
|
|
265
|
+
let changeManifestChangedFiles;
|
|
266
|
+
if (changeManifestRef.exists) {
|
|
267
|
+
try {
|
|
268
|
+
const manifest = JSON.parse(await readFile(path.join(nodeDir, "change-manifest.json"), "utf-8"));
|
|
269
|
+
changeManifestChangedFiles = Array.isArray(manifest.changedFiles)
|
|
270
|
+
? manifest.changedFiles
|
|
271
|
+
.filter((item) => typeof item === "string")
|
|
272
|
+
: undefined;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
// unreadable manifest → leave undefined
|
|
276
|
+
}
|
|
277
|
+
}
|
|
262
278
|
return {
|
|
263
279
|
resultSummary: await artifact("result.summary.md"),
|
|
264
280
|
promptRedacted: await artifact("prompt.redacted.md"),
|
|
@@ -270,6 +286,8 @@ export async function buildNodeArtifactsReport(runDir, nodeId) {
|
|
|
270
286
|
humanEscalationMd: await artifact("human-escalation.md"),
|
|
271
287
|
humanApproval: await artifact("human-approval.json"),
|
|
272
288
|
humanRejection: await artifact("human-rejection.json"),
|
|
289
|
+
changeManifest: changeManifestRef,
|
|
290
|
+
changeManifestChangedFiles,
|
|
273
291
|
};
|
|
274
292
|
}
|
|
275
293
|
function taskById(spec) {
|
|
@@ -726,6 +744,12 @@ function formatArtifactFlags(artifacts) {
|
|
|
726
744
|
flags.push("approval");
|
|
727
745
|
if (artifacts.humanRejection.exists)
|
|
728
746
|
flags.push("rejection");
|
|
747
|
+
if (artifacts.changeManifest.exists) {
|
|
748
|
+
const count = artifacts.changeManifestChangedFiles?.length;
|
|
749
|
+
flags.push(count !== undefined
|
|
750
|
+
? `changed:${count}`
|
|
751
|
+
: "change-manifest");
|
|
752
|
+
}
|
|
729
753
|
return flags.length > 0 ? flags.join(",") : "-";
|
|
730
754
|
}
|
|
731
755
|
function formatTimestamp(value) {
|
|
@@ -749,7 +773,19 @@ function summarizeNodeStatuses(nodes) {
|
|
|
749
773
|
return `${total} total / ${finished} finished / ${failed} failed / ${skipped} skipped / ${pending} pending`;
|
|
750
774
|
}
|
|
751
775
|
function hasArtifact(artifacts) {
|
|
752
|
-
return
|
|
776
|
+
return [
|
|
777
|
+
artifacts.resultSummary,
|
|
778
|
+
artifacts.promptRedacted,
|
|
779
|
+
artifacts.skills,
|
|
780
|
+
artifacts.nodeRecord,
|
|
781
|
+
artifacts.decisionEnvelope,
|
|
782
|
+
artifacts.decisionEnvelopeValidation,
|
|
783
|
+
artifacts.humanEscalationJson,
|
|
784
|
+
artifacts.humanEscalationMd,
|
|
785
|
+
artifacts.humanApproval,
|
|
786
|
+
artifacts.humanRejection,
|
|
787
|
+
artifacts.changeManifest,
|
|
788
|
+
].some((artifact) => artifact.exists);
|
|
753
789
|
}
|
|
754
790
|
function failureRows(run) {
|
|
755
791
|
return run.nodes.filter((node) => node.status === "ERROR" ||
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { parseJsonReviewVerdict } from "./output-protocol.js";
|
|
5
|
+
import { FRONTEND_REVIEW_NODE_ID, readCommittedReviewRequestFact, } from "./frontend-review-findings.js";
|
|
5
6
|
export const MAX_RERUN_FEEDBACK_CHARS = 12_000;
|
|
6
7
|
const RERUN_FEEDBACK_TRUNCATION_MARKER = "\n\n...[上一轮反馈已按长度上限截断]...\n\n";
|
|
7
8
|
const MAX_EVIDENCE_REFS = 8;
|
|
@@ -35,6 +36,44 @@ function requestRevision(text) {
|
|
|
35
36
|
return (emphasized?.[1] ?? trimmed).trim() === "VERDICT: request-revision";
|
|
36
37
|
});
|
|
37
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Deterministically render a typed `request_review_changes` fact into feedback
|
|
41
|
+
* text. Unlike the legacy text channel (VERDICT: request-revision), the
|
|
42
|
+
* frontend review terminal is a committed typed fact; the findings are the
|
|
43
|
+
* actionable body the next round must consume.
|
|
44
|
+
*/
|
|
45
|
+
export function formatTypedReviewRequestFeedback(fact) {
|
|
46
|
+
const lines = [];
|
|
47
|
+
lines.push(`frontend review committed request_review_changes (issueCategory=${typeof fact.issueCategory === "string" ? fact.issueCategory : "unknown"})`);
|
|
48
|
+
if (Array.isArray(fact.findings) && fact.findings.length > 0) {
|
|
49
|
+
lines.push("review findings:");
|
|
50
|
+
for (const finding of fact.findings) {
|
|
51
|
+
if (!finding || typeof finding !== "object")
|
|
52
|
+
continue;
|
|
53
|
+
const record = finding;
|
|
54
|
+
const severity = typeof record.severity === "string" ? record.severity : "";
|
|
55
|
+
const issue = typeof record.issue === "string" ? record.issue : "";
|
|
56
|
+
const location = [
|
|
57
|
+
typeof record.file === "string" ? record.file : "",
|
|
58
|
+
typeof record.line === "number" ? String(record.line) : "",
|
|
59
|
+
]
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.join(":");
|
|
62
|
+
const prefix = location ? `[${location}] ` : "";
|
|
63
|
+
const requiredChange = typeof record.requiredChange === "string" &&
|
|
64
|
+
record.requiredChange.length > 0
|
|
65
|
+
? ` (requiredChange: ${record.requiredChange})`
|
|
66
|
+
: "";
|
|
67
|
+
lines.push(`- ${prefix}${severity}: ${issue}${requiredChange}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(fact.evidenceRefs) && fact.evidenceRefs.length > 0) {
|
|
71
|
+
lines.push(`evidenceRefs: ${fact.evidenceRefs
|
|
72
|
+
.filter((ref) => typeof ref === "string")
|
|
73
|
+
.join(", ")}`);
|
|
74
|
+
}
|
|
75
|
+
return lines.join("\n");
|
|
76
|
+
}
|
|
38
77
|
function pathInside(root, candidate) {
|
|
39
78
|
const relative = path.relative(root, candidate);
|
|
40
79
|
return (relative === "" ||
|
|
@@ -127,6 +166,23 @@ function evidenceRefs(repoRoot, runDir, pass, sourceNodeId) {
|
|
|
127
166
|
}
|
|
128
167
|
return result;
|
|
129
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Read the committed frontend review request fact (typed terminal channel).
|
|
171
|
+
* Returns { nodeId, fact } when the review node committed `request_review_changes`.
|
|
172
|
+
*/
|
|
173
|
+
async function readTypedReviewRequestFact(runDir) {
|
|
174
|
+
const fact = await readCommittedReviewRequestFact({ runDir });
|
|
175
|
+
if (!fact)
|
|
176
|
+
return undefined;
|
|
177
|
+
return {
|
|
178
|
+
nodeId: FRONTEND_REVIEW_NODE_ID,
|
|
179
|
+
fact: {
|
|
180
|
+
issueCategory: fact.issueCategory,
|
|
181
|
+
evidenceRefs: fact.evidenceRefs,
|
|
182
|
+
findings: fact.findings,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
130
186
|
/**
|
|
131
187
|
* Derive only the latest unresolved verify/review feedback from canonical
|
|
132
188
|
* parent-run facts. Provider-only failures intentionally return undefined.
|
|
@@ -144,8 +200,52 @@ export async function deriveDagRerunFeedback(input) {
|
|
|
144
200
|
break;
|
|
145
201
|
}
|
|
146
202
|
}
|
|
147
|
-
if (!sourceNodeId || !sourceNode)
|
|
203
|
+
if (!sourceNodeId || !sourceNode) {
|
|
204
|
+
// Typed terminal channel: the frontend review commits its verdict as a
|
|
205
|
+
// typed fact (request_review_changes) instead of VERDICT text, and the
|
|
206
|
+
// frontend DAG has no convergence passHistory. Fall back to reading the
|
|
207
|
+
// review node's typed event store so findings still flow into the next
|
|
208
|
+
// round as unresolved feedback.
|
|
209
|
+
const typedFact = await readTypedReviewRequestFact(input.runDir);
|
|
210
|
+
if (typedFact) {
|
|
211
|
+
const parentSourceBindingHash = sourceBindingHash(input.spec);
|
|
212
|
+
return withDigest({
|
|
213
|
+
schemaVersion: 1,
|
|
214
|
+
kind: "unresolved-terminal-feedback",
|
|
215
|
+
parentRunId: input.parentRunId,
|
|
216
|
+
taskId: input.taskId,
|
|
217
|
+
sourceNodeId: typedFact.nodeId,
|
|
218
|
+
verdict: "request-revision",
|
|
219
|
+
failureCategory: "review-request-changes",
|
|
220
|
+
feedbackText: scrubAndBoundFeedbackText(formatTypedReviewRequestFeedback(typedFact.fact), input.runDir, input.state.cwd),
|
|
221
|
+
evidenceRefs: [
|
|
222
|
+
{
|
|
223
|
+
nodeId: typedFact.nodeId,
|
|
224
|
+
preservedNodeRecordPath: `${typedFact.nodeId}.json`,
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
attemptSummary: {
|
|
228
|
+
completedPasses: 1,
|
|
229
|
+
maxPasses: 1,
|
|
230
|
+
...(input.state.terminalReason
|
|
231
|
+
? { terminalReason: input.state.terminalReason }
|
|
232
|
+
: {}),
|
|
233
|
+
},
|
|
234
|
+
binding: {
|
|
235
|
+
...(parentSourceBindingHash
|
|
236
|
+
? { sourceCanonicalHash: parentSourceBindingHash }
|
|
237
|
+
: {}),
|
|
238
|
+
...(input.spec.taskContractBinding?.canonicalHash
|
|
239
|
+
? {
|
|
240
|
+
taskContractCanonicalHash: input.spec.taskContractBinding.canonicalHash,
|
|
241
|
+
}
|
|
242
|
+
: {}),
|
|
243
|
+
status: "unknown",
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
}
|
|
148
247
|
return undefined;
|
|
248
|
+
}
|
|
149
249
|
const completedPasses = pass?.pass ?? Math.max(1, input.state.convergence?.currentPass ?? 1);
|
|
150
250
|
const maxPasses = Math.max(completedPasses, input.state.convergence?.maxPasses ?? 1);
|
|
151
251
|
const parentSourceBindingHash = sourceBindingHash(input.spec);
|
|
@@ -32,6 +32,22 @@ export const STRUCTURED_OUTPUT_TRUNCATED_RETRY_CATEGORY = "structured-output-tru
|
|
|
32
32
|
* a corrected instruction; the typed terminal is the only authority.
|
|
33
33
|
*/
|
|
34
34
|
export const REVIEW_TERMINAL_MISSING_RETRY_CATEGORY = "review-terminal-missing";
|
|
35
|
+
/**
|
|
36
|
+
* The plan node issued too many read-only tool calls (read/grep/ls/find),
|
|
37
|
+
* blowing up the context window and eventually a 400 request-too-large. The
|
|
38
|
+
* plan should trust upstream typed facts instead of re-reading contract/scout
|
|
39
|
+
* outputs and source files repeatedly. Read-only and safe to retry with a
|
|
40
|
+
* reduced-reading instruction.
|
|
41
|
+
*/
|
|
42
|
+
export const READ_BURST_RETRY_CATEGORY = "read-burst";
|
|
43
|
+
/**
|
|
44
|
+
* Provider returned a context-overflow error (400 request-too-large / context
|
|
45
|
+
* window exceeded). The SDK compacts and auto-retries once internally; when it
|
|
46
|
+
* still surfaces, the executor classifies this so retry policies and reports
|
|
47
|
+
* treat it as overflow (not empty-output) and can retry with a compacted
|
|
48
|
+
* session / fresh context.
|
|
49
|
+
*/
|
|
50
|
+
export const CONTEXT_OVERFLOW_RETRY_CATEGORY = "context-overflow";
|
|
35
51
|
/** Retry only a proven no-op from an explicitly opt-in bounded Pi writer. */
|
|
36
52
|
export const WRITER_EMPTY_DIFF_RETRY_CATEGORY = "writer-empty-diff";
|
|
37
53
|
/** Retry when a backend-test writer finished but Completeness Gate found missing/broken targets. */
|
|
@@ -65,6 +81,8 @@ export const ALL_DAG_RETRY_CATEGORIES = [
|
|
|
65
81
|
INCOMPLETE_WRITE_SET_RETRY_CATEGORY,
|
|
66
82
|
WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY,
|
|
67
83
|
REVIEW_TERMINAL_MISSING_RETRY_CATEGORY,
|
|
84
|
+
READ_BURST_RETRY_CATEGORY,
|
|
85
|
+
CONTEXT_OVERFLOW_RETRY_CATEGORY,
|
|
68
86
|
];
|
|
69
87
|
const RETRY_SAFE_PI_ROLES = new Set([
|
|
70
88
|
"planner",
|
|
@@ -187,7 +205,10 @@ export const WRITER_TRANSPORT_RETRY_POLICY = {
|
|
|
187
205
|
backoff: "exponential",
|
|
188
206
|
initialDelayMs: 5000,
|
|
189
207
|
maxDelayMs: 30000,
|
|
190
|
-
retryCategories: [
|
|
208
|
+
retryCategories: [
|
|
209
|
+
WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY,
|
|
210
|
+
CONTEXT_OVERFLOW_RETRY_CATEGORY,
|
|
211
|
+
],
|
|
191
212
|
};
|
|
192
213
|
/**
|
|
193
214
|
* Implicit runtime retry for canonical final verify shells. Not a DagRetryPolicy:
|
|
@@ -210,14 +231,20 @@ export const TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE = "standard-reviewed-superv
|
|
|
210
231
|
/**
|
|
211
232
|
* Target-template exclusive implement-pi transport retry. Same schema category
|
|
212
233
|
* as frontend (`writer-clean-timeout`) so DagSpec validation stays unchanged;
|
|
213
|
-
* only the delay cap is wider.
|
|
234
|
+
* only the delay cap is wider. Context overflow is added so a writer whose
|
|
235
|
+
* session hit the context window (compact+retry inside the SDK) can retry once
|
|
236
|
+
* more at the DAG level with a fresh attempt. Do not mutate
|
|
237
|
+
* {@link WRITER_TRANSPORT_RETRY_POLICY}.
|
|
214
238
|
*/
|
|
215
239
|
export const TARGET_TEMPLATE_WRITER_TRANSPORT_RETRY_POLICY = {
|
|
216
240
|
maxAttempts: 2,
|
|
217
241
|
backoff: "exponential",
|
|
218
242
|
initialDelayMs: 5000,
|
|
219
243
|
maxDelayMs: 60000,
|
|
220
|
-
retryCategories: [
|
|
244
|
+
retryCategories: [
|
|
245
|
+
WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY,
|
|
246
|
+
CONTEXT_OVERFLOW_RETRY_CATEGORY,
|
|
247
|
+
],
|
|
221
248
|
};
|
|
222
249
|
/**
|
|
223
250
|
* Backend-test generation writers: empty-diff once, plus bounded incomplete-write-set
|
|
@@ -523,5 +550,6 @@ export const FRONTEND_PLAN_LADDER_RETRY_POLICY = {
|
|
|
523
550
|
retryCategories: [
|
|
524
551
|
...DEFAULT_DAG_RETRY_CATEGORIES,
|
|
525
552
|
STRUCTURED_ARTIFACT_INVALID_RETRY_CATEGORY,
|
|
553
|
+
READ_BURST_RETRY_CATEGORY,
|
|
526
554
|
],
|
|
527
555
|
};
|
|
@@ -26,6 +26,7 @@ import { runConvergencePassController, } from "./convergence/controller.js";
|
|
|
26
26
|
import { executeDagRanksOnce, FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT, FRONTEND_WRITER_NODE_IDS, isConditionSkippedReason, readFrontendPrewriteResult, } from "./scheduler.js";
|
|
27
27
|
import { topoSortToRanks } from "./topo.js";
|
|
28
28
|
import { isFrontendWriterTransientPartialWrite } from "./frontend-writer-recovery.js";
|
|
29
|
+
import { classifyReviewIssueCategoryFailureOwner, readCommittedReviewRequestFact, writeFrontendReviewFindingsPrd, } from "./frontend-review-findings.js";
|
|
29
30
|
import { bindFrontendShapeStateToRunTopology, commitFrontendShapeTransitionCas, commitRuntimeFrontendShapeFact, isCanonicalFrontendShapeDag, } from "./frontend-shape-facts.js";
|
|
30
31
|
import { assertFrontendTopologyBinding, buildFrontendTopologyBinding, computeFrontendShapeSourceDigest, } from "./frontend-shape.js";
|
|
31
32
|
import { ensureFrontendShapeCapsuleRecord } from "./frontend-shape-capsule-store.js";
|
|
@@ -1151,6 +1152,44 @@ export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd) {
|
|
|
1151
1152
|
}
|
|
1152
1153
|
}
|
|
1153
1154
|
}
|
|
1155
|
+
// Phase 7: frontend review "request changes" → not passed. When the review
|
|
1156
|
+
// node FINISHED but committed `request_review_changes` (typed terminal
|
|
1157
|
+
// verdict), the implementation was reviewed and rejected. The run must not
|
|
1158
|
+
// surface as finished: terminal aggregation below stays partial_failed, a
|
|
1159
|
+
// terminal reason is recorded, the committed findings are rendered into a
|
|
1160
|
+
// downstream-task PRD draft (run-owned), and a recovery trigger carries the
|
|
1161
|
+
// issueCategory-derived owner so a later DAG round consumes the findings
|
|
1162
|
+
// instead of repeating the previous flow.
|
|
1163
|
+
const reviewRequestFact = await readCommittedReviewRequestFact({
|
|
1164
|
+
runDir,
|
|
1165
|
+
});
|
|
1166
|
+
if (reviewRequestFact) {
|
|
1167
|
+
state.terminalReason = "review-request-changes";
|
|
1168
|
+
try {
|
|
1169
|
+
await writeFrontendReviewFindingsPrd({
|
|
1170
|
+
runDir,
|
|
1171
|
+
parentRunId: state.runId,
|
|
1172
|
+
fact: reviewRequestFact,
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
catch (error) {
|
|
1176
|
+
// The PRD draft is additive; a write failure must not mask the
|
|
1177
|
+
// not-passed verdict. The terminal aggregation below still applies.
|
|
1178
|
+
console.warn(`[run-dag] warning: failed to write frontend review findings PRD: ${error instanceof Error ? error.message : String(error)}`);
|
|
1179
|
+
}
|
|
1180
|
+
if (!recoveryTrigger && !skipFrontendRecovery) {
|
|
1181
|
+
const continuationCount = state.frontendRecoveryState?.continuationCount ?? 0;
|
|
1182
|
+
if (continuationCount < 1) {
|
|
1183
|
+
recoveryTrigger = {
|
|
1184
|
+
requestId: state.runId,
|
|
1185
|
+
failureSource: "frontend-review-pi",
|
|
1186
|
+
failureOwner: classifyReviewIssueCategoryFailureOwner(reviewRequestFact.issueCategory),
|
|
1187
|
+
protocolFailureReason: `frontend review request_review_changes (issueCategory=${reviewRequestFact.issueCategory})`,
|
|
1188
|
+
recoveryMode: "new-dag",
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1154
1193
|
const finishedCount = Object.values(state.nodes).filter((n) => n.status === "FINISHED").length;
|
|
1155
1194
|
const { supersededIds, supersededFailures } = collectSupersededIntermediateFailures(state);
|
|
1156
1195
|
if (state.convergence && supersededFailures.length > 0) {
|
|
@@ -1166,7 +1205,13 @@ export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd) {
|
|
|
1166
1205
|
if (effectiveErrorCount === 0 &&
|
|
1167
1206
|
successTerminal &&
|
|
1168
1207
|
finishedCount + conditionSkippedCount + supersededErrorCount === taskCount) {
|
|
1169
|
-
|
|
1208
|
+
// Phase 7 guard: a committed `request_review_changes` means the review
|
|
1209
|
+
// rejected the implementation; the run must never surface as finished
|
|
1210
|
+
// even though every node technically FINISHED.
|
|
1211
|
+
state.status =
|
|
1212
|
+
state.terminalReason === "review-request-changes"
|
|
1213
|
+
? "partial_failed"
|
|
1214
|
+
: "finished";
|
|
1170
1215
|
}
|
|
1171
1216
|
else if (finishedCount > 0) {
|
|
1172
1217
|
state.status = "partial_failed";
|
|
@@ -1159,6 +1159,18 @@ export const dagSpecSchema = z
|
|
|
1159
1159
|
isDefault: z.boolean(),
|
|
1160
1160
|
})
|
|
1161
1161
|
.optional(),
|
|
1162
|
+
/** Frozen frontend-test artifact layout; absent = historical testcase/frontend/ layout. */
|
|
1163
|
+
frontendTestLayout: z
|
|
1164
|
+
.object({
|
|
1165
|
+
testRoot: z.string(),
|
|
1166
|
+
ragDir: z.string(),
|
|
1167
|
+
casesDir: z.string(),
|
|
1168
|
+
evidenceDir: z.string(),
|
|
1169
|
+
reportsDir: z.string(),
|
|
1170
|
+
fixturesDir: z.string(),
|
|
1171
|
+
isDefault: z.boolean(),
|
|
1172
|
+
})
|
|
1173
|
+
.optional(),
|
|
1162
1174
|
/** Bound user shared-setup document (plan B); absent = no shared setup references expected. */
|
|
1163
1175
|
backendTestSharedSetup: z
|
|
1164
1176
|
.object({
|
|
@@ -3,8 +3,9 @@ import { resolveShellCommands } from "../../executors/shell-executor.js";
|
|
|
3
3
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
4
4
|
import { resolveRepairTaskForGate } from "./repair-artifact.js";
|
|
5
5
|
import { topoSortToRanks } from "./topo.js";
|
|
6
|
-
import { isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, INCOMPLETE_WRITE_SET_RETRY_CATEGORY, WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY, WRITER_EMPTY_DIFF_RETRY_CATEGORY, } from "./retry-policy.js";
|
|
6
|
+
import { isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, INCOMPLETE_WRITE_SET_RETRY_CATEGORY, WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY, WRITER_EMPTY_DIFF_RETRY_CATEGORY, CONTEXT_OVERFLOW_RETRY_CATEGORY, } from "./retry-policy.js";
|
|
7
7
|
import { isBackendTestCompletenessRetryCandidate } from "./backend-test-writer-completeness.js";
|
|
8
|
+
import { defaultFrontendTestLayout, frontendTestLayoutFromSpec, } from "./frontend-test-layout.js";
|
|
8
9
|
const GOVERNANCE_WARNING_TYPES = new Set([
|
|
9
10
|
"read-only-missing-artifacts-forbidden",
|
|
10
11
|
"read-only-prompt-mentions-artifact-writes",
|
|
@@ -655,12 +656,18 @@ function validateRetryPolicyTaskConfig(task, issues) {
|
|
|
655
656
|
}
|
|
656
657
|
if (isWriterTransportCandidate) {
|
|
657
658
|
const categories = new Set(task.retryPolicy.retryCategories);
|
|
659
|
+
const allowedWriterTransportCategories = new Set([
|
|
660
|
+
WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY,
|
|
661
|
+
CONTEXT_OVERFLOW_RETRY_CATEGORY,
|
|
662
|
+
]);
|
|
663
|
+
const allAllowed = [...categories].every((category) => allowedWriterTransportCategories.has(category));
|
|
658
664
|
if (task.retryPolicy.maxAttempts !== 2 ||
|
|
659
|
-
categories.size
|
|
665
|
+
categories.size === 0 ||
|
|
666
|
+
!allAllowed ||
|
|
660
667
|
!categories.has(WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY)) {
|
|
661
668
|
issues.push({
|
|
662
669
|
type: "invalid-retry-policy",
|
|
663
|
-
message: `task ${task.id} writer transport retryPolicy must use exactly two total attempts and only
|
|
670
|
+
message: `task ${task.id} writer transport retryPolicy must use exactly two total attempts and only writer-clean-timeout (plus optional context-overflow)`,
|
|
664
671
|
});
|
|
665
672
|
}
|
|
666
673
|
}
|
|
@@ -690,22 +697,23 @@ function validateWriterOutcomePolicyTaskConfig(task, issues) {
|
|
|
690
697
|
});
|
|
691
698
|
}
|
|
692
699
|
}
|
|
693
|
-
function isFrontendEvidenceWriteSet(writeSet) {
|
|
700
|
+
function isFrontendEvidenceWriteSet(writeSet, layout = defaultFrontendTestLayout()) {
|
|
694
701
|
const entries = writeSet ?? [];
|
|
695
702
|
if (entries.length === 0)
|
|
696
703
|
return false;
|
|
704
|
+
const prefix = `${layout.evidenceDir}/`;
|
|
705
|
+
const glob = `${layout.evidenceDir}/**`;
|
|
697
706
|
return entries.every((entry) => {
|
|
698
707
|
const normalized = entry.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
699
|
-
return
|
|
700
|
-
normalized.startsWith("testcase/frontend/evidence/"));
|
|
708
|
+
return normalized === glob || normalized.startsWith(prefix);
|
|
701
709
|
});
|
|
702
710
|
}
|
|
703
|
-
function isFrontendBrowserExecutorTask(task) {
|
|
711
|
+
function isFrontendBrowserExecutorTask(task, layout = defaultFrontendTestLayout()) {
|
|
704
712
|
const skills = task.skills ?? [];
|
|
705
713
|
return (task.executor === "pi" &&
|
|
706
714
|
task.toolProfile === "write" &&
|
|
707
715
|
skills.includes("playwright-cli") &&
|
|
708
|
-
isFrontendEvidenceWriteSet(task.writeSet));
|
|
716
|
+
isFrontendEvidenceWriteSet(task.writeSet, layout));
|
|
709
717
|
}
|
|
710
718
|
function taskMentionsPlaywrightCliContract(task) {
|
|
711
719
|
const prompt = `${task.subtask_prompt}\n${task.outputContract ?? ""}`;
|
|
@@ -717,10 +725,11 @@ function taskMentionsPlaywrightCliContract(task) {
|
|
|
717
725
|
* - capability is granted without skill/prompt contract or outside evidence writeSet
|
|
718
726
|
* - non-browser nodes receive command capability
|
|
719
727
|
*/
|
|
720
|
-
function validateCommandPolicyTaskConfig(task, issues) {
|
|
728
|
+
function validateCommandPolicyTaskConfig(task, issues, spec) {
|
|
729
|
+
const layout = frontendTestLayoutFromSpec(spec);
|
|
721
730
|
const policy = resolveDagCommandPolicy(task.commandPolicy);
|
|
722
731
|
const allowsPlaywright = dagCommandPolicyAllows(task.commandPolicy, "playwright-cli");
|
|
723
|
-
const isBrowserExecutor = isFrontendBrowserExecutorTask(task);
|
|
732
|
+
const isBrowserExecutor = isFrontendBrowserExecutorTask(task, layout);
|
|
724
733
|
if (isBrowserExecutor && !allowsPlaywright) {
|
|
725
734
|
issues.push({
|
|
726
735
|
type: "invalid-command-policy",
|
|
@@ -751,10 +760,10 @@ function validateCommandPolicyTaskConfig(task, issues) {
|
|
|
751
760
|
message: `task ${task.id} playwright-cli command capability requires prompt/outputContract to reference playwright_cli`,
|
|
752
761
|
});
|
|
753
762
|
}
|
|
754
|
-
if (!isFrontendEvidenceWriteSet(task.writeSet)) {
|
|
763
|
+
if (!isFrontendEvidenceWriteSet(task.writeSet, layout)) {
|
|
755
764
|
issues.push({
|
|
756
765
|
type: "invalid-command-policy",
|
|
757
|
-
message: `task ${task.id} playwright-cli command capability requires frontend evidence writeSet under
|
|
766
|
+
message: `task ${task.id} playwright-cli command capability requires frontend evidence writeSet under ${layout.evidenceDir}/`,
|
|
758
767
|
});
|
|
759
768
|
}
|
|
760
769
|
if (task.toolProfile !== "write") {
|
|
@@ -765,7 +774,7 @@ function validateCommandPolicyTaskConfig(task, issues) {
|
|
|
765
774
|
}
|
|
766
775
|
}
|
|
767
776
|
}
|
|
768
|
-
function validateDynamicChildCommandPolicy(task, issues) {
|
|
777
|
+
function validateDynamicChildCommandPolicy(task, issues, spec) {
|
|
769
778
|
const expansion = task.dynamicExpansion;
|
|
770
779
|
if (!expansion)
|
|
771
780
|
return;
|
|
@@ -786,7 +795,7 @@ function validateDynamicChildCommandPolicy(task, issues) {
|
|
|
786
795
|
writeSet: child.writeSet,
|
|
787
796
|
outputContract: child.outputContract,
|
|
788
797
|
};
|
|
789
|
-
validateCommandPolicyTaskConfig(synthetic, issues);
|
|
798
|
+
validateCommandPolicyTaskConfig(synthetic, issues, spec);
|
|
790
799
|
}
|
|
791
800
|
function validateDecisionGateTaskConfig(task, issues) {
|
|
792
801
|
if (!task.decisionGate?.enabled) {
|
|
@@ -893,12 +902,13 @@ export function collectForbiddenExecutorIssues(spec, forbiddenExecutors) {
|
|
|
893
902
|
}));
|
|
894
903
|
}
|
|
895
904
|
/** Revalidate a rendered dynamic child before it enters state or executes. */
|
|
896
|
-
export function assertValidMaterializedDagTask(task) {
|
|
905
|
+
export function assertValidMaterializedDagTask(task, parentSpec) {
|
|
897
906
|
const issues = [];
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
907
|
+
const spec = {
|
|
908
|
+
version: 2,
|
|
909
|
+
tasks: [task],
|
|
910
|
+
frontendTestLayout: parentSpec?.frontendTestLayout,
|
|
911
|
+
};
|
|
902
912
|
validateTaskWritePolicy(task, spec, issues);
|
|
903
913
|
validateShellTaskConfig(task, spec, issues);
|
|
904
914
|
validateStaticTaskConfig(task, issues);
|
|
@@ -906,7 +916,7 @@ export function assertValidMaterializedDagTask(task) {
|
|
|
906
916
|
validateRetryPolicyTaskConfig(task, issues);
|
|
907
917
|
validateOutputProtocolTaskConfig(task, issues);
|
|
908
918
|
validateWriterOutcomePolicyTaskConfig(task, issues);
|
|
909
|
-
validateCommandPolicyTaskConfig(task, issues);
|
|
919
|
+
validateCommandPolicyTaskConfig(task, issues, spec);
|
|
910
920
|
if (issues.length > 0) {
|
|
911
921
|
throw new Error(`invalid materialized dynamic child ${task.id}: ${issues.map((issue) => issue.message).join("; ")}`);
|
|
912
922
|
}
|
|
@@ -962,8 +972,8 @@ export function validateDagSpec(spec) {
|
|
|
962
972
|
validateRetryPolicyTaskConfig(task, issues);
|
|
963
973
|
validateOutputProtocolTaskConfig(task, issues);
|
|
964
974
|
validateWriterOutcomePolicyTaskConfig(task, issues);
|
|
965
|
-
validateCommandPolicyTaskConfig(task, issues);
|
|
966
|
-
validateDynamicChildCommandPolicy(task, issues);
|
|
975
|
+
validateCommandPolicyTaskConfig(task, issues, spec);
|
|
976
|
+
validateDynamicChildCommandPolicy(task, issues, spec);
|
|
967
977
|
validateProjectGovernanceTaskConfig(task, spec, issues);
|
|
968
978
|
validatePiExtensionsTaskConfig(task, issues);
|
|
969
979
|
validateFailureAwareDependsOn(task, spec, issues);
|
|
@@ -176,7 +176,8 @@
|
|
|
176
176
|
"humanEscalationJson",
|
|
177
177
|
"humanEscalationMd",
|
|
178
178
|
"humanApproval",
|
|
179
|
-
"humanRejection"
|
|
179
|
+
"humanRejection",
|
|
180
|
+
"changeManifest"
|
|
180
181
|
],
|
|
181
182
|
"properties": {
|
|
182
183
|
"resultSummary": { "$ref": "#/$defs/dagArtifactRef" },
|
|
@@ -188,7 +189,12 @@
|
|
|
188
189
|
"humanEscalationJson": { "$ref": "#/$defs/dagArtifactRef" },
|
|
189
190
|
"humanEscalationMd": { "$ref": "#/$defs/dagArtifactRef" },
|
|
190
191
|
"humanApproval": { "$ref": "#/$defs/dagArtifactRef" },
|
|
191
|
-
"humanRejection": { "$ref": "#/$defs/dagArtifactRef" }
|
|
192
|
+
"humanRejection": { "$ref": "#/$defs/dagArtifactRef" },
|
|
193
|
+
"changeManifest": { "$ref": "#/$defs/dagArtifactRef" },
|
|
194
|
+
"changeManifestChangedFiles": {
|
|
195
|
+
"type": "array",
|
|
196
|
+
"items": { "type": "string" }
|
|
197
|
+
}
|
|
192
198
|
}
|
|
193
199
|
},
|
|
194
200
|
"repairArtifact": {
|
|
@@ -466,7 +466,7 @@
|
|
|
466
466
|
"retryCategories": {
|
|
467
467
|
"type": "array",
|
|
468
468
|
"items": {
|
|
469
|
-
"enum": ["timeout", "network", "rate-limit", "unavailable", "empty-output", "output-too-large", "protocol-invalid", "invalid-output", "writer-empty-diff", "incomplete-write-set", "writer-clean-timeout"]
|
|
469
|
+
"enum": ["timeout", "network", "rate-limit", "unavailable", "empty-output", "output-too-large", "protocol-invalid", "invalid-output", "writer-empty-diff", "incomplete-write-set", "writer-clean-timeout", "read-burst", "context-overflow"]
|
|
470
470
|
},
|
|
471
471
|
"default": ["timeout", "network", "rate-limit", "unavailable", "empty-output"],
|
|
472
472
|
"description": "Failure categories eligible for retry. quota/auth/write-guard are never eligible. invalid-output is allowed only on explicit structured/protocol policies; writer-empty-diff and incomplete-write-set are reserved for requireChangedFiles generation/completeness writers; writer-clean-timeout is reserved for standard bounded writers after explicit zero-write-tool-call and empty-diff evidence."
|
|
@@ -15,7 +15,7 @@ LLM review (when `frontendTest.reviewMode=blocking`) must not invent blocking ru
|
|
|
15
15
|
| `unknown-ac` | When task `sourceBinding.requirementIds` lists ACs, every `acIds` entry must be in that set |
|
|
16
16
|
| `case-id-shape` | `caseId` matches `FE-<FEATURE>-<NNN>-...` (never `AC-FE-*`) |
|
|
17
17
|
| `case-id-is-ac` | Do not use acceptance id as `caseId` / filename |
|
|
18
|
-
| `case-path-mismatch` | `casePath === testcase/frontend/cases/{caseId}.md
|
|
18
|
+
| `case-path-mismatch` | `casePath === {casesDir}/{caseId}.md`(默认 `testcase/frontend/cases/{caseId}.md`) |
|
|
19
19
|
| `case-file-missing` | `casePath` exists |
|
|
20
20
|
|
|
21
21
|
## Tool guidance (non-blocking)
|
|
@@ -40,5 +40,5 @@ LLM review (when `frontendTest.reviewMode=blocking`) must not invent blocking ru
|
|
|
40
40
|
|
|
41
41
|
## Pipeline vs quality
|
|
42
42
|
|
|
43
|
-
- **Pipeline acceptance**: final `
|
|
43
|
+
- **Pipeline acceptance**: final `{reportsDir}/frontend-test-retrospect-*.md` exists after result materialize (default `testcase/frontend/reports/`)
|
|
44
44
|
- **Quality**: `frontend-test-result-v1.outcome=passed` with 0 blocked/failed (opt-in via `frontendTest.strictOutcomeGate`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fe-test-ui-scout
|
|
3
|
-
description: 在写 frontend-test Wave PRD/回归目录之前,对真实运行的被测页面做只读锚点侦察,产出并维护 testcase/frontend/rag/ui-anchors.md
|
|
3
|
+
description: 在写 frontend-test Wave PRD/回归目录之前,对真实运行的被测页面做只读锚点侦察,产出并维护 {testRoot}/rag/ui-anchors.md 锚点台账(默认 testcase/frontend/rag/ui-anchors.md)。用 playwright-cli snapshot/find 逐字面验证命中数与唯一性,防止 PRD 验收标准引用不存在或不唯一的文案。触发词:frontend-test、锚点、台账、ui-anchors、侦察、写 PRD 前、snapshot/find 校准、Wave PRD。
|
|
4
4
|
references:
|
|
5
5
|
- path: references/ledger-schema.md
|
|
6
6
|
required: true
|
|
@@ -13,7 +13,7 @@ references:
|
|
|
13
13
|
## 定位
|
|
14
14
|
|
|
15
15
|
写 Wave PRD / 校准回归目录 AC **之前**的前置侦察。产出锚点台账
|
|
16
|
-
`testcase/frontend/rag/ui-anchors.md
|
|
16
|
+
`{testRoot}/rag/ui-anchors.md`(默认 `testcase/frontend/rag/ui-anchors.md`):每个「页面 × 状态」下真实可见、
|
|
17
17
|
`find` 可命中的字面清单。PRD 的每条 `find "..."` 断言必须能在台账找到对应行;
|
|
18
18
|
台账没有的字面禁止写进验收标准。
|
|
19
19
|
|
|
@@ -23,7 +23,7 @@ references:
|
|
|
23
23
|
|
|
24
24
|
## 输入
|
|
25
25
|
|
|
26
|
-
- 被测 baseUrl(来自 `
|
|
26
|
+
- 被测 baseUrl(来自 `{testRoot}/rag/context.md` 的 `environmentProbe=reachable` URL;默认 `testcase/frontend/rag/context.md`)
|
|
27
27
|
- 目标 Wave 的 AC 清单(回归目录中本 Wave 范围的行)
|
|
28
28
|
- 既有台账(增量更新,不整表重写)
|
|
29
29
|
|
|
@@ -56,7 +56,7 @@ references:
|
|
|
56
56
|
|
|
57
57
|
## 输出
|
|
58
58
|
|
|
59
|
-
- `testcase/frontend/rag/ui-anchors.md
|
|
59
|
+
- `{testRoot}/rag/ui-anchors.md`(增量更新;默认 `testcase/frontend/rag/ui-anchors.md`)
|
|
60
60
|
- 侦察小结(对话内输出,不落盘;结论回写 PRD 草稿的锚点引用)
|
|
61
61
|
|
|
62
62
|
## References
|
|
@@ -55,7 +55,7 @@ request
|
|
|
55
55
|
|
|
56
56
|
## 推荐执行流程
|
|
57
57
|
|
|
58
|
-
使用 `testcase/frontend/rag/context.md
|
|
58
|
+
使用 `{testRoot}/rag/context.md`(默认 `testcase/frontend/rag/context.md`)中已由 environment shell 标记为 `reachable` 的非生产 `baseUrl` 和默认浏览器 session;不得创建 named session。交互前先获取 snapshot,并只使用 snapshot 中可见的 ref 或已知安全 locator。截图写入当前 case 的 `{evidenceDir}/<caseId>/`,canonical 文件名仍是 `final.png`。
|
|
59
59
|
|
|
60
60
|
```text
|
|
61
61
|
playwright-cli open --browser=chrome http://localhost:5173
|