@tea-agent/loop-agent 0.39.0 → 0.39.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 +1 -1
- package/CHANGELOG.md +9 -2
- package/README.md +2 -2
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/commands/dag-rerun.js +1 -1
- package/dist/executors/shell-executor.js +1 -1
- package/dist/shared/dag-prompt-override.js +4 -5
- package/dist/shared/update/npm-client.js +33 -11
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +15 -1
- package/dist/task/contract/schema.js +2 -0
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +474 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/prepare.js +262 -1
- package/dist/task/source-prepare/semantic-intake.js +19 -3
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/worker/cli.js +12 -26
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/operation-store.js +1 -1
- package/dist/worker/console/operator-actions.js +129 -4
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +3 -0
- package/dist/worker/console/server.js +73 -5
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Br92EGb7.js → abnfDiagram-N423BO3Z-CTx1TrNa.js} +1 -1
- package/dist/worker/console/static/assets/{arc-oulImtQq.js → arc-B4i9cVxX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-tyHc6aqO.js → architectureDiagram-T3A2C74G-BAXE8_Rl.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CY86BSpT.js → blockDiagram-VBNYF7ZC-C5Xh59at.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BKKsDmNs.js → c4Diagram-5PPSVZJV-B_asOzAu.js} +1 -1
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CXZdjl3B.js → chunk-2GRJ4B5K-wRO_iRNc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B-aIFDQ5.js → chunk-2Q5K7J3B-ByOaSh_G.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BDgAW_B2.js → chunk-5RXB4S5H-DjLzudan.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DABa4UPR.js → chunk-5VM5RSS4-JCNGkXYT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-aGbysVOx.js → chunk-6Q2QTUOP-BhrPXXMZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B8MSZDVs.js → chunk-GF5L2VYU-TJ5JuEBT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CpelV73g.js → chunk-JWPE2WC7-VVthn_mD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BE04ty3z.js → chunk-KBJHAD2P-D60-Mny4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BUxTkaaR.js → chunk-RYQCIY6F-zXv7aVye.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CYYBVKfX.js → chunk-XXDRQBXY-By9VQoi1.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CEQF_xmQ.js → cose-bilkent-JH36ORCC-C4zQLe-M.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CqpxN5IT.js → cynefin-VYW2F7L2-wh5N2ReN.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-B3PzanVi.js → cynefinDiagram-MW4NZA55-Dlu5wIqs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BSUGCNx1.js → dagre-VZM6K2ZE-CrDgthc6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CdLevU5T.js → diagram-7IWD3JNH-zN1clg27.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BjpF_Kok.js → diagram-B4RE2ZJO-BN-o7J7-.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C4I6NJny.js → diagram-LBJQPF4R-C8SuPt0d.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C8hecCr5.js → diagram-Q27KOJAE-BoizuHbZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Daod6tOS.js → diagram-UB23O5K3-BzUzXixG.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BvvmazGv.js → ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DeN_vfV4.js → erDiagram-JOGREHBK-GALUzRbs.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-D6dq1t9z.js → flowDiagram-UKHOOZJN-CZJN65Sb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DziIdNY4.js → ganttDiagram-PKOTCBZU-JLB9OESl.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dy4s5CXr.js → gitGraphDiagram-DS77QQ5N-CcvCr4GD.js} +1 -1
- package/dist/worker/console/static/assets/{index-Ci8ksvBm.js → index-CW88Fjbw.js} +81 -81
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czv5eYx2.js → infoDiagram-6WML65LV-HG5z7aVg.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-Bsk3nFRT.js → ishikawaDiagram-WSZJBQD7-BHEu9chR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DxGmNj5K.js → journeyDiagram-NVQOT4AX-Cp_Up8-a.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CYCFqkO_.js → kanban-definition-27J2QSJJ-DvVeY-em.js} +1 -1
- package/dist/worker/console/static/assets/{linear-B0YzRwrd.js → linear-aGmcjgTn.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-t3EZtIGh.js → mermaid.core-Biyo-tqA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BTXpTv0T.js → mindmap-definition-FAOFIHXS-DyxY2pIb.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DU8UkKqA.js → pegDiagram-VL7TDLO6-CkdGuc50.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DP4y8ohp.js → pieDiagram-7S7Q4E2Y-NNQI9xVR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B07vNaYR.js → quadrantDiagram-CIZ2JOQS-PAJkuz_G.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-xDWktb2i.js → railroadDiagram-AXF67PYL-ZwrQod0O.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BLNMKqeS.js → requirementDiagram-LRYGKXZP-D3ZfrzM2.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-n0adalxH.js → sankeyDiagram-W5VNT64P-DDGFmWk5.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BbnS-2DL.js → sequenceDiagram-SI44F4Z6-DO6mmXTA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4uPpCLu.js → sizeCapture-X5ZJPWSS-DMbCQiJt.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA--MMbB0fX.js → stateDiagram-OKZ733FA-Di_YUEfC.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BXUgp2QY.js → swimlanes-SLNWSIFB-Bn2PphNJ.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CsaMxki7.js → timeline-definition-Z64GVDOM-DPh8pRsp.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-C2IuG9iH.js → vennDiagram-T6HMQDX7-GMfl26pW.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CmEWOXCW.js → wardleyDiagram-T6FBY63Y-DTlmVrmF.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-D_9LE0MG.js → xychartDiagram-ELKLHX3M-YGBjKAS5.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +40 -1
- package/dist/worker/console/static-src/app/useOperatorActions.js +5 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +73 -1
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observe/node-input.js +88 -2
- package/dist/worker/observe/routes.js +188 -0
- package/dist/worker/observe/static/api.js +3 -3
- package/dist/worker/observe/static/format.js +37 -0
- package/dist/worker/observe/static/index.html +5 -0
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/styles.css +12 -0
- package/dist/worker/observe/static/views/dag-inspector.js +316 -16
- package/dist/worker/observe/static/views/task.js +55 -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/frontend-implementation-contract.js +88 -2
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +197 -0
- package/dist/workflows/dag/frontend-review-context.js +92 -10
- package/dist/workflows/dag/init-hybrid.js +125 -4
- package/dist/workflows/dag/node-execution.js +45 -0
- package/dist/workflows/dag/types.js +42 -11
- package/docs/README.md +1 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/frontend-implementation-contract.schema.json +681 -24
- package/package.json +1 -1
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/dist/worker/console/static/assets/channel-TY36zt-i.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Dk7QCChM.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Dk7QCChM.js +0 -1
- package/dist/worker/console/static/assets/index-C3GgHg-g.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BqOigMQ4.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Caxz9UA2.js +0 -8
|
@@ -19,8 +19,10 @@ import {
|
|
|
19
19
|
postOperatorAction,
|
|
20
20
|
fetchOperatorOperation,
|
|
21
21
|
} from "../api.js";
|
|
22
|
-
import { badge, formatMs, prettyJsonBlock } from "../format.js";
|
|
22
|
+
import { badge, formatLedgerSummary, formatMs, prettyJsonBlock } from "../format.js";
|
|
23
23
|
import { renderMarkdown, renderInspectorDocument } from "../markdown-render.js";
|
|
24
|
+
import { buildPromptRestartCandidateOptions } from "../prompt-restart-candidates.js";
|
|
25
|
+
import { copyText } from "../copy.js";
|
|
24
26
|
import {
|
|
25
27
|
uiState,
|
|
26
28
|
makeSessionEventIdentity,
|
|
@@ -52,6 +54,85 @@ import { createCustomSelect } from "../custom-select.js";
|
|
|
52
54
|
// reference, always resolving the currently selected node identity.
|
|
53
55
|
const inspectorSelection = { dagRunId: null, node: null };
|
|
54
56
|
|
|
57
|
+
function createPromptRestartCopyButton(readCandidate) {
|
|
58
|
+
const buildIcon = (kind) => {
|
|
59
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
60
|
+
svg.setAttribute(
|
|
61
|
+
"class",
|
|
62
|
+
`dags-copy-icon dags-copy-icon-${kind === "check" ? "check" : "rects"}`,
|
|
63
|
+
);
|
|
64
|
+
svg.setAttribute("viewBox", "0 0 16 16");
|
|
65
|
+
svg.setAttribute("width", "16");
|
|
66
|
+
svg.setAttribute("height", "16");
|
|
67
|
+
svg.setAttribute("aria-hidden", "true");
|
|
68
|
+
svg.setAttribute("focusable", "false");
|
|
69
|
+
if (kind === "check") {
|
|
70
|
+
const check = document.createElementNS(
|
|
71
|
+
"http://www.w3.org/2000/svg",
|
|
72
|
+
"path",
|
|
73
|
+
);
|
|
74
|
+
check.setAttribute("d", "M3.5 8.5 6.5 11.5 12.5 4.5");
|
|
75
|
+
check.setAttribute("fill", "none");
|
|
76
|
+
check.setAttribute("stroke", "currentColor");
|
|
77
|
+
check.setAttribute("stroke-width", "1.8");
|
|
78
|
+
check.setAttribute("stroke-linecap", "round");
|
|
79
|
+
check.setAttribute("stroke-linejoin", "round");
|
|
80
|
+
svg.appendChild(check);
|
|
81
|
+
return svg;
|
|
82
|
+
}
|
|
83
|
+
for (const [x, y] of [
|
|
84
|
+
[2, 2],
|
|
85
|
+
[5, 5],
|
|
86
|
+
]) {
|
|
87
|
+
const rect = document.createElementNS(
|
|
88
|
+
"http://www.w3.org/2000/svg",
|
|
89
|
+
"rect",
|
|
90
|
+
);
|
|
91
|
+
rect.setAttribute("x", String(x));
|
|
92
|
+
rect.setAttribute("y", String(y));
|
|
93
|
+
rect.setAttribute("width", "9");
|
|
94
|
+
rect.setAttribute("height", "9");
|
|
95
|
+
rect.setAttribute("rx", "1.5");
|
|
96
|
+
rect.setAttribute("fill", "none");
|
|
97
|
+
rect.setAttribute("stroke", "currentColor");
|
|
98
|
+
rect.setAttribute("stroke-width", "1.5");
|
|
99
|
+
svg.appendChild(rect);
|
|
100
|
+
}
|
|
101
|
+
return svg;
|
|
102
|
+
};
|
|
103
|
+
const button = el(
|
|
104
|
+
"button",
|
|
105
|
+
"dags-copy-btn dags-copy-btn-icon prompt-restart-copy-icon",
|
|
106
|
+
);
|
|
107
|
+
button.type = "button";
|
|
108
|
+
button.setAttribute("data-testid", "prompt-restart-copy");
|
|
109
|
+
button.setAttribute("aria-label", "复制当前提示词候选");
|
|
110
|
+
button.setAttribute("title", "复制当前提示词候选");
|
|
111
|
+
button.appendChild(buildIcon("copy"));
|
|
112
|
+
button.appendChild(buildIcon("check"));
|
|
113
|
+
button.addEventListener("click", (event) => {
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
event.stopPropagation();
|
|
116
|
+
const candidate = readCandidate();
|
|
117
|
+
void copyText(candidate?.prompt ?? "").then((ok) => {
|
|
118
|
+
const feedback = ok
|
|
119
|
+
? candidate?.truncated
|
|
120
|
+
? "已复制截断预览"
|
|
121
|
+
: "已复制"
|
|
122
|
+
: "复制失败";
|
|
123
|
+
button.classList.toggle("is-copied", ok);
|
|
124
|
+
button.setAttribute("aria-label", feedback);
|
|
125
|
+
button.setAttribute("title", feedback);
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
button.classList.remove("is-copied");
|
|
128
|
+
button.setAttribute("aria-label", "复制当前提示词候选");
|
|
129
|
+
button.setAttribute("title", "复制当前提示词候选");
|
|
130
|
+
}, 1500);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
return button;
|
|
134
|
+
}
|
|
135
|
+
|
|
55
136
|
function renderDagInspectorForCurrent() {
|
|
56
137
|
if (!inspectorSelection.dagRunId || !inspectorSelection.node) return;
|
|
57
138
|
renderDagInspector(
|
|
@@ -983,6 +1064,121 @@ async function renderDagNodeInput(content, dagRunId, node) {
|
|
|
983
1064
|
|
|
984
1065
|
renderDagNodeInputDetail(content, data);
|
|
985
1066
|
finishInputRender();
|
|
1067
|
+
// Frontend prewrite 门禁 / source fidelity ledger 展示(AC-009):只读安全摘要,
|
|
1068
|
+
// 从 run 产物(frontend-prewrite-result.json)与 /api/tasks/:id/ledger 拉取;
|
|
1069
|
+
// 接口失败进入安全降级,不泄漏敏感内容。
|
|
1070
|
+
if (/prewrite/.test(nodeId)) {
|
|
1071
|
+
void renderFrontendGateLedger(content, dagRunId);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Frontend prewrite 门禁 + ledger 摘要行(AC-BLOCK-005):纯函数,测试直接断言。
|
|
1077
|
+
* prewriteResult = {ok:true, body}|{ok:false, status}|null(网络失败);ledgerBody 为
|
|
1078
|
+
* /api/tasks/:id/ledger 响应或 null。绝不把 envelope/预览原文塞入 <pre>(删除
|
|
1079
|
+
* 旧 lines.push(previewText) 路径),缺失/拒绝显示明确 fail-closed 文案。
|
|
1080
|
+
*/
|
|
1081
|
+
export function frontendGateLedgerLines(input) {
|
|
1082
|
+
const { prewriteResult, ledgerBody, taskId } = input;
|
|
1083
|
+
const lines = [];
|
|
1084
|
+
if (prewriteResult?.ok && prewriteResult.body) {
|
|
1085
|
+
const parsed = prewriteResult.body;
|
|
1086
|
+
if (parsed?.classification) {
|
|
1087
|
+
lines.push(`classification: ${String(parsed.classification)}`);
|
|
1088
|
+
if (parsed.failureCode) {
|
|
1089
|
+
lines.push(`failureCode: ${String(parsed.failureCode)}`);
|
|
1090
|
+
}
|
|
1091
|
+
if (parsed.failureReason) {
|
|
1092
|
+
lines.push(`failureReason: ${String(parsed.failureReason)}`);
|
|
1093
|
+
}
|
|
1094
|
+
} else {
|
|
1095
|
+
lines.push("frontend-prewrite-result.json 内容无法解析为结构化结果。");
|
|
1096
|
+
}
|
|
1097
|
+
} else if (prewriteResult && prewriteResult.status === 404) {
|
|
1098
|
+
lines.push("frontend-prewrite-result.json 尚未生成(门禁未执行或缺失)。");
|
|
1099
|
+
} else if (prewriteResult) {
|
|
1100
|
+
lines.push(
|
|
1101
|
+
"frontend-prewrite-result.json 读取失败(只读接口拒绝/服务异常,安全降级)。",
|
|
1102
|
+
);
|
|
1103
|
+
} else {
|
|
1104
|
+
lines.push("frontend-prewrite-result.json 尚未生成(门禁未执行或缺失)。");
|
|
1105
|
+
}
|
|
1106
|
+
if (ledgerBody && ledgerBody.present === true) {
|
|
1107
|
+
lines.push(formatLedgerSummary(ledgerBody));
|
|
1108
|
+
} else if (ledgerBody) {
|
|
1109
|
+
lines.push("ledger: 无(任务无引用或 ledger 缺失,不伪造计数)");
|
|
1110
|
+
} else if (taskId) {
|
|
1111
|
+
lines.push("ledger: 暂不可查(只读接口失败,安全降级)");
|
|
1112
|
+
}
|
|
1113
|
+
return lines;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/** DAG run 专属 prewrite-result 只读拉取(内联 fetch,不新增 api.js 依赖)。 */
|
|
1117
|
+
async function fetchPrewriteResultJson(dagRunId) {
|
|
1118
|
+
try {
|
|
1119
|
+
const res = await fetch(
|
|
1120
|
+
`/api/dag-runs/${encodeURIComponent(dagRunId)}/prewrite-result`,
|
|
1121
|
+
{ credentials: "same-origin" },
|
|
1122
|
+
);
|
|
1123
|
+
if (!res.ok) return { ok: false, status: res.status };
|
|
1124
|
+
return { ok: true, body: await res.json() };
|
|
1125
|
+
} catch {
|
|
1126
|
+
return null; // 网络失败
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* Resolve the DAG-owned task identity, prewrite result, and safe ledger summary.
|
|
1132
|
+
* Exported so the real browser request sequence can be regression-tested without
|
|
1133
|
+
* relying on source-string assertions.
|
|
1134
|
+
*/
|
|
1135
|
+
export async function fetchFrontendGateLedgerData(dagRunId) {
|
|
1136
|
+
const run = await fetchJson(
|
|
1137
|
+
`/api/dag-runs/${encodeURIComponent(dagRunId)}`,
|
|
1138
|
+
);
|
|
1139
|
+
const taskId = typeof run?.taskId === "string" ? run.taskId : "";
|
|
1140
|
+
const prewriteResult = await fetchPrewriteResultJson(dagRunId);
|
|
1141
|
+
let ledgerBody = null;
|
|
1142
|
+
if (taskId) {
|
|
1143
|
+
ledgerBody = await fetchJson(
|
|
1144
|
+
`/api/tasks/${encodeURIComponent(taskId)}/ledger`,
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
return { taskId, prewriteResult, ledgerBody };
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
/**
|
|
1151
|
+
* Inspect 节点面板:frontend prewrite 门禁分类/稳定 failure code + ledger 安全摘要。
|
|
1152
|
+
* 数据全部来自只读接口与确定性产物(AC-BLOCK-005):task identity 从 DAG run
|
|
1153
|
+
* GET /api/dag-runs/:dagRunId 读取,prewrite 门禁调 DAG run 专属
|
|
1154
|
+
* GET /api/dag-runs/:dagRunId/prewrite-result;任何失败仅显示安全降级文案,
|
|
1155
|
+
* 绝不 push envelope。
|
|
1156
|
+
*/
|
|
1157
|
+
async function renderFrontendGateLedger(content, dagRunId) {
|
|
1158
|
+
const section = el("section", "node-input-section");
|
|
1159
|
+
section.appendChild(
|
|
1160
|
+
el("h4", null, "Frontend prewrite 门禁 / source fidelity ledger"),
|
|
1161
|
+
);
|
|
1162
|
+
const status = el("p", "muted", "加载中…");
|
|
1163
|
+
status.setAttribute("role", "status");
|
|
1164
|
+
section.appendChild(status);
|
|
1165
|
+
content.appendChild(section);
|
|
1166
|
+
try {
|
|
1167
|
+
const { taskId, prewriteResult, ledgerBody } =
|
|
1168
|
+
await fetchFrontendGateLedgerData(dagRunId);
|
|
1169
|
+
const lines = frontendGateLedgerLines({
|
|
1170
|
+
prewriteResult,
|
|
1171
|
+
ledgerBody,
|
|
1172
|
+
taskId,
|
|
1173
|
+
});
|
|
1174
|
+
const pre = el("pre", "cmd", lines.join("\n"));
|
|
1175
|
+
pre.dataset.testid = "frontend-prewrite-ledger-summary";
|
|
1176
|
+
status.replaceWith(pre);
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
status.textContent = `加载失败:${
|
|
1179
|
+
error instanceof Error ? error.message : String(error)
|
|
1180
|
+
}(安全降级,不展示敏感内容)`;
|
|
1181
|
+
}
|
|
986
1182
|
}
|
|
987
1183
|
|
|
988
1184
|
/**
|
|
@@ -1017,29 +1213,78 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1017
1213
|
content.appendChild(error);
|
|
1018
1214
|
return;
|
|
1019
1215
|
}
|
|
1020
|
-
const
|
|
1216
|
+
const frozenBasePrompt =
|
|
1021
1217
|
typeof input.body.prompt?.text === "string" ? input.body.prompt.text : "";
|
|
1022
|
-
if (!
|
|
1218
|
+
if (!frozenBasePrompt) {
|
|
1023
1219
|
const error = el("p", "node-input-error", "该节点没有冻结提示词。");
|
|
1024
1220
|
error.setAttribute("role", "alert");
|
|
1025
1221
|
content.appendChild(error);
|
|
1026
1222
|
return;
|
|
1027
1223
|
}
|
|
1224
|
+
const promptCandidateOptions = buildPromptRestartCandidateOptions(
|
|
1225
|
+
frozenBasePrompt,
|
|
1226
|
+
input.body.promptCandidates,
|
|
1227
|
+
);
|
|
1228
|
+
let selectedCandidate = promptCandidateOptions[0];
|
|
1229
|
+
const historicalCandidatesMissing = Array.isArray(input.body.warnings)
|
|
1230
|
+
? input.body.warnings.some((warning) =>
|
|
1231
|
+
String(warning).startsWith("Failed prompt candidate missing for attempt "),
|
|
1232
|
+
)
|
|
1233
|
+
: false;
|
|
1028
1234
|
|
|
1029
1235
|
if (node.status !== "ERROR") {
|
|
1030
1236
|
// Non-failed nodes still show their frozen prompt — read-only.
|
|
1031
1237
|
const hint = el(
|
|
1032
1238
|
"p",
|
|
1033
1239
|
"muted",
|
|
1034
|
-
`当前节点状态为 ${node.status ?? "unknown"};仅失败(ERROR
|
|
1240
|
+
`当前节点状态为 ${node.status ?? "unknown"};仅失败(ERROR)节点支持编辑提示词并重启。以下可只读查看冻结提示词与历史失败候选。`,
|
|
1035
1241
|
);
|
|
1036
1242
|
hint.setAttribute("role", "status");
|
|
1037
1243
|
content.appendChild(hint);
|
|
1244
|
+
const picker = el("label", "prompt-restart-field");
|
|
1245
|
+
picker.appendChild(el("span", "muted", "提示词版本(只读)"));
|
|
1246
|
+
const select = document.createElement("select");
|
|
1247
|
+
select.className = "prompt-restart-mode-select";
|
|
1248
|
+
select.setAttribute("data-testid", "prompt-restart-candidate");
|
|
1249
|
+
for (const option of promptCandidateOptions) {
|
|
1250
|
+
const element = document.createElement("option");
|
|
1251
|
+
element.value = option.key;
|
|
1252
|
+
element.textContent = option.label;
|
|
1253
|
+
select.appendChild(element);
|
|
1254
|
+
}
|
|
1255
|
+
picker.appendChild(select);
|
|
1256
|
+
if (promptCandidateOptions.length === 1) {
|
|
1257
|
+
picker.appendChild(
|
|
1258
|
+
el(
|
|
1259
|
+
"span",
|
|
1260
|
+
"muted",
|
|
1261
|
+
historicalCandidatesMissing
|
|
1262
|
+
? "该历史运行未保存失败 attempt 的实际提示词;新运行会在此列出。"
|
|
1263
|
+
: "该节点没有已保存的失败 attempt。",
|
|
1264
|
+
),
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
content.appendChild(picker);
|
|
1268
|
+
|
|
1269
|
+
const promptCard = el("div", "prompt-restart-prompt-card");
|
|
1270
|
+
const promptHead = el("div", "prompt-restart-prompt-head");
|
|
1271
|
+
promptHead.appendChild(el("span", "muted", "当前提示词"));
|
|
1272
|
+
promptHead.appendChild(
|
|
1273
|
+
createPromptRestartCopyButton(() => selectedCandidate),
|
|
1274
|
+
);
|
|
1275
|
+
promptCard.appendChild(promptHead);
|
|
1038
1276
|
const pre = document.createElement("pre");
|
|
1039
1277
|
pre.className = "prompt-restart-readonly";
|
|
1040
1278
|
pre.setAttribute("data-testid", "prompt-restart-readonly");
|
|
1041
|
-
pre.textContent =
|
|
1042
|
-
|
|
1279
|
+
pre.textContent = selectedCandidate.prompt;
|
|
1280
|
+
promptCard.appendChild(pre);
|
|
1281
|
+
content.appendChild(promptCard);
|
|
1282
|
+
select.addEventListener("change", () => {
|
|
1283
|
+
selectedCandidate =
|
|
1284
|
+
promptCandidateOptions.find((option) => option.key === select.value) ??
|
|
1285
|
+
promptCandidateOptions[0];
|
|
1286
|
+
pre.textContent = selectedCandidate.prompt;
|
|
1287
|
+
});
|
|
1043
1288
|
return;
|
|
1044
1289
|
}
|
|
1045
1290
|
|
|
@@ -1062,6 +1307,29 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1062
1307
|
"编辑该失败节点的提示词并从它重启:父 run spec 保持冻结,将创建带修改提示词的续跑 run(实验能力,仅 ERROR 节点)。",
|
|
1063
1308
|
),
|
|
1064
1309
|
);
|
|
1310
|
+
const candidateRow = el("label", "prompt-restart-field");
|
|
1311
|
+
candidateRow.appendChild(el("span", "muted", "提示词版本"));
|
|
1312
|
+
const candidateSelect = document.createElement("select");
|
|
1313
|
+
candidateSelect.className = "prompt-restart-mode-select";
|
|
1314
|
+
candidateSelect.setAttribute("data-testid", "prompt-restart-candidate");
|
|
1315
|
+
for (const option of promptCandidateOptions) {
|
|
1316
|
+
const element = document.createElement("option");
|
|
1317
|
+
element.value = option.key;
|
|
1318
|
+
element.textContent = option.label;
|
|
1319
|
+
candidateSelect.appendChild(element);
|
|
1320
|
+
}
|
|
1321
|
+
candidateRow.appendChild(candidateSelect);
|
|
1322
|
+
const candidateHint = el(
|
|
1323
|
+
"span",
|
|
1324
|
+
"muted",
|
|
1325
|
+
promptCandidateOptions.length > 1
|
|
1326
|
+
? "选择失败候选会载入全文替换;冻结原始提示词仍可使用追加模式。"
|
|
1327
|
+
: historicalCandidatesMissing
|
|
1328
|
+
? "该历史运行未保存失败 attempt 的实际提示词;新运行会在此列出。"
|
|
1329
|
+
: "该节点没有已保存的失败 attempt;当前仅有冻结原始提示词。",
|
|
1330
|
+
);
|
|
1331
|
+
candidateRow.appendChild(candidateHint);
|
|
1332
|
+
box.appendChild(candidateRow);
|
|
1065
1333
|
|
|
1066
1334
|
const modeRow = el("div", "prompt-restart-mode");
|
|
1067
1335
|
modeRow.appendChild(el("span", "muted", "编辑模式"));
|
|
@@ -1081,14 +1349,20 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1081
1349
|
modeRow.appendChild(modeSelect);
|
|
1082
1350
|
box.appendChild(modeRow);
|
|
1083
1351
|
|
|
1084
|
-
const baseLabel = el(
|
|
1085
|
-
|
|
1352
|
+
const baseLabel = el(
|
|
1353
|
+
"div",
|
|
1354
|
+
"prompt-restart-field prompt-restart-prompt-card",
|
|
1355
|
+
);
|
|
1356
|
+
const baseHead = el("div", "prompt-restart-prompt-head");
|
|
1357
|
+
baseHead.appendChild(
|
|
1086
1358
|
el("span", "muted", `冻结提示词(只读底稿 · ${nodeId})`),
|
|
1087
1359
|
);
|
|
1360
|
+
baseHead.appendChild(createPromptRestartCopyButton(() => selectedCandidate));
|
|
1361
|
+
baseLabel.appendChild(baseHead);
|
|
1088
1362
|
const baseBlock = document.createElement("pre");
|
|
1089
1363
|
baseBlock.className = "prompt-restart-readonly";
|
|
1090
1364
|
baseBlock.setAttribute("data-testid", "prompt-restart-base");
|
|
1091
|
-
baseBlock.textContent =
|
|
1365
|
+
baseBlock.textContent = selectedCandidate.prompt;
|
|
1092
1366
|
baseLabel.appendChild(baseBlock);
|
|
1093
1367
|
box.appendChild(baseLabel);
|
|
1094
1368
|
|
|
@@ -1113,7 +1387,7 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1113
1387
|
const replaceArea = document.createElement("textarea");
|
|
1114
1388
|
replaceArea.className = "prompt-restart-textarea";
|
|
1115
1389
|
replaceArea.rows = 16;
|
|
1116
|
-
replaceArea.value =
|
|
1390
|
+
replaceArea.value = selectedCandidate.prompt;
|
|
1117
1391
|
replaceArea.setAttribute("data-testid", "prompt-restart-textarea");
|
|
1118
1392
|
replaceLabel.appendChild(replaceArea);
|
|
1119
1393
|
box.appendChild(replaceLabel);
|
|
@@ -1125,6 +1399,25 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1125
1399
|
replaceLabel.style.display = append ? "none" : "";
|
|
1126
1400
|
};
|
|
1127
1401
|
modeSelect.addEventListener("change", applyModeVisibility);
|
|
1402
|
+
candidateSelect.addEventListener("change", () => {
|
|
1403
|
+
selectedCandidate =
|
|
1404
|
+
promptCandidateOptions.find(
|
|
1405
|
+
(option) => option.key === candidateSelect.value,
|
|
1406
|
+
) ?? promptCandidateOptions[0];
|
|
1407
|
+
baseBlock.textContent = selectedCandidate.prompt;
|
|
1408
|
+
replaceArea.value = selectedCandidate.prompt;
|
|
1409
|
+
if (selectedCandidate.key !== "frozen") {
|
|
1410
|
+
modeSelect.value = "replace";
|
|
1411
|
+
}
|
|
1412
|
+
if (candidateHint) {
|
|
1413
|
+
candidateHint.textContent = selectedCandidate.truncated
|
|
1414
|
+
? "该候选已截断,仅供查看,不能直接提交重启。"
|
|
1415
|
+
: selectedCandidate.key === "frozen"
|
|
1416
|
+
? "冻结原始提示词可使用追加或全文替换。"
|
|
1417
|
+
: "已载入该失败候选;可在全文替换编辑器中修订后重启。";
|
|
1418
|
+
}
|
|
1419
|
+
applyModeVisibility();
|
|
1420
|
+
});
|
|
1128
1421
|
applyModeVisibility();
|
|
1129
1422
|
|
|
1130
1423
|
const reasonLabel = el("label", "prompt-restart-field");
|
|
@@ -1160,6 +1453,10 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1160
1453
|
|
|
1161
1454
|
submit.addEventListener("click", () => {
|
|
1162
1455
|
void (async () => {
|
|
1456
|
+
if (selectedCandidate.truncated) {
|
|
1457
|
+
setStatus("所选失败提示词已截断,不能作为完整提示词重启。", true);
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1163
1460
|
const mode = modeSelect.value === "replace" ? "replace" : "append";
|
|
1164
1461
|
const prompt =
|
|
1165
1462
|
mode === "append" ? addendumArea.value : replaceArea.value;
|
|
@@ -1171,7 +1468,11 @@ async function renderPromptRestart(content, dagRunId, node) {
|
|
|
1171
1468
|
);
|
|
1172
1469
|
return;
|
|
1173
1470
|
}
|
|
1174
|
-
if (
|
|
1471
|
+
if (
|
|
1472
|
+
mode === "replace" &&
|
|
1473
|
+
selectedCandidate.key === "frozen" &&
|
|
1474
|
+
prompt === frozenBasePrompt
|
|
1475
|
+
) {
|
|
1175
1476
|
setStatus("提示词未修改;如需按原提示词重跑请使用恢复工作台。", true);
|
|
1176
1477
|
return;
|
|
1177
1478
|
}
|
|
@@ -2106,11 +2407,10 @@ function buildDagInspectorHeader(dagRunId, node) {
|
|
|
2106
2407
|
["timeline", "执行过程", "ri-route-line"],
|
|
2107
2408
|
["spec-evidence", "规范证据", "ri-book-open-line"],
|
|
2108
2409
|
];
|
|
2109
|
-
//
|
|
2110
|
-
//
|
|
2111
|
-
//
|
|
2112
|
-
//
|
|
2113
|
-
// strictly read-only.
|
|
2410
|
+
// Prompt-override rerun: the server injects the SCOPED inspect mutation
|
|
2411
|
+
// token into /inspect HTML by default (it only authorizes
|
|
2412
|
+
// dagRerunPlan/dagRerun). The emergency kill switch suppresses the token;
|
|
2413
|
+
// no token → no tab → the inspector stays strictly read-only.
|
|
2114
2414
|
if (globalThis.__INSPECT_MUTATION_TOKEN__) {
|
|
2115
2415
|
tabDefs.push(["prompt-restart", "提示词重启", "ri-restart-line"]);
|
|
2116
2416
|
}
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
repoBaseName,
|
|
35
35
|
parseMarkdownBlocks,
|
|
36
36
|
markdownLinkHref,
|
|
37
|
+
formatLedgerSummary,
|
|
37
38
|
} from "../format.js";
|
|
38
39
|
import { parseRoute, navigate, buildHashPath } from "../router.js";
|
|
39
40
|
import {
|
|
@@ -94,6 +95,7 @@ export async function renderTaskDetail(featureId, taskId) {
|
|
|
94
95
|
const linksEl = document.getElementById("task-links");
|
|
95
96
|
const evidenceEl = document.getElementById("task-evidence");
|
|
96
97
|
const runsEl = document.getElementById("task-runs");
|
|
98
|
+
const ledgerEl = document.getElementById("task-ledger");
|
|
97
99
|
if (!metaEl || !linksEl || !evidenceEl || !runsEl) return false;
|
|
98
100
|
if (titleEl) titleEl.textContent = displayLabel;
|
|
99
101
|
mountViewState(metaEl, "loading", "加载 Task…");
|
|
@@ -252,9 +254,62 @@ export async function renderTaskDetail(featureId, taskId) {
|
|
|
252
254
|
const active = ["running", "pending", "blocked", "stale"].includes(
|
|
253
255
|
(task.status ?? "").toLowerCase(),
|
|
254
256
|
);
|
|
257
|
+
if (ledgerEl) {
|
|
258
|
+
void renderTaskLedger(ledgerEl, taskId);
|
|
259
|
+
}
|
|
255
260
|
return active;
|
|
256
261
|
}
|
|
257
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Task 详情页 ledger 安全摘要(AC-009):从只读 GET /api/tasks/:id/ledger 拉取,
|
|
265
|
+
* 计数/状态/failure code + 有界排除理由;无 ledger 显示空态,失败安全降级。
|
|
266
|
+
*/
|
|
267
|
+
async function renderTaskLedger(ledgerEl, taskId) {
|
|
268
|
+
clearNode(ledgerEl);
|
|
269
|
+
ledgerEl.appendChild(el("h3", "section-heading", "Source fidelity ledger"));
|
|
270
|
+
mountViewState(ledgerEl, "loading", "加载 ledger…");
|
|
271
|
+
let body = null;
|
|
272
|
+
try {
|
|
273
|
+
body = await fetchJson(`/api/tasks/${encodeURIComponent(taskId)}/ledger`);
|
|
274
|
+
} catch {
|
|
275
|
+
body = null;
|
|
276
|
+
}
|
|
277
|
+
if (!body) {
|
|
278
|
+
mountViewState(ledgerEl, "error", "ledger 只读接口失败(安全降级)。");
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (body.present !== true) {
|
|
282
|
+
mountViewState(
|
|
283
|
+
ledgerEl,
|
|
284
|
+
body.error ? "error" : "empty",
|
|
285
|
+
body.error
|
|
286
|
+
? `ledger 不可用(${body.error})。`
|
|
287
|
+
: "无 source fidelity ledger(任务无引用或 ledger 缺失,不伪造计数)。",
|
|
288
|
+
);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const pre = el(
|
|
292
|
+
"pre",
|
|
293
|
+
"cmd",
|
|
294
|
+
formatLedgerSummary({
|
|
295
|
+
present: true,
|
|
296
|
+
fragmentCount: body.fragmentCount,
|
|
297
|
+
canonicalRequirementCount: body.canonicalRequirementCount,
|
|
298
|
+
contextOnlyCount: body.contextOnlyCount,
|
|
299
|
+
duplicateCount: body.duplicateCount,
|
|
300
|
+
unresolvedCount: body.unresolvedCount,
|
|
301
|
+
conflictCount: body.conflictCount,
|
|
302
|
+
gateStatus: body.gateStatus,
|
|
303
|
+
failureCode: body.failureCode,
|
|
304
|
+
exclusions: body.exclusions ?? [],
|
|
305
|
+
}),
|
|
306
|
+
);
|
|
307
|
+
pre.dataset.testid = "task-ledger-summary";
|
|
308
|
+
clearNode(ledgerEl);
|
|
309
|
+
ledgerEl.appendChild(el("h3", "section-heading", "Source fidelity ledger"));
|
|
310
|
+
ledgerEl.appendChild(pre);
|
|
311
|
+
}
|
|
312
|
+
|
|
258
313
|
export async function loadOlderTaskRuns(featureId, taskId, before, runsEl) {
|
|
259
314
|
if (runsEl === undefined) {
|
|
260
315
|
runsEl = before;
|
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import YAML from "yaml";
|
|
7
7
|
import { resolveBackendTestLayout, } from "./backend-test-layout.js";
|
|
8
|
+
import { resolveBackendTestManifestModules, backendManifestStatusFinding } from "./backend-test-markdown-workflow.js";
|
|
8
9
|
import { applyOpenApiPartitionDomainPolicy, assessScenarioPartitionCoverage, parseScenarioPartitions, } from "./backend-test-scenario-partitions.js";
|
|
9
10
|
import { backendTestCaseManifestSchema, computeCaseManifestCoverageSummary, } from "./backend-test-case-manifest.js";
|
|
10
11
|
const CASE_HEADING = /^##\s+(BE-[A-Z0-9_-]+-\d{3})\b.*$/gm;
|
|
@@ -659,10 +660,18 @@ async function markdownModuleFiles(workspaceRoot, layout) {
|
|
|
659
660
|
const resolved = layout ?? resolveBackendTestLayout(undefined);
|
|
660
661
|
const dir = path.join(workspaceRoot, ...resolved.markdownDir.split("/"));
|
|
661
662
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
662
|
-
|
|
663
|
+
const all = entries
|
|
663
664
|
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md") && entry.name.toLowerCase() !== "readme.md")
|
|
664
665
|
.map((entry) => path.join(dir, entry.name))
|
|
665
666
|
.sort();
|
|
667
|
+
// Current-round Markdown scope is the README Module Index manifest: unmanifested
|
|
668
|
+
// legacy .md files must not enter Case/coverage/partition statistics. Workspaces
|
|
669
|
+
// without a Module Index fall back to the whole directory (prior behaviour).
|
|
670
|
+
const manifest = await resolveBackendTestManifestModules(workspaceRoot, resolved);
|
|
671
|
+
if (manifest.manifestStatus !== "valid")
|
|
672
|
+
return [];
|
|
673
|
+
const declaredAbs = new Set(manifest.declaredMdFiles.map((rel) => path.join(workspaceRoot, ...rel.split("/")).toLowerCase()));
|
|
674
|
+
return all.filter((file) => declaredAbs.has(file.toLowerCase())).sort();
|
|
666
675
|
}
|
|
667
676
|
async function parseMarkdownCases(workspaceRoot, layout) {
|
|
668
677
|
const result = [];
|
|
@@ -874,6 +883,16 @@ export async function analyzeBackendTestCaseCoverage(input) {
|
|
|
874
883
|
findings.push(...coverageScope.findings);
|
|
875
884
|
const matrix = parseCoverageMatrix(readme);
|
|
876
885
|
findings.push(...matrix.findings);
|
|
886
|
+
const manifest = await resolveBackendTestManifestModules(input.workspaceRoot, layout);
|
|
887
|
+
// Fail closed on README Module Index entries declared but never materialized.
|
|
888
|
+
if (manifest.declaredMissing.length > 0) {
|
|
889
|
+
findings.push(`declared Markdown module is missing (Module Index): ${manifest.declaredMissing.join(", ")}`);
|
|
890
|
+
}
|
|
891
|
+
// Fail closed on every non-valid Module Index state: without a valid unique
|
|
892
|
+
// Module Index the current round must not re-scan historical `.md` files.
|
|
893
|
+
if (manifest.manifestStatus !== "valid") {
|
|
894
|
+
findings.push(backendManifestStatusFinding(manifest.manifestStatus, manifest.moduleIndexHeadingCount));
|
|
895
|
+
}
|
|
877
896
|
const cases = await parseMarkdownCases(input.workspaceRoot, layout);
|
|
878
897
|
for (const file of await markdownModuleFiles(input.workspaceRoot, layout)) {
|
|
879
898
|
const markdown = await readFile(file, "utf8");
|
|
@@ -1109,6 +1128,15 @@ export async function analyzeBackendTestCaseCoverage(input) {
|
|
|
1109
1128
|
"",
|
|
1110
1129
|
facts.status,
|
|
1111
1130
|
"",
|
|
1131
|
+
`- Manifest scope: ${manifest.manifestStatus === "valid" ? "declared-modules" : `fails-closed:${manifest.manifestStatus}`} (declared-missing=${manifest.declaredMissing.length}, unmanifested-advisory=${manifest.unmanifested.length}, module-index-heading-count=${manifest.moduleIndexHeadingCount})`,
|
|
1132
|
+
"",
|
|
1133
|
+
"## Unmanifested Markdown Modules",
|
|
1134
|
+
"",
|
|
1135
|
+
"- Advisory: existing .md files not declared by the README Module Index are excluded from Case/coverage/partition statistics for this round.",
|
|
1136
|
+
...(manifest.unmanifested.length > 0
|
|
1137
|
+
? manifest.unmanifested.map((item) => `- ${item}`)
|
|
1138
|
+
: ["- None"]),
|
|
1139
|
+
"",
|
|
1112
1140
|
"## Coverage Scope",
|
|
1113
1141
|
"",
|
|
1114
1142
|
`- Change Classification: ${facts.coverageScope.changeClassification}`,
|