@tea-agent/loop-agent 0.44.0-next.12 → 0.44.0-next.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -2
- package/README.md +12 -12
- package/dist/adapters/loop-agent.js +34 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +5 -5
- package/dist/cli/program.js +1 -4
- package/dist/commands/init-legacy-audit.js +330 -0
- package/dist/commands/init-upgrade.js +644 -104
- package/dist/commands/init.js +686 -330
- package/dist/commands/operator-guide.js +99 -0
- package/dist/commands/operator.js +16 -3
- package/dist/executors/dag-pi-executor.js +11 -6
- package/dist/executors/pi-sdk-executor.js +3 -1
- package/dist/executors/pi-writer-tool-policy.js +60 -4
- package/dist/executors/playwright-cli-launcher.js +31 -45
- package/dist/executors/shell-write-guard.js +24 -67
- package/dist/governance/exec-plans.js +10 -1
- package/dist/governance/harness.js +2 -7
- package/dist/governance/manifest-types.js +28 -9
- package/dist/governance/project-capability-scanner.js +263 -0
- package/dist/governance/project-config.js +96 -0
- package/dist/governance/resource-catalog.js +151 -0
- package/dist/infrastructure/console/app-data.js +6 -2
- package/dist/shared/operator/capabilities.js +2 -2
- package/dist/shared/package-metadata.js +23 -7
- package/dist/shared/pi-context-pressure/extension.js +23 -0
- package/dist/shared/pi-context-pressure/index.js +1 -0
- package/dist/shared/pi-context-pressure/telemetry.js +4 -0
- package/dist/shared/pi-context-pressure/tool-result-pruner.js +98 -0
- package/dist/shared/update/npm-client.js +49 -1
- package/dist/task/config-types.js +4 -2
- package/dist/task/source-prepare/build-draft.js +1 -0
- package/dist/task/source-prepare/path-policy.js +17 -7
- package/dist/worker/cli.js +3 -5
- package/dist/worker/console/chat/browser-automation.js +455 -41
- package/dist/worker/console/chat/browser-content.js +74 -0
- package/dist/worker/console/chat/browser-diagnostics.js +250 -0
- package/dist/worker/console/chat/browser-evaluation.js +58 -0
- package/dist/worker/console/chat/browser-operation-queue.js +19 -0
- package/dist/worker/console/chat/browser-pick.js +113 -0
- package/dist/worker/console/chat/browser-routes.js +54 -5
- package/dist/worker/console/chat/browser-screenshot-result.js +47 -0
- package/dist/worker/console/chat/browser-storage.js +89 -0
- package/dist/worker/console/chat/chat-event-store.js +7 -6
- package/dist/worker/console/chat/context-insights.js +84 -19
- package/dist/worker/console/chat/pet/pet-affinity.js +169 -0
- package/dist/worker/console/chat/pet/pet-persist.js +109 -0
- package/dist/worker/console/chat/pet/pet-registry.js +142 -0
- package/dist/worker/console/chat/pet/pet-remarks.js +48 -0
- package/dist/worker/console/chat/pet/pet-routes.js +125 -0
- package/dist/worker/console/chat/pet/pet-service.js +262 -0
- package/dist/worker/console/chat/pet/pet-state.js +99 -0
- package/dist/worker/console/chat/pi-mode-loop-isolation.js +17 -2
- package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +125 -174
- package/dist/worker/console/chat/pi-runtime.js +8 -3
- package/dist/worker/console/chat/repo-browser.js +80 -2
- package/dist/worker/console/chat/repo-preview-kind.js +155 -0
- package/dist/worker/console/chat/routes.js +43 -7
- package/dist/worker/console/chat/scheduled-goal-request.js +14 -6
- package/dist/worker/console/chat/session-mode.js +1 -1
- package/dist/worker/console/chat/session-store.js +24 -5
- package/dist/worker/console/chat/tools.js +12 -0
- package/dist/worker/console/console-shutdown.js +34 -0
- package/dist/worker/console/server.js +2 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-r1NAPU_5.js → abnfDiagram-N423BO3Z-DASc69AM.js} +1 -1
- package/dist/worker/console/static/assets/{arc-dT5T4sVL.js → arc-h09p-LAc.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CP_cOq46.js → architectureDiagram-T3A2C74G-CZ3dtLkh.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-sToEZxEf.js → blockDiagram-VBNYF7ZC-DMrRhMXG.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DSxwwi2m.js → c4Diagram-5PPSVZJV-DQxisHV6.js} +1 -1
- package/dist/worker/console/static/assets/channel-D1zgKZfg.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-B9fh0IkF.js → chunk-2GRJ4B5K-CefVN9zh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CAUNl-A6.js → chunk-2Q5K7J3B-DmR1Koq2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BomeqbNX.js → chunk-5RXB4S5H-NlzrSW7L.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BNAuTOLE.js → chunk-5VM5RSS4-DfelJv-0.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DfzjRD5y.js → chunk-6Q2QTUOP-BK6RFy58.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-C3uE-yc2.js → chunk-GF5L2VYU-BAeh9lz4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-C3sQMMYr.js → chunk-JWPE2WC7-ChPS5WzG.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D_AtEYQL.js → chunk-KBJHAD2P-BoVe5IM6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DW4TwhKv.js → chunk-RYQCIY6F-C4q_0Lw9.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CTQDmJcp.js → chunk-XXDRQBXY-Bb9TvjeY.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CXNNsY2M.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CXNNsY2M.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-iMqPuBsL.js → cose-bilkent-JH36ORCC-DmNnL8Ff.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DktIuDWs.js → cynefin-VYW2F7L2-Contvx58.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BgVnjyEs.js → cynefinDiagram-MW4NZA55-Cigq7cjk.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CR25E4bu.js → dagre-VZM6K2ZE-dEbC0nxb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CPM2h5HY.js → diagram-7IWD3JNH-CzIztESV.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DqF0x2g1.js → diagram-B4RE2ZJO-Bo14zbaY.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-G8BYp2eb.js → diagram-LBJQPF4R-C4xUyil6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BbC4kT_D.js → diagram-Q27KOJAE-CaBY_uYa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CKZwZ-G5.js → diagram-UB23O5K3-ClUXUeiu.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-kJDmheia.js → ebnfDiagram-BXEA7PRR-BbGCgjC6.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DFTVHyxB.js → erDiagram-JOGREHBK-Dr-AcwSl.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BqdN5_os.js → flowDiagram-UKHOOZJN-BG8Gcfyc.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-SYDjVakh.js → ganttDiagram-PKOTCBZU-CFVaPs5r.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bf8bNpGA.js → gitGraphDiagram-DS77QQ5N-ChJ8pm-O.js} +1 -1
- package/dist/worker/console/static/assets/index-B_5GhVIO.js +488 -0
- package/dist/worker/console/static/assets/index-C7YMBluX.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DIeAmJ45.js → infoDiagram-6WML65LV-C5J9-uCZ.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-oikqUAlo.js → ishikawaDiagram-WSZJBQD7-BcfjqzDv.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-mKEF_xYT.js → journeyDiagram-NVQOT4AX-BzzCO-rD.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BdWJHPnN.js → kanban-definition-27J2QSJJ-NCHUZ46h.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DGRlieMH.js → linear-BKZlWCUU.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-4P9yPC9c.js → mermaid.core-DbTaQjFE.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-5PTSkos6.js → mindmap-definition-FAOFIHXS-CroYl5xQ.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DONIFpG5.js → pegDiagram-VL7TDLO6-DgMBx-9k.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-D_Z4S70M.js → pieDiagram-7S7Q4E2Y-CyWDmbq1.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CAsjh8iY.js → quadrantDiagram-CIZ2JOQS-BgGEOqSP.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Cx4Dd6ac.js → railroadDiagram-AXF67PYL-BMZ7oeeV.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-cQb7mwyH.js → requirementDiagram-LRYGKXZP-DxqyGhcc.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cd-DhUdf.js → sankeyDiagram-W5VNT64P-DnSvNCG8.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-AlKYRgIk.js → sequenceDiagram-SI44F4Z6-BiZh6f-R.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CUy_ET4e.js → sizeCapture-X5ZJPWSS-BKXSI8WB.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CTuVe6Sa.js → stateDiagram-OKZ733FA-CfSgoYFm.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DgHi3vjb.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-ChFLJb-v.js → swimlanes-SLNWSIFB-CyGADcHe.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bgvdt1Y0.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DZZeiAPr.js → timeline-definition-Z64GVDOM-HmPaSPOK.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-t4O91cZU.js → vennDiagram-T6HMQDX7-VA2VBbez.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CV5qGHZu.js → wardleyDiagram-T6FBY63Y-BxjiBPZa.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DyXbytUI.js → xychartDiagram-ELKLHX3M--hUY-qFj.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static/pets/ember/pet.json +167 -0
- package/dist/worker/console/static/pets/ember/spritesheet.png +0 -0
- package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +4 -3
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +57 -9
- package/dist/worker/console/static-src/operator-chat/use-stat-dialog.js +95 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +6 -0
- package/dist/worker/console/workspace-context.js +15 -0
- package/dist/worker/console/workspace-initialization.js +25 -16
- package/dist/worker/delivery/package.js +4 -1
- package/dist/worker/materialize/harness-task-lineage.js +4 -2
- package/dist/worker/observe/static/console-theme.css +33 -29
- package/dist/worker/observe/static/operator-chrome.js +1 -42
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +3 -1
- package/dist/workflows/dag/backend-test-layout.js +27 -7
- package/dist/workflows/dag/backend-test-markdown-workflow.js +3 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +4 -1
- package/dist/workflows/dag/final-verification.js +45 -10
- package/dist/workflows/dag/frontend-test-case-checklist.js +12 -3
- package/dist/workflows/dag/frontend-test-environment-probe.js +0 -2
- package/dist/workflows/dag/frontend-test-layout.js +41 -12
- package/dist/workflows/dag/hybrid/sources.js +35 -16
- package/dist/workflows/dag/hybrid/templates/backend-test.js +70 -55
- package/dist/workflows/dag/hybrid/templates/frontend-test.js +62 -41
- package/dist/workflows/dag/hybrid/templates/kg-bootstrap.js +6 -6
- package/dist/workflows/dag/hybrid/templates/knowledge-sync.js +5 -5
- package/dist/workflows/dag/skill-instructions.js +102 -32
- package/dist/workflows/dag/skill-snapshot.js +3 -0
- package/dist/workflows/dag/skills.js +7 -7
- package/dist/workflows/dag/test-artifact-namespace.js +73 -0
- package/dist/workflows/dag/validate.js +24 -5
- package/dist/workflows/dag/workspace-checkpoint.js +13 -1
- package/docs/architecture/runtime-boundaries.md +28 -22
- package/docs/init-surface.manifest.json +43 -295
- package/docs/templates/README.md +4 -4
- package/docs/templates/agent-dag.base.json +9 -9
- package/docs/templates/agent-dag.final-verification.json +6 -6
- package/docs/templates/agent-dag.supervised-implementation.json +12 -12
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +3 -3
- package/docs/templates/backend-test-dag.json +708 -708
- package/docs/templates/backend-test-dag.review-cases.prompt.md +3 -3
- package/docs/templates/frontend-implementation-dag.json +2 -2
- package/docs/templates/frontend-test-case-checklist.md +2 -2
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +7 -7
- package/docs/templates/frontend-test-dag.json +48 -39
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +2 -2
- package/docs/templates/frontend-test-dag.retrospect.prompt.md +1 -1
- package/docs/templates/frontend-test-dag.review-cases.prompt.md +1 -1
- package/docs/templates/hybrid-dag.json +9 -9
- package/docs/templates/knowledge-sync-dag.json +8 -8
- package/package.json +12 -3
- package/resource-catalog.v1.json +1973 -0
- package/resources/agent-bridge/loop-agent/SKILL.md +40 -0
- package/resources/init-history/legacy-surface.v1.json +33 -0
- package/skills/fe-test-ui-scout/SKILL.md +4 -4
- package/skills/fe-test-ui-scout/references/ledger-schema.md +1 -1
- package/skills/init-update/SKILL.md +34 -0
- package/skills/init-update/agents/openai.yaml +6 -0
- package/skills/playwright-cli/SKILL.md +1 -1
- package/skills/playwright-cli-case-generator/SKILL.md +4 -4
- package/dist/worker/console/static/assets/channel-CyBY_yGk.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D5lN4E_E.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D5lN4E_E.js +0 -1
- package/dist/worker/console/static/assets/index-B28Onmfy.css +0 -1
- package/dist/worker/console/static/assets/index-CTMZsDmh.js +0 -486
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BsWhs8O7.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CJcioYGH.js +0 -8
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
/** DAG hybrid backend-test 模板族:pytest 收集/执行/归类/回溯与 gap-fill DAG 装配。 */
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import { deflateRawSync } from "node:zlib";
|
|
3
4
|
import { assertValidDagSpec } from "../../validate.js";
|
|
4
5
|
import { DEFAULT_DAG_EXECUTOR_MODELS, DEFAULT_DAG_OUTPUT_LANGUAGE, parseDagSpec } from "../../types.js";
|
|
5
6
|
import { BACKEND_TEST_MARKDOWN_BINDING_RETRY_POLICY, BACKEND_TEST_MD_PLAN_RETRY_POLICY, BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY } from "../../retry-policy.js";
|
|
6
7
|
import { BACKEND_TEST_MODULE_INDEX_HEADER, BACKEND_TEST_MODULE_SPLIT_REASONS } from "../../backend-test-plan-protocol.js";
|
|
8
|
+
import { isHarnessNamespacePath } from "../../test-artifact-namespace.js";
|
|
7
9
|
import { DEFAULT_VERIFY_TIMEOUT_MS } from "../../../../executors/shell-verification.js";
|
|
8
10
|
import { BACKEND_TEST_EXECUTION_DEFAULT_TEST_ROOT, buildBackendTestExecutionPreflightShellSnippet } from "../../backend-test-execution-contract.js";
|
|
9
11
|
import { resolveBackendTestLayout } from "../../backend-test-layout.js";
|
|
@@ -66,7 +68,7 @@ export function buildBackendTestAnalysisContractGateNode(sources) {
|
|
|
66
68
|
},
|
|
67
69
|
};
|
|
68
70
|
}
|
|
69
|
-
export function buildBackendTestEnvironmentScoutNode(sources) {
|
|
71
|
+
export function buildBackendTestEnvironmentScoutNode(sources, layout) {
|
|
70
72
|
return {
|
|
71
73
|
id: "backend-test-environment-scout-pi",
|
|
72
74
|
depends_on: ["backend-test-analysis-contract-shell"],
|
|
@@ -85,10 +87,10 @@ export function buildBackendTestEnvironmentScoutNode(sources) {
|
|
|
85
87
|
"Do NOT search the whole repo for secrets, .env values, tokens, private keys, or production credentials.",
|
|
86
88
|
'framework must be "pytest". Default targetMode to "in-process" unless evidence clearly shows an external service base URL env name or documented managed start/stop with sourceRef.',
|
|
87
89
|
'Do NOT select targetMode "managed-command" unless task source documents a safe start/stop command with an explicit sourceRef; otherwise leave managedCommand absent (do not invent managed mode). For external-running-service, missing managed start/stop is expected and is NOT an evidenceGap.',
|
|
88
|
-
|
|
90
|
+
`testRoot and workingDirectory must be repo-relative posix paths without .. or absolute form. Adapter default testRoot is ${layout.testRoot} when evidence is incomplete.`,
|
|
89
91
|
"runner must not include secret values. report.format must be junit with a relativeHint under the run (e.g. reports/backend-test-junit.xml).",
|
|
90
92
|
"requiredEnvNames lists env NAMES only. baseUrlEnvName is required only for external-running-service and must match ^[A-Z_][A-Z0-9_]*$.",
|
|
91
|
-
|
|
93
|
+
`evidenceGaps are optional notes only. Do NOT list greenfield/expected-later items as gaps: missing test_*.py / conftest (generate-pytest will create them), missing pytest.ini when testRoot defaults to ${layout.testRoot}/, projected schema under ai_workspace/** instead of docs/templates/**, or optional API_BASE_URL when a documented default base URL exists.`,
|
|
92
94
|
"Prefer evidenceGaps: [] for MVP greenfield external pytest. Use evidenceGaps only for true blockers the later generate nodes cannot fix (e.g. no viable testRoot at all). Populate evidenceRefs with repo-relative paths actually read.",
|
|
93
95
|
"Required top-level keys: schemaVersion, framework, runner, testRoot, workingDirectory, report, targetMode, existingFixtures, authenticationMode, requiredEnvNames, dataIsolation, evidenceGaps, evidenceRefs.",
|
|
94
96
|
"Read-only: do not modify code, docs, artifacts, or repository files.",
|
|
@@ -121,7 +123,7 @@ export function buildBackendTestExecutionContractGateNode(sources) {
|
|
|
121
123
|
},
|
|
122
124
|
};
|
|
123
125
|
}
|
|
124
|
-
export function buildGenerateBackendFunctionalCasesNode(sources) {
|
|
126
|
+
export function buildGenerateBackendFunctionalCasesNode(sources, layout) {
|
|
125
127
|
return {
|
|
126
128
|
id: "generate-backend-functional-cases-pi",
|
|
127
129
|
depends_on: [
|
|
@@ -133,8 +135,8 @@ export function buildGenerateBackendFunctionalCasesNode(sources) {
|
|
|
133
135
|
toolProfile: "write",
|
|
134
136
|
complexity: "MED",
|
|
135
137
|
writePolicy: "exclusive",
|
|
136
|
-
writeSet: [
|
|
137
|
-
allowedPaths: [
|
|
138
|
+
writeSet: [`${layout.markdownDir}/**`],
|
|
139
|
+
allowedPaths: [`${layout.markdownDir}/**`],
|
|
138
140
|
forbiddenPaths: commonForbiddenPaths(sources),
|
|
139
141
|
// 注意:Pi 节点超时由 executor 层控制(默认 30 分钟)
|
|
140
142
|
// 如需调整,在 harness.json 的 executors.pi 中配置 modelConfig.timeoutMs
|
|
@@ -147,7 +149,7 @@ export function buildGenerateBackendFunctionalCasesNode(sources) {
|
|
|
147
149
|
"",
|
|
148
150
|
"## Output Steps (do in order):",
|
|
149
151
|
"1. First, output a brief summary: how many modules, how many cases planned per module",
|
|
150
|
-
|
|
152
|
+
`2. Then write each test case file under ${layout.markdownDir}/`,
|
|
151
153
|
"",
|
|
152
154
|
"## Format Rules:",
|
|
153
155
|
"- Each test case ID: BE-<MODULE>-<NNN> (e.g. BE-ORDER-001) — always write the FULL id; never abbreviate as 002, 003 in matrices",
|
|
@@ -177,13 +179,13 @@ export function buildGenerateBackendFunctionalCasesNode(sources) {
|
|
|
177
179
|
"- If not mentioned, do NOT generate these test cases",
|
|
178
180
|
"",
|
|
179
181
|
"## Constraints:",
|
|
180
|
-
|
|
182
|
+
`- Stay within writeSet: ${layout.markdownDir}/**`,
|
|
181
183
|
"- Do NOT re-read source documents or fall back to free-form analysis; use the two validated run-owned contracts only",
|
|
182
184
|
"- Do not write root artifacts/**",
|
|
183
185
|
].join("\n\n"),
|
|
184
186
|
};
|
|
185
187
|
}
|
|
186
|
-
export function buildEmitBackendCaseManifestNode(sources, options) {
|
|
188
|
+
export function buildEmitBackendCaseManifestNode(sources, layout, options) {
|
|
187
189
|
return {
|
|
188
190
|
id: options?.id ?? "emit-backend-case-manifest-pi",
|
|
189
191
|
depends_on: options?.dependsOn ?? [
|
|
@@ -200,7 +202,7 @@ export function buildEmitBackendCaseManifestNode(sources, options) {
|
|
|
200
202
|
subtask_prompt: [
|
|
201
203
|
"Emit Backend Test Case Manifest v1 as pure JSON (or one fenced json block with no trailing text).",
|
|
202
204
|
BACKEND_TEST_CASE_MANIFEST_OUTPUT_INSTRUCTIONS,
|
|
203
|
-
|
|
205
|
+
`Read-only: use validated contracts/backend-test-analysis.json pointer + ${layout.markdownDir}/** only. Do not write repository files or .harness/**.`,
|
|
204
206
|
"No secrets or credential-shaped fields.",
|
|
205
207
|
].join("\n\n"),
|
|
206
208
|
};
|
|
@@ -231,7 +233,7 @@ export function buildBackendTestCaseManifestGateNode(sources, options) {
|
|
|
231
233
|
},
|
|
232
234
|
};
|
|
233
235
|
}
|
|
234
|
-
export function buildBackendTestTraceabilityGateNode(sources, options = {}) {
|
|
236
|
+
export function buildBackendTestTraceabilityGateNode(sources, layout, options = {}) {
|
|
235
237
|
return {
|
|
236
238
|
id: options.id ?? "backend-test-traceability-gate-shell",
|
|
237
239
|
depends_on: options.dependsOn ?? [
|
|
@@ -249,7 +251,7 @@ export function buildBackendTestTraceabilityGateNode(sources, options = {}) {
|
|
|
249
251
|
writePolicy: "read-only",
|
|
250
252
|
allowedPaths: commonReadOnlyPaths(sources),
|
|
251
253
|
forbiddenPaths: commonForbiddenPaths(sources),
|
|
252
|
-
outputContract:
|
|
254
|
+
outputContract: `Deterministic traceability: generated cases have real file/symbol; skipped/unsupported have gapReason; convention symbols scanned under ${layout.testRoot}/**/test_*.py.`,
|
|
253
255
|
subtask_prompt: "Fail closed when generated automation claims do not resolve to workspace pytest symbols, or skip/unsupported lacks gapReason.",
|
|
254
256
|
shell: {
|
|
255
257
|
commands: ["backend-test-traceability-gate"],
|
|
@@ -258,7 +260,7 @@ export function buildBackendTestTraceabilityGateNode(sources, options = {}) {
|
|
|
258
260
|
},
|
|
259
261
|
};
|
|
260
262
|
}
|
|
261
|
-
export function buildReviewBackendCasesNode(sources, options) {
|
|
263
|
+
export function buildReviewBackendCasesNode(sources, layout, options) {
|
|
262
264
|
return {
|
|
263
265
|
id: "review-backend-cases-pi",
|
|
264
266
|
depends_on: options?.dependsOn ?? [
|
|
@@ -273,7 +275,7 @@ export function buildReviewBackendCasesNode(sources, options) {
|
|
|
273
275
|
forbiddenPaths: commonForbiddenPaths(sources),
|
|
274
276
|
outputContract: "advisory case review evidence whose first non-empty line is VERDICT: pass or VERDICT: request-revision; followed by Findings and Coverage Assessment. No file writes; this review neither authorizes nor blocks pytest generation.",
|
|
275
277
|
subtask_prompt: [
|
|
276
|
-
|
|
278
|
+
`Review the generated backend functional test cases under ${layout.markdownDir}/ and the validated Case Manifest v1.`,
|
|
277
279
|
"",
|
|
278
280
|
"## Mandatory First Line:",
|
|
279
281
|
"First non-empty line must be exactly: VERDICT: pass or VERDICT: request-revision",
|
|
@@ -313,7 +315,7 @@ export function buildReviewBackendCasesNode(sources, options) {
|
|
|
313
315
|
"## Constraints:",
|
|
314
316
|
"- Read-only: do not modify files",
|
|
315
317
|
"- Read validated analysis + case manifest artifacts; do not recompute coverage percentages",
|
|
316
|
-
|
|
318
|
+
`- Use ${layout.markdownDir}/ files for case review`,
|
|
317
319
|
]
|
|
318
320
|
.filter((line) => line !== "")
|
|
319
321
|
.join("\n\n"),
|
|
@@ -523,9 +525,13 @@ export function buildL5MetricsNode(sources) {
|
|
|
523
525
|
};
|
|
524
526
|
}
|
|
525
527
|
export function applyBackendTestLayoutToText(text, layout) {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
+
// No isDefault short-circuit: legacy `testcase/`-rooted template text must
|
|
529
|
+
// converge onto the canonical default root too, so both legacy and
|
|
530
|
+
// canonical templates produce identical default DAGs.
|
|
528
531
|
const replacements = [
|
|
532
|
+
// Legacy pre-2026-09-17 default-root tokens AND tokens already written in
|
|
533
|
+
// the current canonical default form both map onto the resolved layout,
|
|
534
|
+
// so legacy templates and canonical templates converge identically.
|
|
529
535
|
["testcase/test_", `${layout.scriptDir}/test_`],
|
|
530
536
|
["testcase/md/", `${layout.markdownDir}/`],
|
|
531
537
|
["testcase/**", `${layout.testRoot}/**`],
|
|
@@ -835,7 +841,7 @@ export async function buildBackendTestGapFillDag(sources) {
|
|
|
835
841
|
allowedPaths: ro,
|
|
836
842
|
forbiddenPaths: forbidden,
|
|
837
843
|
outputContract: "Run-owned contracts/backend-test-gap-plan-v1.json with targetModules, newTestPoints, reuseSetupRefs, conflicts, alreadyPresent and exact targetPaths; identity mismatch (taskId/requirement hash) fails closed before any writer runs.",
|
|
838
|
-
subtask_prompt:
|
|
844
|
+
subtask_prompt: `Parse the bound gap document (backend-test-gap-v1) and union it with previous coverage facts missingSlots; emit the deterministic gap plan. No file writes to ${layout.testRoot} assets.`,
|
|
839
845
|
shell: {
|
|
840
846
|
commands: [],
|
|
841
847
|
backendTestPipeline: "ingest-backend-test-gap",
|
|
@@ -878,7 +884,7 @@ export async function buildBackendTestGapFillDag(sources) {
|
|
|
878
884
|
writePolicy: "none",
|
|
879
885
|
allowedPaths: [],
|
|
880
886
|
forbiddenPaths: forbidden,
|
|
881
|
-
outputContract:
|
|
887
|
+
outputContract: `Serial aggregate of per-module Markdown patch writers; each child writes exactly one planned ${layout.testRoot} module file with a bounded append/patch diff.`,
|
|
882
888
|
subtask_prompt: "Expand the gap plan targetModules into one sharded Markdown patch writer child per module and run them serially. Child failures fail-close the map barrier.",
|
|
883
889
|
static: {
|
|
884
890
|
resultMarkdown: "Backend-test gap-fill Markdown patch map expansion barrier.",
|
|
@@ -910,7 +916,7 @@ export async function buildBackendTestGapFillDag(sources) {
|
|
|
910
916
|
retryPolicy: BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY,
|
|
911
917
|
outputContract: "Patch exactly one planned module Markdown file: add missing slot Test Points (existing Case variant lists first; new BE-<MODULE>-<max+1> Case only when no Case can own the slot). Case IDs are never renumbered; sections follow the full-chain contract.",
|
|
912
918
|
subtaskPromptTemplate: [
|
|
913
|
-
|
|
919
|
+
`Read contracts/backend-test-gap-plan-v1.json and patch exactly ${layout.markdownDir}/{{item.stem}}.md. For every planned slot owned by this module: prefer appending the variant to the Case named by the plan (or the most related existing Case); create a new Case only when no existing Case can own it, numbering BE-<MODULE>-<NNN> from the module's current maximum +1. Keep every required h3 section; cite Matrix Rule Keys exactly; TP-SP slots use the exact deterministic slot IDs from the plan.`,
|
|
914
920
|
sharedSetup
|
|
915
921
|
? `When the slot needs shared pre-steps defined by the bound user document, reference them with 引用前置: ${sharedSetup.path}#<SS-ID|SS-DEFAULT> instead of duplicating steps.`
|
|
916
922
|
: "Prepare any needed state locally inside this Case (本地准备); no shared setup document is bound.",
|
|
@@ -950,7 +956,7 @@ export async function buildBackendTestGapFillDag(sources) {
|
|
|
950
956
|
writePolicy: "none",
|
|
951
957
|
allowedPaths: [],
|
|
952
958
|
forbiddenPaths: forbidden,
|
|
953
|
-
outputContract:
|
|
959
|
+
outputContract: `Serial aggregate of per-module pytest patch writers; each child appends to exactly one planned ${layout.scriptDir}/test_<stem>.py without rewriting the file.`,
|
|
954
960
|
subtask_prompt: "Expand the gap plan targetModules into one sharded pytest patch writer child per module and run them serially. Child failures fail-close the map barrier.",
|
|
955
961
|
static: {
|
|
956
962
|
resultMarkdown: "Backend-test gap-fill pytest patch map expansion barrier.",
|
|
@@ -989,7 +995,7 @@ export async function buildBackendTestGapFillDag(sources) {
|
|
|
989
995
|
retryPolicy: BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY,
|
|
990
996
|
outputContract: "Append-only patch of exactly one planned pytest module: add pytest.param rows / test functions for planned slots with exact TP ids; never rewrite or delete existing functions.",
|
|
991
997
|
subtaskPromptTemplate: [
|
|
992
|
-
|
|
998
|
+
`Read contracts/backend-test-gap-plan-v1.json and the patched module Markdown, then patch only ${layout.scriptDir}/test_{{item.stem}}.py. For each planned slot owned by this module: append an exact literal pytest.param(..., id=\"<slot-id>\") row to the owning Case's primary symbol, or append a new test function test_BE_<MODULE>_<NNN>_<desc> when a new Case was created. Existing functions, params and assertions must remain byte-stable; append-only edits.`,
|
|
993
999
|
sharedSetup
|
|
994
1000
|
? "Reuse the module-top user_shared_setup fixture for shared pre-steps; never re-create documented setup steps inside test bodies, and never create or modify conftest.py."
|
|
995
1001
|
: "Keep any needed setup local to the new function; never create or modify conftest.py.",
|
|
@@ -1040,7 +1046,7 @@ export async function buildBackendTestGapFillDag(sources) {
|
|
|
1040
1046
|
writerOutcomePolicy: { type: "implementation-outcome-v1", requireChangedFiles: true },
|
|
1041
1047
|
outputContract: "First non-empty line is IMPLEMENTATION_OUTCOME: changed|blocked. Repair only generated pytest defects on initial facts repairPaths; preserve every Markdown Case, Test Point, primary symbol and assertion meaning.",
|
|
1042
1048
|
subtask_prompt: [
|
|
1043
|
-
|
|
1049
|
+
`Repair the generated backend pytest asset as one bounded program using the direct upstream collection assessment. Treat any upstream \`Repair paths:\` line as complete authoritative repairPaths evidence. Only planned ${layout.scriptDir} test_*.py files may change.`,
|
|
1044
1050
|
"Do not modify Markdown, conftest, pytest config, production code or dependencies. Do not add skip/xfail, remove tests, loosen assertions or replace the real API with mocks.",
|
|
1045
1051
|
].join("\n\n"),
|
|
1046
1052
|
};
|
|
@@ -1178,7 +1184,7 @@ export async function buildBackendTestGapFillDag(sources) {
|
|
|
1178
1184
|
],
|
|
1179
1185
|
};
|
|
1180
1186
|
spec.backendTestLayout = layout;
|
|
1181
|
-
applyBackendTestWorkspaceControl(spec, taskConfig.backendTest?.workspaceControl ?? "git");
|
|
1187
|
+
applyBackendTestWorkspaceControl(spec, taskConfig.backendTest?.workspaceControl ?? "git", layout.testRoot, taskConfig.backendTest?.workspaceControl !== undefined);
|
|
1182
1188
|
if (sharedSetup) {
|
|
1183
1189
|
spec.backendTestSharedSetup = { ...sharedSetup };
|
|
1184
1190
|
}
|
|
@@ -1198,7 +1204,7 @@ export async function buildBackendTestHybridDag(sources) {
|
|
|
1198
1204
|
const intake = await buildBackendTestIntakeContext(sources);
|
|
1199
1205
|
// Plan A: resolve the frozen artifact layout once; every generated path
|
|
1200
1206
|
// (README, module Markdown, pytest script, pytest target) derives from it.
|
|
1201
|
-
// Default config resolves to the
|
|
1207
|
+
// Default config resolves to the canonical .harness/testcase/ layout.
|
|
1202
1208
|
const layout = resolveBackendTestLayout(taskConfig.backendTest);
|
|
1203
1209
|
const applyLayout = (text) => applyBackendTestLayoutToText(text, layout);
|
|
1204
1210
|
// Plan B: when the user binds exactly one shared-setup document, generated
|
|
@@ -1269,12 +1275,12 @@ export async function buildBackendTestHybridDag(sources) {
|
|
|
1269
1275
|
],
|
|
1270
1276
|
forbiddenPaths: forbidden,
|
|
1271
1277
|
retryPolicy: BACKEND_TEST_MD_PLAN_RETRY_POLICY,
|
|
1272
|
-
outputContract:
|
|
1278
|
+
outputContract: `Return a Chinese, human-readable Markdown-first plan with Coverage Scope, Coverage Matrix, Scenario Partitions when applicable, and a machine-parseable Module Index. The runtime persists it as a run-owned Harness artifact; do not write ${layout.markdownDir}/README.md, execute pytest, or modify project files.`,
|
|
1273
1279
|
subtask_prompt: [
|
|
1274
|
-
|
|
1280
|
+
`This is a required plan-generation node. Read only the strict read set and return the complete Markdown plan in the assistant response. Start the response with the final Markdown artifact immediately; never narrate analysis, reasoning, source summaries, or plans for producing the plan. The runtime persists the response as a run-owned Harness artifact named generate-backend-md-plan-pi/plan.md. Do not write ${layout.markdownDir}/README.md or any project file; module case cards are written by downstream sharded nodes.`,
|
|
1275
1281
|
"Output budget protocol (hard, max output <=16K per turn): Emit the complete required section skeleton before filling long table rows, including exactly one Module Index table with at least one module row. Never paste full Matrix, case bodies, source text, analysis or reasoning into assistant chat. README holds only Scope+Matrix+module index; never inline full case bodies. If a Completeness Gate / OUTPUT_LIMIT_RECOVERY retry is injected, continue only listed target paths.",
|
|
1276
1282
|
"Return the complete plan as plain Markdown. Do not emit JSON or code fences. The plan must contain the exact English protocol headings ## Coverage Scope, ## Coverage Matrix, ## Scenario Partitions when applicable, and ## Module Index. Never translate those headings into 覆盖范围/覆盖矩阵/场景分区/模块索引.",
|
|
1277
|
-
|
|
1283
|
+
`Read the upstream environment report only through the strict read set. Generate the Markdown-first backend test plan; it will be persisted under the current DAG run's Harness artifacts, not under ${layout.markdownDir}/.`,
|
|
1278
1284
|
"Write human-readable content in Simplified Chinese by default. Keep English protocol literals exact: section headings, table headers, Partition IDs, TP IDs, Case IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and source citations. Never translate ## Module Index into ## 模块索引.",
|
|
1279
1285
|
"Create the concise plan entry page: test objective, target/environment, isolation/cleanup, module summary and a linked case index table with Case ID, Chinese case name, scenario type, endpoint and expected status/result. Avoid repeating every case body in the plan artifact.",
|
|
1280
1286
|
"Before the Coverage Matrix, write a mandatory machine-readable `## Coverage Scope` section in the plan artifact using exactly `| Field | Value |`, immediately followed by the separator row `|---|---|`, and these six unique rows: `Change Classification`, `Coverage Policy`, `Affected Operations`, `Affected Rule Keys`, `Regression Floor`, `Scope Evidence`. Always set `Change Classification` to `new-operation` and `Coverage Policy` to `full-contract`; do NOT reason about whether operations are new or existing. Cover all in-scope rules from the requirement document at full depth; treat the product requirement as the coverage baseline and use API contract evidence (fields/status/enum/boundary/format) to supplement scenario dimensions. Scope is limited to operations/rules the requirement document (or its referenced API contract) explicitly describes; do not expand to unrelated operations that the requirement does not mention. List affected operations exactly as `METHOD /path`, stable rule keys separated by semicolons, and precise source pointers as Scope Evidence.",
|
|
@@ -1311,7 +1317,7 @@ export async function buildBackendTestHybridDag(sources) {
|
|
|
1311
1317
|
allowedPaths: ro,
|
|
1312
1318
|
forbiddenPaths: forbidden,
|
|
1313
1319
|
outputContract: "Stdout JSON {modules:[{stem,planReadPath}],planReadPath,planSha256,moduleLayout} parsed from the run-owned generate-backend-md-plan-pi/plan.md artifact after strict-layout validation and at most one deterministic Plan-only repair.",
|
|
1314
|
-
subtask_prompt:
|
|
1320
|
+
subtask_prompt: `Parse only $HARNESS_DAG_RUN_DIR/generate-backend-md-plan-pi/plan.md, validate the optional strict module layout and output-budget proof, apply at most one deterministic Plan-only Module Index repair without project writes, then emit one JSON line. No ${layout.markdownDir}/README.md fallback.`,
|
|
1315
1321
|
shell: {
|
|
1316
1322
|
commands: [buildBackendTestModuleManifestShellCommand(layout, taskConfig.backendTest?.moduleLayout)],
|
|
1317
1323
|
cwd: ".",
|
|
@@ -1327,7 +1333,7 @@ export async function buildBackendTestHybridDag(sources) {
|
|
|
1327
1333
|
writePolicy: "none",
|
|
1328
1334
|
allowedPaths: [],
|
|
1329
1335
|
forbiddenPaths: forbidden,
|
|
1330
|
-
outputContract:
|
|
1336
|
+
outputContract: `Serial aggregate of sharded Markdown module case-card writers. Each child writes exactly one ${layout.markdownDir}/<stem>.md with its own 16K Pi budget.`,
|
|
1331
1337
|
subtask_prompt: "Expand the README module manifest into one sharded Markdown writer child per module and run them serially. Child failures fail-close the map barrier.",
|
|
1332
1338
|
static: {
|
|
1333
1339
|
resultMarkdown: "Backend-test Markdown case-card map expansion barrier.",
|
|
@@ -1368,12 +1374,12 @@ export async function buildBackendTestHybridDag(sources) {
|
|
|
1368
1374
|
retryPolicy: BACKEND_TEST_MARKDOWN_BINDING_RETRY_POLICY,
|
|
1369
1375
|
outputContract: "Write exactly the frozen `{{item.markdownPath}}` Chinese module Markdown case-card file with BE-<MODULE>-<NNN> cases and the seven required h3 sections; keep machine IDs/literals exact and do not execute pytest or modify production code/config or the README.",
|
|
1370
1376
|
subtaskPromptTemplate: [
|
|
1371
|
-
|
|
1377
|
+
`This is a required file-generation node for exactly one Markdown module. Read the upstream run-owned Markdown plan artifact at \`{{item.planReadPath}}\` (Coverage Scope + Coverage Matrix + Module Index) and the bounded references, then immediately use write tools to create the single frozen file \`{{item.markdownPath}}\`. Do not read or recreate ${layout.markdownDir}/README.md. Do not end after analysis or planning, and do not return before a non-empty bounded diff exists. Do not modify any other module file.`,
|
|
1372
1378
|
"Output budget protocol (hard, max output <=16K per turn): Never paste full Matrix, other modules' case bodies, or source text into assistant chat. Each write/edit tool call touches at most one file (this module). Compact tables/lists are required; omitting required sections or in-scope variants is forbidden. If a Completeness Gate / OUTPUT_LIMIT_RECOVERY retry is injected, continue only listed target paths.",
|
|
1373
1379
|
"The first non-empty response line must be exactly IMPLEMENTATION_OUTCOME: changed after the module file has been written, or IMPLEMENTATION_OUTCOME: blocked when precise missing evidence prevents safe generation. already-satisfied is not valid for this node.",
|
|
1374
1380
|
"Write human-readable content in Simplified Chinese by default. Keep English only for machine-readable IDs and technical literals such as Case/AC/REQ/BR IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and exact source citations.",
|
|
1375
1381
|
'Write the module {{item.stem}} as readable case cards covering every in-scope rule/Test Point the README Coverage Matrix assigns to this module. Every case starts with `## BE-<MODULE>-<NNN>|<中文用例名称>`. `<NNN>` is exactly three zero-padded digits (`001`, `002`, ...), never two digits (`01`), a bare number, or an alphabetic suffix such as `011A`. Every case must include `### 覆盖规则`, `### 测试点`, `### 场景类型`, `### 前置条件`, `### 操作步骤`, `### 预期结果`, and `### 自动化映射` Do not group cases under "## 测试类 ..." (or any h2 grouping) headings that force Cases down to h3; each Case must be a direct h2 (`##`), and its seven sections must be h3 (`###`) children of that Case. If you need to convey a pytest class, state it inside the Case\'s `### 自动化映射` instead. Forbidden: `## 测试类 X` then `### BE-PD-001` and `### 覆盖规则` at the same h3 level. Required: `## BE-PD-001` then `### 覆盖规则`.; `覆盖规则` and `测试点` must reference exact Matrix Rule Keys/Test Points. Add `测试目的`, `验收标准`, `需求依据`, and `测试数据` for readable evidence. The `验收标准` section must list the exact applicable `AC-...` IDs, and every explicit task AC must appear in at least one Case. Every automatable case explicitly names its target pytest script and exactly one primary symbol so traceability scans only that script/symbol. Evidence-only meta cases that exist solely for non-executable assertion/cross-cutting process evidence may declare `脚本:无` and `primary symbol:无` with empty `变体测试点`, and must not invent a business pytest item.',
|
|
1376
|
-
|
|
1382
|
+
`Name this module file with the exact frozen Module Index stem \`{{item.stem}}\` (filename \`{{item.markdownPath}}\`). Never reinterpret or rename an explicit-user-layout stem. Priority-only stems \`p0\`, \`p1\` and \`p2\` are forbidden and must never produce \`p0.md\` or \`test_p0.py\`. Pure hexadecimal/hash-like opaque stems such as \`a401606\` and \`deadbeef\` are also forbidden. Do not use Case-ID-like module filenames. For every automatable case, \`自动化映射\` must name exactly \`{{item.pytestPath}}\`, where the module stem is this Markdown filename without \`.md\`, lowercased, with non-alphanumeric characters replaced by underscores. Example: \`health\` → \`${layout.scriptDir}/test_health.py\`; \`resource_notes\` → \`${layout.scriptDir}/test_resource_notes.py\`. Never invent a different pytest path in Markdown than the module stem implies.`,
|
|
1377
1383
|
"AUTOMATION_BINDING_FORMAT_V1 is a literal machine contract. Under every Case's `### 自动化映射`, write these independent lines exactly: `- 脚本:<path|无>`, `- primary symbol:<symbol|无>`, `- 变体测试点:<semicolon-separated TP IDs|无>`, `- 场景断言测试点:<semicolon-separated TP IDs|无>`, `- 横切证据测试点:<semicolon-separated TP IDs|无>`. TP IDs must be on the same line after the colon. Forbidden classification forms include `TP-X(变体测试点)`, `[变体测试点] TP-X`, `【变体测试点】:TP-X`, pipe-delimited annotations, tables, or nested TP lists. Before returning, verify that the Case `### 测试点` exact set equals the pairwise-disjoint union of the three canonical binding lines; do not add, remove, rename or duplicate a TP to make the format pass.",
|
|
1378
1384
|
"Scenario Partition slots: requiredVariantSlots for this module are `{{item.requiredVariantSlots}}`. Every listed ID MUST appear in this module's Cases as exactly one variant Test Point in both `### 测试点` and `变体测试点`. Slot IDs copy the declared Partition ID exactly; never drop the HTTP method, invent, merge, renumber or split slot IDs. Ordinary alias Test Points do not satisfy a partition slot, and aggregate aliases such as `SINGLE`/`MULTIPLE` are forbidden. Scheme A: one Case may carry many slots; do not create one Case per enum value just to match Case count. Prefer ONE Case per partition with a parameter table over duplicated Cases per value. The not-in-set slot value must be a concrete literal absent from the Domain (e.g. `UNKNOWN_TYPE`) and its expected result must come from the bound source — when Expected by Slot is GAP, the Case states the expectation as GAP evidence, never a guessed 空列表/400. Never create cross-axis combination variants beyond the single documented nominal.",
|
|
1379
1385
|
"For every variant Test Point, write its machine-checkable `场景意图: <TP-ID>; operation=...; target=...; intent=...` line inside that same Case body/自动化映射. Never collect Scenario Intent lines in a file-level appendix, implementation-details block, or another Case; local TP ownership is mandatory.",
|
|
@@ -1410,14 +1416,14 @@ export async function buildBackendTestHybridDag(sources) {
|
|
|
1410
1416
|
allowedPaths: Array.from(new Set([...ro, `${layout.markdownDir}/**`])),
|
|
1411
1417
|
forbiddenPaths: Array.from(new Set([...forbidden, layout.readmePath])),
|
|
1412
1418
|
writerOutcomePolicy: { type: "implementation-outcome-v1" },
|
|
1413
|
-
outputContract:
|
|
1419
|
+
outputContract: `First non-empty line is IMPLEMENTATION_OUTCOME: changed|already-satisfied|blocked. Perform exactly one bounded incremental synchronization of ${layout.markdownDir}/** against all bound source references; preserve valid Cases and report a concise summary.`,
|
|
1414
1420
|
subtask_prompt: [
|
|
1415
|
-
|
|
1421
|
+
`Perform one gap-targeted synchronization, not a full-suite rewrite or stylistic review. Read the immutable run-owned Markdown plan from the direct upstream manifest's planReadPath. Start from explicit bound source IDs/error codes/DTO fields/normative quoted rules and the plan Coverage Matrix; open and edit only modules that own a missing or conflicting rule. Never create or edit ${layout.markdownDir}/README.md and never modify the run-owned plan artifact. Preserve unrelated valid modules byte-for-byte and avoid optional wording cleanup.`,
|
|
1416
1422
|
"Output budget protocol: never dump full Matrix/case bodies into assistant chat. Inspect the immutable run-owned plan first, build a concise target list from its Matrix and Module Index, then read/write only target modules one file per tool call. Do not traverse every module when the Matrix and source token inventory show no gap; return `already-satisfied`. When adding omitted in-scope cases, keep every required section. Do not bulk-delete in-scope cases to save tokens.",
|
|
1417
1423
|
"For every variant Test Point, ensure the Markdown scenario intent is machine-checkable and located inside that same Case body/自动化映射, never in a file-level appendix, implementation-details block, or another Case. Use an exact transport target: `场景意图: <TP-ID>; operation=<METHOD /path>; target=<body.field|query.field|path.field|header.field|request>; intent=<empty|missing|null|min-1|min|max|max+1|pattern-invalid|enum-invalid|wrong-type|nominal-operation|custom-literal:V>; bound=<n optional>; example=<optional>; expectedCode=<optional>`. Never use vague targets such as field=resource/health. Keep pytest params aligned to the exact target. For intent=missing/empty/default-omit, pytest may use `_OMIT` or delete the key; for intent=enum-invalid use a concrete invalid enum literal (for example `UNKNOWN_STATUS`), never `_OMIT`/missing-key; for trim/padded samples use `custom-literal:trim` or a real padded string, not a bare token like `filter-active` when the intent is `custom-literal:ACTIVE`.",
|
|
1418
1424
|
"Treat the requirement document as the coverage baseline; scope is limited to operations/rules it (or its referenced API contract) describes, and API contract evidence supplements scenario dimensions. For every in-scope operation, check applicable lifecycle/uniqueness states (including deleted-existing when in scope), valid enum values, bounded invalid classes, min-1/min/nominal/max/max+1, allowed/forbidden format classes, required/null/missing/wrong-type semantics, status/error codes, auth and state transitions. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same affected path can affect them; unresolved impact stays visible as GAP/CONFLICT. Directly add in-scope omissions; reject scope expansion to operations absent from the requirement document; undefined impact remains GAP/CONFLICT rather than invented behavior.",
|
|
1419
1425
|
"Check AC completeness/meaning, endpoint, fields/shape, status/error codes, rules, states, documented boundaries/auth, positive/negative coverage, executable steps and assertable results. Require the exact `## Coverage Scope` Field/Value table with the `|---|---|` separator row, a valid classification-policy pair, non-empty Affected Operations/Rule Keys/Scope Evidence, and the classification-specific Regression Floor. Require the exact unnumbered `## Coverage Matrix` heading in the immutable run-owned plan artifact, exact headers, exactly 9 cells in every data row (including a non-empty Dimension), deterministic OpenAPI Rule Keys for every in-scope affected operation, exactly one Matrix row per Rule Key (merge multi-dimension product rows), and bidirectional Matrix Rule/Test Point ↔ Case bindings. Never describe affected-scope coverage as whole-API completeness. Every explicit AC ID must appear in at least one Case `验收标准`; every explicit in-scope AC/REQ/BR Rule Key cited by a Case must have exactly one Coverage Matrix row, and no Case may cite a source Rule Key omitted from the Matrix. Every Matrix Case ID must share at least one of that row's Required Test Points and the Case must cite that Rule Key. Perform an explicit execution-redundancy review: merge checkpoint-only parameter rows, repeated default/read-back assertions, DELETE status/body/follow-up-read checks, response schema/Content-Type checks, PUT full-update/timestamp checks, repeated list setup and identical null/empty inputs when endpoint, input partition, precondition state and expected outcome are the same. Preserve separate POST/PUT, boundary, enum, wrong-type, role/tenant and distinct business-state variants. Directly repair malformed headings/rows/keys and binding modes rather than merely commenting on them. Reject avoidable English prose, duplicated bilingual wording, repeated boilerplate, oversized unstructured sections, a `### 操作步骤` section that contains only a table without any numbered executable line, vague results such as ‘符合预期’, Case-ID-like module filenames (for example `BE-HEALTH.md`), dropped exact `### 操作步骤`/`### 预期结果` headings, and missing or drifted script/function mapping where it can be derived.",
|
|
1420
|
-
|
|
1426
|
+
`Correct LAYOUT.markdownDir}/** directly: add documented omissions, remove unsupported cases, preserve every frozen Module Index filename exactly (never rename an explicit-user-layout module; model-derived invalid stems must have been rejected before map expansion), normalize every Case ID to hyphen-separated module segments plus exactly three zero-padded digits (\`BE-RESOURCE_NOTES-01\` → \`BE-RESOURCE-NOTES-001\`; \`BE-RN-011A\` must be renumbered or merged) consistently across headings/index/mappings, fix automation mappings so each automatable case points at \`LAYOUT.scriptDir}/test_<module>.py\` derived from that module filename and declares exactly one primary symbol (evidence-only meta cases may keep \`脚本/primary symbol=无\` with empty variants), assign every Test Point exactly one of \`变体测试点\`/\`场景断言测试点\`/\`横切证据测试点\`, then perform an exact-set check: each Case's \`### 测试点\` set must equal (not merely contain) the union of those three binding lists; delete stale/legacy aliases and ensure every binding-list Test Point is present, expand every variant parameter row into its own atomic TP ID, make every non-cross-cutting TP Case-specific and owned by exactly one Case, require every primary symbol to start with the canonical Case prefix, ensure every explicit AC ID appears in an applicable Case \`验收标准\`, merge execution duplicates, improve navigation/tables/Chinese wording, or record gaps in Chinese. Remove every credential/header value, placeholder, fake token and anti-example from Markdown. Sensitive key names may remain only as a plain list; values must be described as runtime-only and omitted, with no colon/value pair or literal example anywhere, including details blocks and explanatory text. Keep Case IDs, AC/REQ/BR IDs, HTTP methods, paths, fields, enum values, filenames, code symbols and source citations as exact machine-readable identifiers; only normalize Case ID separator/sequence formatting as specified above. Recalculate predicted collected items as \`sum(max(1, variant count per Case))\`; when the task declares a budget, directly merge redundant journeys/reclassify same-request checkpoints until the prediction is within budget, while preserving all required coverage. The validator accepts Chinese and legacy English section aliases; retain or converge to the Chinese human-readable headings without losing structure.`,
|
|
1421
1427
|
"This is the single Markdown incremental synchronization round. Read every authoritative reference index entry whose role hints include acceptance-criteria, api-contract, data-contract or business-rule; do not rely on the derived PRD as a complete inventory. Preserve every explicit AC/REQ/BR ID, every documented HTTP/business error code, every DTO/JSON field, enum value, boundary, format, nested shape, transaction/state/idempotency/uniqueness/auth/tenant/cross-field rule. For each natural-language normative business rule preserved as required scope, include its exact source sentence without paraphrase together with source path and line/heading anchor so the deterministic ledger can verify quote/hash provenance. Ensure every Case declares exactly `Payload Contract: none` or the three labels `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum`; every label must occupy its own machine-readable list line, and a Case must never concatenate target/setup operations or multiple `Payload Contract` tokens onto one line, and explanatory prose/details must not repeat any `Payload Contract:` token; never infer missing keys or enum values. A target GET/DELETE operation with no request body must remain `Payload Contract: none` even when its setup journey performs POST/PUT with a DTO; setup payloads never redefine the target Case payload contract. Add only missing Matrix rows/Test Points/Cases/assertions or repair exact drift; do not rewrite already-valid unrelated modules. Work gap-targeted: inspect source anchors and affected modules first, leave unrelated valid modules byte-stable, and return `already-satisfied` without restating the full suite when no gap exists.",
|
|
1422
1428
|
"For affected API fields, use one valid nominal payload plus atomic required/missing/null/empty/wrong-type, every documented enum value plus bounded invalid classes, documented min-1/min/nominal/max/max+1, formats and nested object/array constraints. Do not generate a Cartesian product or invent undocumented constraints. Do not invent a concrete identifier type when the source only requires presence; for a missing-resource 404 path with unspecified identifier syntax/type, synchronize the Case to a create-delete-derived valid identifier journey rather than an arbitrary UUID/text placeholder.",
|
|
1423
1429
|
"Scenario Partitions synchronization: when the run-owned plan declares `## Scenario Partitions`, verify each declared partition's slots are fully materialized as variant Test Points with exact `TP-<Partition ID>-...` IDs (each-value per Domain value, OMITTED only for optional axes, exactly one NOT-IN-SET with intent=enum-invalid). Before returning, derive the complete exact slot set from every legal Scenario Partitions row and compare it with both the binding Coverage Matrix Rule's Required Test Points and the final Case `### 测试点`/`变体测试点` sets; directly add every missing exact slot to the already-assigned Case IDs; ordinary alias Test Points do not satisfy a partition slot, and aggregate aliases such as `SINGLE`/`MULTIPLE` are forbidden. Scheme A: keep existing Case structure and add missing exact variant Test Points to already-assigned Cases instead of creating one Case per enum value. Directly add missing slot rows/Cases. Record an illegal plan Partition row that has no source-backed finite domain as GAP/CONFLICT and remove only its derived `TP-SP-*` slots/Cases from target modules; never modify the immutable plan artifact. Never delete a legal source-backed partition or drop its complement slot to force coverage green. When the bound source does not document the complement expectation, keep the slot with GAP expected instead of guessing. Body-field validation enums (`TP-<FIELD>-ENUM-*`) are NOT partitions — do not add partition rows for them.",
|
|
@@ -1448,7 +1454,7 @@ export async function buildBackendTestHybridDag(sources) {
|
|
|
1448
1454
|
forbiddenPaths: forbidden,
|
|
1449
1455
|
outputContract: "Deterministic pytest generation handoff. Per-module map children write self-contained test_<module>.py files with bounded local fixtures, HTTP logging/redaction and payload builders; no model invocation, JSON, shared-asset writes or pytest execution.",
|
|
1450
1456
|
subtask_prompt: [
|
|
1451
|
-
|
|
1457
|
+
`Convert ${layout.markdownDir}/** into pytest using upstream environment and advisory validation evidence plus only bounded pytest config/conftest. This node is a deterministic handoff: NO shared helper/factory/fixture files are generated anywhere in the pipeline; each module's self-contained test_<module>.py (module-local fixtures, HTTP logging/redaction, payload builders) is written by a downstream sharded node. A FAIL advisory report does not authorize inventing missing behavior; use the final Markdown facts that are present.`,
|
|
1452
1458
|
"Output budget protocol (hard, max output <=16K per turn): downstream module writers write exactly one test_<module>.py per write/edit tool call. Never paste full Python modules into assistant chat. Do not reduce params/assertions/skips semantics to fit.",
|
|
1453
1459
|
"Align every variant pytest.param payload with the Markdown scenario intent (empty/missing/null/length/pattern/enum/wrong-type/nominal). Prefer literal payloads over Faker for intent-critical fields so pre-execution scenario-param checks can verify them. Hard contract: intent=enum-invalid MUST pass a concrete invalid value literal (string/number/boolean), never `_OMIT`/None/missing key; intent=missing/empty may use `_OMIT` or delete the key; intent=custom-literal:trim|whitespace-padded requires a leading/trailing whitespace string with non-empty trimmed content (all-whitespace belongs to empty/whitespace-only, not trim); intent=custom-literal:ACTIVE|ARCHIVED requires the exact enum string, never descriptive tokens like filter-active; intent=max/min/max+1 should pass a repeated-string length expression, a bare length number N, or a helper named _*_LEN{N} / _*_MAX_LENGTH / _*_OVER_LENGTH — never a bare 1 for oversize. Hard contract: request payload dicts may only contain DTO field keys from Payload Allowed Paths; never put expect/expected/echo_* helper keys inside the JSON body dict. Path/query/header identifiers and scenario-control metadata (including `id`, expected codes, and selector labels) must stay in separate pytest parameters and helper arguments; never merge them into a DTO patch or JSON body unless that exact path is allowed by the Markdown payload contract. Normalize the configured API base URL with `rstrip(\"/\")` (or equivalently join exactly one slash) before appending endpoint paths; generated requests must never contain a `//api/...` path. When the bound source documents a concrete non-secret local API URL, generated clients must use it as the fallback in `os.environ.get(\"API_BASE_URL\", \"<documented-url>\")`; do not require an otherwise-uninjected environment variable or fail setup solely because it is absent. Missing-field helpers must remove keys idempotently with `payload.pop(field, None)`, never `del payload[field]`, because optional fields may already be absent.",
|
|
1454
1460
|
"Generate a reusable HTTP logging helper (or equivalent client wrapper) and call it for every interface request. The request log must include method, URL/path, and request parameters (query plus JSON/body/payload summary). The response log must include status code and response result (JSON/text/body summary), and both records must be visible in pytest stdout/stderr without changing assertions. If shared pytest fixtures are generated, keep their dependency graph in one provider module and require each downstream test module to register that provider with an exact pytest_plugins tuple; importing only the outer fixture is insufficient and will be rejected by fixture-resolution preflight.",
|
|
@@ -1471,7 +1477,7 @@ export async function buildBackendTestHybridDag(sources) {
|
|
|
1471
1477
|
allowedPaths: ro,
|
|
1472
1478
|
forbiddenPaths: forbidden,
|
|
1473
1479
|
outputContract: "Stdout JSON {modules:[{stem,planReadPath}],planReadPath,planSha256,moduleLayout} parsed from the final strict-layout-validated run-owned Markdown plan artifact, matching the Markdown map manifest.",
|
|
1474
|
-
subtask_prompt:
|
|
1480
|
+
subtask_prompt: `Parse only the final run-owned generate-backend-md-plan-pi/plan.md artifact with the same strict module-layout and output-budget contract; do not search for or fall back to ${layout.testRoot}/**/README.md. No project file writes.`,
|
|
1475
1481
|
shell: {
|
|
1476
1482
|
commands: [buildBackendTestModuleManifestShellCommand(layout, taskConfig.backendTest?.moduleLayout)],
|
|
1477
1483
|
cwd: ".",
|
|
@@ -1487,7 +1493,7 @@ export async function buildBackendTestHybridDag(sources) {
|
|
|
1487
1493
|
writePolicy: "none",
|
|
1488
1494
|
allowedPaths: [],
|
|
1489
1495
|
forbiddenPaths: forbidden,
|
|
1490
|
-
outputContract:
|
|
1496
|
+
outputContract: `Serial aggregate of sharded pytest module writers. Each child writes exactly one self-contained ${layout.scriptDir}/test_<stem>.py with its own 16K Pi budget and no generated shared-asset dependency.`,
|
|
1491
1497
|
subtask_prompt: "Expand the README module manifest into one sharded pytest writer child per module and run them serially. Child failures fail-close the map barrier.",
|
|
1492
1498
|
static: {
|
|
1493
1499
|
resultMarkdown: "Backend-test pytest module map expansion barrier.",
|
|
@@ -1512,7 +1518,7 @@ export async function buildBackendTestHybridDag(sources) {
|
|
|
1512
1518
|
allowedPaths: ["{{item.pytestPath}}"],
|
|
1513
1519
|
forbiddenPaths: Array.from(new Set([
|
|
1514
1520
|
...forbidden,
|
|
1515
|
-
|
|
1521
|
+
`${layout.markdownDir}/**`,
|
|
1516
1522
|
"conftest.py",
|
|
1517
1523
|
"pytest.ini",
|
|
1518
1524
|
"pyproject.toml",
|
|
@@ -1526,12 +1532,12 @@ export async function buildBackendTestHybridDag(sources) {
|
|
|
1526
1532
|
retryPolicy: BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY,
|
|
1527
1533
|
outputContract: "Write exactly the frozen pytest module file `{{item.pytestPath}}` whose actual test function region contains the exact Case ID, preferably in the function name or docstring. the frozen `{{item.markdownPath}}` maps one-to-one to `{{item.pytestPath}}`; never merge or split modules. No JSON and no pytest execution.",
|
|
1528
1534
|
subtaskPromptTemplate: [
|
|
1529
|
-
|
|
1535
|
+
`Convert the single frozen Markdown module \`{{item.markdownPath}}\` into one self-contained pytest module. Before writing, also read the run-owned Markdown plan artifact at \`{{item.planReadPath}}\` and use its explicit API target/environment table as the authoritative fallback base URL for every module. A task/Markdown \`API_BASE_URL\` target takes precedence over project README dev-server URLs; never infer a backend API fallback from a frontend/Vite port such as localhost:3000. After reading the module Markdown, the run-owned plan artifact, and the bounded pytest config/conftest, immediately use write tools to create the single frozen file \`{{item.pytestPath}}\`. Define any bounded HTTP client fixture, request logging/redaction/truncation helper and payload builders needed by this module inside that same file; do not import generated ${layout.testRoot}/**/helpers/** or ${layout.testRoot}/**/factories/** assets. Do not end after analysis or planning. Do not modify Markdown, conftest, helpers/factories, or any other module's pytest script.`,
|
|
1530
1536
|
"Output budget protocol (hard, max output <=16K per turn): Write exactly the frozen `{{item.pytestPath}}`. Never paste full Python modules into assistant chat. Do not merge or split modules. Do not reduce params/assertions/skips to fit. If OUTPUT_LIMIT_RECOVERY is injected, continue only listed missing/broken scripts.",
|
|
1531
1537
|
"Align every variant pytest.param payload with the Markdown scenario intent (empty/missing/null/length/pattern/enum/wrong-type/nominal). Prefer literal payloads over Faker for intent-critical fields so pre-execution scenario-param checks can verify them. Hard contract: intent=enum-invalid MUST pass a concrete invalid value literal (string/number/boolean), never `_OMIT`/None/missing key; intent=missing/empty may use `_OMIT` or delete the key; intent=custom-literal:trim|whitespace-padded requires a leading/trailing whitespace string with non-empty trimmed content (all-whitespace belongs to empty/whitespace-only, not trim); intent=custom-literal:ACTIVE|ARCHIVED requires the exact enum string, never descriptive tokens like filter-active; intent=max/min/max+1 should pass a repeated-string length expression, a bare length number N, or a helper named _*_LEN{N} / _*_MAX_LENGTH / _*_OVER_LENGTH — never a bare 1 for oversize. Hard contract: request payload dicts may only contain DTO field keys from Payload Allowed Paths; never put expect/expected/echo_* helper keys inside the JSON body dict. Path/query/header identifiers and scenario-control metadata (including `id`, expected codes, and selector labels) must stay in separate pytest parameters and helper arguments; never merge them into a DTO patch or JSON body unless that exact path is allowed by the Markdown payload contract. Normalize the configured API base URL with `rstrip(\"/\")` (or equivalently join exactly one slash) before appending endpoint paths; generated requests must never contain a `//api/...` path. When the bound source documents a concrete non-secret local API URL, generated clients must use it as the fallback in `os.environ.get(\"API_BASE_URL\", \"<documented-url>\")`; do not require an otherwise-uninjected environment variable or fail setup solely because it is absent. Missing-field helpers must remove keys idempotently with `payload.pop(field, None)`, never `del payload[field]`, because optional fields may already be absent.",
|
|
1532
1538
|
"For every response contract that requires an object or pagination envelope, first assert that each envelope/data value is a dict and that required keys exist, then index fields and assert values. Never let an incidental KeyError or list/string TypeError stand in for the explicit response-shape contract failure.",
|
|
1533
1539
|
'Ensure every automatable final Markdown Case ID in this module appears in exactly one primary pytest test function or pytest test class method region, using the exact `primary symbol` declared by Markdown. Skip evidence-only meta Cases that declare `脚本/primary symbol=无` with empty variants; do not invent a business pytest symbol for them. The symbol must start with `test_BE_<MODULE>_<NNN>_` so every parameterized collected item remains associated with its Case. Module-level functions and class-based pytest methods are both supported. Only `变体测试点` may use stable `pytest.param(..., id="TP-...")` IDs, and every atomic variant ID must appear exactly once with a genuine input/state/outcome change. A Case with exactly one variant Test Point still needs one literal `pytest.param(..., id="TP-...")` row; never leave a single-variant Case as a bare function with the TP only in the docstring. Use a literal direct `pytest.param(..., id=...)` expression for every row; never hide or wrap it behind `_post_case`, `_put_case`, row-factory functions, comprehensions, generators, or dynamically returned parameter lists; do not use decorator-level `ids=[...]`, generated suffixes, or IDs that extend/shorten the exact Markdown TP. Do not parameterize `场景断言测试点` or `横切证据测试点`; execute all assertion checkpoints within the same business journey/item and use shared helpers for cross-cutting evidence. Governance-only cross-cutting bindings such as writeSet compliance, execution count, report existence, or orchestration state are metadata-only in business pytest: preserve their IDs in `Cross-Cutting-Test-Points`, but never assert `__file__`, filesystem placement, pytest invocation count, Harness state, or report artifacts inside the business test. Harness-owned evidence verifies those bindings. The primary symbol docstring must contain exact metadata lines `Case-ID: BE-...`, `Assertion-Test-Points: TP-...;TP-...` and `Cross-Cutting-Test-Points: TP-...;TP-...` (use `none` when empty). Implement request dictionaries so their direct and nested key paths and enum literals exactly satisfy the Case `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum`; for `Payload Contract: none`, do not invent a JSON/body DTO. GET/list filters still declare query fields in those payload labels when the Case varies `params=`/`query=` keys. Python `True`/`False` may implement JSON/OpenAPI `true`/`false` query or body booleans. GET/DELETE setup journeys may create resources, but their setup DTO must not change the target operation\'s no-body payload contract. No Test Point may be invented, renamed, omitted or bound in two modes. The generated pytest collection shape must equal the Markdown prediction `sum(max(1, variant count per Case))`; keep it at or below the task\'s explicit budget by removing duplicate execution, never by collapsing multiple parameter rows under a coarse family TP. Assertions come only from 预期结果 and setup comes only from 前置条件/测试数据/自动化映射.',
|
|
1534
|
-
|
|
1540
|
+
`Name the generated pytest file so it corresponds one-to-one with its source Markdown module file: this module stem \`{{item.stem}}\` maps to exactly the frozen \`{{item.pytestPath}}\`. The <module> stem is the Markdown filename without the \`.md\` extension, lowercased and with non-alphanumeric characters replaced by underscores. For example, \`resource_notes\` → \`${layout.scriptDir}/test_resource_notes.py\`, \`health\` → \`${layout.scriptDir}/test_health.py\`. If Markdown automation mapping names a different path than this module stem path, still write the frozen manifest pytest path and do not invent prefixes. Never merge multiple Markdown modules into one pytest file, never split one module across several files, and never invent pytest filenames unrelated to the Markdown modules.`,
|
|
1535
1541
|
"Scenario Partition slots: every `TP-<Partition ID>-...` variant Test Point declared by this module's Markdown MUST become exactly one literal direct `pytest.param(..., id=\"TP-<Partition ID>-...\")` row with the exact slot ID; the not-in-set slot passes a concrete literal absent from the documented Domain (e.g. `UNKNOWN_TYPE`) — never `_OMIT`, never a descriptive token. Never split one slot into multiple params or merge several slots under a family TP id. Slot filtering requests hit the documented list endpoint with the slot value as the query/path filter.",
|
|
1536
1542
|
"Keep this module self-contained: define module-local fixtures and helpers directly in `{{item.pytestPath}}`, so pytest discovers every fixture dependency without external plugin registration. The request log must include method, URL/path, and request parameters (query plus JSON/body/payload summary). The response log must include status code and response result (JSON/text/body summary), and both records must be visible in pytest stdout/stderr without changing assertions. Recursively redact sensitive values and apply bounded truncation before logging.",
|
|
1537
1543
|
"Materialize every automatable Markdown Case exactly once as one canonical primary pytest symbol. Preserve every explicit variant Test Point as a stable pytest.param id and every assertion/cross-cutting binding as declared. Build request payloads from the effective Markdown test data literally: keep all declared DTO keys, nested shapes, enum values, missing/null/boundary variants and business-state preconditions; never substitute guessed convenience fields or rename contract fields. Never assert an identifier's concrete Python/JSON type unless the Markdown or bound contract explicitly declares that type; when only presence is required, accept any non-null scalar identifier and serialize it safely into the path. For a nonexistent-resource 404 Case whose identifier syntax/type is not declared, obtain a syntactically valid identifier from a live create response and delete it before the 404 request; never invent an arbitrary UUID/text identifier that may fail path conversion with 400. Respect every local helper's actual return signature: never tuple-unpack a scalar status/id/helper result, and never treat a tuple response as a scalar.",
|
|
@@ -1578,7 +1584,7 @@ export async function buildBackendTestHybridDag(sources) {
|
|
|
1578
1584
|
allowedPaths: Array.from(new Set([...ro, `${layout.testRoot}/**`])),
|
|
1579
1585
|
forbiddenPaths: Array.from(new Set([
|
|
1580
1586
|
...forbidden,
|
|
1581
|
-
|
|
1587
|
+
`${layout.markdownDir}/**`,
|
|
1582
1588
|
"conftest.py",
|
|
1583
1589
|
"pytest.ini",
|
|
1584
1590
|
"pyproject.toml",
|
|
@@ -1587,10 +1593,10 @@ export async function buildBackendTestHybridDag(sources) {
|
|
|
1587
1593
|
writerOutcomePolicy: { type: "implementation-outcome-v1", requireChangedFiles: true },
|
|
1588
1594
|
outputContract: "First non-empty line is IMPLEMENTATION_OUTCOME: changed|blocked, followed by a concise repair summary. This node runs only for REPAIRABLE initial facts, so already-satisfied is invalid and a successful outcome requires a non-empty bounded diff. Modify only generated pytest scripts/helpers/factories and preserve every Markdown Case, Test Point, primary symbol and assertion meaning.",
|
|
1589
1595
|
subtask_prompt: [
|
|
1590
|
-
|
|
1591
|
-
|
|
1596
|
+
`Repair the generated backend pytest asset as one bounded program using the direct upstream collection assessment. This is the only repair attempt and happens before any business test body execution. The direct upstream JSON includes authoritative \`repairPaths\` and bounded \`repairFindings\`; treat both as the complete mandatory checklist without searching for a run directory or report file. Treat any upstream line such as \`Repair paths: ${layout.scriptDir}/test_x.py\` as equivalent authoritative repairPaths evidence. Directly read and edit that ${layout.testRoot} path; do not search for separate root-level \`contracts/**\`, guess a DAG run directory, or require another report artifact. If the read tool successfully returns the ${layout.testRoot} file, the path exists—continue the bounded repair and never later claim that file is absent.`,
|
|
1597
|
+
`Initial status REPAIRABLE means at least one listed finding remains: \`already-satisfied\` is forbidden, and you must produce a non-empty bounded diff on repairPaths before returning \`IMPLEMENTATION_OUTCOME: changed\`. Fix only readiness-proven generated ${layout.testRoot}-local defects on initial facts repairPaths: create exact safe missing mapped test_*.py paths, repair syntax/import/symbol/decorator/parameterization, close generated fixture dependencies/plugin registration, and repair initial Markdown-to-pytest correspondence findings. Use this deterministic repair map instead of reading analyzer implementation: findings about \`Case-ID\`, \`Assertion-Test-Points\`, or \`Cross-Cutting-Test-Points\` are fixed by editing the declared primary symbol docstring metadata lines; variant binding findings are fixed in the literal direct \`pytest.param(..., id=\"TP-...\")\` row; primary-symbol cardinality/name findings are fixed in the function name or duplicate primary symbols; script mismatch is fixed only on the authoritative assessment repairPaths; payload findings are fixed in request payload construction. Do not read controller \`src/**\` or inspect JS/TS analyzer code. Do not search for \`${layout.testRoot}/**/README.md\`. Never invent a business pytest symbol for evidence-only Markdown Cases that declare \`脚本/primary symbol=无\` with empty variants. For fixture defects inspect both provider and importer listed by repairPaths; fix ScopeMismatch by aligning fixture scopes or inlining request-scoped values so module fixtures never depend on function fixtures; when a shared fixture depends on sibling fixtures, register the whole provider module through an exact pytest_plugins declaration rather than importing only the outer fixture. Do not create unrelated pytest scripts.`,
|
|
1592
1598
|
"This is the single pytest incremental synchronization round. The `Findings` in `reports/backend-test-pytest-collection-initial.md` are the mandatory repair checklist: resolve every repairable listed finding on every authoritative `Repair paths` file before considering any other advisory evidence, and never substitute an unrelated scenario-param cleanup for a listed correspondence/collection defect. For every assessment-listed path, compare the effective Markdown Case/Test Points/test data and its `Payload Contract`/`Payload Required Paths`/`Payload Allowed Paths`/`Payload Enum` labels with the generated module. Incrementally add or repair only missing symbols, params, assertions and payload builders. Repair every assessment-listed missing nested path, unexpected key and enum mismatch; preserve exact DTO keys, nested shapes, enum/boundary literals, operation transport and business preconditions; remove guessed replacement keys only when the effective Markdown proves the exact contract. Keep path/query/header identifiers and scenario-control metadata separate from DTO patches and JSON bodies; an `id` used for a path target must be passed to the request path/helper, never inserted into a body patch unless `id` is explicitly listed in Payload Allowed Paths. Flatten every variant into a literal direct `pytest.param(..., id=\"TP-...\")` row; replace `_post_case`/`_put_case` or other parameter-row factories because correspondence and scenario readiness require the actual row values and IDs to be statically visible. Also repair helper call sites to match their defined return signatures; do not tuple-unpack a helper that returns one scalar value.",
|
|
1593
|
-
|
|
1599
|
+
`Preserve final ${layout.markdownDir}/** semantics, every Case ID, Rule/Test Point binding, primary symbol, parameter ID, expected status/body/schema assertion, HTTP logging, redaction and truncation behavior.`,
|
|
1594
1600
|
"Use local edit only on assessment-listed paths; keep summaries short; never rewrite unrelated modules.",
|
|
1595
1601
|
"Do not reinterpret requirements beyond the effective Markdown and bounded assessment diagnostics. Do not modify Markdown, conftest, pytest config, production code or dependencies.",
|
|
1596
1602
|
"Do not add skip/skipif/xfail, remove tests, reduce collected items, loosen assertions, swallow exceptions, use try/except ImportError fallback, mutate sys.path/PYTHONPATH, or replace the real API with mocks.",
|
|
@@ -1688,7 +1694,7 @@ export async function buildBackendTestHybridDag(sources) {
|
|
|
1688
1694
|
// against the resolved layout. The default layout is identity, so the
|
|
1689
1695
|
// packaged template stays byte-identical with the historical contract.
|
|
1690
1696
|
applyBackendTestLayoutToDagSpec(spec, layout);
|
|
1691
|
-
applyBackendTestWorkspaceControl(spec, taskConfig.backendTest?.workspaceControl ?? "git");
|
|
1697
|
+
applyBackendTestWorkspaceControl(spec, taskConfig.backendTest?.workspaceControl ?? "git", layout.testRoot, taskConfig.backendTest?.workspaceControl !== undefined);
|
|
1692
1698
|
// Plan B: carry the bound shared-setup document on the spec so runtime
|
|
1693
1699
|
// validators (N6 markdown case validation) see the same binding as prompts.
|
|
1694
1700
|
if (intake.sharedSetup) {
|
|
@@ -1752,7 +1758,7 @@ export const BACKEND_TEST_CASE_MANIFEST_OUTPUT_INSTRUCTIONS = [
|
|
|
1752
1758
|
"Every case must map to at least one semantically applicable explicit AC-* in acIds. If no AC applies, omit that case and bind an evidence gap to the nearest applicable acId or caseId; never emit an unbound informational gap.",
|
|
1753
1759
|
"acIds MUST exactly match the explicit AC-* values in the written case body; do not infer ACs from Business Rules or summary matrices.",
|
|
1754
1760
|
"Use full BE-<MODULE>-<NNN> caseId strings. Do not invent coverage percentages and do not emit coverageSummary; shell always writes the canonical summary.",
|
|
1755
|
-
'Minimal shape example: {"schemaVersion":1,"sourceBinding":{"taskId":"...","requirementPath":"source/需求.md","requirementSha256":"<64 lowercase hex>","referencePaths":[],"requirementIds":["AC-001"]},"cases":[{"caseId":"BE-MODULE-001","acIds":["AC-001"],"title":"...","category":"positive","automationStatus":"planned","evidenceRef"
|
|
1761
|
+
'Minimal shape example: {"schemaVersion":1,"sourceBinding":{"taskId":"...","requirementPath":"source/需求.md","requirementSha256":"<64 lowercase hex>","referencePaths":[],"requirementIds":["AC-001"]},"cases":[{"caseId":"BE-MODULE-001","acIds":["AC-001"],"title":"...","category":"positive","automationStatus":"planned","evidenceRef":`${layout.markdownDir}/module.md`}],"evidenceGaps":[]}',
|
|
1756
1762
|
].join("\n\n");
|
|
1757
1763
|
export function collectBackendTestShellEnvAllowlist(sources) {
|
|
1758
1764
|
const names = new Set();
|
|
@@ -1781,19 +1787,28 @@ export const BACKEND_TEST_DEFAULTS = {
|
|
|
1781
1787
|
writePolicy: "read-only",
|
|
1782
1788
|
};
|
|
1783
1789
|
export const BACKEND_TEST_SKILLS_BY_ROLE = {
|
|
1784
|
-
planner: ["loop-agent"],
|
|
1790
|
+
planner: ["builtin:loop-agent"],
|
|
1785
1791
|
scout: [],
|
|
1786
|
-
implementer: ["test-driven-development", "verification-before-completion"],
|
|
1787
|
-
reviewer: ["requesting-code-review", "code-review-core"],
|
|
1788
|
-
verifier: ["verification-before-completion", "systematic-debugging"],
|
|
1789
|
-
closeout: ["loop-agent", "verification-before-completion"],
|
|
1792
|
+
implementer: ["builtin:test-driven-development", "builtin:verification-before-completion"],
|
|
1793
|
+
reviewer: ["builtin:requesting-code-review", "builtin:code-review-core"],
|
|
1794
|
+
verifier: ["builtin:verification-before-completion", "builtin:systematic-debugging"],
|
|
1795
|
+
closeout: ["builtin:loop-agent", "builtin:verification-before-completion"],
|
|
1790
1796
|
};
|
|
1791
|
-
export function applyBackendTestWorkspaceControl(spec, control) {
|
|
1792
|
-
|
|
1797
|
+
export function applyBackendTestWorkspaceControl(spec, control, layoutTestRoot, controlExplicit = false) {
|
|
1798
|
+
// D4: the canonical `.harness/testcase` root is Git-ignored, so a Git-status
|
|
1799
|
+
// write guard would see empty diffs for real writes. Force the bounded
|
|
1800
|
+
// filesystem snapshot channel for in-harness roots and reject an explicit
|
|
1801
|
+
// incompatible `git` control instead of silently producing empty diffs.
|
|
1802
|
+
const harnessRoot = layoutTestRoot !== undefined && isHarnessNamespacePath(layoutTestRoot);
|
|
1803
|
+
if (harnessRoot && controlExplicit && control === "git") {
|
|
1804
|
+
throw new Error("backendTest.workspaceControl=git is incompatible with the ignored .harness/testcase root; use filesystem-only (default) or a custom non-hidden testRoot");
|
|
1805
|
+
}
|
|
1806
|
+
const effective = harnessRoot ? "filesystem-only" : control;
|
|
1807
|
+
if (effective !== "filesystem-only") {
|
|
1793
1808
|
delete spec.backendTestWorkspaceControl;
|
|
1794
1809
|
return;
|
|
1795
1810
|
}
|
|
1796
|
-
spec.backendTestWorkspaceControl =
|
|
1811
|
+
spec.backendTestWorkspaceControl = effective;
|
|
1797
1812
|
for (const task of spec.tasks) {
|
|
1798
1813
|
if ((task.executor === "pi" && task.toolProfile === "write") ||
|
|
1799
1814
|
task.shell?.backendTestPipeline) {
|