@tea-agent/loop-agent 0.37.0 → 0.37.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -6
- package/CHANGELOG.md +71 -144
- package/README.md +1 -1
- package/bin/loop-agent.js +1 -37
- package/dist/application/task-lifecycle/advance.js +0 -1
- package/dist/application/task-lifecycle/observe.js +0 -15
- package/dist/application/task-lifecycle/plan-transitions.js +0 -15
- package/dist/cli/command-definitions.js +0 -7
- package/dist/cli/program.js +3 -8
- package/dist/commands/init-upgrade.js +19 -351
- package/dist/commands/init.js +67 -14
- package/dist/commands/run-dag-progress.js +0 -14
- package/dist/commands/task-advance.js +3 -33
- package/dist/executors/dag-pi-executor.js +30 -47
- package/dist/executors/pi-sdk-executor.js +0 -34
- package/dist/executors/shell-executor.js +95 -6
- package/dist/shared/operator/capabilities.js +13 -200
- package/dist/shared/package-metadata.js +0 -42
- package/dist/task/contract/project.js +0 -4
- package/dist/task/source-prepare/completeness.js +0 -53
- package/dist/task/source-prepare/parse-intent.js +12 -82
- package/dist/task/source-prepare/prepare.js +5 -102
- package/dist/task/source-prepare/semantic-intake.js +23 -144
- package/dist/worker/console/chat/artifact-card.js +1 -8
- package/dist/worker/console/chat/chat-event-store.js +0 -152
- package/dist/worker/console/chat/human-gate-card.js +1 -9
- package/dist/worker/console/chat/operation-card.js +2 -71
- package/dist/worker/console/chat/pi-runtime.js +62 -111
- package/dist/worker/console/chat/routes.js +10 -43
- package/dist/worker/console/chat/session-store.js +1 -9
- package/dist/worker/console/chat/shortcuts.js +7 -9
- package/dist/worker/console/chat/turn-process.js +23 -204
- package/dist/worker/console/chat/workspace-landing.js +1 -2
- package/dist/worker/console/operation-runner.js +6 -155
- package/dist/worker/console/operator-actions.js +14 -391
- package/dist/worker/console/operator-user-error.js +0 -4
- package/dist/worker/console/recovery-cta.js +3 -50
- package/dist/worker/console/static/assets/index-HX1pbOyl.css +1 -0
- package/dist/worker/console/static/assets/index-M0BLEBfh.js +56 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -13
- package/dist/worker/console/static-src/app/useOperatorActions.js +2 -4
- package/dist/worker/console/static-src/app/useRecoveryActions.js +56 -65
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +1 -73
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +30 -114
- package/dist/worker/console/static-src/operator-chat/refs.js +0 -3
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +13 -67
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -12
- package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -45
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -7
- package/dist/worker/loop-agent/loop-agent-client.js +3 -17
- package/dist/worker/observability/read-model.js +0 -20
- package/dist/worker/observe/spec-evidence.js +8 -3
- package/dist/worker/observe/static/operator-chrome.d.ts +0 -1
- package/dist/worker/observe/static/operator-chrome.js +3 -10
- package/dist/worker/observe/static/views/dag-inspector.js +71 -6
- package/dist/worker/observe/static/views/dag.js +0 -12
- package/dist/worker/preflight.js +1 -2
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +707 -37
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +109 -7
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
- package/dist/workflows/dag/backend-test-scenario-param.js +890 -164
- package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -9
- package/dist/workflows/dag/frontend-implementation-contract.js +39 -233
- package/dist/workflows/dag/frontend-prewrite-gate.js +61 -364
- package/dist/workflows/dag/frontend-repair.js +18 -219
- package/dist/workflows/dag/frontend-verification-trace.js +32 -47
- package/dist/workflows/dag/init-hybrid.js +82 -165
- package/dist/workflows/dag/node-execution.js +0 -89
- package/dist/workflows/dag/recovery-recommendation.js +0 -58
- package/dist/workflows/dag/runner.js +15 -333
- package/dist/workflows/dag/scheduler.js +3 -257
- package/dist/workflows/dag/types.js +9 -130
- package/docs/README.md +3 -3
- package/docs/architecture/evolution.md +67 -102
- package/docs/templates/backend-test-dag.json +50 -32
- package/docs/templates/init-managed-agents.md +2 -5
- package/harness.json +2 -2
- package/package.json +3 -9
- package/skills/loop-agent/SKILL.md +0 -1
- package/skills/loop-agent/references/command-reference.md +0 -4
- package/dist/build-stamp.json +0 -6
- package/dist/commands/dag-request-interrupt.js +0 -20
- package/dist/worker/console/chat/assistant-content.js +0 -121
- package/dist/worker/console/chat/semantic-activity.js +0 -477
- package/dist/worker/console/operation-run-facts.js +0 -190
- package/dist/worker/console/operation-wait.js +0 -355
- package/dist/worker/console/recovery-error-copy.js +0 -198
- package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-DSvF6RXQ.js +0 -1
- package/dist/worker/console/static/assets/arc-DuI4ogtJ.js +0 -1
- package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-DdBbgD4y.js +0 -36
- package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-xQC2HELm.js +0 -132
- package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-DPMSgwIZ.js +0 -10
- package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
- package/dist/worker/console/static/assets/chunk-2GRJ4B5K-WTEKTxin.js +0 -1
- package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B6vZ0Pv_.js +0 -1
- package/dist/worker/console/static/assets/chunk-5RXB4S5H-BrerNAix.js +0 -231
- package/dist/worker/console/static/assets/chunk-5VM5RSS4-D_ch2aaI.js +0 -15
- package/dist/worker/console/static/assets/chunk-6Q2QTUOP-VvfacNrq.js +0 -88
- package/dist/worker/console/static/assets/chunk-GF5L2VYU-B-uGFIg2.js +0 -206
- package/dist/worker/console/static/assets/chunk-JWPE2WC7-BlysCtod.js +0 -1
- package/dist/worker/console/static/assets/chunk-KBJHAD2P-BjAUAfcT.js +0 -1
- package/dist/worker/console/static/assets/chunk-RYQCIY6F-BeZnBmb-.js +0 -1
- package/dist/worker/console/static/assets/chunk-XXDRQBXY-Bbfe3UJG.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-BKJQnYVM.js +0 -1
- package/dist/worker/console/static/assets/cynefin-VYW2F7L2-B1wtI3hU.js +0 -166
- package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DCF6DBSu.js +0 -62
- package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +0 -321
- package/dist/worker/console/static/assets/dagre-VZM6K2ZE-BJyNGRlP.js +0 -4
- package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +0 -1
- package/dist/worker/console/static/assets/diagram-7IWD3JNH-B7j1-VsZ.js +0 -30
- package/dist/worker/console/static/assets/diagram-B4RE2ZJO-BREJ140a.js +0 -3
- package/dist/worker/console/static/assets/diagram-LBJQPF4R-CDPHmHGH.js +0 -24
- package/dist/worker/console/static/assets/diagram-Q27KOJAE-BtZLrez1.js +0 -24
- package/dist/worker/console/static/assets/diagram-UB23O5K3-mzybu8oU.js +0 -41
- package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-Dk3I2uO6.js +0 -1
- package/dist/worker/console/static/assets/erDiagram-JOGREHBK-BT97u_tQ.js +0 -85
- package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-CMOtYzui.js +0 -156
- package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-DiNydkuY.js +0 -292
- package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js +0 -106
- package/dist/worker/console/static/assets/graph-DOmOIIwC.js +0 -1
- package/dist/worker/console/static/assets/index-DQxiso1w.js +0 -325
- package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
- package/dist/worker/console/static/assets/infoDiagram-6WML65LV-AsGIGKKB.js +0 -2
- package/dist/worker/console/static/assets/init-Gi6I4Gst.js +0 -1
- package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-BL8ITZh3.js +0 -70
- package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-VPKAUX8M.js +0 -139
- package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BL56hfGp.js +0 -89
- package/dist/worker/console/static/assets/layout-D-LzfAck.js +0 -1
- package/dist/worker/console/static/assets/linear-BmSG3nIZ.js +0 -1
- package/dist/worker/console/static/assets/map-DxJ2ADlA.js +0 -1
- package/dist/worker/console/static/assets/mermaid.core-DWGK9kwP.js +0 -308
- package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS--vvKLjo0.js +0 -96
- package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +0 -1
- package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-B41mxb2-.js +0 -1
- package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-DTlu77sw.js +0 -39
- package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-Bk5A7alv.js +0 -7
- package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-Csf9jzQy.js +0 -1
- package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-CeptoSNx.js +0 -84
- package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-A-DLCLer.js +0 -40
- package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-22VbPdOI.js +0 -162
- package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-DWvAyBC9.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-qICZ75yT.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
- package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-xi7lcMyp.js +0 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +0 -8
- package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-Bu6Nl6O2.js +0 -120
- package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-BzMz4Bn9.js +0 -34
- package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-CxaJnzc5.js +0 -78
- package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-5JK-omQK.js +0 -7
- package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/worker/console/static-src/operator-chat/activity-journey.js +0 -125
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/activity-references.js +0 -20
- package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +0 -24
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
- package/dist/worker/observability/interrupt-eligibility.js +0 -264
- package/dist/workflows/dag/contract-output-registry.js +0 -14
- package/dist/workflows/dag/contract-validator-registrations.js +0 -8
- package/dist/workflows/dag/frontend-recovery-plan.js +0 -73
- package/dist/workflows/dag/frontend-recovery-root-manifest.js +0 -123
- package/dist/workflows/dag/frontend-recovery-run.js +0 -539
- package/dist/workflows/dag/frontend-writer-recovery.js +0 -106
- package/dist/workflows/dag/frontend-writer-rollback.js +0 -821
- package/dist/workflows/dag/interrupt-request.js +0 -559
|
@@ -730,7 +730,6 @@ function resolveImplementPaths(taskConfig, options = {}) {
|
|
|
730
730
|
const allowed = [...taskConfig.allowedPaths];
|
|
731
731
|
const forbidden = mergeForbiddenPaths(taskConfig);
|
|
732
732
|
const repoRoot = options.repoRoot;
|
|
733
|
-
let companions = [];
|
|
734
733
|
// Only consult the catalog when writer paths can touch docs/* so non-docs
|
|
735
734
|
// temp fixtures without a catalog keep working; docs writers still fail closed.
|
|
736
735
|
const mayNeedDocIndex = allowed.some((entry) => {
|
|
@@ -743,7 +742,6 @@ function resolveImplementPaths(taskConfig, options = {}) {
|
|
|
743
742
|
paths: allowed,
|
|
744
743
|
forbiddenPaths: forbidden,
|
|
745
744
|
});
|
|
746
|
-
companions = closure.companions;
|
|
747
745
|
const explicitAllowedPaths = new Set(allowed.map((entry) => entry.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "")));
|
|
748
746
|
const missingCompanions = closure.companions.filter((companion) => !explicitAllowedPaths.has(companion));
|
|
749
747
|
if (missingCompanions.length > 0) {
|
|
@@ -753,13 +751,11 @@ function resolveImplementPaths(taskConfig, options = {}) {
|
|
|
753
751
|
return {
|
|
754
752
|
allowedPaths: allowed,
|
|
755
753
|
writeSet: [...allowed],
|
|
756
|
-
docIndexCompanions: companions,
|
|
757
754
|
};
|
|
758
755
|
}
|
|
759
756
|
return {
|
|
760
757
|
allowedPaths: ["**"],
|
|
761
758
|
writeSet: [IMPLEMENT_WRITESET_PLACEHOLDER],
|
|
762
|
-
docIndexCompanions: [],
|
|
763
759
|
};
|
|
764
760
|
}
|
|
765
761
|
function shellQuote(value) {
|
|
@@ -1806,15 +1802,6 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
1806
1802
|
subtask_prompt: [
|
|
1807
1803
|
"Implement the approved plan with minimal focused changes.",
|
|
1808
1804
|
"Stay within writeSet. Do not write root artifacts/** unless artifacts paths are explicitly declared in writeSet.",
|
|
1809
|
-
"Apply changes by calling the write/edit tools so files change on disk. Do not paste the new code in your response instead of writing it: the runner verifies the working-tree diff mechanically, and a response-only change is an empty diff that fails this node.",
|
|
1810
|
-
"Edit existing files with the structured edit/write tools. NEVER rewrite Markdown (or any file with quoting/backticks/indentation-sensitive content) via bash sed/awk/echo redirection: escaping mistakes silently corrupt the file and self-repair loops burn the run. If a bash text-substitution already damaged a file, restore the damaged region with the edit tool against the original content instead of stacking more substitutions.",
|
|
1811
|
-
"The plan node already designed the changes — that design is your input, not your output. Your job is to write those changes into the files in writeSet. The task is not done until the files on disk contain the changes.",
|
|
1812
|
-
"If the task only adds tests/docs that use existing code as-is, forbidden implementation paths (e.g. src/**) do not block you: you never need to write them. Write the tests/docs and report changed.",
|
|
1813
|
-
...(implementPaths.docIndexCompanions.length > 0
|
|
1814
|
-
? [
|
|
1815
|
-
`Doc index sync is MANDATORY: ${implementPaths.docIndexCompanions.join(", ")} are catalog index files for this writeSet. When you add, rename, or remove any indexed file, you MUST also update ${implementPaths.docIndexCompanions.join(" and ")} in the same run (append or fix the matching index entry following the existing line style). Verification runs check-doc-index and fails the run when a new file is missing from the index. Skipping this sync is not "minimal change" — it is an incomplete delivery.`,
|
|
1816
|
-
]
|
|
1817
|
-
: []),
|
|
1818
1805
|
writerDeliveryContract(taskConfig),
|
|
1819
1806
|
sourceContext,
|
|
1820
1807
|
]
|
|
@@ -2212,26 +2199,6 @@ function pruneFrontendTasksForRisk(tasks, risk) {
|
|
|
2212
2199
|
return { ...task, depends_on };
|
|
2213
2200
|
});
|
|
2214
2201
|
}
|
|
2215
|
-
/**
|
|
2216
|
-
* AC-1: shared frontend writer node defaults. frontend-implement-pi and
|
|
2217
|
-
* frontend-repair-pi share one prompt/contract/writeSet-guard surface and an
|
|
2218
|
-
* identical writeSet; only id, depends_on, runIf, outputContract, and
|
|
2219
|
-
* subtask_prompt differ per phase.
|
|
2220
|
-
*/
|
|
2221
|
-
function buildFrontendWriterNodeDefaults(input) {
|
|
2222
|
-
return {
|
|
2223
|
-
role: "implementer",
|
|
2224
|
-
executor: "pi",
|
|
2225
|
-
toolProfile: "write",
|
|
2226
|
-
complexity: input.complexity,
|
|
2227
|
-
writePolicy: "exclusive",
|
|
2228
|
-
writeSet: input.writeSet,
|
|
2229
|
-
allowedPaths: input.allowedPaths,
|
|
2230
|
-
forbiddenPaths: input.forbiddenPaths,
|
|
2231
|
-
skills: FRONTEND_BOUNDED_IMPLEMENT_SKILLS,
|
|
2232
|
-
writerOutcomePolicy: { type: "implementation-outcome-v1" },
|
|
2233
|
-
};
|
|
2234
|
-
}
|
|
2235
2202
|
async function buildFrontendHybridDagFromTask(sources) {
|
|
2236
2203
|
const { taskConfig } = sources;
|
|
2237
2204
|
const mockCapability = sources.frontendMockCapability ?? {
|
|
@@ -2372,12 +2339,6 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2372
2339
|
`Every requirement MUST have a non-empty expectedOutcome. Every interaction MUST have non-empty trigger and expectedBehavior. UI states with applicable=true MUST have non-empty expectedBehavior. Empty strings or omitted fields for these will cause contract rejection.`,
|
|
2373
2340
|
].join("\n")
|
|
2374
2341
|
: "";
|
|
2375
|
-
const verificationTargetFileInstruction = [
|
|
2376
|
-
`## Verification target file semantics`,
|
|
2377
|
-
`verificationTargets[].file is the code file that the target verifies (the file the writer changes), NOT where the command is defined.`,
|
|
2378
|
-
`Non-static targets (type unit/component/integration/mock) MUST set file to a concrete code file inside the implementation writeSet (task allowedPaths); the prewrite gate rejects any non-static target whose file falls outside the writeSet.`,
|
|
2379
|
-
`Command-level checks that run project-wide (all tests, typecheck, build, governance) MUST use type "static" and must NOT be bound as non-static targets with file=package.json/tsconfig.json/vite.config.ts/scripts/*. Static targets are exempt from the writeSet containment check.`,
|
|
2380
|
-
].join("\n");
|
|
2381
2342
|
const strategy = resolveDagVerifyStrategy(taskConfig);
|
|
2382
2343
|
const readOnlyPaths = taskConfig.allowedPaths.length > 0 ? taskConfig.allowedPaths : ["**"];
|
|
2383
2344
|
const behaviorPaths = deriveFrontendBehaviorPaths(taskConfig);
|
|
@@ -2571,14 +2532,10 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2571
2532
|
writePolicy: "read-only",
|
|
2572
2533
|
outputMode: "structured-required",
|
|
2573
2534
|
retryPolicy: STRUCTURED_REQUIRED_PI_RETRY_POLICY,
|
|
2574
|
-
structuredContractOutput: {
|
|
2575
|
-
schemaId: "frontend-implementation-contract-v1",
|
|
2576
|
-
retryOnInvalid: true,
|
|
2577
|
-
},
|
|
2578
2535
|
allowedPaths: readOnlyPaths,
|
|
2579
2536
|
forbiddenPaths,
|
|
2580
2537
|
skills: FRONTEND_IMPLEMENTATION_SKILLS,
|
|
2581
|
-
outputContract: "Markdown implementation plan with Requirement Coverage, Implementation Steps, Target Files, UI State Handling, Styling / Component Strategy, Interaction Notes, Mock / API Strategy, Dependency Policy, Verification Plan, Real Integration Gap, and Residual Risks, ending with exactly ONE fenced json object (
|
|
2538
|
+
outputContract: "Markdown implementation plan with Requirement Coverage, Implementation Steps, Target Files, UI State Handling, Styling / Component Strategy, Interaction Notes, Mock / API Strategy, Dependency Policy, Verification Plan, Real Integration Gap, and Residual Risks, ending with exactly ONE fenced json object (\`\`\`json ... \`\`\`) conforming to frontend-implementation-contract-v1. This fenced block is the single authoritative implementation contract the prewrite gate materializes; it must appear exactly once and must not contain or be followed by any raw JSON or extra fenced block. No file writes.",
|
|
2582
2539
|
subtask_prompt: [
|
|
2583
2540
|
"Based on frontend-contract-pi, frontend-scout-pi, task sources, and the generation-time Mock capability evidence, return a minimal frontend implementation plan.",
|
|
2584
2541
|
"Select the Mock / API strategy inside the plan and structured contract. Carry endpoint/fixture mapping, explicit activation, production-default-off rule, verification commands, and Real Integration Gap into both outputs.",
|
|
@@ -2588,8 +2545,6 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2588
2545
|
"End with exactly one fenced json object conforming to frontend-implementation-contract-v1. This node is the single contract JSON producer: the fenced block is the authoritative contract the prewrite gate materializes. Do not emit any raw JSON, JSON in prose, or a second fenced block anywhere in the response; the plan text must not contain other balanced JSON objects.",
|
|
2589
2546
|
"Each requirement must state its user-observable or logic-observable expectedOutcome. Each interaction must state its trigger and expectedBehavior. IDs plus file paths are not sufficient behavior semantics.",
|
|
2590
2547
|
requirementCoverageInstruction,
|
|
2591
|
-
"verificationTargets[].commandLabel MUST be one of the frozen command labels listed above. Any other value will be rejected at contract materialization.",
|
|
2592
|
-
verificationTargetFileInstruction,
|
|
2593
2548
|
"Read-only: do not modify code, docs, artifacts, or repository files.",
|
|
2594
2549
|
fixedVerificationContext,
|
|
2595
2550
|
sourceContext,
|
|
@@ -2629,14 +2584,10 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2629
2584
|
writePolicy: "read-only",
|
|
2630
2585
|
outputMode: "structured-required",
|
|
2631
2586
|
retryPolicy: STRUCTURED_REQUIRED_PI_RETRY_POLICY,
|
|
2632
|
-
structuredContractOutput: {
|
|
2633
|
-
schemaId: "frontend-implementation-contract-v1",
|
|
2634
|
-
retryOnInvalid: true,
|
|
2635
|
-
},
|
|
2636
2587
|
allowedPaths: readOnlyPaths,
|
|
2637
2588
|
forbiddenPaths,
|
|
2638
2589
|
skills: FRONTEND_IMPLEMENTATION_SKILLS,
|
|
2639
|
-
outputContract: "When the initial design review requests revision, return a complete Markdown revision plan followed by exactly ONE fenced json object (
|
|
2590
|
+
outputContract: "When the initial design review requests revision, return a complete Markdown revision plan followed by exactly ONE fenced json object (\`\`\`json ... \`\`\`) conforming to frontend-implementation-contract-v1. This fenced block is the single authoritative implementation contract the prewrite gate materializes; it must appear exactly once and must not contain or be followed by any raw JSON or extra fenced block. Do NOT include multiple fenced JSON blocks; only the single authoritative contract JSON block is accepted. No file writes.",
|
|
2640
2591
|
subtask_prompt: [
|
|
2641
2592
|
"Consume frontend-plan-pi (original plan) and frontend-design-review-pi (first design review findings).",
|
|
2642
2593
|
"This node runs only when frontend-design-review-pi emitted VERDICT: request-revision. Produce a complete revised implementation plan that addresses every Required Plan Correction from the design findings.",
|
|
@@ -2647,7 +2598,6 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2647
2598
|
"End the response with exactly one fenced json object conforming to frontend-implementation-contract-v1. This node is the single contract JSON producer when the design review requests revision: the fenced block is the authoritative contract the prewrite gate materializes. Do not emit any raw JSON, JSON in prose, or a second fenced block anywhere in the response. Bind it to the supplied task sources; map every requirement and applicable UI state to concrete implementation and verification targets or an explicit blocking evidence gap. Do not include secrets or unsafe paths.",
|
|
2648
2599
|
"Preserve each requirement expectedOutcome and each interaction trigger/expectedBehavior in the revised contract; do not reduce behavior semantics to IDs and paths.",
|
|
2649
2600
|
"verificationTargets[].commandLabel MUST be one of the frozen command labels listed above. Any other value will be rejected at contract materialization.",
|
|
2650
|
-
verificationTargetFileInstruction,
|
|
2651
2601
|
fixedVerificationContext,
|
|
2652
2602
|
sourceContext,
|
|
2653
2603
|
frontendContractSchemaBlock,
|
|
@@ -2766,12 +2716,18 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2766
2716
|
? ["frontend-lint-baseline-shell"]
|
|
2767
2717
|
: []),
|
|
2768
2718
|
],
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2719
|
+
role: "implementer",
|
|
2720
|
+
executor: "pi",
|
|
2721
|
+
toolProfile: "write",
|
|
2722
|
+
complexity: resolveWriterComplexity(taskConfig),
|
|
2723
|
+
writePolicy: "exclusive",
|
|
2724
|
+
writeSet: implementPaths.writeSet,
|
|
2725
|
+
allowedPaths: implementPaths.allowedPaths,
|
|
2726
|
+
forbiddenPaths,
|
|
2727
|
+
skills: FRONTEND_BOUNDED_IMPLEMENT_SKILLS,
|
|
2728
|
+
writerOutcomePolicy: {
|
|
2729
|
+
type: "implementation-outcome-v1",
|
|
2730
|
+
},
|
|
2775
2731
|
outputContract: "First non-empty line must be exactly one of: IMPLEMENTATION_OUTCOME: changed; IMPLEMENTATION_OUTCOME: already-satisfied; IMPLEMENTATION_OUTCOME: blocked. Then a Markdown delivery summary with Contract Ref (path/schema/hash), Changed Files, Requirements Implemented, UI States, Tests Changed, Verification Attempts, Deviations, and Residual Risks. Follow fixed stages: contract confirm → tests → component/state → API/Mock → focused checks → diff cleanup.",
|
|
2776
2732
|
subtask_prompt: [
|
|
2777
2733
|
"Implement against the validated run-owned Frontend Implementation Contract from frontend-prewrite-gate-shell (path/schema/hash). Do not rebuild the contract from Markdown alone.",
|
|
@@ -2783,12 +2739,6 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2783
2739
|
"frontend-prewrite-gate-shell confirmed the effective plan/review, requirement coverage, Mock policy, and contract. Stay within writeSet and preserve unrelated files.",
|
|
2784
2740
|
"For native, browser-intercept, or request-adapter, implement contract-aligned fixtures/states and a dev/test-only activation boundary in this same writer. For not-needed, do not add Mock files or a framework and state the positive reason.",
|
|
2785
2741
|
"Do not write root artifacts/** unless explicitly included in writeSet. Do not claim Browser/visual verification.",
|
|
2786
|
-
"Edit existing files with the structured edit/write tools. NEVER rewrite Markdown (or any file with quoting/backticks/indentation-sensitive content) via bash sed/awk/echo redirection: escaping mistakes silently corrupt the file and self-repair loops burn the run.",
|
|
2787
|
-
...(implementPaths.docIndexCompanions.length > 0
|
|
2788
|
-
? [
|
|
2789
|
-
`Doc index sync is MANDATORY: ${implementPaths.docIndexCompanions.join(", ")} are catalog index files for this writeSet. When you add, rename, or remove any indexed file, you MUST also update ${implementPaths.docIndexCompanions.join(" and ")} in the same run (append or fix the matching index entry following the existing line style). Verification runs check-doc-index and fails the run when a new file is missing from the index.`,
|
|
2790
|
-
]
|
|
2791
|
-
: []),
|
|
2792
2742
|
writerDeliveryContract(taskConfig),
|
|
2793
2743
|
]
|
|
2794
2744
|
.filter((value) => Boolean(value))
|
|
@@ -2831,12 +2781,18 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2831
2781
|
id: "frontend-repair-pi",
|
|
2832
2782
|
depends_on: ["frontend-verify-assess-shell", implementId],
|
|
2833
2783
|
runIf: "$.nodes['frontend-verify-assess-shell'].json.eligible == true",
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2784
|
+
role: "implementer",
|
|
2785
|
+
executor: "pi",
|
|
2786
|
+
toolProfile: "write",
|
|
2787
|
+
complexity: resolveWriterComplexity(taskConfig),
|
|
2788
|
+
writePolicy: "exclusive",
|
|
2789
|
+
writeSet: implementPaths.writeSet,
|
|
2790
|
+
allowedPaths: implementPaths.allowedPaths,
|
|
2791
|
+
forbiddenPaths,
|
|
2792
|
+
skills: FRONTEND_BOUNDED_IMPLEMENT_SKILLS,
|
|
2793
|
+
writerOutcomePolicy: {
|
|
2794
|
+
type: "implementation-outcome-v1",
|
|
2795
|
+
},
|
|
2840
2796
|
outputContract: "First non-empty line must be exactly one of: IMPLEMENTATION_OUTCOME: changed; IMPLEMENTATION_OUTCOME: already-satisfied; IMPLEMENTATION_OUTCOME: blocked. Then a repair summary for an eligible repairable assessment. Must not expand writeSet, re-interpret requirements, skip tests, or enable Mock by default.",
|
|
2841
2797
|
subtask_prompt: [
|
|
2842
2798
|
"Read contracts/frontend-repair-assessment.json and the validated frontend implementation contract.",
|
|
@@ -2844,11 +2800,6 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2844
2800
|
"The repair assessment and canonical implementation contract are complete inputs for this phase. Do not re-open task sources, OpenSpec, AI workspace, plan/revision, or design-review prose, and do not repeat repository-wide discovery.",
|
|
2845
2801
|
"Do not change lint/type/test config, do not add .skip/.only, do not comment out real requests, do not default-enable Mock, do not add dependencies.",
|
|
2846
2802
|
"Do not re-plan requirements or expand allowed paths. Browser/visual remain not-run.",
|
|
2847
|
-
...(implementPaths.docIndexCompanions.length > 0
|
|
2848
|
-
? [
|
|
2849
|
-
`Doc index sync is MANDATORY: ${implementPaths.docIndexCompanions.join(", ")} are catalog index files for this writeSet. When your repair adds, renames, or removes any indexed file, update ${implementPaths.docIndexCompanions.join(" and ")} in the same run; verification runs check-doc-index and fails the run on a missing index entry.`,
|
|
2850
|
-
]
|
|
2851
|
-
: []),
|
|
2852
2803
|
writerDeliveryContract(taskConfig),
|
|
2853
2804
|
]
|
|
2854
2805
|
.filter((value) => Boolean(value))
|
|
@@ -3572,23 +3523,29 @@ const readme=fs.existsSync('testcase/md/README.md')?fs.readFileSync('testcase/md
|
|
|
3572
3523
|
const norm=s=>String(s).toLowerCase().replace(/[^a-z0-9]+/g,'_').replace(/^_+|_+$/g,'').replace(/_+/g,'_');
|
|
3573
3524
|
const bt=String.fromCharCode(96);
|
|
3574
3525
|
const stripBackticks=s=>s.split(bt).join('');
|
|
3575
|
-
const invalidReason=raw=>{const st=norm(raw);if(/^p[0-2]$/.test(st))return 'priority-only-module-stem';if(st==='readme')return 'reserved-module-stem';if(!/^[a-z][a-z0-9_]*$/.test(st))return 'invalid-syntax';if(/^(?:be|tp|ac|req|br)[_-]/i.test(st))return 'case-like-module-stem';return null;};
|
|
3526
|
+
const invalidReason=raw=>{const st=norm(raw);if(/^p[0-2]$/.test(st))return 'priority-only-module-stem';if(/^[a-f][a-f0-9]{6,63}$/.test(st))return 'opaque-hash-module-stem';if(st==='readme')return 'reserved-module-stem';if(!/^[a-z][a-z0-9_]*$/.test(st))return 'invalid-syntax';if(/^(?:be|tp|ac|req|br)[_-]/i.test(st))return 'case-like-module-stem';return null;};
|
|
3576
3527
|
const valid=raw=>invalidReason(raw)===null;
|
|
3577
3528
|
const rxMdPath=/testcase\\/md\\/([A-Za-z0-9_.-]+)\\.md/g;
|
|
3578
3529
|
const rxTableRow=/\\|\\s*([A-Za-z0-9_.-]+)\\s*\\|\\s*testcase\\/test_/g;
|
|
3579
3530
|
const rxRelLink=/\\[[^\\]]+\\]\\(\\.\\/([A-Za-z0-9_.-]+)\\.md\\)/g;
|
|
3531
|
+
const allLines=readme.replace(/\\r\\n/g,'\\n').replace(/\\r/g,'\\n').split('\\n');
|
|
3532
|
+
const headings=[];for(let i=0;i<allLines.length;i++){if(allLines[i].trim()==='## Module Index')headings.push(i);}
|
|
3533
|
+
if(headings.length!==1){process.stderr.write((headings.length===0?'missing-module-index':'duplicate-module-index')+'; require exactly one exact ## Module Index section\\n');process.exit(2);}
|
|
3534
|
+
const start=headings[0]+1;let end=allLines.length;for(let i=start;i<allLines.length;i++){if(/^##\\s+\\S/.test(allLines[i].trim())){end=i;break;}}
|
|
3535
|
+
const section=allLines.slice(start,end).join('\\n');
|
|
3580
3536
|
const raw=[];
|
|
3581
|
-
const lines=
|
|
3537
|
+
const lines=section.split('\\n').filter(l=>l.includes('|'));
|
|
3582
3538
|
for(const line of lines){
|
|
3583
3539
|
const bare=stripBackticks(line);
|
|
3584
3540
|
for(const m of bare.matchAll(rxMdPath)){raw.push(m[1]);}
|
|
3585
|
-
for(const m of bare.matchAll(rxTableRow)){if(valid(m[1])||invalidReason(m[1])
|
|
3541
|
+
for(const m of bare.matchAll(rxTableRow)){if(valid(m[1])||invalidReason(m[1])!=='invalid-syntax')raw.push(m[1]);}
|
|
3586
3542
|
}
|
|
3587
|
-
for(const m of
|
|
3543
|
+
for(const m of section.matchAll(rxRelLink)){raw.push(m[1]);}
|
|
3588
3544
|
const invalid=[];for(const r of raw){const reason=invalidReason(r);if(reason)invalid.push({stem:norm(r),reason});}
|
|
3589
3545
|
if(invalid.length){for(const item of invalid)process.stderr.write(item.reason+': '+item.stem+'; use a stable business resource/domain stem\\n');process.exit(2);}
|
|
3590
3546
|
const seen=new Set();const modules=[];
|
|
3591
3547
|
for(const r of raw){const st=norm(r);if(valid(r)&&!seen.has(st)){seen.add(st);modules.push({stem:st});}}
|
|
3548
|
+
if(modules.length>8){process.stderr.write('excessive-module-count: '+modules.length+' > 8; merge by the smallest stable business resource/domain set\\n');process.exit(2);}
|
|
3592
3549
|
process.stdout.write(JSON.stringify({modules}));
|
|
3593
3550
|
`;
|
|
3594
3551
|
const encoded = Buffer.from(script, "utf8").toString("base64");
|
|
@@ -3666,7 +3623,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3666
3623
|
"Each Rule Key must appear in exactly one Matrix row. Preserve each AC/REQ/BR Rule Key as one row; if one product rule spans multiple dimensions, use a concise composite Dimension in that single row instead of duplicating the key. Derive OpenAPI Rule Keys exactly as the deterministic analyzer does: operation token is `<HTTP-METHOD>-<PATH>` with braces removed and every non-alphanumeric run replaced by a hyphen, uppercase (for example POST `/api/resource-notes` → `POST-API-RESOURCE-NOTES`); response statuses use `API-<OPERATION>-RESPONSE-STATUS`; body/parameter fields use `API-<OPERATION>-<FIELD>-REQUIRED|ENUM|MIN-LENGTH|MAX-LENGTH|MINIMUM|MAXIMUM|PATTERN|FORMAT`. Do not invent aliases such as API-CREATE-FIELDS when a deterministic key applies.",
|
|
3667
3624
|
"Coverage priority is strict inside the declared scope: P0 product requirements/task hard constraints always remain in scope; P1 exhaustively supplements documented operations, fields, business rules, statuses and errors only for Affected Operations; P2 adds bounded protocol robustness only when it is relevant to the change and does not invent product behavior. Coverage percentages describe the declared affected scope, never whole-API completeness unless every operation is explicitly listed. Conflicts or undefined expectations must stay visible as GAP/CONFLICT with precise source pointers, never guessed.",
|
|
3668
3625
|
"For uniqueness/lifecycle rules cover absent, active-existing, deleted-existing, create-delete-recreate, restore-then-recreate and documented scope/case-normalization states. For every enum cover every valid value plus bounded invalid equivalence classes (unknown, case variant, whitespace, empty, null/missing and wrong types as applicable). For every length/number rule cover min-1, min, nominal, max and max+1. For format rules cover each allowed class separately plus a valid mixed value, and representative forbidden classes including uppercase, internal/leading/trailing whitespace, tab/newline, unsupported punctuation, slash, emoji or control characters when the source contract supports that expectation.",
|
|
3669
|
-
"Mandatory module index: include a `## Module Index` table in README that lists every planned module as a canonical relative link of the exact form `[label](./<stem>.md)` plus a `testcase/md/<stem>.md` path cell, so a downstream deterministic manifest can parse the module list. Group by stable business resource/domain, not by CRUD operation: one resource's list/detail/create/update/delete cases belong in one module such as `resource_notes`; split only when a single module would exceed the per-child 16K output
|
|
3626
|
+
"Mandatory module index: include a `## Module Index` table in README that lists every planned module as a canonical relative link of the exact form `[label](./<stem>.md)` plus a `testcase/md/<stem>.md` path cell, so a downstream deterministic manifest can parse the module list. Group by stable business resource/domain, not by CRUD operation: one resource's list/detail/create/update/delete cases belong in one module such as `resource_notes`; split only when a single module would exceed the per-child 16K output protocol, keep the total module count at the smallest safe value, and never exceed 8 modules. Name each module file with a stable lowercase business stem such as `health` or `resource_notes`. Pure hexadecimal/hash-like opaque stems such as `a401606` or `deadbeef` are forbidden. Do not use priority-only stems `p0`, `p1` or `p2`; Priority belongs only in the Coverage Matrix and never defines module files. Do not use Case-ID-like module filenames such as `BE-HEALTH.md` or `BE-NOTES.md`. The relative link target MUST equal the on-disk filename stem the sharded writer will create. For every automatable case, `自动化映射` must name exactly `testcase/test_<module>.py`, where <module> is that Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `testcase/md/health.md` → `testcase/test_health.py`; `testcase/md/resource_notes.md` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.",
|
|
3670
3627
|
"Before finalizing README, calculate the predicted collected-item count as `sum(max(1, number of variant Test Points in each Case))`. If the task declares an item budget, the prediction must not exceed it. Reduce excess only by removing duplicate execution and converting same-request checkpoints to assertions; never drop required rules, boundaries, enums, operation-specific inputs, or business states. Record the prediction in README. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.",
|
|
3671
3628
|
intake.boundedSourceContext,
|
|
3672
3629
|
"## Authoritative reference index",
|
|
@@ -3711,10 +3668,11 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3711
3668
|
workflowNodeId: "generate-backend-md-cases-map",
|
|
3712
3669
|
itemsFrom: "$.nodes['materialize-backend-md-module-manifest-shell'].output.modules",
|
|
3713
3670
|
itemName: "item",
|
|
3714
|
-
maxItems:
|
|
3715
|
-
maxExpandedNodes:
|
|
3671
|
+
maxItems: 8,
|
|
3672
|
+
maxExpandedNodes: 8,
|
|
3716
3673
|
childIdPrefix: "generate-backend-md-case",
|
|
3717
|
-
tokenBudget: {
|
|
3674
|
+
tokenBudget: { maxTotalTokens: 3000000 },
|
|
3675
|
+
failOnTokenBudgetExhaustion: true,
|
|
3718
3676
|
childTask: {
|
|
3719
3677
|
executor: "pi",
|
|
3720
3678
|
role: "implementer",
|
|
@@ -3736,10 +3694,11 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3736
3694
|
"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.",
|
|
3737
3695
|
"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.",
|
|
3738
3696
|
"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.",
|
|
3739
|
-
'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.',
|
|
3740
|
-
"Name this module file with the stable lowercase business stem `{{item.stem}}` (filename `testcase/md/{{item.stem}}.md`). Priority-only stems `p0`, `p1` and `p2` are forbidden and must never produce `p0.md` or `test_p0.py`. Do not use Case-ID-like module filenames. For every automatable case, `自动化映射` must name exactly `testcase/test_{{item.stem}}.py`, where the module stem is this Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `health` → `testcase/test_health.py`; `resource_notes` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.",
|
|
3697
|
+
'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.',
|
|
3698
|
+
"Name this module file with the stable lowercase business stem `{{item.stem}}` (filename `testcase/md/{{item.stem}}.md`). 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 `testcase/test_{{item.stem}}.py`, where the module stem is this Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `health` → `testcase/test_health.py`; `resource_notes` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.",
|
|
3699
|
+
"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.",
|
|
3741
3700
|
"Every Case must keep at least one numbered executable line under `### 操作步骤`; a compact variant/result table may follow but must not replace the numbered action anchor. Keep numbered/bulleted independently assertable results under `### 预期结果`. The exact `### 操作步骤` and `### 预期结果` headings must remain present for every Case, including compact/table-based Cases; never compress later Cases by dropping required headings. Every result must name the observable HTTP status, response field/value, state transition or membership condition, never vague wording such as ‘符合预期’.",
|
|
3742
|
-
"In every `自动化映射`, use exactly these machine-readable list labels: `脚本`, `primary symbol`, `变体测试点`, `场景断言测试点`,
|
|
3701
|
+
"In every `自动化映射`, use exactly these machine-readable list labels: `脚本`, `primary symbol`, `变体测试点`, `场景断言测试点`, `横切证据测试点`, plus a deterministic payload contract. For operations without a request body write `Payload Contract: none`. Otherwise write `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum` (write `none` when there is no enum); nested fields use dot paths such as `approver.name`. Each Case describes exactly one target request payload contract: put every payload label on its own list line, never concatenate multiple operations or setup POST/PUT contracts into one label line, and never repeat a `Payload Contract:` token inside explanatory prose/details after the machine-readable line. Values must come only from bound API/DTO evidence, never guesses. Each Test Point from `### 测试点` must appear in exactly one binding list, and every Test Point named in any binding list must also be declared in that Case's `### 测试点`; write `无` for an empty list. A variant Test Point is atomic: one exact endpoint/input/precondition/outcome row equals one exact pytest item and one exact TP ID. If a parameter table has five rows, declare five distinct variant TP IDs in Markdown; never declare one family TP and append row suffixes only in pytest. Classify as `variant` only when endpoint, request input, precondition business state, or expected outcome genuinely changes and therefore needs an independent pytest parameter item. Classify CRUD checkpoints, status/body/header/schema assertions and multiple checks over the same response/journey as `assertion`; classify shared HTTP logging/redaction/truncation evidence as `cross-cutting`. Never create a Test Point merely to parameterize a checkpoint. Every non-cross-cutting TP ID is owned by exactly one Case; when the same response/schema/error assertion is needed in different Cases, use distinct Case-specific TP IDs instead of reusing one assertion TP across Cases. Keep the script path identical to the module one-to-one path and declare exactly one primary symbol named with the canonical Case prefix, for example `BE-RN-003` → `test_BE_RN_003_<description>`; non-Case-prefixed primary symbols are forbidden because parameterized item association must remain deterministic. For evidence-only meta Cases with no executable business journey, write `脚本:无` and `primary symbol:无`, keep `变体测试点:无`, and place process evidence only in assertion/cross-cutting lists. If the bound contract only says an identifier is returned/present, do not declare a concrete identifier type. If a 404 Case needs a nonexistent path identifier but its syntax/type is unspecified, define a create-delete-derived valid identifier journey instead of an arbitrary UUID/text placeholder. For redaction scenarios, list sensitive header/field key names only. Never write any header-name-and-value pair, credential placeholder, fake token, anti-example, or other secret-shaped literal in Markdown; state only that a test-only value is supplied at runtime and omitted. Put implementation-only restrictions in a concise `<details>` block rather than dominating the main case flow. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.",
|
|
3743
3702
|
intake.boundedSourceContext,
|
|
3744
3703
|
"## Authoritative reference index",
|
|
3745
3704
|
JSON.stringify(intake.referenceIndex, null, 2),
|
|
@@ -3752,32 +3711,34 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3752
3711
|
const reviewCases = {
|
|
3753
3712
|
id: "review-and-revise-backend-md-cases-pi",
|
|
3754
3713
|
depends_on: [generateMdCasesMap.id],
|
|
3755
|
-
role: "
|
|
3756
|
-
executor: "
|
|
3757
|
-
|
|
3714
|
+
role: "implementer",
|
|
3715
|
+
executor: "pi",
|
|
3716
|
+
toolProfile: "write",
|
|
3717
|
+
complexity: "MED",
|
|
3758
3718
|
writePolicy: "exclusive",
|
|
3759
3719
|
writeSet: ["testcase/md/**"],
|
|
3760
|
-
allowedPaths: ["testcase/md/**"],
|
|
3720
|
+
allowedPaths: Array.from(new Set([...ro, "testcase/md/**"])),
|
|
3761
3721
|
forbiddenPaths: forbidden,
|
|
3762
|
-
|
|
3722
|
+
writerOutcomePolicy: { type: "implementation-outcome-v1" },
|
|
3723
|
+
outputContract: "First non-empty line is IMPLEMENTATION_OUTCOME: changed|already-satisfied|blocked. Perform exactly one bounded incremental synchronization of testcase/md/** against all bound source references; preserve valid Cases and report a concise summary.",
|
|
3763
3724
|
subtask_prompt: [
|
|
3764
|
-
"
|
|
3765
|
-
"Output budget protocol:
|
|
3766
|
-
"For every variant Test Point, ensure the Markdown scenario intent is machine-checkable
|
|
3725
|
+
"Perform one gap-targeted synchronization, not a full-suite rewrite or stylistic review. Start from explicit bound source IDs/error codes/DTO fields/normative quoted rules and the README Matrix; open and edit only modules that own a missing or conflicting rule. Preserve unrelated valid modules byte-for-byte and avoid optional wording cleanup.",
|
|
3726
|
+
"Output budget protocol: never dump full Matrix/case bodies into assistant chat. Inspect README first, build a concise target list, 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.",
|
|
3727
|
+
"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`.",
|
|
3767
3728
|
"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.",
|
|
3768
3729
|
"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 `testcase/md/README.md`, 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.",
|
|
3769
|
-
"Correct testcase/md/** directly: add documented omissions, remove unsupported cases, rename module files to stable lowercase stems when needed, 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 case points at `testcase/test_<module>.py` derived from that module filename and declares exactly one primary symbol, assign every Test Point exactly one of `变体测试点`/`场景断言测试点`/`横切证据测试点`, ensure every binding-list Test Point is
|
|
3770
|
-
"Read
|
|
3730
|
+
"Correct testcase/md/** directly: add documented omissions, remove unsupported cases, rename module files to stable lowercase stems when needed, 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 `testcase/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.",
|
|
3731
|
+
"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.",
|
|
3732
|
+
"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.",
|
|
3733
|
+
"Before returning, verify that every explicit source AC/REQ/BR, error code and strong DTO field token appears in README or an applicable module Case. If a fact cannot be safely automated, retain it as GAP/CONFLICT with its exact source pointer instead of dropping it. Return already-satisfied only when no target file needs an incremental edit.",
|
|
3734
|
+
"Read only indexed source paths. Do not scan the repository, modify source/**, generate pytest, execute tests, or emit JSON.",
|
|
3771
3735
|
intake.boundedSourceContext,
|
|
3772
3736
|
"## Authoritative reference index",
|
|
3773
3737
|
JSON.stringify(intake.referenceIndex, null, 2),
|
|
3774
3738
|
"For each index entry, use `readPath` for Pi read-tool calls and keep `path` as the exact Markdown Source References citation. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.",
|
|
3775
3739
|
].join("\n\n"),
|
|
3776
|
-
static: {
|
|
3777
|
-
resultMarkdown: "Markdown module writers completed. Deterministic node 6 validation owns advisory structure/coverage findings; no model review or rewrite was invoked.",
|
|
3778
|
-
},
|
|
3779
3740
|
};
|
|
3780
|
-
const validateCases = shellNode("validate-backend-md-cases-shell", [reviewCases.id], "markdown-cases", "Record advisory findings for Markdown structure and deterministically analyze the final README Coverage Scope and Coverage Matrix against final Case rule/test-point bindings. Validate the classification-policy pair, affected operations/rules, scope evidence and regression floor; require documented OpenAPI completeness only for declared affected operations, while all explicit AC/REQ/BR remain in scope. Detect missing in-scope product/API rules, enum values, invalid equivalence classes, boundaries, format classes, business lifecycle states, GAP/CONFLICT, bidirectional Matrix/Case drift, non-canonical Case IDs, unclassified Test Points, duplicate binding modes and non-cross-cutting Test Points bound by multiple Cases. Do not validate source-reference existence. Write human and machine evidence from the same facts. Keep quality findings advisory, but fail closed after writing the report when secret-shaped values are detected. Coverage FAIL stays advisory.", "Run-owned reports/backend-md-case-validation.md, reports/backend-test-case-coverage-analysis.md and contracts/backend-test-case-coverage-facts.json
|
|
3741
|
+
const validateCases = shellNode("validate-backend-md-cases-shell", [reviewCases.id], "markdown-cases", "Record advisory findings for Markdown structure and deterministically analyze the final README Coverage Scope and Coverage Matrix against final Case rule/test-point bindings. Validate the classification-policy pair, affected operations/rules, scope evidence and regression floor; require documented OpenAPI completeness only for declared affected operations, while all explicit AC/REQ/BR remain in scope. Detect missing in-scope product/API rules, enum values, invalid equivalence classes, boundaries, format classes, business lifecycle states, GAP/CONFLICT, bidirectional Matrix/Case drift, non-canonical Case IDs, unclassified Test Points, duplicate binding modes and non-cross-cutting Test Points bound by multiple Cases. Do not validate source-reference existence. Write human and machine evidence from the same facts. Keep quality findings advisory, but fail closed after writing the report when secret-shaped values are detected. Coverage FAIL stays advisory.", "Run-owned reports/backend-md-case-validation.md, reports/backend-test-case-coverage-analysis.md and contracts/backend-test-case-coverage-facts.json v4 with Coverage Scope plus PASS/FAIL/UNAVAILABLE advisory facts; downstream execution continues.");
|
|
3781
3742
|
// N5 line (sharded): pytest shared-asset plan → manifest shell → map_agent barrier.
|
|
3782
3743
|
// Shared helpers/factories are written once by the plan node; each module's
|
|
3783
3744
|
// test_<stem>.py is written by an independent Pi child (own 16K budget).
|
|
@@ -3794,7 +3755,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3794
3755
|
subtask_prompt: [
|
|
3795
3756
|
"Convert testcase/md/** into pytest using upstream environment and advisory validation evidence plus only bounded pytest config/conftest. This node writes ONLY the shared pytest support assets (HTTP logging/redaction helper, request factories, shared fixtures); each module's test_<module>.py is written by a downstream sharded node that imports these helpers. A FAIL advisory report does not authorize inventing missing behavior; use the final Markdown facts that are present.",
|
|
3796
3757
|
"Output budget protocol (hard, max output <=16K per turn): Write helpers/factories one file per write/edit tool call. Never paste full Python modules into assistant chat. Do not reduce params/assertions/skips semantics to fit. If OUTPUT_LIMIT_RECOVERY is injected, continue only listed missing/broken shared files.",
|
|
3797
|
-
"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.",
|
|
3758
|
+
"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.",
|
|
3798
3759
|
"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.",
|
|
3799
3760
|
"HTTP response header names are case-insensitive. If the helper stores a lower-case normalized header map, every Content-Type or other header assertion must query the lower-case key (for example `content-type`) or use an explicitly case-insensitive accessor; never call a case-sensitive plain dict with `Content-Type` when the stored key is lower-case. Preserve the actual media-type assertion rather than dropping it.",
|
|
3800
3761
|
"Compare timestamps and other semantically equivalent protocol values by parsed meaning, not byte-for-byte serialization. In particular, normalize valid ISO-8601 instants before equality/order assertions so differences such as omitted trailing fractional seconds do not create TestBug failures; preserve exact-string assertions only when the Markdown explicitly requires representation equality.",
|
|
@@ -3841,10 +3802,11 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3841
3802
|
workflowNodeId: "generate-backend-pytest-cases-map",
|
|
3842
3803
|
itemsFrom: "$.nodes['materialize-backend-pytest-module-manifest-shell'].output.modules",
|
|
3843
3804
|
itemName: "item",
|
|
3844
|
-
maxItems:
|
|
3845
|
-
maxExpandedNodes:
|
|
3805
|
+
maxItems: 8,
|
|
3806
|
+
maxExpandedNodes: 8,
|
|
3846
3807
|
childIdPrefix: "generate-backend-pytest-case",
|
|
3847
|
-
tokenBudget: {
|
|
3808
|
+
tokenBudget: { maxTotalTokens: 3000000 },
|
|
3809
|
+
failOnTokenBudgetExhaustion: true,
|
|
3848
3810
|
childTask: {
|
|
3849
3811
|
executor: "pi",
|
|
3850
3812
|
role: "implementer",
|
|
@@ -3869,12 +3831,14 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3869
3831
|
retryPolicy: BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY,
|
|
3870
3832
|
outputContract: "Write exactly one pytest module file testcase/test_<stem>.py whose actual test function region contains the exact Case ID, preferably in the function name or docstring. testcase/md/<module>.md (excluding README.md) maps one-to-one to testcase/test_<module>.py; never merge or split modules. No JSON and no pytest execution.",
|
|
3871
3833
|
subtaskPromptTemplate: [
|
|
3872
|
-
"Convert the single Markdown module testcase/md/{{item.stem}}.md into one self-contained pytest module. After reading the module Markdown and the bounded pytest config/conftest, immediately use write tools to create the single file testcase/test_{{item.stem}}.py. 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 testcase/**/helpers/** or testcase/**/factories/** assets. Do not end after analysis or planning. Do not modify Markdown, conftest, helpers/factories, or any other module's pytest script.",
|
|
3834
|
+
"Convert the single Markdown module testcase/md/{{item.stem}}.md into one self-contained pytest module. Before writing, also read testcase/md/README.md 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, testcase/md/README.md, and the bounded pytest config/conftest, immediately use write tools to create the single file testcase/test_{{item.stem}}.py. 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 testcase/**/helpers/** or testcase/**/factories/** assets. Do not end after analysis or planning. Do not modify Markdown, conftest, helpers/factories, or any other module's pytest script.",
|
|
3873
3835
|
"Output budget protocol (hard, max output <=16K per turn): Write exactly one test_{{item.stem}}.py. 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.",
|
|
3874
|
-
"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.",
|
|
3875
|
-
|
|
3836
|
+
"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.",
|
|
3837
|
+
"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.",
|
|
3838
|
+
'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. 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. 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/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 前置条件/测试数据/自动化映射.',
|
|
3876
3839
|
"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 one `testcase/test_{{item.stem}}.py`. The <module> stem is the Markdown filename without the `.md` extension, lowercased and with non-alphanumeric characters replaced by underscores. For example, `resource_notes` → `testcase/test_resource_notes.py`, `health` → `testcase/test_health.py`. If Markdown automation mapping names a different path than this module stem path, still write the module stem 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.",
|
|
3877
3840
|
"Keep this module self-contained: define module-local fixtures and helpers directly in testcase/test_{{item.stem}}.py, 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.",
|
|
3841
|
+
"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.",
|
|
3878
3842
|
"Do not read source/**, add cases, reassign ACs, modify conftest/config/production code, use skip/xfail, swallow assertions, execute pytest, or emit JSON. For best-effort cleanup, catch only the narrow transport exception actually raised by the selected HTTP client (for example `requests.RequestException` or `urllib.error.URLError`); never use bare `except`, `Exception`, or `BaseException` with `pass`.",
|
|
3879
3843
|
].join("\n\n"),
|
|
3880
3844
|
},
|
|
@@ -3888,7 +3852,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3888
3852
|
role: "implementer",
|
|
3889
3853
|
executor: "pi",
|
|
3890
3854
|
toolProfile: "write",
|
|
3891
|
-
complexity: "
|
|
3855
|
+
complexity: "MED",
|
|
3892
3856
|
writePolicy: "exclusive",
|
|
3893
3857
|
writeSet: [
|
|
3894
3858
|
"testcase/**/test_*.py",
|
|
@@ -3904,27 +3868,28 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3904
3868
|
"pyproject.toml",
|
|
3905
3869
|
"setup.cfg",
|
|
3906
3870
|
])),
|
|
3907
|
-
writerOutcomePolicy: { type: "implementation-outcome-v1" },
|
|
3908
|
-
outputContract: "First non-empty line is IMPLEMENTATION_OUTCOME: changed|
|
|
3871
|
+
writerOutcomePolicy: { type: "implementation-outcome-v1", requireChangedFiles: true },
|
|
3872
|
+
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.",
|
|
3909
3873
|
subtask_prompt: [
|
|
3910
|
-
"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.",
|
|
3911
|
-
"Fix only readiness-proven generated testcase-local defects on initial facts repairPaths: create exact safe missing mapped test_*.py paths, repair syntax/import/symbol/decorator/parameterization,
|
|
3874
|
+
"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. Treat any upstream line such as `Repair paths: testcase/test_x.py` as complete authoritative repairPaths evidence. Directly read and edit that testcase 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 testcase file, the path exists—continue the bounded repair and never later claim that file is absent.",
|
|
3875
|
+
"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 testcase-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 (missing/multiple primary symbol, script mismatch, parameter ID or assertion binding). 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; 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.",
|
|
3876
|
+
"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.",
|
|
3912
3877
|
"Preserve final testcase/md/** semantics, every Case ID, Rule/Test Point binding, primary symbol, parameter ID, expected status/body/schema assertion, HTTP logging, redaction and truncation behavior.",
|
|
3913
3878
|
"Use local edit only on assessment-listed paths; keep summaries short; never rewrite unrelated modules.",
|
|
3914
|
-
"Do not
|
|
3879
|
+
"Do not reinterpret requirements beyond the effective Markdown and bounded assessment diagnostics. Do not modify Markdown, conftest, pytest config, production code or dependencies.",
|
|
3915
3880
|
"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.",
|
|
3916
3881
|
"Do not execute pytest; the deterministic effective collection gate owns the final collection attempt.",
|
|
3917
3882
|
].join("\n\n"),
|
|
3918
3883
|
};
|
|
3919
|
-
const collectionEffective = shellNode("effective-backend-pytest-collection-gate-shell", [collectionAssess.id, repairPytest.id], "markdown-collection-effective", "If initial collection+fixture readiness passed, verify unchanged asset hashes and reuse it. If the single repair ran, rerun scenario-param preflight, final collection and no-business-body fixture resolution once. BLOCKED facts, repair failure, residual fixture failure or hash drift prevent business pytest execution. Materialize canonical backend-test-execution-readiness.json.", "Run-owned effective collection-v3 facts
|
|
3884
|
+
const collectionEffective = shellNode("effective-backend-pytest-collection-gate-shell", [collectionAssess.id, repairPytest.id], "markdown-collection-effective", "If initial collection+fixture readiness passed, verify unchanged asset hashes and reuse it. If the single repair ran, rerun scenario-param preflight, final collection and no-business-body fixture resolution once. Recompute effective correspondence/payload shape and materialize Case/symbol/item `ELIGIBLE|NOT_ELIGIBLE` facts; Payload-contract-bound field-target MISMATCH/UNDETERMINED scenario items, non-exact mappings and payload UNSAFE/UNAVAILABLE items are excluded. Generic request-level nominal/health observations remain advisory when payload shape is SAFE and correspondence exact. BLOCKED facts, repair failure, residual fixture failure, zero eligible items or hash drift prevent business pytest execution. Materialize canonical backend-test-execution-readiness.json v2.", "Run-owned effective collection-v3 facts, reports/backend-test-execution-eligibility.md, contracts/backend-test-execution-eligibility.json and contracts/backend-test-execution-readiness.json v2 proving exact final assets are collectable, fixture-resolvable, payload-safe at item level and hash-bound; initial PASS is reused, repair path records attempt=1.", [], 120000);
|
|
3920
3885
|
collectionEffective.dependsPolicy = "all-or-condition-skip";
|
|
3921
|
-
const traceability = shellNode("backend-test-traceability-gate-shell", [collectionEffective.id], "markdown-traceability", "Deterministically scan only final readiness-authorized Markdown-mapped pytest scripts. Produce bidirectional Markdown module/Case/Test Point ↔ pytest file/primary symbol correspondence and logging findings. scenario-param assessment/repair already ran before collection; consume and display its final PASS/PARTIAL/FAIL/UNAVAILABLE facts without modifying pytest assets after readiness was frozen. Correspondence findings remain advisory; Never block pytest solely on correspondence FAIL.", "Run-owned reports/backend-test-traceability.md, reports/backend-test-markdown-pytest-correspondence.md, contracts/backend-test-markdown-pytest-correspondence-facts.json, reports/backend-test-scenario-param-consistency.md and contracts/backend-test-scenario-param-consistency-facts.json (initial+final) with optional repair audit; PASS/FAIL/UNAVAILABLE correspondence facts bound after effective collection.");
|
|
3886
|
+
const traceability = shellNode("backend-test-traceability-gate-shell", [collectionEffective.id], "markdown-traceability", "Deterministically scan only final readiness-authorized Markdown-mapped pytest scripts. Produce bidirectional Markdown module/Case/Test Point ↔ pytest file/primary symbol correspondence, payload-shape safety, per-item execution eligibility and logging findings. scenario-param assessment/repair already ran before collection; consume and display its final PASS/PARTIAL/FAIL/UNAVAILABLE facts without modifying pytest assets after readiness was frozen. Correspondence findings remain advisory; Never block pytest solely on correspondence FAIL.", "Run-owned reports/backend-test-traceability.md, reports/backend-test-markdown-pytest-correspondence.md, contracts/backend-test-markdown-pytest-correspondence-facts.json, reports/backend-test-scenario-param-consistency.md and contracts/backend-test-scenario-param-consistency-facts.json (initial+final) with optional repair audit; PASS/FAIL/UNAVAILABLE correspondence facts bound after effective collection.");
|
|
3922
3887
|
const manifest = shellNode("backend-test-case-manifest-shell", [traceability.id], "markdown-manifest", "Materialize the canonical Backend Test Case Manifest only from contracts/backend-test-case-coverage-facts.json and contracts/backend-test-markdown-pytest-correspondence-facts.json. Validate schema, task binding, input hashes and freshness; never re-read source semantics, re-analyze Coverage Matrix, rescan pytest symbols or recompute a second set of metrics. Missing/stale/conflicting facts produce partial/unavailable diagnostics rather than fabricated zeros.", "Run-owned contracts/backend-test-case-manifest.json with materializationStatus, sourceFactsIssues, validated coverageScope, coverageSummary, ruleCoverageSummary and correspondenceSummary; this is the single machine input for L-5 and closeout.");
|
|
3923
3888
|
const pytestCommand = [
|
|
3924
3889
|
'mkdir -p "${HARNESS_DAG_RUN_DIR}/reports"',
|
|
3925
|
-
'echo "pytest targets are resolved at runtime from
|
|
3890
|
+
'echo "pytest targets are resolved at runtime from execution-readiness v2 eligibleItemIds"',
|
|
3926
3891
|
].join("; ");
|
|
3927
|
-
const execute = shellNode("execute-backend-pytest-and-html-report-shell", [manifest.id], "markdown-execute-html", "Read canonical contracts/backend-test-execution-readiness.json, verify final asset hashes, then
|
|
3892
|
+
const execute = shellNode("execute-backend-pytest-and-html-report-shell", [manifest.id], "markdown-execute-html", "Read canonical contracts/backend-test-execution-readiness.json v2, verify final asset and eligibility-input hashes, then execute exactly its `eligibleItemIds` pytest node IDs once. Never execute `excludedItems`; retain each exclusion reason as residual TestBug/automation evidence rather than ProductBug. Prefer the deterministic module one-to-one path when a mapped script is missing but the module stem file exists. Generate a native pytest-html self-contained report, then render the primary self-contained Chinese HTML report from the same pytest-html plus final Markdown case metadata without rerun. Keep 测试结论 and quality status; make node 6 Markdown validation + case coverage and node 13 traceability + Markdown-to-pytest correspondence expandable to their full escaped details; show each failure overview item with its original pytest message plus deterministic evidence-based reason analysis; list failure/error case cards before the remaining cases while preserving stable order. Each polished per-case result card includes concise scenario, automation test name, result, duration, and redacted bounded HTTP request parameters/response results for both passed and failed cases. Do not render a technical/execution evidence section in HTML; retain auditable paths and hashes in facts.", "One scoped pytest execution over readiness-authorized eligible pytest items producing a valid pytest-html report with per-case captured output, self-contained reports/backend-test.html, reports/backend-test.md, reports/backend-test-facts.md, a deterministic self-contained reports/backend-test-l5-dashboard.html (machine-computed L-5 metrics, no JSON), and an optional contracts/code-coverage-v1.json when jacocoCoverage is configured (JaCoCo TCP dump → jacoco.xml → parsed; failure-safe); exit 0/1 with valid evidence continues.", [pytestCommand], 300000);
|
|
3928
3893
|
if (execute.shell) {
|
|
3929
3894
|
execute.shell.envAllowlist = collectBackendTestShellEnvAllowlist(sources);
|
|
3930
3895
|
}
|
|
@@ -4738,53 +4703,6 @@ function buildMultiPerspectiveReviewNodes(input) {
|
|
|
4738
4703
|
].join("\n\n"),
|
|
4739
4704
|
}));
|
|
4740
4705
|
const reviewIds = reviewNodes.map((n) => n.id);
|
|
4741
|
-
if (input.structuredVerdictGate) {
|
|
4742
|
-
const aggregateNodeId = `${input.nodePrefix}aggregate-pi`;
|
|
4743
|
-
const aggregateNode = {
|
|
4744
|
-
id: aggregateNodeId,
|
|
4745
|
-
depends_on: reviewIds,
|
|
4746
|
-
role: "reviewer",
|
|
4747
|
-
executor: "pi",
|
|
4748
|
-
complexity: "HIGH",
|
|
4749
|
-
writePolicy: "read-only",
|
|
4750
|
-
allowedPaths: input.allowedPaths,
|
|
4751
|
-
forbiddenPaths: commonForbiddenPaths(input.sources),
|
|
4752
|
-
outputContract: "Plain Markdown; first non-empty line is exactly VERDICT: pass only when every upstream perspective emitted VERDICT: pass, otherwise VERDICT: request-revision. No file writes.",
|
|
4753
|
-
subtask_prompt: [
|
|
4754
|
-
`Aggregate ${input.gateLabel} without changing any review finding.`,
|
|
4755
|
-
`Inspect every upstream result: ${reviewIds.join(", ")}.`,
|
|
4756
|
-
"First non-empty line must be exactly VERDICT: pass only when every upstream first verdict is VERDICT: pass; otherwise use VERDICT: request-revision.",
|
|
4757
|
-
"List each upstream node and its observed verdict as evidence. Do not invent or override a missing/request-revision verdict.",
|
|
4758
|
-
"Read-only: do not modify files.",
|
|
4759
|
-
].join("\n\n"),
|
|
4760
|
-
};
|
|
4761
|
-
const gateNode = {
|
|
4762
|
-
id: input.gateId,
|
|
4763
|
-
depends_on: [aggregateNodeId],
|
|
4764
|
-
role: "verifier",
|
|
4765
|
-
executor: "shell",
|
|
4766
|
-
complexity: "LOW",
|
|
4767
|
-
writePolicy: "read-only",
|
|
4768
|
-
allowedPaths: commonReadOnlyPaths(input.sources),
|
|
4769
|
-
forbiddenPaths: commonForbiddenPaths(input.sources),
|
|
4770
|
-
outputContract: `Structured verdict gate: exit 0 only when ${aggregateNodeId} emits VERDICT: pass.`,
|
|
4771
|
-
subtask_prompt: `Structured gate for ${input.gateLabel}: all perspectives must pass before downstream apply/promote.`,
|
|
4772
|
-
shell: {
|
|
4773
|
-
commands: [],
|
|
4774
|
-
verdictGate: {
|
|
4775
|
-
fromNodeId: aggregateNodeId,
|
|
4776
|
-
accept: ["pass"],
|
|
4777
|
-
routingAccept: ["request-revision"],
|
|
4778
|
-
source: "verdict-line",
|
|
4779
|
-
lineMode: "first-verdict-line",
|
|
4780
|
-
label: input.gateLabel,
|
|
4781
|
-
},
|
|
4782
|
-
cwd: ".",
|
|
4783
|
-
timeoutMs: 60000,
|
|
4784
|
-
},
|
|
4785
|
-
};
|
|
4786
|
-
return [...reviewNodes, aggregateNode, gateNode];
|
|
4787
|
-
}
|
|
4788
4706
|
const aggregateScript = buildMultiPerspectiveReviewAggregateScript(reviewIds, input.gateLabel);
|
|
4789
4707
|
const gateNode = {
|
|
4790
4708
|
id: input.gateId,
|
|
@@ -5044,7 +4962,6 @@ function buildKnowledgeSyncMultiReviewNodes(sources, featureId) {
|
|
|
5044
4962
|
`Primary draft path: ${knowledgeSyncDraftRelPath(featureId)}.`,
|
|
5045
4963
|
"Apply is blocked until all perspectives pass. Do not approve fabricated verification pass.",
|
|
5046
4964
|
],
|
|
5047
|
-
structuredVerdictGate: true,
|
|
5048
4965
|
});
|
|
5049
4966
|
}
|
|
5050
4967
|
function buildKnowledgeSyncApplyNode(sources, featureId) {
|