@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.
Files changed (192) hide show
  1. package/AGENTS.md +2 -6
  2. package/CHANGELOG.md +71 -144
  3. package/README.md +1 -1
  4. package/bin/loop-agent.js +1 -37
  5. package/dist/application/task-lifecycle/advance.js +0 -1
  6. package/dist/application/task-lifecycle/observe.js +0 -15
  7. package/dist/application/task-lifecycle/plan-transitions.js +0 -15
  8. package/dist/cli/command-definitions.js +0 -7
  9. package/dist/cli/program.js +3 -8
  10. package/dist/commands/init-upgrade.js +19 -351
  11. package/dist/commands/init.js +67 -14
  12. package/dist/commands/run-dag-progress.js +0 -14
  13. package/dist/commands/task-advance.js +3 -33
  14. package/dist/executors/dag-pi-executor.js +30 -47
  15. package/dist/executors/pi-sdk-executor.js +0 -34
  16. package/dist/executors/shell-executor.js +95 -6
  17. package/dist/shared/operator/capabilities.js +13 -200
  18. package/dist/shared/package-metadata.js +0 -42
  19. package/dist/task/contract/project.js +0 -4
  20. package/dist/task/source-prepare/completeness.js +0 -53
  21. package/dist/task/source-prepare/parse-intent.js +12 -82
  22. package/dist/task/source-prepare/prepare.js +5 -102
  23. package/dist/task/source-prepare/semantic-intake.js +23 -144
  24. package/dist/worker/console/chat/artifact-card.js +1 -8
  25. package/dist/worker/console/chat/chat-event-store.js +0 -152
  26. package/dist/worker/console/chat/human-gate-card.js +1 -9
  27. package/dist/worker/console/chat/operation-card.js +2 -71
  28. package/dist/worker/console/chat/pi-runtime.js +62 -111
  29. package/dist/worker/console/chat/routes.js +10 -43
  30. package/dist/worker/console/chat/session-store.js +1 -9
  31. package/dist/worker/console/chat/shortcuts.js +7 -9
  32. package/dist/worker/console/chat/turn-process.js +23 -204
  33. package/dist/worker/console/chat/workspace-landing.js +1 -2
  34. package/dist/worker/console/operation-runner.js +6 -155
  35. package/dist/worker/console/operator-actions.js +14 -391
  36. package/dist/worker/console/operator-user-error.js +0 -4
  37. package/dist/worker/console/recovery-cta.js +3 -50
  38. package/dist/worker/console/static/assets/index-HX1pbOyl.css +1 -0
  39. package/dist/worker/console/static/assets/index-M0BLEBfh.js +56 -0
  40. package/dist/worker/console/static/index.html +2 -2
  41. package/dist/worker/console/static-src/app/console-types.js +9 -13
  42. package/dist/worker/console/static-src/app/useOperatorActions.js +2 -4
  43. package/dist/worker/console/static-src/app/useRecoveryActions.js +56 -65
  44. package/dist/worker/console/static-src/app/useRecoveryConsole.js +1 -73
  45. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +30 -114
  46. package/dist/worker/console/static-src/operator-chat/refs.js +0 -3
  47. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  48. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +13 -67
  49. package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -12
  50. package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -45
  51. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -7
  52. package/dist/worker/loop-agent/loop-agent-client.js +3 -17
  53. package/dist/worker/observability/read-model.js +0 -20
  54. package/dist/worker/observe/spec-evidence.js +8 -3
  55. package/dist/worker/observe/static/operator-chrome.d.ts +0 -1
  56. package/dist/worker/observe/static/operator-chrome.js +3 -10
  57. package/dist/worker/observe/static/views/dag-inspector.js +71 -6
  58. package/dist/worker/observe/static/views/dag.js +0 -12
  59. package/dist/worker/preflight.js +1 -2
  60. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +707 -37
  61. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  62. package/dist/workflows/dag/backend-test-markdown-workflow.js +109 -7
  63. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  64. package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
  65. package/dist/workflows/dag/backend-test-scenario-param.js +890 -164
  66. package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
  67. package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
  68. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -9
  69. package/dist/workflows/dag/frontend-implementation-contract.js +39 -233
  70. package/dist/workflows/dag/frontend-prewrite-gate.js +61 -364
  71. package/dist/workflows/dag/frontend-repair.js +18 -219
  72. package/dist/workflows/dag/frontend-verification-trace.js +32 -47
  73. package/dist/workflows/dag/init-hybrid.js +82 -165
  74. package/dist/workflows/dag/node-execution.js +0 -89
  75. package/dist/workflows/dag/recovery-recommendation.js +0 -58
  76. package/dist/workflows/dag/runner.js +15 -333
  77. package/dist/workflows/dag/scheduler.js +3 -257
  78. package/dist/workflows/dag/types.js +9 -130
  79. package/docs/README.md +3 -3
  80. package/docs/architecture/evolution.md +67 -102
  81. package/docs/templates/backend-test-dag.json +50 -32
  82. package/docs/templates/init-managed-agents.md +2 -5
  83. package/harness.json +2 -2
  84. package/package.json +3 -9
  85. package/skills/loop-agent/SKILL.md +0 -1
  86. package/skills/loop-agent/references/command-reference.md +0 -4
  87. package/dist/build-stamp.json +0 -6
  88. package/dist/commands/dag-request-interrupt.js +0 -20
  89. package/dist/worker/console/chat/assistant-content.js +0 -121
  90. package/dist/worker/console/chat/semantic-activity.js +0 -477
  91. package/dist/worker/console/operation-run-facts.js +0 -190
  92. package/dist/worker/console/operation-wait.js +0 -355
  93. package/dist/worker/console/recovery-error-copy.js +0 -198
  94. package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-DSvF6RXQ.js +0 -1
  95. package/dist/worker/console/static/assets/arc-DuI4ogtJ.js +0 -1
  96. package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-DdBbgD4y.js +0 -36
  97. package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-xQC2HELm.js +0 -132
  98. package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-DPMSgwIZ.js +0 -10
  99. package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
  100. package/dist/worker/console/static/assets/chunk-2GRJ4B5K-WTEKTxin.js +0 -1
  101. package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B6vZ0Pv_.js +0 -1
  102. package/dist/worker/console/static/assets/chunk-5RXB4S5H-BrerNAix.js +0 -231
  103. package/dist/worker/console/static/assets/chunk-5VM5RSS4-D_ch2aaI.js +0 -15
  104. package/dist/worker/console/static/assets/chunk-6Q2QTUOP-VvfacNrq.js +0 -88
  105. package/dist/worker/console/static/assets/chunk-GF5L2VYU-B-uGFIg2.js +0 -206
  106. package/dist/worker/console/static/assets/chunk-JWPE2WC7-BlysCtod.js +0 -1
  107. package/dist/worker/console/static/assets/chunk-KBJHAD2P-BjAUAfcT.js +0 -1
  108. package/dist/worker/console/static/assets/chunk-RYQCIY6F-BeZnBmb-.js +0 -1
  109. package/dist/worker/console/static/assets/chunk-XXDRQBXY-Bbfe3UJG.js +0 -1
  110. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
  111. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
  112. package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-BKJQnYVM.js +0 -1
  113. package/dist/worker/console/static/assets/cynefin-VYW2F7L2-B1wtI3hU.js +0 -166
  114. package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DCF6DBSu.js +0 -62
  115. package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +0 -321
  116. package/dist/worker/console/static/assets/dagre-VZM6K2ZE-BJyNGRlP.js +0 -4
  117. package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +0 -1
  118. package/dist/worker/console/static/assets/diagram-7IWD3JNH-B7j1-VsZ.js +0 -30
  119. package/dist/worker/console/static/assets/diagram-B4RE2ZJO-BREJ140a.js +0 -3
  120. package/dist/worker/console/static/assets/diagram-LBJQPF4R-CDPHmHGH.js +0 -24
  121. package/dist/worker/console/static/assets/diagram-Q27KOJAE-BtZLrez1.js +0 -24
  122. package/dist/worker/console/static/assets/diagram-UB23O5K3-mzybu8oU.js +0 -41
  123. package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-Dk3I2uO6.js +0 -1
  124. package/dist/worker/console/static/assets/erDiagram-JOGREHBK-BT97u_tQ.js +0 -85
  125. package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-CMOtYzui.js +0 -156
  126. package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-DiNydkuY.js +0 -292
  127. package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js +0 -106
  128. package/dist/worker/console/static/assets/graph-DOmOIIwC.js +0 -1
  129. package/dist/worker/console/static/assets/index-DQxiso1w.js +0 -325
  130. package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
  131. package/dist/worker/console/static/assets/infoDiagram-6WML65LV-AsGIGKKB.js +0 -2
  132. package/dist/worker/console/static/assets/init-Gi6I4Gst.js +0 -1
  133. package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-BL8ITZh3.js +0 -70
  134. package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-VPKAUX8M.js +0 -139
  135. package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BL56hfGp.js +0 -89
  136. package/dist/worker/console/static/assets/layout-D-LzfAck.js +0 -1
  137. package/dist/worker/console/static/assets/linear-BmSG3nIZ.js +0 -1
  138. package/dist/worker/console/static/assets/map-DxJ2ADlA.js +0 -1
  139. package/dist/worker/console/static/assets/mermaid.core-DWGK9kwP.js +0 -308
  140. package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS--vvKLjo0.js +0 -96
  141. package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +0 -1
  142. package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-B41mxb2-.js +0 -1
  143. package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-DTlu77sw.js +0 -39
  144. package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-Bk5A7alv.js +0 -7
  145. package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-Csf9jzQy.js +0 -1
  146. package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-CeptoSNx.js +0 -84
  147. package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-A-DLCLer.js +0 -40
  148. package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-22VbPdOI.js +0 -162
  149. package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-DWvAyBC9.js +0 -1
  150. package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-qICZ75yT.js +0 -1
  151. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
  152. package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-xi7lcMyp.js +0 -2
  153. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +0 -8
  154. package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-Bu6Nl6O2.js +0 -120
  155. package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-BzMz4Bn9.js +0 -34
  156. package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-CxaJnzc5.js +0 -78
  157. package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-5JK-omQK.js +0 -7
  158. package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
  159. package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
  160. package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
  161. package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
  162. package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
  163. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
  164. package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
  165. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
  166. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
  167. package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
  168. package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
  169. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
  170. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
  171. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
  172. package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
  173. package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
  174. package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
  175. package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
  176. package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
  177. package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
  178. package/dist/worker/console/static-src/operator-chat/activity-journey.js +0 -125
  179. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  180. package/dist/worker/console/static-src/operator-chat/activity-references.js +0 -20
  181. package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +0 -24
  182. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
  183. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
  184. package/dist/worker/observability/interrupt-eligibility.js +0 -264
  185. package/dist/workflows/dag/contract-output-registry.js +0 -14
  186. package/dist/workflows/dag/contract-validator-registrations.js +0 -8
  187. package/dist/workflows/dag/frontend-recovery-plan.js +0 -73
  188. package/dist/workflows/dag/frontend-recovery-root-manifest.js +0 -123
  189. package/dist/workflows/dag/frontend-recovery-run.js +0 -539
  190. package/dist/workflows/dag/frontend-writer-recovery.js +0 -106
  191. package/dist/workflows/dag/frontend-writer-rollback.js +0 -821
  192. 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|sk)[-_A-Za-z0-9]{12,}\b/i;
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
- if (exceptionType !== "requests.RequestException") {
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
- const hasResponseLog = /(?:print|log(?:ger)?\.(?:debug|info|warning|error))\s*\([^\n]*(?:response|响应)[^\n]*(?:status|status_code|body|json|text|result|结果)/i.test(source);
371
- 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);
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 (mappedScripts.length === 0) {
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);