@tea-agent/loop-agent 0.40.0-next.1 → 0.40.0-next.10
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 +106 -109
- 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/dag-pi-executor.js +69 -7
- package/dist/executors/pi-executor.js +4 -2
- package/dist/executors/pi-sdk-executor.js +46 -1
- package/dist/executors/pi-writer-tool-policy.js +57 -1
- package/dist/executors/shell-executor.js +43 -3
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/infrastructure/harness/atomic-write.js +23 -0
- package/dist/shared/dag-prompt-override.js +4 -5
- package/dist/shared/update/npm-client.js +33 -11
- package/dist/task/config-types.js +10 -0
- 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/chat/chat-event-store.js +75 -5
- package/dist/worker/console/chat/context-panel.js +7 -0
- package/dist/worker/console/chat/deferred-turn.js +12 -0
- package/dist/worker/console/chat/pi-runtime.js +331 -18
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/routes.js +389 -56
- package/dist/worker/console/chat/session-stats.js +179 -0
- package/dist/worker/console/chat/session-store.js +93 -63
- package/dist/worker/console/chat/todos.js +115 -0
- package/dist/worker/console/chat/tool-preview.js +42 -0
- package/dist/worker/console/chat/turn-process.js +20 -55
- package/dist/worker/console/chat/user-questions.js +266 -0
- package/dist/worker/console/chat/workspace-landing.js +79 -0
- 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/native-directory-picker.js +13 -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 +312 -1
- package/dist/worker/console/security.js +4 -4
- package/dist/worker/console/server.js +583 -172
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Br92EGb7.js → abnfDiagram-N423BO3Z-Bs-CDAXM.js} +1 -1
- package/dist/worker/console/static/assets/{arc-oulImtQq.js → arc-CAkA3We3.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-tyHc6aqO.js → architectureDiagram-T3A2C74G-DEmI_zqr.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CY86BSpT.js → blockDiagram-VBNYF7ZC-uDHANebE.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BKKsDmNs.js → c4Diagram-5PPSVZJV-Bv97b4_B.js} +1 -1
- package/dist/worker/console/static/assets/channel-amxUpk7o.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CXZdjl3B.js → chunk-2GRJ4B5K-shmQKsFz.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B-aIFDQ5.js → chunk-2Q5K7J3B-BAraNvcN.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BDgAW_B2.js → chunk-5RXB4S5H-BirhxUop.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DABa4UPR.js → chunk-5VM5RSS4-C9LgUJpR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-aGbysVOx.js → chunk-6Q2QTUOP-DrU-s-t7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B8MSZDVs.js → chunk-GF5L2VYU-Tx-H_FA2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CpelV73g.js → chunk-JWPE2WC7-BjLtNFrR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BE04ty3z.js → chunk-KBJHAD2P-UNLVwSsy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BUxTkaaR.js → chunk-RYQCIY6F-CBEUX8XD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CYYBVKfX.js → chunk-XXDRQBXY-WTL7IZOq.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CEQF_xmQ.js → cose-bilkent-JH36ORCC-CkHG6W3s.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CqpxN5IT.js → cynefin-VYW2F7L2-BRzFqTaL.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-B3PzanVi.js → cynefinDiagram-MW4NZA55-2_CFp_i7.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BSUGCNx1.js → dagre-VZM6K2ZE-BJtsmdkE.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CdLevU5T.js → diagram-7IWD3JNH-DNEFFo6w.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BjpF_Kok.js → diagram-B4RE2ZJO-B_IWV7eo.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C4I6NJny.js → diagram-LBJQPF4R-DvvRx2dz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C8hecCr5.js → diagram-Q27KOJAE-CsibsWLg.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Daod6tOS.js → diagram-UB23O5K3-les_djBQ.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BvvmazGv.js → ebnfDiagram-BXEA7PRR-BEm2n7qJ.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DeN_vfV4.js → erDiagram-JOGREHBK-BI7vHHVm.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-D6dq1t9z.js → flowDiagram-UKHOOZJN-CUFCERrB.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DziIdNY4.js → ganttDiagram-PKOTCBZU-wxyLiGWt.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dy4s5CXr.js → gitGraphDiagram-DS77QQ5N-Bt897K8Z.js} +1 -1
- package/dist/worker/console/static/assets/index-BHhUOTri.css +1 -0
- package/dist/worker/console/static/assets/index-DqJbO3-p.js +407 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czv5eYx2.js → infoDiagram-6WML65LV-DYNx_IPv.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-Bsk3nFRT.js → ishikawaDiagram-WSZJBQD7-CzpLNEu6.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DxGmNj5K.js → journeyDiagram-NVQOT4AX-97f9owNU.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CYCFqkO_.js → kanban-definition-27J2QSJJ-BxM9hyBn.js} +1 -1
- package/dist/worker/console/static/assets/{linear-B0YzRwrd.js → linear-ByuxcHvp.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-t3EZtIGh.js → mermaid.core-DwPGvpCv.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BTXpTv0T.js → mindmap-definition-FAOFIHXS-DCoqG9RT.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DU8UkKqA.js → pegDiagram-VL7TDLO6-BLSBmBMx.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DP4y8ohp.js → pieDiagram-7S7Q4E2Y-B0t_0oJW.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B07vNaYR.js → quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-xDWktb2i.js → railroadDiagram-AXF67PYL-B3jRdATC.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BLNMKqeS.js → requirementDiagram-LRYGKXZP-D-cTtXEN.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-n0adalxH.js → sankeyDiagram-W5VNT64P-B25igJRE.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BbnS-2DL.js → sequenceDiagram-SI44F4Z6-jW_lM1_D.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4uPpCLu.js → sizeCapture-X5ZJPWSS-CrzTgsHE.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA--MMbB0fX.js → stateDiagram-OKZ733FA-B01MvlqE.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BXUgp2QY.js → swimlanes-SLNWSIFB-YcF12FiU.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CsaMxki7.js → timeline-definition-Z64GVDOM-ClnRcg2s.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-C2IuG9iH.js → vennDiagram-T6HMQDX7-B6gr_z-q.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CmEWOXCW.js → wardleyDiagram-T6FBY63Y-CvSgguGH.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-D_9LE0MG.js → xychartDiagram-ELKLHX3M-23OtSZCb.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +68 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +10 -11
- package/dist/worker/console/static-src/app/useRunProgress.js +2 -1
- package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
- package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +3 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +159 -44
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
- package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
- package/dist/worker/console/workspace-context.js +234 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- 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/server.js +4 -0
- package/dist/worker/observe/static/api.js +29 -9
- package/dist/worker/observe/static/app.js +18 -70
- package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
- package/dist/worker/observe/static/dag-node-purpose.js +203 -0
- package/dist/worker/observe/static/format.js +37 -0
- package/dist/worker/observe/static/index.html +6 -1
- package/dist/worker/observe/static/inspect-workspace.js +307 -0
- package/dist/worker/observe/static/operator-chrome.css +145 -20
- package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
- package/dist/worker/observe/static/operator-chrome.js +330 -100
- 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/router.d.ts +46 -0
- package/dist/worker/observe/static/router.js +25 -1
- package/dist/worker/observe/static/state.d.ts +50 -0
- package/dist/worker/observe/static/state.js +174 -2
- package/dist/worker/observe/static/styles.css +357 -35
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +533 -79
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +6 -4
- package/dist/worker/observe/static/views/dashboard.js +6 -4
- package/dist/worker/observe/static/views/night.js +2 -2
- package/dist/worker/observe/static/views/pool.js +1 -0
- package/dist/worker/observe/static/views/run.js +3 -1
- package/dist/worker/observe/static/views/task.js +56 -1
- 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 +98 -14
- package/dist/workflows/dag/backend-test-writer-completeness.js +17 -17
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
- 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 +238 -51
- package/dist/workflows/dag/interrupt-request.js +3 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/node-execution.js +45 -0
- package/dist/workflows/dag/types.js +62 -12
- package/docs/README.md +2 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/operations/local-development-environment.md +21 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +18 -0
- package/docs/templates/backend-test-dag.json +265 -237
- package/docs/templates/frontend-implementation-contract.schema.json +681 -24
- package/harness.json +3 -3
- package/package.json +3 -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/index-Ci8ksvBm.js +0 -406
- 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
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface DagNodeRemark {
|
|
2
|
+
failed: boolean;
|
|
3
|
+
text: string;
|
|
4
|
+
isErrorSummary: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function resolveNodeRemark(node: {
|
|
8
|
+
status?: string | null;
|
|
9
|
+
errorPreview?: string | null;
|
|
10
|
+
label?: string | null;
|
|
11
|
+
nodeId: string;
|
|
12
|
+
rank?: string | number | null;
|
|
13
|
+
} | null | undefined): DagNodeRemark;
|
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
dashboardVisibleFeatures,
|
|
71
71
|
findDagForRun,
|
|
72
72
|
} from "../dag-model.js";
|
|
73
|
+
import { describeDagNodePurpose } from "../dag-node-purpose.js";
|
|
73
74
|
import { layoutDag } from "../dag-layout.js";
|
|
74
75
|
import {
|
|
75
76
|
showView,
|
|
@@ -136,7 +137,7 @@ export function extractFirstErrorSummary(errorPreview) {
|
|
|
136
137
|
return effective ?? "";
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
/** Resolve 备注 cell content for a DAG node (failed → summary; else
|
|
140
|
+
/** Resolve 备注 cell content for a DAG node (failed → summary; else purpose). */
|
|
140
141
|
export function resolveNodeRemark(node) {
|
|
141
142
|
const failed = isNodeFailed(node?.status);
|
|
142
143
|
if (failed) {
|
|
@@ -147,10 +148,10 @@ export function resolveNodeRemark(node) {
|
|
|
147
148
|
isErrorSummary: Boolean(summary),
|
|
148
149
|
};
|
|
149
150
|
}
|
|
150
|
-
const
|
|
151
|
+
const purpose = describeDagNodePurpose(node?.nodeId, node);
|
|
151
152
|
return {
|
|
152
153
|
failed: false,
|
|
153
|
-
text:
|
|
154
|
+
text: purpose || "—",
|
|
154
155
|
isErrorSummary: false,
|
|
155
156
|
};
|
|
156
157
|
}
|
|
@@ -363,7 +364,7 @@ export async function renderDagDetail(dagRunId, initial = true) {
|
|
|
363
364
|
"page-action-btn",
|
|
364
365
|
"在操作台中止此运行 →",
|
|
365
366
|
);
|
|
366
|
-
interruptLink.href = `/#/recovery?dagRunId=${encodeURIComponent(dag.dagRunId)}&intent=interrupt`;
|
|
367
|
+
interruptLink.href = `/#/operate/recovery?dagRunId=${encodeURIComponent(dag.dagRunId)}&intent=interrupt`;
|
|
367
368
|
interruptLink.setAttribute("data-testid", "inspect-dag-interrupt-link");
|
|
368
369
|
interruptLink.title = "只跳转到操作台 Recovery,不会在观测页执行中止";
|
|
369
370
|
primaryEntries.push(["协作中止", interruptLink]);
|
|
@@ -481,6 +482,7 @@ export function startDagPolling(dagRunId, initial = true) {
|
|
|
481
482
|
const active = await renderDagDetail(dagRunId, isInitial);
|
|
482
483
|
if (generation !== uiState.pollingGeneration || !active) return;
|
|
483
484
|
const delay = detailPollDelay("running", isPageVisible());
|
|
485
|
+
if (delay == null) return;
|
|
484
486
|
uiState.dagPollTimer = setTimeout(() => {
|
|
485
487
|
uiState.dagPollTimer = null;
|
|
486
488
|
void poll(false);
|
|
@@ -84,7 +84,7 @@ let dashboardLoadingNode = null;
|
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* 「昨夜班次」条带:夜间结果在观测侧的唯一一级呈现(替代旧「夜间任务」tab)。
|
|
87
|
-
* 管理动作(晨审处置)始终落在操作侧 /#/night,保持 Inspect 只读不变式。
|
|
87
|
+
* 管理动作(晨审处置)始终落在操作侧 /#/operate/night,保持 Inspect 只读不变式。
|
|
88
88
|
*/
|
|
89
89
|
async function renderNightStrip() {
|
|
90
90
|
const strip = document.getElementById("dashboard-night");
|
|
@@ -118,14 +118,14 @@ async function renderNightStrip() {
|
|
|
118
118
|
|
|
119
119
|
const actions = el("div", "night-strip-actions");
|
|
120
120
|
const detail = el("a", "page-action-btn", "夜间详情");
|
|
121
|
-
detail.href = "
|
|
121
|
+
detail.href = "/#/inspect/night";
|
|
122
122
|
detail.addEventListener("click", (e) => {
|
|
123
123
|
e.preventDefault();
|
|
124
124
|
navigate("/night");
|
|
125
125
|
});
|
|
126
126
|
actions.appendChild(detail);
|
|
127
127
|
const manage = el("a", "page-action-btn night-strip-manage", "晨审处置 →");
|
|
128
|
-
manage.href = "/#/night";
|
|
128
|
+
manage.href = "/#/operate/night";
|
|
129
129
|
manage.title = "夜间计划的管理与晨审处置在操作台完成(Inspect 保持只读)";
|
|
130
130
|
actions.appendChild(manage);
|
|
131
131
|
strip.appendChild(actions);
|
|
@@ -738,10 +738,12 @@ export function startDashboardPolling(scrollTo) {
|
|
|
738
738
|
const poll = async (initialScrollTo) => {
|
|
739
739
|
await renderDashboard(initialScrollTo);
|
|
740
740
|
if (generation !== uiState.pollingGeneration) return;
|
|
741
|
+
const delay = dashboardPollDelay();
|
|
742
|
+
if (delay == null) return;
|
|
741
743
|
uiState.dashboardTimer = setTimeout(() => {
|
|
742
744
|
uiState.dashboardTimer = null;
|
|
743
745
|
void poll();
|
|
744
|
-
},
|
|
746
|
+
}, delay);
|
|
745
747
|
};
|
|
746
748
|
void poll(scrollTo);
|
|
747
749
|
}
|
|
@@ -46,8 +46,8 @@ export async function renderNightJobs(scheduleId) {
|
|
|
46
46
|
showView("night");
|
|
47
47
|
bindNightToolbar();
|
|
48
48
|
const crumbs = [
|
|
49
|
-
{ label: UI_TEXT.dashboard, href: "
|
|
50
|
-
{ label: UI_TEXT.nightJobs, href: "
|
|
49
|
+
{ label: UI_TEXT.dashboard, href: "/#/inspect" },
|
|
50
|
+
{ label: UI_TEXT.nightJobs, href: "/#/inspect/night" },
|
|
51
51
|
];
|
|
52
52
|
if (scheduleId) crumbs.push({ label: scheduleId });
|
|
53
53
|
setBreadcrumb(crumbs);
|
|
@@ -93,6 +93,7 @@ function startPoolAutoPolling() {
|
|
|
93
93
|
if (generation !== uiState.pollingGeneration) return;
|
|
94
94
|
if (!poolAutoRefreshEnabled()) return;
|
|
95
95
|
const delay = isPageVisible() ? POOL_POLL_MS : dashboardPollDelay();
|
|
96
|
+
if (delay == null) return;
|
|
96
97
|
uiState.poolTimer = setTimeout(() => {
|
|
97
98
|
uiState.poolTimer = null;
|
|
98
99
|
void poll();
|
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
} from "../relations.js";
|
|
51
51
|
import { copyText, renderRecommendedCommand } from "../copy.js";
|
|
52
52
|
import {
|
|
53
|
+
fetchWithWorkspaceScope,
|
|
53
54
|
isPageVisible,
|
|
54
55
|
detailPollDelay,
|
|
55
56
|
dashboardPollDelay,
|
|
@@ -126,7 +127,7 @@ export function renderInlineArtifactList(container, artifacts) {
|
|
|
126
127
|
list.appendChild(li);
|
|
127
128
|
void (async () => {
|
|
128
129
|
try {
|
|
129
|
-
const res = await
|
|
130
|
+
const res = await fetchWithWorkspaceScope(artifactUrl(artifactPath));
|
|
130
131
|
const text = await res.text();
|
|
131
132
|
if (!res.ok) {
|
|
132
133
|
let message = text;
|
|
@@ -444,6 +445,7 @@ export function startRunPolling(workerRunId, initial = true) {
|
|
|
444
445
|
const active = await renderRun(workerRunId, isInitial);
|
|
445
446
|
if (generation !== uiState.pollingGeneration || !active) return;
|
|
446
447
|
const delay = detailPollDelay("running", isPageVisible());
|
|
448
|
+
if (delay == null) return;
|
|
447
449
|
uiState.runPollTimer = setTimeout(() => {
|
|
448
450
|
uiState.runPollTimer = null;
|
|
449
451
|
void poll(false);
|
|
@@ -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 {
|
|
@@ -85,7 +86,7 @@ export async function renderTaskDetail(featureId, taskId) {
|
|
|
85
86
|
showView("task");
|
|
86
87
|
setBreadcrumb([
|
|
87
88
|
{ label: UI_TEXT.dashboard, href: "#/" },
|
|
88
|
-
{ label: "资源池", href: "
|
|
89
|
+
{ label: "资源池", href: "/#/inspect/pool" },
|
|
89
90
|
{ label: displayLabel },
|
|
90
91
|
]);
|
|
91
92
|
|
|
@@ -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}`,
|
|
@@ -200,14 +200,170 @@ export async function inspectBackendTestEnvironment(input) {
|
|
|
200
200
|
serverEntries,
|
|
201
201
|
};
|
|
202
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Filenames declared by the canonical `## Module Index` section of the backend
|
|
205
|
+
* README. Only a unique exact `## Module Index` heading is authoritative; a
|
|
206
|
+
* missing/duplicate section yields an empty declaration set (callers then fall
|
|
207
|
+
* back to the whole directory so legacy workspaces without a manifest keep
|
|
208
|
+
* their prior behaviour). Reads `[x](./x.md)`, `` `testcase/md/<x>.md` `` and
|
|
209
|
+
* inline `x.md` tokens inside the section; README.md itself is never a module.
|
|
210
|
+
*/
|
|
211
|
+
export function extractDeclaredModuleIndexFilenames(readme) {
|
|
212
|
+
const lines = readme
|
|
213
|
+
.replaceAll("\r\n", "\n")
|
|
214
|
+
.replaceAll("\r", "\n")
|
|
215
|
+
.split("\n");
|
|
216
|
+
const headings = lines.flatMap((line, index) => line.trim() === "## Module Index" ? [index] : []);
|
|
217
|
+
if (headings.length !== 1)
|
|
218
|
+
return [];
|
|
219
|
+
const start = headings[0] + 1;
|
|
220
|
+
const relativeEnd = lines
|
|
221
|
+
.slice(start)
|
|
222
|
+
.findIndex((line) => /^##\s+\S/.test(line.trim()));
|
|
223
|
+
const end = relativeEnd < 0 ? lines.length : start + relativeEnd;
|
|
224
|
+
const section = lines.slice(start, end).join("\n");
|
|
225
|
+
return unique([...section.matchAll(/`?[A-Za-z0-9_.-]+\.md`?/g)]
|
|
226
|
+
.map((match) => match[0].replaceAll("`", ""))
|
|
227
|
+
.filter((name) => name.toLowerCase() !== "readme.md"));
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Count exact `## Module Index` headings in the backend README.
|
|
231
|
+
*/
|
|
232
|
+
function moduleIndexHeadingCountIn(readme) {
|
|
233
|
+
return readme
|
|
234
|
+
.replaceAll("\r\n", "\n")
|
|
235
|
+
.replaceAll("\r", "\n")
|
|
236
|
+
.split("\n")
|
|
237
|
+
.filter((line) => line.trim() === "## Module Index").length;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Machine-readable fail-closed finding for an invalid backend-test Module Index.
|
|
241
|
+
* These are distinct states (missing/unreadable/duplicate/empty-unparsable) so a
|
|
242
|
+
* stripped Module Index can never silently fall back to a whole-directory scan.
|
|
243
|
+
*/
|
|
244
|
+
export function backendManifestStatusFinding(manifestStatus, moduleIndexHeadingCount = 0) {
|
|
245
|
+
switch (manifestStatus) {
|
|
246
|
+
case "missing":
|
|
247
|
+
return "README is missing; backend-test current round fails closed without a declarative Module Index";
|
|
248
|
+
case "unreadable":
|
|
249
|
+
return "README is unreadable; backend-test current round fails closed without a usable Module Index";
|
|
250
|
+
case "duplicate":
|
|
251
|
+
return moduleIndexHeadingCount === 0
|
|
252
|
+
? "README Module Index heading is missing; backend-test current round fails closed without a declarative Module Index"
|
|
253
|
+
: `duplicate ## Module Index heading (${moduleIndexHeadingCount} occurrences); backend-test current round fails closed without a unique Module Index`;
|
|
254
|
+
case "empty-unparsable":
|
|
255
|
+
return "README Module Index is empty/unparsable; backend-test current round fails closed with no declared module";
|
|
256
|
+
case "valid":
|
|
257
|
+
return "";
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Resolve the current-round Markdown module manifest. The README `## Module
|
|
262
|
+
* Index` is the authoritative source for which module files belong to this
|
|
263
|
+
* round. Fail closed on every non-`valid` state (missing/unreadable/duplicate/
|
|
264
|
+
* empty-unparsable): the backend-test current round must NEVER re-scan the whole
|
|
265
|
+
* directory, or historical `.md` files could leak back into Case/coverage/
|
|
266
|
+
* partition facts.
|
|
267
|
+
*
|
|
268
|
+
* - `manifestStatus==="valid"` — unique heading with >= 1 declared `.md` file.
|
|
269
|
+
* - `missing` — README file does not exist.
|
|
270
|
+
* - `unreadable` — README exists but could not be read.
|
|
271
|
+
* - `duplicate` — README has 0 or duplicate `## Module Index`.
|
|
272
|
+
* - `empty-unparsable` — unique heading but no `.md` filename parsed.
|
|
273
|
+
* - `declaredMdFiles` — declared filenames that exist on disk (relative paths).
|
|
274
|
+
* - `declaredMissing` — declared filenames with no file on disk (fail-closed
|
|
275
|
+
* evidence, never silently dropped).
|
|
276
|
+
* - `unmanifested` — existing `.md` files that the Module Index does not
|
|
277
|
+
* declare (non-silent advisory; they must not enter Case/coverage/partition).
|
|
278
|
+
*/
|
|
279
|
+
export async function resolveBackendTestManifestModules(workspaceRoot, layout) {
|
|
280
|
+
const resolved = layout ?? resolveBackendTestLayout(undefined);
|
|
281
|
+
const readmePath = path.join(workspaceRoot, ...resolved.readmePath.split("/"));
|
|
282
|
+
const invalid = (manifestStatus, moduleIndexHeadingCount) => ({
|
|
283
|
+
hasManifest: false,
|
|
284
|
+
manifestStatus,
|
|
285
|
+
moduleIndexHeadingCount,
|
|
286
|
+
declaredStems: [],
|
|
287
|
+
declaredMdFiles: [],
|
|
288
|
+
declaredMissing: [],
|
|
289
|
+
unmanifested: [],
|
|
290
|
+
});
|
|
291
|
+
let readme = "";
|
|
292
|
+
if (await exists(readmePath)) {
|
|
293
|
+
try {
|
|
294
|
+
readme = await readFile(readmePath, "utf8");
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
// Unreadable README is fail-closed (not silently treated as no manifest).
|
|
298
|
+
return invalid("unreadable", 0);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
return invalid("missing", 0);
|
|
303
|
+
}
|
|
304
|
+
const moduleIndexHeadingCount = moduleIndexHeadingCountIn(readme);
|
|
305
|
+
if (moduleIndexHeadingCount !== 1) {
|
|
306
|
+
return invalid("duplicate", moduleIndexHeadingCount);
|
|
307
|
+
}
|
|
308
|
+
const declaredStems = extractDeclaredModuleIndexFilenames(readme);
|
|
309
|
+
if (declaredStems.length === 0) {
|
|
310
|
+
return invalid("empty-unparsable", moduleIndexHeadingCount);
|
|
311
|
+
}
|
|
312
|
+
const hasManifest = true;
|
|
313
|
+
const directory = path.join(workspaceRoot, ...resolved.markdownDir.split("/"));
|
|
314
|
+
let existingNames = [];
|
|
315
|
+
try {
|
|
316
|
+
existingNames = (await readdir(directory, { withFileTypes: true }))
|
|
317
|
+
.filter((entry) => entry.isFile() &&
|
|
318
|
+
entry.name.toLowerCase().endsWith(".md") &&
|
|
319
|
+
entry.name.toLowerCase() !== "readme.md")
|
|
320
|
+
.map((entry) => entry.name);
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
// missing markdown directory -> no existing modules
|
|
324
|
+
}
|
|
325
|
+
const declaredByLower = new Map();
|
|
326
|
+
for (const name of declaredStems) {
|
|
327
|
+
const base = path.posix.basename(name);
|
|
328
|
+
declaredByLower.set(base.toLowerCase(), base);
|
|
329
|
+
}
|
|
330
|
+
const declaredMdFiles = [];
|
|
331
|
+
const declaredMissing = [];
|
|
332
|
+
for (const base of declaredByLower.values()) {
|
|
333
|
+
const disk = existingNames.find((name) => name.toLowerCase() === base.toLowerCase());
|
|
334
|
+
if (disk)
|
|
335
|
+
declaredMdFiles.push(`${resolved.markdownDir}/${disk}`);
|
|
336
|
+
else
|
|
337
|
+
declaredMissing.push(`${resolved.markdownDir}/${base}`);
|
|
338
|
+
}
|
|
339
|
+
const unmanifested = existingNames
|
|
340
|
+
.filter((name) => !declaredByLower.has(name.toLowerCase()))
|
|
341
|
+
.map((name) => `${resolved.markdownDir}/${name}`);
|
|
342
|
+
return {
|
|
343
|
+
hasManifest,
|
|
344
|
+
manifestStatus: "valid",
|
|
345
|
+
moduleIndexHeadingCount,
|
|
346
|
+
declaredStems,
|
|
347
|
+
declaredMdFiles: declaredMdFiles.sort(),
|
|
348
|
+
declaredMissing: declaredMissing.sort(),
|
|
349
|
+
unmanifested: unmanifested.sort(),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
203
352
|
export async function markdownFiles(root, layout) {
|
|
204
353
|
const resolved = layout ?? resolveBackendTestLayout(undefined);
|
|
205
354
|
const directory = path.join(root, ...resolved.markdownDir.split("/"));
|
|
206
355
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
207
|
-
|
|
356
|
+
const all = entries
|
|
208
357
|
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md"))
|
|
209
358
|
.map((entry) => path.join(directory, entry.name))
|
|
210
359
|
.sort();
|
|
360
|
+
const manifest = await resolveBackendTestManifestModules(root, resolved);
|
|
361
|
+
if (manifest.manifestStatus !== "valid")
|
|
362
|
+
return [];
|
|
363
|
+
const declaredAbs = new Set(manifest.declaredMdFiles.map((rel) => path.join(root, ...rel.split("/")).toLowerCase()));
|
|
364
|
+
return all
|
|
365
|
+
.filter((file) => declaredAbs.has(file.toLowerCase()))
|
|
366
|
+
.sort();
|
|
211
367
|
}
|
|
212
368
|
function canonicalBackendCaseId(value) {
|
|
213
369
|
const upper = value.toUpperCase();
|
|
@@ -453,6 +609,17 @@ export async function validateBackendMarkdownCases(input) {
|
|
|
453
609
|
const readme = path.join(input.workspaceRoot, ...layout.readmePath.split("/"));
|
|
454
610
|
if (!(await exists(readme)))
|
|
455
611
|
findings.push(`missing ${layout.readmePath}`);
|
|
612
|
+
const manifest = await resolveBackendTestManifestModules(input.workspaceRoot, layout);
|
|
613
|
+
// Fail closed on README Module Index entries that were declared but never
|
|
614
|
+
// materialized — never let a declared module silently vanish from the round.
|
|
615
|
+
if (manifest.declaredMissing.length > 0) {
|
|
616
|
+
findings.push(`declared Markdown module is missing (Module Index): ${manifest.declaredMissing.join(", ")}`);
|
|
617
|
+
}
|
|
618
|
+
// Fail closed on every non-valid Module Index state: without a valid unique
|
|
619
|
+
// Module Index the current round must not re-scan historical `.md` files.
|
|
620
|
+
if (manifest.manifestStatus !== "valid") {
|
|
621
|
+
findings.push(backendManifestStatusFinding(manifest.manifestStatus, manifest.moduleIndexHeadingCount));
|
|
622
|
+
}
|
|
456
623
|
const moduleFiles = files.filter((file) => path.basename(file).toLowerCase() !== "readme.md");
|
|
457
624
|
if (moduleFiles.length === 0)
|
|
458
625
|
findings.push(`no ${layout.markdownDir} module case file found`);
|
|
@@ -562,6 +729,7 @@ export async function validateBackendMarkdownCases(input) {
|
|
|
562
729
|
`- Cases: ${caseCount}`,
|
|
563
730
|
`- Covered AC IDs: ${[...coveredAc].sort().join(", ") || "none"}`,
|
|
564
731
|
`- Environment evidence present: ${input.environmentMarkdown.trim() ? "yes" : "no"}`,
|
|
732
|
+
`- 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})`,
|
|
565
733
|
`- Findings: ${findings.length}`,
|
|
566
734
|
"- Required sections: Preconditions/前置条件, Steps/操作步骤, Expected Results/预期结果",
|
|
567
735
|
"- Source-reference validity check: not applied",
|
|
@@ -583,6 +751,13 @@ export async function validateBackendMarkdownCases(input) {
|
|
|
583
751
|
? safetyFindings.map((finding) => `- ${finding}`)
|
|
584
752
|
: ["- No secret-shaped values detected."]),
|
|
585
753
|
"",
|
|
754
|
+
"## Unmanifested Markdown Modules",
|
|
755
|
+
"",
|
|
756
|
+
"- Advisory: existing .md files not declared by the README Module Index are excluded from this round's Case/coverage/partition scope.",
|
|
757
|
+
...(manifest.unmanifested.length > 0
|
|
758
|
+
? manifest.unmanifested.map((item) => `- ${item}`)
|
|
759
|
+
: ["- None"]),
|
|
760
|
+
"",
|
|
586
761
|
"## Findings",
|
|
587
762
|
"",
|
|
588
763
|
...(findings.length > 0 ? findings.map((finding) => `- ${finding}`) : ["- None"]),
|