@tea-agent/loop-agent 0.37.0 → 0.37.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -6
- package/CHANGELOG.md +71 -144
- package/README.md +1 -1
- package/bin/loop-agent.js +1 -37
- package/dist/application/task-lifecycle/advance.js +0 -1
- package/dist/application/task-lifecycle/observe.js +0 -15
- package/dist/application/task-lifecycle/plan-transitions.js +0 -15
- package/dist/cli/command-definitions.js +0 -7
- package/dist/cli/program.js +3 -8
- package/dist/commands/init-upgrade.js +19 -351
- package/dist/commands/init.js +67 -14
- package/dist/commands/run-dag-progress.js +0 -14
- package/dist/commands/task-advance.js +3 -33
- package/dist/executors/dag-pi-executor.js +30 -47
- package/dist/executors/pi-sdk-executor.js +0 -34
- package/dist/executors/shell-executor.js +95 -6
- package/dist/shared/operator/capabilities.js +13 -200
- package/dist/shared/package-metadata.js +0 -42
- package/dist/task/contract/project.js +0 -4
- package/dist/task/source-prepare/completeness.js +0 -53
- package/dist/task/source-prepare/parse-intent.js +12 -82
- package/dist/task/source-prepare/prepare.js +5 -102
- package/dist/task/source-prepare/semantic-intake.js +23 -144
- package/dist/worker/console/chat/artifact-card.js +1 -8
- package/dist/worker/console/chat/chat-event-store.js +0 -152
- package/dist/worker/console/chat/human-gate-card.js +1 -9
- package/dist/worker/console/chat/operation-card.js +2 -71
- package/dist/worker/console/chat/pi-runtime.js +62 -111
- package/dist/worker/console/chat/routes.js +10 -43
- package/dist/worker/console/chat/session-store.js +1 -9
- package/dist/worker/console/chat/shortcuts.js +7 -9
- package/dist/worker/console/chat/turn-process.js +23 -204
- package/dist/worker/console/chat/workspace-landing.js +1 -2
- package/dist/worker/console/operation-runner.js +6 -155
- package/dist/worker/console/operator-actions.js +14 -391
- package/dist/worker/console/operator-user-error.js +0 -4
- package/dist/worker/console/recovery-cta.js +3 -50
- package/dist/worker/console/static/assets/index-HX1pbOyl.css +1 -0
- package/dist/worker/console/static/assets/index-M0BLEBfh.js +56 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -13
- package/dist/worker/console/static-src/app/useOperatorActions.js +2 -4
- package/dist/worker/console/static-src/app/useRecoveryActions.js +56 -65
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +1 -73
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +30 -114
- package/dist/worker/console/static-src/operator-chat/refs.js +0 -3
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +13 -67
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -12
- package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -45
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -7
- package/dist/worker/loop-agent/loop-agent-client.js +3 -17
- package/dist/worker/observability/read-model.js +0 -20
- package/dist/worker/observe/spec-evidence.js +8 -3
- package/dist/worker/observe/static/operator-chrome.d.ts +0 -1
- package/dist/worker/observe/static/operator-chrome.js +3 -10
- package/dist/worker/observe/static/views/dag-inspector.js +71 -6
- package/dist/worker/observe/static/views/dag.js +0 -12
- package/dist/worker/preflight.js +1 -2
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +707 -37
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +109 -7
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
- package/dist/workflows/dag/backend-test-scenario-param.js +890 -164
- package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -9
- package/dist/workflows/dag/frontend-implementation-contract.js +39 -233
- package/dist/workflows/dag/frontend-prewrite-gate.js +61 -364
- package/dist/workflows/dag/frontend-repair.js +18 -219
- package/dist/workflows/dag/frontend-verification-trace.js +32 -47
- package/dist/workflows/dag/init-hybrid.js +82 -165
- package/dist/workflows/dag/node-execution.js +0 -89
- package/dist/workflows/dag/recovery-recommendation.js +0 -58
- package/dist/workflows/dag/runner.js +15 -333
- package/dist/workflows/dag/scheduler.js +3 -257
- package/dist/workflows/dag/types.js +9 -130
- package/docs/README.md +3 -3
- package/docs/architecture/evolution.md +67 -102
- package/docs/templates/backend-test-dag.json +50 -32
- package/docs/templates/init-managed-agents.md +2 -5
- package/harness.json +2 -2
- package/package.json +3 -9
- package/skills/loop-agent/SKILL.md +0 -1
- package/skills/loop-agent/references/command-reference.md +0 -4
- package/dist/build-stamp.json +0 -6
- package/dist/commands/dag-request-interrupt.js +0 -20
- package/dist/worker/console/chat/assistant-content.js +0 -121
- package/dist/worker/console/chat/semantic-activity.js +0 -477
- package/dist/worker/console/operation-run-facts.js +0 -190
- package/dist/worker/console/operation-wait.js +0 -355
- package/dist/worker/console/recovery-error-copy.js +0 -198
- package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-DSvF6RXQ.js +0 -1
- package/dist/worker/console/static/assets/arc-DuI4ogtJ.js +0 -1
- package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-DdBbgD4y.js +0 -36
- package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-xQC2HELm.js +0 -132
- package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-DPMSgwIZ.js +0 -10
- package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
- package/dist/worker/console/static/assets/chunk-2GRJ4B5K-WTEKTxin.js +0 -1
- package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B6vZ0Pv_.js +0 -1
- package/dist/worker/console/static/assets/chunk-5RXB4S5H-BrerNAix.js +0 -231
- package/dist/worker/console/static/assets/chunk-5VM5RSS4-D_ch2aaI.js +0 -15
- package/dist/worker/console/static/assets/chunk-6Q2QTUOP-VvfacNrq.js +0 -88
- package/dist/worker/console/static/assets/chunk-GF5L2VYU-B-uGFIg2.js +0 -206
- package/dist/worker/console/static/assets/chunk-JWPE2WC7-BlysCtod.js +0 -1
- package/dist/worker/console/static/assets/chunk-KBJHAD2P-BjAUAfcT.js +0 -1
- package/dist/worker/console/static/assets/chunk-RYQCIY6F-BeZnBmb-.js +0 -1
- package/dist/worker/console/static/assets/chunk-XXDRQBXY-Bbfe3UJG.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-BKJQnYVM.js +0 -1
- package/dist/worker/console/static/assets/cynefin-VYW2F7L2-B1wtI3hU.js +0 -166
- package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DCF6DBSu.js +0 -62
- package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +0 -321
- package/dist/worker/console/static/assets/dagre-VZM6K2ZE-BJyNGRlP.js +0 -4
- package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +0 -1
- package/dist/worker/console/static/assets/diagram-7IWD3JNH-B7j1-VsZ.js +0 -30
- package/dist/worker/console/static/assets/diagram-B4RE2ZJO-BREJ140a.js +0 -3
- package/dist/worker/console/static/assets/diagram-LBJQPF4R-CDPHmHGH.js +0 -24
- package/dist/worker/console/static/assets/diagram-Q27KOJAE-BtZLrez1.js +0 -24
- package/dist/worker/console/static/assets/diagram-UB23O5K3-mzybu8oU.js +0 -41
- package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-Dk3I2uO6.js +0 -1
- package/dist/worker/console/static/assets/erDiagram-JOGREHBK-BT97u_tQ.js +0 -85
- package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-CMOtYzui.js +0 -156
- package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-DiNydkuY.js +0 -292
- package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js +0 -106
- package/dist/worker/console/static/assets/graph-DOmOIIwC.js +0 -1
- package/dist/worker/console/static/assets/index-DQxiso1w.js +0 -325
- package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
- package/dist/worker/console/static/assets/infoDiagram-6WML65LV-AsGIGKKB.js +0 -2
- package/dist/worker/console/static/assets/init-Gi6I4Gst.js +0 -1
- package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-BL8ITZh3.js +0 -70
- package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-VPKAUX8M.js +0 -139
- package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BL56hfGp.js +0 -89
- package/dist/worker/console/static/assets/layout-D-LzfAck.js +0 -1
- package/dist/worker/console/static/assets/linear-BmSG3nIZ.js +0 -1
- package/dist/worker/console/static/assets/map-DxJ2ADlA.js +0 -1
- package/dist/worker/console/static/assets/mermaid.core-DWGK9kwP.js +0 -308
- package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS--vvKLjo0.js +0 -96
- package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +0 -1
- package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-B41mxb2-.js +0 -1
- package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-DTlu77sw.js +0 -39
- package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-Bk5A7alv.js +0 -7
- package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-Csf9jzQy.js +0 -1
- package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-CeptoSNx.js +0 -84
- package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-A-DLCLer.js +0 -40
- package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-22VbPdOI.js +0 -162
- package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-DWvAyBC9.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-qICZ75yT.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
- package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-xi7lcMyp.js +0 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +0 -8
- package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-Bu6Nl6O2.js +0 -120
- package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-BzMz4Bn9.js +0 -34
- package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-CxaJnzc5.js +0 -78
- package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-5JK-omQK.js +0 -7
- package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/worker/console/static-src/operator-chat/activity-journey.js +0 -125
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/activity-references.js +0 -20
- package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +0 -24
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
- package/dist/worker/observability/interrupt-eligibility.js +0 -264
- package/dist/workflows/dag/contract-output-registry.js +0 -14
- package/dist/workflows/dag/contract-validator-registrations.js +0 -8
- package/dist/workflows/dag/frontend-recovery-plan.js +0 -73
- package/dist/workflows/dag/frontend-recovery-root-manifest.js +0 -123
- package/dist/workflows/dag/frontend-recovery-run.js +0 -539
- package/dist/workflows/dag/frontend-writer-recovery.js +0 -106
- package/dist/workflows/dag/frontend-writer-rollback.js +0 -821
- package/dist/workflows/dag/interrupt-request.js +0 -559
|
@@ -149,6 +149,10 @@ export const backendTestCaseManifestSchema = z
|
|
|
149
149
|
crossCuttingTestPointCount: z.number().int().min(0),
|
|
150
150
|
unclassifiedTestPointCount: z.number().int().min(0),
|
|
151
151
|
duplicateBindingTestPointCount: z.number().int().min(0),
|
|
152
|
+
payloadSafeCount: z.number().int().min(0),
|
|
153
|
+
payloadUnsafeCount: z.number().int().min(0),
|
|
154
|
+
payloadUnavailableCount: z.number().int().min(0),
|
|
155
|
+
duplicateExecutionSignatureCount: z.number().int().min(0),
|
|
152
156
|
}).strict().optional(),
|
|
153
157
|
artifactRefs: z.object({
|
|
154
158
|
caseCoverageFacts: z.object({ path: z.string().min(1), sha256: z.string().regex(/^[a-f0-9]{64}$/).optional() }).strict(),
|
|
@@ -17,7 +17,7 @@ const CANONICAL_CASE_ID = /\bBE-[A-Z0-9]+(?:-[A-Z0-9]+)*-\d{3}\b/;
|
|
|
17
17
|
const AC_ID = /\bAC-[A-Z0-9]+(?:-[A-Z0-9]+)*\b/g;
|
|
18
18
|
const SECRET = /(?:password|passwd|secret|token|api[_-]?key|private[_-]?key|authorization)\s*[:=]\s*\S+/i;
|
|
19
19
|
const SECRET_ASSIGNMENT = /\b(password|passwd|secret|token|api[_-]?key|private[_-]?key|authorization|cookie|set-cookie|credential)\b\s*[:=]\s*([^\r\n,;]+)/gi;
|
|
20
|
-
const SECRET_VALUE = /-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:ghp|github_pat|xox[baprs]|AKIA
|
|
20
|
+
const SECRET_VALUE = /-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:ghp|github_pat|xox[baprs]|AKIA)[-_A-Za-z0-9]{12,}\b|\bsk[-_][-_A-Za-z0-9]{10,}\b/i;
|
|
21
21
|
const PLACEHOLDER_SECRET_VALUE = /^(?:<[^>]+>|\$\{[^}]+\}|\*+|x+|redacted|masked|example(?:[-_].*)?|sample(?:[-_].*)?|dummy(?:[-_].*)?|test(?:[-_].*)?|your[-_].*)$/i;
|
|
22
22
|
const PLACEHOLDER = /\b(?:TODO|TBD|FIXME)\b|后续补充|暂不实现|接口正常|结果正确|按实际情况处理/i;
|
|
23
23
|
const CASE_SECTION_ALIASES = {
|
|
@@ -303,6 +303,8 @@ function isCleanupStatement(value) {
|
|
|
303
303
|
return false;
|
|
304
304
|
if (/\b(?:assert|pytest\.fail|raise)\b/.test(trimmed))
|
|
305
305
|
return false;
|
|
306
|
+
if (/\b[A-Za-z_][A-Za-z0-9_.]*request\s*\(\s*["']DELETE["']/i.test(trimmed))
|
|
307
|
+
return true;
|
|
306
308
|
return /(?:^|[._])(?:cleanup|clean_up|teardown|delete|remove|close|release|rollback|disconnect|dispose|unlink)(?:[_(.]|$)/i.test(trimmed);
|
|
307
309
|
}
|
|
308
310
|
function findUnsafeSwallowedException(source) {
|
|
@@ -326,7 +328,18 @@ function findUnsafeSwallowedException(source) {
|
|
|
326
328
|
continue;
|
|
327
329
|
}
|
|
328
330
|
const exceptionType = exceptMatch[2]?.trim() || "bare except";
|
|
329
|
-
|
|
331
|
+
const narrowDecodeTypes = exceptionType
|
|
332
|
+
.replace(/[()]/g, "")
|
|
333
|
+
.split(",")
|
|
334
|
+
.map((value) => value.trim())
|
|
335
|
+
.filter(Boolean);
|
|
336
|
+
const precedingDecodeRegion = lines.slice(Math.max(0, index - 8), index).join("\n");
|
|
337
|
+
if (narrowDecodeTypes.length > 0 &&
|
|
338
|
+
narrowDecodeTypes.every((value) => value === "UnicodeDecodeError" || value === "json.JSONDecodeError") &&
|
|
339
|
+
/\.json\s*\(\s*\)/.test(precedingDecodeRegion)) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (!new Set(["requests.RequestException", "urllib.error.URLError", "httpx.RequestError"]).has(exceptionType)) {
|
|
330
343
|
return exceptionType;
|
|
331
344
|
}
|
|
332
345
|
let tryIndex = index - 1;
|
|
@@ -366,9 +379,18 @@ function httpLoggingFindings(relativeScript, source) {
|
|
|
366
379
|
if (!hasHttpRequest)
|
|
367
380
|
return [];
|
|
368
381
|
const findings = [];
|
|
369
|
-
const hasRequestLog = /(?:print|log(?:ger)?\.(?:debug|info|warning|error))\s*\([^\n]*(?:request|请求)[^\n]*(?:method|url|path|params|query|json|body|payload|参数)/i.test(source)
|
|
370
|
-
|
|
371
|
-
|
|
382
|
+
const hasRequestLog = /(?:print|log(?:ger)?\.(?:debug|info|warning|error))\s*\([^\n]*(?:request|请求)[^\n]*(?:method|url|path|params|query|json|body|payload|参数)/i.test(source)
|
|
383
|
+
|| /["']record["']\s*:\s*["']request["'][\s\S]{0,600}["'](?:method|url|path|params|query|json|body|payload)["']\s*:/i.test(source)
|
|
384
|
+
|| /\b[A-Za-z_][A-Za-z0-9_]*log[A-Za-z0-9_]*\s*\(\s*["'](?:HTTP|API)_REQUEST["'][\s\S]{0,600}["'](?:method|url|path|params|query|json|body|payload)["']\s*:/i.test(source)
|
|
385
|
+
|| /print\s*\(\s*f?["'](?:HTTP|API)(?:_|\s+)REQUEST\b/i.test(source) && /["'](?:method|url|path|params|query|json|body|payload)["']\s*:/i.test(source)
|
|
386
|
+
|| /\b[A-Za-z_][A-Za-z0-9_]*log\s*\(\s*["']REQUEST["'][\s\S]{0,600}["'](?:method|url|path|params|query|json|body|payload)["']\s*:/i.test(source);
|
|
387
|
+
const hasResponseLog = /(?:print|log(?:ger)?\.(?:debug|info|warning|error))\s*\([^\n]*(?:response|响应)[^\n]*(?:status|status_code|body|json|text|result|结果)/i.test(source)
|
|
388
|
+
|| /["']record["']\s*:\s*["']response["'][\s\S]{0,600}["'](?:status|status_code|body|json|text|result)["']\s*:/i.test(source)
|
|
389
|
+
|| /\b[A-Za-z_][A-Za-z0-9_]*log[A-Za-z0-9_]*\s*\(\s*["'](?:HTTP|API)_RESPONSE["'][\s\S]{0,600}["'](?:status|status_code|body|json|text|result)["']\s*:/i.test(source)
|
|
390
|
+
|| /print\s*\(\s*f?["'](?:HTTP|API)(?:_|\s+)RESPONSE\b/i.test(source) && /["'](?:status|status_code|body|json|text|result)["']\s*:/i.test(source)
|
|
391
|
+
|| /\b[A-Za-z_][A-Za-z0-9_]*log\s*\(\s*["']RESPONSE["'][\s\S]{0,600}["'](?:status|status_code|body|json|text|result)["']\s*:/i.test(source);
|
|
392
|
+
const hasSanitizer = /\b(?:sanitize|sanitise|redact|mask)[A-Za-z0-9_]*\s*\(|\b[A-Za-z0-9_]*(?:sanitize|sanitise|redact|mask)[A-Za-z0-9_]*\s*\(/i.test(source)
|
|
393
|
+
|| /(?:SENSITIVE_KEYS|sensitive_keys)[\s\S]{0,1600}["']<redacted>["']/i.test(source);
|
|
372
394
|
const hasBoundedOutput = /(?:MAX_[A-Z_]*(?:LOG|BODY|RESPONSE)|max_(?:log|body|response)|truncate|truncat|\[:\s*\d{3,}\s*\])/i.test(source);
|
|
373
395
|
if (!hasRequestLog)
|
|
374
396
|
findings.push(`request logging evidence is missing in mapped backend pytest: ${relativeScript}`);
|
|
@@ -459,6 +481,15 @@ export async function validateBackendMarkdownCases(input) {
|
|
|
459
481
|
findings.push(`${testCase.id} missing section: ${sectionAliases.join(" or ")}`);
|
|
460
482
|
}
|
|
461
483
|
}
|
|
484
|
+
const bindingFinding = markdownTestPointBindingFinding(testCase.id, testCase.body);
|
|
485
|
+
if (bindingFinding)
|
|
486
|
+
findings.push(bindingFinding);
|
|
487
|
+
const payloadLabelFinding = markdownPayloadLabelFinding(testCase.id, testCase.body);
|
|
488
|
+
if (payloadLabelFinding)
|
|
489
|
+
findings.push(payloadLabelFinding);
|
|
490
|
+
const scenarioIntentFinding = markdownScenarioIntentFinding(testCase.id, testCase.body);
|
|
491
|
+
if (scenarioIntentFinding)
|
|
492
|
+
findings.push(scenarioIntentFinding);
|
|
462
493
|
const acIds = unique(testCase.body.match(AC_ID) ?? []);
|
|
463
494
|
acIds.forEach((id) => coveredAc.add(id));
|
|
464
495
|
const steps = sectionBody(testCase.body, CASE_SECTION_ALIASES.steps);
|
|
@@ -471,7 +502,10 @@ export async function validateBackendMarkdownCases(input) {
|
|
|
471
502
|
}
|
|
472
503
|
const expectedScript = expectedBackendTestPytestScriptForMarkdownModule(relativeFile);
|
|
473
504
|
const mappedScripts = extractMappedPytestScripts(testCase.body);
|
|
474
|
-
if (
|
|
505
|
+
if (isEvidenceOnlyMarkdownCaseBody(testCase.body)) {
|
|
506
|
+
// Evidence-only meta cases intentionally declare 脚本/primary symbol as 无 and keep only assertion/cross-cutting TPs.
|
|
507
|
+
}
|
|
508
|
+
else if (mappedScripts.length === 0) {
|
|
475
509
|
findings.push(`${testCase.id} has no mapped pytest script in Automation Notes/自动化映射 (expected ${expectedScript} from module ${path.basename(relativeFile)})`);
|
|
476
510
|
}
|
|
477
511
|
else {
|
|
@@ -560,6 +594,71 @@ function extractMappedPytestScripts(testCaseBody) {
|
|
|
560
594
|
.map((match) => match[1].replaceAll("\\", "/"))
|
|
561
595
|
.filter((value) => !value.includes("..")));
|
|
562
596
|
}
|
|
597
|
+
function markdownScenarioIntentFinding(testCaseId, testCaseBody) {
|
|
598
|
+
const automation = sectionBody(testCaseBody, CASE_SECTION_ALIASES.automationNotes);
|
|
599
|
+
if (!automation.trim())
|
|
600
|
+
return null;
|
|
601
|
+
const variantLine = /^(?:\s*[-*+]\s*)?(?:变体测试点|Variant Test Points)\s*[::]\s*(.*)$/im.exec(automation)?.[1] ?? "";
|
|
602
|
+
const variants = unique(variantLine.match(/\bTP-[A-Z0-9-]+\b/g) ?? []);
|
|
603
|
+
if (variants.length === 0)
|
|
604
|
+
return null;
|
|
605
|
+
const localIntentIds = unique([...testCaseBody.matchAll(/(?:场景意图|Scenario Intent)\s*[::]\s*(TP-[A-Z0-9-]+)/gi)].map((match) => match[1].toUpperCase()));
|
|
606
|
+
const missing = variants.filter((id) => !localIntentIds.includes(id.toUpperCase()));
|
|
607
|
+
return missing.length > 0
|
|
608
|
+
? `${testCaseId} variant Test Points require a local Scenario Intent line inside the same Case: missing=${missing.join(", ")}`
|
|
609
|
+
: null;
|
|
610
|
+
}
|
|
611
|
+
function markdownPayloadLabelFinding(testCaseId, testCaseBody) {
|
|
612
|
+
const automation = sectionBody(testCaseBody, CASE_SECTION_ALIASES.automationNotes);
|
|
613
|
+
if (!automation.trim())
|
|
614
|
+
return null;
|
|
615
|
+
const payloadLabel = /Payload (?:Contract|Required Paths|Allowed Paths|Enum)\s*[::]/gi;
|
|
616
|
+
for (const line of automation.split(/\r?\n/)) {
|
|
617
|
+
const matches = line.match(payloadLabel) ?? [];
|
|
618
|
+
if (matches.length === 0)
|
|
619
|
+
continue;
|
|
620
|
+
if (matches.length !== 1 || !/^\s*[-*+]\s*Payload (?:Contract|Required Paths|Allowed Paths|Enum)\s*[::]/i.test(line)) {
|
|
621
|
+
return `${testCaseId} payload contract labels must each occupy one independent machine-readable list line`;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
function markdownTestPointBindingFinding(testCaseId, testCaseBody) {
|
|
627
|
+
const declaredSection = sectionBody(testCaseBody, ["测试点", "Test Points"]);
|
|
628
|
+
const automation = sectionBody(testCaseBody, CASE_SECTION_ALIASES.automationNotes);
|
|
629
|
+
if (!declaredSection.trim() || !automation.trim())
|
|
630
|
+
return null;
|
|
631
|
+
const declared = unique(declaredSection.match(/\bTP-[A-Z0-9-]+\b/g) ?? []);
|
|
632
|
+
const bindingLines = [
|
|
633
|
+
/^(?:\s*[-*+]\s*)?(?:变体测试点|Variant Test Points)\s*[::]\s*(.*)$/gim,
|
|
634
|
+
/^(?:\s*[-*+]\s*)?(?:场景断言测试点|Assertion Test Points)\s*[::]\s*(.*)$/gim,
|
|
635
|
+
/^(?:\s*[-*+]\s*)?(?:横切证据测试点|Cross-Cutting Test Points)\s*[::]\s*(.*)$/gim,
|
|
636
|
+
];
|
|
637
|
+
const bound = unique(bindingLines.flatMap((pattern) => [...automation.matchAll(pattern)].flatMap((match) => match[1]?.match(/\bTP-[A-Z0-9-]+\b/g) ?? [])));
|
|
638
|
+
const missingBindings = declared.filter((id) => !bound.includes(id));
|
|
639
|
+
const undeclaredBindings = bound.filter((id) => !declared.includes(id));
|
|
640
|
+
if (missingBindings.length === 0 && undeclaredBindings.length === 0)
|
|
641
|
+
return null;
|
|
642
|
+
return `${testCaseId} Test Point set must exactly equal variant/assertion/cross-cutting binding union: unclassified=${missingBindings.join(", ") || "none"}; undeclared-bindings=${undeclaredBindings.join(", ") || "none"}`;
|
|
643
|
+
}
|
|
644
|
+
function isEvidenceOnlyMarkdownCaseBody(testCaseBody) {
|
|
645
|
+
const automation = sectionBody(testCaseBody, CASE_SECTION_ALIASES.automationNotes);
|
|
646
|
+
if (!automation.trim())
|
|
647
|
+
return false;
|
|
648
|
+
const scriptLine = /^\s*[-*+]\s*(?:脚本|script)\s*[::]\s*(.*)$/im.exec(automation)?.[1]?.replaceAll("`", "").trim() ??
|
|
649
|
+
"";
|
|
650
|
+
const primaryLine = /^\s*[-*+]\s*(?:primary symbol|Primary Symbol|主测试符号)\s*[::]\s*(.*)$/im.exec(automation)?.[1]
|
|
651
|
+
?.replaceAll("`", "")
|
|
652
|
+
.trim() ?? "";
|
|
653
|
+
const noneLike = /^(?:none|无|n\/a|not-applicable)(?:\s*[((].*[))])?$/i;
|
|
654
|
+
const scriptIsNone = noneLike.test(scriptLine) || scriptLine.length === 0;
|
|
655
|
+
const primaryIsNone = noneLike.test(primaryLine) || primaryLine.length === 0;
|
|
656
|
+
const hasMappedScript = extractMappedPytestScripts(testCaseBody).length > 0;
|
|
657
|
+
const hasPrimary = /\btest_[A-Za-z0-9_]+\b/.test(primaryLine);
|
|
658
|
+
const hasVariant = /(?:变体测试点|Variant Test Points)\s*[::]\s*(?!\s*(?:无|none)\b)[^\n]*\bTP-[A-Z0-9-]+/i.test(automation);
|
|
659
|
+
const hasAssertionOrCross = /(?:场景断言测试点|Assertion Test Points|横切证据测试点|Cross-Cutting Test Points)\s*[::]\s*(?!\s*(?:无|none)\b)[^\n]*\bTP-[A-Z0-9-]+/i.test(automation);
|
|
660
|
+
return scriptIsNone && primaryIsNone && !hasMappedScript && !hasPrimary && !hasVariant && hasAssertionOrCross;
|
|
661
|
+
}
|
|
563
662
|
function isSafeBackendPytestScript(script, workspaceRoot) {
|
|
564
663
|
const absolute = path.resolve(workspaceRoot, script);
|
|
565
664
|
const relative = path.relative(workspaceRoot, absolute).replaceAll(path.sep, "/");
|
|
@@ -619,8 +718,11 @@ export async function validateBackendMarkdownTraceability(workspaceRoot) {
|
|
|
619
718
|
continue;
|
|
620
719
|
const content = await readFile(file, "utf8");
|
|
621
720
|
for (const testCase of splitCases(content)) {
|
|
622
|
-
markdownIds.add(testCase.id);
|
|
623
721
|
const scripts = extractMappedPytestScripts(testCase.body);
|
|
722
|
+
if (isEvidenceOnlyMarkdownCaseBody(testCase.body)) {
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
markdownIds.add(testCase.id);
|
|
624
726
|
if (scripts.length === 0) {
|
|
625
727
|
findings.push(`${testCase.id} has no mapped pytest script in Automation Notes/自动化映射`);
|
|
626
728
|
continue;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
const PRIORITY_ONLY_MODULE_STEMS = new Set(["p0", "p1", "p2"]);
|
|
3
|
+
const OPAQUE_HASH_MODULE_STEM = /^[a-f][a-f0-9]{6,63}$/;
|
|
4
|
+
export const BACKEND_TEST_MAX_MODULE_COUNT = 8;
|
|
3
5
|
export function normalizeBackendTestModuleStemCandidate(raw) {
|
|
4
6
|
return path.basename(raw)
|
|
5
7
|
.replace(/\.(?:md|py)$/i, "")
|
|
@@ -11,6 +13,9 @@ export function normalizeBackendTestModuleStemCandidate(raw) {
|
|
|
11
13
|
export function isPriorityOnlyBackendTestModuleStem(raw) {
|
|
12
14
|
return PRIORITY_ONLY_MODULE_STEMS.has(normalizeBackendTestModuleStemCandidate(raw));
|
|
13
15
|
}
|
|
16
|
+
export function isOpaqueHashBackendTestModuleStem(raw) {
|
|
17
|
+
return OPAQUE_HASH_MODULE_STEM.test(normalizeBackendTestModuleStemCandidate(raw));
|
|
18
|
+
}
|
|
14
19
|
export function isPriorityOnlyBackendPytestScript(script) {
|
|
15
20
|
const basename = path.posix.basename(script.replaceAll("\\", "/"));
|
|
16
21
|
const match = /^test_(.+)\.py$/i.exec(basename);
|