@tea-agent/loop-agent 0.43.0-next.18 → 0.43.0-next.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +60 -2308
- package/README.md +7 -0
- package/dist/application/task-lifecycle/advance.js +38 -7
- package/dist/build-stamp.json +3 -3
- package/dist/commands/init.js +13 -9
- package/dist/commands/task-advance.js +32 -0
- package/dist/executors/dag-pi-executor.js +1615 -188
- package/dist/executors/pi-executor.js +85 -3
- package/dist/executors/pi-sdk-executor.js +18 -0
- package/dist/executors/shell-executor.js +92 -7
- package/dist/shared/backend-dogfood-preflight.js +47 -0
- package/dist/shared/dag-failure-category.js +3 -0
- package/dist/shared/operator/capabilities.js +180 -0
- package/dist/task/config-types.js +29 -0
- package/dist/task/contract/project.js +9 -0
- package/dist/task/contract/schema.js +17 -0
- package/dist/task/source-prepare/build-draft.js +60 -0
- package/dist/worker/console/chat/pi-mode-loop-isolation.js +155 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +24 -16
- package/dist/worker/console/chat/pi-runtime/custom-tools/scheduled-goal-tools.js +22 -0
- package/dist/worker/console/chat/pi-runtime/progressive-tools.js +195 -0
- package/dist/worker/console/chat/pi-runtime.js +86 -27
- package/dist/worker/console/chat/routes.js +19 -0
- package/dist/worker/console/chat/scheduled-goal-booking.js +59 -0
- package/dist/worker/console/chat/scheduled-goal-delivery.js +27 -0
- package/dist/worker/console/chat/scheduled-goal-request.js +190 -0
- package/dist/worker/console/chat/session-mode.js +7 -3
- package/dist/worker/console/chat/session-store.js +42 -9
- package/dist/worker/console/chat/tool-preview.js +115 -0
- package/dist/worker/console/chat/turn-order.js +13 -0
- package/dist/worker/console/chat/turn-process.js +32 -30
- package/dist/worker/console/operator-actions.js +50 -0
- package/dist/worker/console/prd-intake-bridge.js +54 -1
- package/dist/worker/console/scheduled-goal-host.js +98 -0
- package/dist/worker/console/scheduled-goal-operation-adapter.js +127 -0
- package/dist/worker/console/server.js +15 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B_UDknhR.js → abnfDiagram-N423BO3Z-8-j6y-sd.js} +1 -1
- package/dist/worker/console/static/assets/{arc-D6hU0drN.js → arc-eoQiMvuk.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DmVikdRK.js → architectureDiagram-T3A2C74G-C4A3uMcI.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DydABCRg.js → blockDiagram-VBNYF7ZC-D4zD2F-Q.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DrXKC2Jl.js → c4Diagram-5PPSVZJV-j1RkJziL.js} +1 -1
- package/dist/worker/console/static/assets/channel-ChE7y-cx.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D2-qFxr7.js → chunk-2GRJ4B5K-YBHmhik1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-ChfeF41P.js → chunk-2Q5K7J3B-COfWyo9P.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Drwrz55x.js → chunk-5RXB4S5H-I99OUkHY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CAQGErL7.js → chunk-5VM5RSS4-XKxoJNJ4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DHO38w5U.js → chunk-6Q2QTUOP-DLT_cYx1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-EYIN0EOI.js → chunk-GF5L2VYU-CxcKZ9mV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CrFLKqkT.js → chunk-JWPE2WC7-V1EqXdjY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DMchffoo.js → chunk-KBJHAD2P-DebTtdFp.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DaSvtKQa.js → chunk-RYQCIY6F-B-ivNRDf.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DcW8W9Qj.js → chunk-XXDRQBXY-DC_Ds11b.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BUirrMEi.js → cose-bilkent-JH36ORCC-sWEqKwIb.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXzO8iXR.js → cynefin-VYW2F7L2-BXa_dcu4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DAtgaxIn.js → cynefinDiagram-MW4NZA55-C-Mle84F.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C1re9Noh.js → dagre-VZM6K2ZE-CXPDBITe.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-N8Kj08J9.js → diagram-7IWD3JNH-CC-WJQfa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-D6BpSLs3.js → diagram-B4RE2ZJO-CDAV5Vs4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C2Vlgnl4.js → diagram-LBJQPF4R-CmFiAcNz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CMbqtVLS.js → diagram-Q27KOJAE-DPBZHuyn.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-A770Eb-0.js → diagram-UB23O5K3-jUlm_Ds3.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-LV2w_2pT.js → ebnfDiagram-BXEA7PRR-DWhQ3mfY.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-bwqf56ah.js → erDiagram-JOGREHBK-Tr2gMqet.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BnVtHhZh.js → flowDiagram-UKHOOZJN-DJjVQHPA.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BGVToEqc.js → ganttDiagram-PKOTCBZU-D74dQ4u0.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DL2l7Vne.js → gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js} +1 -1
- package/dist/worker/console/static/assets/index-BWkIfcrK.css +1 -0
- package/dist/worker/console/static/assets/{index-B_V4wvXs.js → index-Cdkvw_H6.js} +97 -97
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BWIpubOx.js → infoDiagram-6WML65LV-ILCbxJyb.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DjYBD8vv.js → ishikawaDiagram-WSZJBQD7-DeuWBQ89.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-C0_mBaHb.js → journeyDiagram-NVQOT4AX-CF5ih8Fk.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Cy71zbX6.js → kanban-definition-27J2QSJJ-C7yOSuRO.js} +1 -1
- package/dist/worker/console/static/assets/{linear-Dt3_w3Vn.js → linear-BDZ9riWi.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DnNOlfEP.js → mermaid.core-7pKqYtpZ.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DAbTspwq.js → mindmap-definition-FAOFIHXS-LeJDybSU.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-NNiM141p.js → pegDiagram-VL7TDLO6-BJvT3pMD.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CN80Z1Do.js → pieDiagram-7S7Q4E2Y-_rGqpMan.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bxyfh0JU.js → quadrantDiagram-CIZ2JOQS-DPcSv5aA.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BlWnC79M.js → railroadDiagram-AXF67PYL-Drxx4hkJ.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BrEu4P_e.js → requirementDiagram-LRYGKXZP-BCTUdU4z.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bt70igBr.js → sankeyDiagram-W5VNT64P-B6wzbZmB.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C8DQR-r9.js → sequenceDiagram-SI44F4Z6-BGt8d3QQ.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DEMinJAT.js → sizeCapture-X5ZJPWSS-7nlhJKo7.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-DDdDBzQc.js → stateDiagram-OKZ733FA-Cv2stqMA.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-G2wcBs3T.js → swimlanes-SLNWSIFB-qDAo4Yc1.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Dzn4g2gJ.js → timeline-definition-Z64GVDOM-CKbDNKTr.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-5v2rP9oO.js → vennDiagram-T6HMQDX7-DI-9EHic.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-B4EOx7ew.js → wardleyDiagram-T6FBY63Y-BrzzRDpX.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CQ1v8eYP.js → xychartDiagram-ELKLHX3M-BclH5hGh.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +41 -11
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +3 -17
- package/dist/worker/console/static-src/operator-chat/timeline-merge.js +29 -0
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +2 -5
- package/dist/worker/console/workspace-context.js +11 -0
- package/dist/worker/observe/static/operator-chrome.js +3 -1
- package/dist/worker/scheduler/scheduled-goal-dispatch.js +117 -0
- package/dist/worker/scheduler/scheduled-goal-evidence.js +167 -0
- package/dist/worker/scheduler/scheduled-goal-recovery.js +62 -0
- package/dist/worker/scheduler/scheduled-goal-store.js +699 -0
- package/dist/worker/scheduler/scheduled-goal-supervisor.js +132 -0
- package/dist/worker/scheduler/scheduled-goal-time.js +102 -0
- package/dist/worker/scheduler/scheduled-goal-types.js +95 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +6 -24
- package/dist/workflows/dag/backend-test-plan-protocol.js +82 -5
- package/dist/workflows/dag/dag-retry-schema.js +11 -0
- package/dist/workflows/dag/frontend-closeout.js +4 -2
- package/dist/workflows/dag/frontend-committed-facts.js +461 -0
- package/dist/workflows/dag/frontend-durable-tools.js +15 -3
- package/dist/workflows/dag/frontend-implementation-contract.js +365 -8
- package/dist/workflows/dag/frontend-plan-canary.js +53 -0
- package/dist/workflows/dag/frontend-plan-decision-contract.js +803 -0
- package/dist/workflows/dag/frontend-plan-render.js +0 -2
- package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +8 -61
- package/dist/workflows/dag/frontend-recovery-run.js +57 -0
- package/dist/workflows/dag/frontend-review-context.js +43 -70
- package/dist/workflows/dag/frontend-risk.js +92 -10
- package/dist/workflows/dag/frontend-session-budget.js +117 -3
- package/dist/workflows/dag/frontend-shape.js +11 -55
- package/dist/workflows/dag/frontend-test-execution-evidence.js +35 -10
- package/dist/workflows/dag/frontend-typed-event-store.js +32 -25
- package/dist/workflows/dag/frontend-verification-trace.js +11 -2
- package/dist/workflows/dag/frontend-writer-admission.js +3 -47
- package/dist/workflows/dag/frontend-writer-status.js +0 -23
- package/dist/workflows/dag/init-hybrid.js +182 -185
- package/dist/workflows/dag/lifecycle.js +7 -2
- package/dist/workflows/dag/node-execution.js +48 -13
- package/dist/workflows/dag/rerun-plan.js +10 -0
- package/dist/workflows/dag/rerun-task.js +77 -1
- package/dist/workflows/dag/retry-policy.js +18 -0
- package/dist/workflows/dag/scheduler.js +2 -4
- package/dist/workflows/dag/types.js +23 -0
- package/docs/README.md +1 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/operations/README.md +2 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +2 -2
- package/docs/templates/backend-test-dag.json +14 -10
- package/docs/templates/frontend-implementation-contract.schema.json +0 -7
- package/docs/templates/frontend-implementation-dag.json +8 -9
- package/package.json +6 -3
- package/skills/frontend-bounded-implement/SKILL.md +3 -4
- package/skills/frontend-design-review/SKILL.md +7 -12
- package/skills/frontend-design-review/references/review-checklist.md +7 -7
- package/skills/frontend-plan/SKILL.md +8 -18
- package/skills/frontend-plan/references/decision-contract.md +19 -26
- package/skills/frontend-review/SKILL.md +21 -25
- package/skills/frontend-review/references/review-findings.md +48 -24
- package/dist/worker/console/static/assets/channel-Drecd94a.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-8Udu0t8-.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-8Udu0t8-.js +0 -1
- package/dist/worker/console/static/assets/index-DcudonhZ.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CyCiS0Lt.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-U0lhi6N0.js +0 -8
- package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -975
|
@@ -30,6 +30,7 @@ import { extractRequirementFactsFromMarkdown } from "../../task/source-prepare/p
|
|
|
30
30
|
import { computeLedgerInputDigest, parseLedgerJson, recoverLedgerInputContract, validateRequirementLedger, } from "../../task/source-prepare/ledger.js";
|
|
31
31
|
import { REQUIREMENT_LEDGER_FILE_NAME } from "../../task/contract/constants.js";
|
|
32
32
|
import { dagHasWriterExecution } from "./task-contract-binding.js";
|
|
33
|
+
import { BACKEND_TEST_MODULE_INDEX_HEADER, BACKEND_TEST_MODULE_SPLIT_REASONS, backendTestModuleIndexHeaderMarkdown, canonicalBackendTestModuleMarkdownPath, } from "./backend-test-plan-protocol.js";
|
|
33
34
|
import { DEFAULT_VERIFY_TIMEOUT_MS, resolveVerifyPreset, } from "../../executors/shell-verification.js";
|
|
34
35
|
import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
|
|
35
36
|
import { normalizeTaskRequirementText, resolveTaskDagTemplateSelection, } from "./task-demand-routing.js";
|
|
@@ -699,6 +700,7 @@ export function frontendSourceMentionsMock(sources) {
|
|
|
699
700
|
*/
|
|
700
701
|
export function resolveFrontendMockMode(capability, taskConfig, hasApiDep) {
|
|
701
702
|
const policy = taskConfig.frontendMock?.policy ?? "auto";
|
|
703
|
+
const hasDeterministicMockVerification = capability.verifyCommands.length > 0;
|
|
702
704
|
if (capability.safetyViolation ||
|
|
703
705
|
!frontendMockServiceRootAllowed(taskConfig)) {
|
|
704
706
|
return "blocked";
|
|
@@ -714,7 +716,7 @@ export function resolveFrontendMockMode(capability, taskConfig, hasApiDep) {
|
|
|
714
716
|
}
|
|
715
717
|
// required policy
|
|
716
718
|
if (policy === "required") {
|
|
717
|
-
if (capability.status === "present") {
|
|
719
|
+
if (capability.status === "present" && hasDeterministicMockVerification) {
|
|
718
720
|
return "required";
|
|
719
721
|
}
|
|
720
722
|
return "blocked";
|
|
@@ -723,8 +725,13 @@ export function resolveFrontendMockMode(capability, taskConfig, hasApiDep) {
|
|
|
723
725
|
if (!hasApiDep) {
|
|
724
726
|
return "not-required";
|
|
725
727
|
}
|
|
726
|
-
//
|
|
727
|
-
|
|
728
|
+
// In auto mode, frontend Mock is optional. Only enter Mock-backed "required"
|
|
729
|
+
// mode when the project has a confirmed native Mock capability AND
|
|
730
|
+
// deterministic Mock verification commands. Without executable verification
|
|
731
|
+
// commands a non-not-needed strategy could never be verified, so auto falls
|
|
732
|
+
// back to not-required; the prewrite gate then forces not-needed and the
|
|
733
|
+
// plan records the Real Integration Gap instead of inventing commands.
|
|
734
|
+
return capability.status === "present" && hasDeterministicMockVerification
|
|
728
735
|
? "required"
|
|
729
736
|
: "not-required";
|
|
730
737
|
}
|
|
@@ -1020,127 +1027,6 @@ function extractFrontendVerifyCommandsFromMarkdown(input) {
|
|
|
1020
1027
|
}
|
|
1021
1028
|
return { staticCommands, behaviorCommands };
|
|
1022
1029
|
}
|
|
1023
|
-
/**
|
|
1024
|
-
* Resolve the static verification commands the frontend DAG will freeze.
|
|
1025
|
-
*
|
|
1026
|
-
* The frontend contract is "the implementation must be statically verifiable -
|
|
1027
|
-
* never a no-op". It used to mean exactly `npm run build`, which made the whole
|
|
1028
|
-
* run fail at the verify node (8 of 11) for any project that legitimately has no
|
|
1029
|
-
* build step: a zero-dependency Node app, a static-site repo, a plain-ESM
|
|
1030
|
-
* browser module. The failure landed *after* `frontend-implement-pi` had already
|
|
1031
|
-
* delivered correct, test-passing work, so the run reported failure for a project
|
|
1032
|
-
* shape that was never going to pass. That is a meaningless failure and a
|
|
1033
|
-
* gratuitous loss of trust, not a safety property.
|
|
1034
|
-
*
|
|
1035
|
-
* Order, keeping every choice a real check:
|
|
1036
|
-
* 1. `npm run build` when the project defines it - unchanged for every project
|
|
1037
|
-
* that already worked.
|
|
1038
|
-
* 2. static commands the task declared via `--verify`. These were already being
|
|
1039
|
-
* classified onto the static lane and then discarded, so an operator who
|
|
1040
|
-
* declared `--verify typecheck:npm run typecheck` was silently ignored.
|
|
1041
|
-
* 3. the project's own `typecheck` / `check` script.
|
|
1042
|
-
* 4. otherwise the historical `npm run build` is kept. That still fails at the
|
|
1043
|
-
* verify node, exactly as before this change - it is deliberately NOT a new
|
|
1044
|
-
* generation-time error. Introducing one would turn a late failure into an
|
|
1045
|
-
* early one for projects that never declared static verification, which is
|
|
1046
|
-
* more failure, not less; the operator can still declare one via `--verify`.
|
|
1047
|
-
*
|
|
1048
|
-
* Lint is deliberately never chosen: the frontend topology hard-codes
|
|
1049
|
-
* `lintShellCommands = []` so existing lint debt stays outside this workflow, and
|
|
1050
|
-
* auto-selecting a lint script would turn that debt into a fresh failure.
|
|
1051
|
-
*/
|
|
1052
|
-
const FRONTEND_STATIC_SCRIPT_PREFERENCE = ["typecheck", "check"];
|
|
1053
|
-
async function resolveFrontendStaticCommands(input) {
|
|
1054
|
-
let scripts = {};
|
|
1055
|
-
try {
|
|
1056
|
-
const manifest = JSON.parse(await readFile(path.join(input.repoRoot, "package.json"), "utf8"));
|
|
1057
|
-
scripts = manifest.scripts ?? {};
|
|
1058
|
-
}
|
|
1059
|
-
catch (error) {
|
|
1060
|
-
if (error.code !== "ENOENT")
|
|
1061
|
-
throw error;
|
|
1062
|
-
}
|
|
1063
|
-
const hasScript = (name) => {
|
|
1064
|
-
const script = scripts[name];
|
|
1065
|
-
return typeof script === "string" && script.trim() !== "";
|
|
1066
|
-
};
|
|
1067
|
-
if (hasScript("build")) {
|
|
1068
|
-
return [
|
|
1069
|
-
{
|
|
1070
|
-
label: "build",
|
|
1071
|
-
args: ["bash", "-lc", "npm run build"],
|
|
1072
|
-
cwd: input.repoRoot,
|
|
1073
|
-
},
|
|
1074
|
-
];
|
|
1075
|
-
}
|
|
1076
|
-
if (input.explicitStatic.length > 0)
|
|
1077
|
-
return input.explicitStatic;
|
|
1078
|
-
for (const name of FRONTEND_STATIC_SCRIPT_PREFERENCE) {
|
|
1079
|
-
if (!hasScript(name))
|
|
1080
|
-
continue;
|
|
1081
|
-
return [
|
|
1082
|
-
{
|
|
1083
|
-
label: name,
|
|
1084
|
-
args: ["bash", "-lc", `npm run ${name}`],
|
|
1085
|
-
cwd: input.repoRoot,
|
|
1086
|
-
},
|
|
1087
|
-
];
|
|
1088
|
-
}
|
|
1089
|
-
// Nothing static is discoverable: keep the historical command rather than
|
|
1090
|
-
// inventing a new failure. See the doc comment above for why.
|
|
1091
|
-
return [
|
|
1092
|
-
{
|
|
1093
|
-
label: "build",
|
|
1094
|
-
args: ["bash", "-lc", "npm run build"],
|
|
1095
|
-
cwd: input.repoRoot,
|
|
1096
|
-
},
|
|
1097
|
-
];
|
|
1098
|
-
}
|
|
1099
|
-
/** Reuse the project's test entrypoint; never install a runner for an untested project. */
|
|
1100
|
-
async function resolveFrontendExistingTests(sources) {
|
|
1101
|
-
const repoRoot = sources.repoRoot ?? process.cwd();
|
|
1102
|
-
const explicit = buildExplicitFrontendVerifyCommands(sources.taskConfig, repoRoot).behaviorCommands;
|
|
1103
|
-
if (explicit.length > 0)
|
|
1104
|
-
return explicit;
|
|
1105
|
-
const declared = extractFrontendVerifyCommandsFromMarkdown({ ...sources, repoRoot }).behaviorCommands;
|
|
1106
|
-
if (declared.length > 0)
|
|
1107
|
-
return declared;
|
|
1108
|
-
let manifest;
|
|
1109
|
-
try {
|
|
1110
|
-
manifest = JSON.parse(await readFile(path.join(repoRoot, "package.json"), "utf8"));
|
|
1111
|
-
}
|
|
1112
|
-
catch (error) {
|
|
1113
|
-
if (error.code === "ENOENT")
|
|
1114
|
-
return [];
|
|
1115
|
-
throw error;
|
|
1116
|
-
}
|
|
1117
|
-
const script = manifest.scripts?.test;
|
|
1118
|
-
if (!script || /no test specified/i.test(script))
|
|
1119
|
-
return [];
|
|
1120
|
-
// Focus concrete existing tests; unknown runners and broad scopes retain the
|
|
1121
|
-
// project entrypoint instead of receiving guessed filtering flags.
|
|
1122
|
-
const files = [];
|
|
1123
|
-
for (const file of sources.taskConfig.allowedPaths) {
|
|
1124
|
-
if (/[*?\[\]{}]/.test(file) || !/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(file))
|
|
1125
|
-
continue;
|
|
1126
|
-
if (sources.taskConfig.forbiddenPaths.some((pattern) => pathMatchesPattern(file, pattern)))
|
|
1127
|
-
continue;
|
|
1128
|
-
if (existsSync(path.join(repoRoot, file)))
|
|
1129
|
-
files.push(file);
|
|
1130
|
-
}
|
|
1131
|
-
const runner = /(?:^|[\s/])jest(?:[\s/.]|$)/.test(script) ? "jest"
|
|
1132
|
-
: /(?:^|[\s/])vitest(?:[\s/.]|$)/.test(script) ? "vitest" : undefined;
|
|
1133
|
-
const args = ["npm", "test"];
|
|
1134
|
-
if (runner) {
|
|
1135
|
-
args.push("--");
|
|
1136
|
-
if (runner === "jest")
|
|
1137
|
-
args.push("--runInBand");
|
|
1138
|
-
else if (!/\b(?:run|--run)\b/.test(script))
|
|
1139
|
-
args.push("--run");
|
|
1140
|
-
args.push(...files);
|
|
1141
|
-
}
|
|
1142
|
-
return [{ label: "existing unit tests", args, cwd: repoRoot }];
|
|
1143
|
-
}
|
|
1144
1030
|
function extractFrontendMockVerifyCommandsFromMarkdown(input) {
|
|
1145
1031
|
const commands = [];
|
|
1146
1032
|
const markdown = [
|
|
@@ -1597,7 +1483,6 @@ function deriveParallelScoutPaths(taskConfig) {
|
|
|
1597
1483
|
: allowed,
|
|
1598
1484
|
};
|
|
1599
1485
|
}
|
|
1600
|
-
const FRONTEND_NO_STATIC_VERIFICATION_MARKER = `node -e "console.log('${FRONTEND_NO_VERIFICATION_MARKER_TEXT}; static/behavior verification not-run')"`;
|
|
1601
1486
|
/**
|
|
1602
1487
|
* Resolve frontend verification fallbacks from the target project's own
|
|
1603
1488
|
* package scripts. The DAG builder is also used by unit fixtures without a
|
|
@@ -1607,6 +1492,7 @@ const FRONTEND_NO_STATIC_VERIFICATION_MARKER = `node -e "console.log('${FRONTEND
|
|
|
1607
1492
|
* at verify time, so such projects fall through to the tsc probe and may end
|
|
1608
1493
|
* up with no fallback commands plus a generation-time advisory.
|
|
1609
1494
|
*/
|
|
1495
|
+
const FRONTEND_NO_STATIC_VERIFICATION_MARKER = `node -e "console.log('${FRONTEND_NO_VERIFICATION_MARKER_TEXT}; static/behavior verification not-run')"`;
|
|
1610
1496
|
async function discoverFrontendFallbackVerifyCommands(repoRoot) {
|
|
1611
1497
|
const genericFallback = {
|
|
1612
1498
|
staticCommands: ["npm run typecheck", "npm run build"],
|
|
@@ -2659,7 +2545,7 @@ function resolveFrontendMockContextBlock(sources) {
|
|
|
2659
2545
|
parts.push(`Evidence Paths: ${capability.evidencePaths.join(", ")}`);
|
|
2660
2546
|
}
|
|
2661
2547
|
if (capability.verifyCommands.length > 0) {
|
|
2662
|
-
parts.push(`
|
|
2548
|
+
parts.push(`Frozen Mock Verify Commands: ${capability.verifyCommands
|
|
2663
2549
|
.map((command) => command.label)
|
|
2664
2550
|
.join(", ")}`);
|
|
2665
2551
|
}
|
|
@@ -2670,7 +2556,7 @@ function resolveFrontendMockContextBlock(sources) {
|
|
|
2670
2556
|
parts.push(`Reasons: ${capability.reasons.join("; ")}`);
|
|
2671
2557
|
}
|
|
2672
2558
|
if (mode === "required") {
|
|
2673
|
-
parts.push("
|
|
2559
|
+
parts.push("Mock-backed frontend verification is required. Prefer the detected native service; otherwise the assessment may select an existing browser interception harness or reversible request adapter. Any handler, fixture, adapter, and UI changes stay in the single frontend-implement-pi writeSet.");
|
|
2674
2560
|
}
|
|
2675
2561
|
if (mode === "not-required") {
|
|
2676
2562
|
// The assessment sentence is deliberately split: every branch keeps the
|
|
@@ -2688,8 +2574,8 @@ function resolveFrontendMockContextBlock(sources) {
|
|
|
2688
2574
|
const mockAssessment = "Generation-time evidence does not require Mock. The assessment must still use contract/scout evidence: select not-needed when Mock is intentionally skipped";
|
|
2689
2575
|
if (frontendMockStrategyMustBeNotNeeded(sources)) {
|
|
2690
2576
|
parts.push(`${mockAssessment}.`);
|
|
2691
|
-
parts.push('Auto mode has no confirmed project Mock capability. The structured contract must set mockApi.strategy to "not-needed". Keep the real request path as the default, record any unproved backend behavior as Real Integration Gap, and do not add Mock files or dependencies within this run.');
|
|
2692
|
-
parts.push('HARD CONSTRAINT (frozen at generation time): this DAG allows only mockApi.strategy "not-needed"; the prewrite gate rejects any other strategy. If project governance (openspec / ai_workspace / decision records, e.g. a DEC rule requiring native) demands Mock-backed verification, that is a generation-time contract gap, not a plan-revision defect:
|
|
2577
|
+
parts.push('Auto mode has no confirmed project Mock capability or no deterministic Mock verification command. The structured contract must set mockApi.strategy to "not-needed". Keep the real request path as the default, record any unproved backend behavior as Real Integration Gap, and do not add Mock files or dependencies within this run.');
|
|
2578
|
+
parts.push('HARD CONSTRAINT (frozen at generation time): this DAG allows only mockApi.strategy "not-needed"; the prewrite gate rejects any other strategy. If project governance (openspec / ai_workspace / decision records, e.g. a DEC rule requiring native) demands Mock-backed verification, that is a generation-time contract gap, not a plan-revision defect: declare frontendMock.verifyCommands (or policy: "required") in task.json and regenerate the DAG.');
|
|
2693
2579
|
}
|
|
2694
2580
|
else {
|
|
2695
2581
|
parts.push(`${mockAssessment}, or select a safe Mock strategy if project evidence supports one.`);
|
|
@@ -2704,10 +2590,12 @@ function frontendMockStrategyMustBeNotNeeded(sources) {
|
|
|
2704
2590
|
const policy = sources.taskConfig.frontendMock?.policy ?? "auto";
|
|
2705
2591
|
const capability = sources.frontendMockCapability;
|
|
2706
2592
|
const capabilityStatus = capability?.status;
|
|
2593
|
+
const hasDeterministicMockVerification = (capability?.verifyCommands.length ?? 0) > 0;
|
|
2707
2594
|
return (policy === "auto" &&
|
|
2708
2595
|
(sources.frontendMockMode ?? "not-required") === "not-required" &&
|
|
2709
2596
|
(capabilityStatus === "absent" ||
|
|
2710
|
-
capabilityStatus === "ambiguous"
|
|
2597
|
+
capabilityStatus === "ambiguous" ||
|
|
2598
|
+
!hasDeterministicMockVerification));
|
|
2711
2599
|
}
|
|
2712
2600
|
/**
|
|
2713
2601
|
* 候选规范懒加载:prompt 只内联与任务相关的候选,不全部塞给模型。
|
|
@@ -2878,7 +2766,7 @@ async function resolveFrontendOpenspecGateConfig(sources) {
|
|
|
2878
2766
|
const frontendComponentConformanceInstruction = [
|
|
2879
2767
|
"## Component Selection conformance (uiComponentChoices; hard rule)",
|
|
2880
2768
|
"每个 UI 用途必须在契约的 uiComponentChoices[] 中声明组件选型:{ purpose, component, decision, specReference, rationale }。",
|
|
2881
|
-
"purpose
|
|
2769
|
+
"覆盖关系以 covers 列表为准:每个 applicable UI state 与 interaction 必须出现在某条 choice 的 purpose 或 covers 中,且运行时已在 plan finalize 阶段确定性预检——不要重复翻案。purpose 是描述用途的自由文本,不得仅因未精确匹配 interaction.name 或 uiState.name 而判缺陷;职责语义由被覆盖 id 的 expectedBehavior 与 rationale 表达。",
|
|
2882
2770
|
"- decision=specified:前端规范(候选组件/主题桶 + 任务源显式引用)已定义该用途组件 → 必须使用该组件,并给精确 specReference { path, section, line }(path 必须是 openspec/ai_workspace 受支持规范路径)。",
|
|
2883
2771
|
"- decision=reuse-existing:仅当该组件/惯例**确实已存在于仓库当前代码**(如复用现有 ActiveRunBadge 的 oc- class 惯例)→ specReference 可为 null,rationale 必须指明复用的具体现有组件/文件与依据。",
|
|
2884
2772
|
"- decision=new:任务源/PRD 要求**新增**该组件(仓库当前不存在该组件文件)→ decision 必须为 new,不得标 reuse-existing;调用 record_component_choice 时传 sourceRequirementIds(关联的 frozen requirement ID)与 plan checklist 列出的 sourceFragmentId,runtime 校验其隶属关系并物化精确的任务源 PRD { path, section, line }。不要读取 PRD 或手填/猜测 specReference;rationale 说明新增纯展示组件、复用既有 CSS 命名与主题变量约定。",
|
|
@@ -3175,7 +3063,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3175
3063
|
"- interactions[]: name, trigger, expectedBehavior, implementationTargets, verificationTargetIds",
|
|
3176
3064
|
"- targets: routes, publicApiChanges (files are runtime-owned)",
|
|
3177
3065
|
"- mockApi: strategy, productionDefaultOff, activation, endpoints[]",
|
|
3178
|
-
"- verificationTargets[]: id (
|
|
3066
|
+
"- verificationTargets[]: id (a stable id you choose, e.g. VT-001; it is not derived from test titles), commandId (frozen directory key), mode + commandLabel (runtime-resolved), file, requirementIds, uiStates, scope? (display-only)",
|
|
3179
3067
|
"- designEvidence: source, paths, conflicts; evidenceGaps[] (optional)",
|
|
3180
3068
|
"- optional: stylingStrategy, uiComponentChoices[], dependencyPolicy, residualRisks[], realIntegrationGap",
|
|
3181
3069
|
"- uiComponentChoices[]: purpose, component, decision (specified|reuse-existing|new), specReference { path, section, line } | null, rationale",
|
|
@@ -3202,8 +3090,12 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3202
3090
|
includeReferenceDocuments: false,
|
|
3203
3091
|
}),
|
|
3204
3092
|
};
|
|
3093
|
+
const hasMockVerifyCommands = (taskConfig.frontendMock?.verifyCommands.length ?? 0) > 0 ||
|
|
3094
|
+
mockCapability.verifyCommands.length > 0;
|
|
3205
3095
|
const requirementIds = frontendSourceBinding.requirementIds;
|
|
3096
|
+
const strategy = resolveDagVerifyStrategy(taskConfig);
|
|
3206
3097
|
const readOnlyPaths = taskConfig.allowedPaths.length > 0 ? taskConfig.allowedPaths : ["**"];
|
|
3098
|
+
const behaviorPaths = deriveFrontendBehaviorPaths(taskConfig);
|
|
3207
3099
|
const globalConstraints = [
|
|
3208
3100
|
...taskConfig.hardConstraints,
|
|
3209
3101
|
...(sources.constraintMarkdown
|
|
@@ -3217,7 +3109,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3217
3109
|
"Frontend planning must consume the read-only Mock assessment strategy produced after scouting; MOCK_STRATEGY: blocked must not pass the deterministic Mock contract gate.",
|
|
3218
3110
|
"Mock implementations must preserve the real request path as the default, require explicit test/dev activation, and never rely on commenting out the real request.",
|
|
3219
3111
|
"Mock-backed behavior evidence proves only the documented frontend contract, never real API integration.",
|
|
3220
|
-
"frontend-implementation
|
|
3112
|
+
"frontend-implementation DAGs must complete deterministic static verification before final review. Behavior verification is also required when the task declares a behavior entrypoint or the implementation contract contains a behavior-mode verification target; static-only contracts must map every target to the declared static entrypoint.",
|
|
3221
3113
|
"Frontend closeout renders only from committed facts; a weak status (failed / not-run / baseline-debt / mock-backed / pending) can never be rewritten into a stronger one (passed / real-integrated).",
|
|
3222
3114
|
`Frontend risk classification: ${frontendRisk.selectedRisk} — ${frontendRisk.reason}`,
|
|
3223
3115
|
frontendRisk.forceFullGates
|
|
@@ -3229,36 +3121,111 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3229
3121
|
const blockedReason = mockCapability.safetyViolation
|
|
3230
3122
|
? `Mock contract blocked: ${mockCapability.safetyViolation}`
|
|
3231
3123
|
: (taskConfig.frontendMock?.policy ?? "auto") === "required"
|
|
3232
|
-
? "Mock strategy is required, but no authorized Mock
|
|
3124
|
+
? "Mock strategy is required, but no authorized Mock verification command was found."
|
|
3233
3125
|
: "Mock contract is blocked by deterministic generation-time Mock safety constraints.";
|
|
3234
3126
|
return buildBlockedFrontendMockDag(frontendSources, readOnlyPaths, forbiddenPaths, globalConstraints, blockedReason);
|
|
3235
3127
|
}
|
|
3236
|
-
|
|
3237
|
-
//
|
|
3238
|
-
//
|
|
3239
|
-
//
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3128
|
+
const fallbackVerifyCommands = await discoverFrontendFallbackVerifyCommands(sources.repoRoot);
|
|
3129
|
+
// The verification bundle schema requires at least one static command. When
|
|
3130
|
+
// a real project exposes no usable verification command at all, run an
|
|
3131
|
+
// explicit no-op marker instead of a command that is guaranteed to fail:
|
|
3132
|
+
// the trace then records not-run honestly and the advisory asks the task
|
|
3133
|
+
// to declare verification commands and regenerate.
|
|
3134
|
+
const staticFallbackCommands = fallbackVerifyCommands.staticCommands.length > 0
|
|
3135
|
+
? fallbackVerifyCommands.staticCommands
|
|
3136
|
+
: [FRONTEND_NO_STATIC_VERIFICATION_MARKER];
|
|
3137
|
+
const behaviorFallbackCommands = fallbackVerifyCommands.behaviorCommands;
|
|
3138
|
+
const parsedFrontendVerifyCommands = extractFrontendVerifyCommandsFromMarkdown({
|
|
3139
|
+
repoRoot: sources.repoRoot,
|
|
3140
|
+
requirementMarkdown: sources.requirementMarkdown,
|
|
3141
|
+
constraintMarkdown: sources.constraintMarkdown,
|
|
3142
|
+
});
|
|
3143
|
+
const explicitFrontendVerifyCommands = buildExplicitFrontendVerifyCommands(taskConfig, sources.repoRoot);
|
|
3144
|
+
const explicitCommandKeys = new Set([
|
|
3145
|
+
...explicitFrontendVerifyCommands.staticCommands,
|
|
3146
|
+
...explicitFrontendVerifyCommands.behaviorCommands,
|
|
3147
|
+
].map(verifyCommandKey));
|
|
3148
|
+
const adapterVerifyCommands = (sources.verifyCommands?.final ?? []).filter((command) => !explicitCommandKeys.has(verifyCommandKey(command)));
|
|
3149
|
+
const hasDeclaredFrontendVerification = explicitFrontendVerifyCommands.staticCommands.length > 0 ||
|
|
3150
|
+
explicitFrontendVerifyCommands.behaviorCommands.length > 0 ||
|
|
3151
|
+
parsedFrontendVerifyCommands.staticCommands.length > 0 ||
|
|
3152
|
+
parsedFrontendVerifyCommands.behaviorCommands.length > 0;
|
|
3153
|
+
const staticVerifyCommands = chooseFrontendVerifyCommands({
|
|
3154
|
+
explicitCommands: explicitFrontendVerifyCommands.staticCommands,
|
|
3155
|
+
parsedCommands: parsedFrontendVerifyCommands.staticCommands,
|
|
3156
|
+
adapterCommands: adapterVerifyCommands,
|
|
3157
|
+
});
|
|
3158
|
+
const partitionedStaticVerifyCommands = partitionFrontendStaticVerifyCommands({
|
|
3159
|
+
repoRoot: sources.repoRoot,
|
|
3160
|
+
commands: staticVerifyCommands.commands,
|
|
3161
|
+
commandSource: staticVerifyCommands.commandSource,
|
|
3162
|
+
});
|
|
3163
|
+
const behaviorVerifyCommands = chooseFrontendVerifyCommands({
|
|
3164
|
+
explicitCommands: explicitFrontendVerifyCommands.behaviorCommands,
|
|
3165
|
+
parsedCommands: parsedFrontendVerifyCommands.behaviorCommands,
|
|
3166
|
+
adapterCommands: adapterVerifyCommands,
|
|
3167
|
+
// A declared task verifier owns this task's verification boundary. A
|
|
3168
|
+
// static-only task must not inherit unrelated root-level test commands.
|
|
3169
|
+
allowAdapter: !hasDeclaredFrontendVerification,
|
|
3244
3170
|
});
|
|
3245
3171
|
const staticShellCommands = buildVerifyShellCommands({
|
|
3246
|
-
repoRoot: sources.repoRoot
|
|
3172
|
+
repoRoot: sources.repoRoot,
|
|
3173
|
+
commands: partitionedStaticVerifyCommands.static.commands,
|
|
3174
|
+
fallbackCommands: staticFallbackCommands,
|
|
3247
3175
|
});
|
|
3176
|
+
const lintShellCommands = buildVerifyShellCommands({
|
|
3177
|
+
repoRoot: sources.repoRoot,
|
|
3178
|
+
commands: partitionedStaticVerifyCommands.lint.commands,
|
|
3179
|
+
fallbackCommands: [],
|
|
3180
|
+
});
|
|
3181
|
+
const behaviorShellCommands = buildVerifyShellCommands({
|
|
3182
|
+
repoRoot: sources.repoRoot,
|
|
3183
|
+
commands: behaviorVerifyCommands.commands,
|
|
3184
|
+
fallbackCommands: hasDeclaredFrontendVerification
|
|
3185
|
+
? []
|
|
3186
|
+
: behaviorFallbackCommands,
|
|
3187
|
+
});
|
|
3188
|
+
const effectiveBehaviorFallbackCommands = hasDeclaredFrontendVerification
|
|
3189
|
+
? []
|
|
3190
|
+
: behaviorFallbackCommands;
|
|
3248
3191
|
const staticVerifyEvidence = buildVerifyEvidence({
|
|
3249
|
-
phase: "
|
|
3250
|
-
|
|
3251
|
-
|
|
3192
|
+
phase: "intermediate",
|
|
3193
|
+
quota: strategy.intermediateQuota ?? "full",
|
|
3194
|
+
commandSource: partitionedStaticVerifyCommands.static.commandSource,
|
|
3195
|
+
commands: partitionedStaticVerifyCommands.static.commands,
|
|
3196
|
+
fallbackCommands: staticFallbackCommands,
|
|
3197
|
+
commandTexts: staticShellCommands,
|
|
3198
|
+
commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
3199
|
+
preflight: sources.verificationPreflight,
|
|
3252
3200
|
});
|
|
3253
|
-
const
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3201
|
+
const lintVerifyEvidence = lintShellCommands.length > 0
|
|
3202
|
+
? buildVerifyEvidence({
|
|
3203
|
+
phase: "intermediate",
|
|
3204
|
+
quota: strategy.intermediateQuota ?? "full",
|
|
3205
|
+
commandSource: partitionedStaticVerifyCommands.lint.commandSource,
|
|
3206
|
+
commands: partitionedStaticVerifyCommands.lint.commands,
|
|
3207
|
+
fallbackCommands: [],
|
|
3208
|
+
commandTexts: lintShellCommands,
|
|
3209
|
+
commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
3210
|
+
preflight: sources.verificationPreflight,
|
|
3211
|
+
})
|
|
3212
|
+
: undefined;
|
|
3257
3213
|
const behaviorVerifyEvidence = buildVerifyEvidence({
|
|
3258
|
-
phase: "final",
|
|
3259
|
-
|
|
3214
|
+
phase: "final",
|
|
3215
|
+
quota: "full",
|
|
3216
|
+
commandSource: behaviorVerifyCommands.commandSource,
|
|
3217
|
+
commands: behaviorVerifyCommands.commands,
|
|
3218
|
+
fallbackCommands: effectiveBehaviorFallbackCommands,
|
|
3219
|
+
commandTexts: behaviorShellCommands,
|
|
3220
|
+
finalFullRequired: true,
|
|
3260
3221
|
commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
3222
|
+
preflight: sources.verificationPreflight,
|
|
3261
3223
|
});
|
|
3224
|
+
const mockVerifyTemplate = mockMode === "required" && hasMockVerifyCommands
|
|
3225
|
+
? buildFrontendMockVerifyNode(frontendSources, implementId, readOnlyPaths, forbiddenPaths)
|
|
3226
|
+
: undefined;
|
|
3227
|
+
const mockShellCommands = mockVerifyTemplate?.shell?.commands ?? [];
|
|
3228
|
+
const mockVerifyEvidence = mockVerifyTemplate?.shell?.verifyEvidence;
|
|
3262
3229
|
// Contract v2: the frozen command directory is the only command reference
|
|
3263
3230
|
// the plan may use. Modes are assigned here once from the generation-time
|
|
3264
3231
|
// lane split; downstream materialization resolves the same directory from
|
|
@@ -3266,23 +3233,28 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3266
3233
|
const frontendVerifyDirectory = buildFrontendVerifyCommandDirectory({
|
|
3267
3234
|
staticLabels: staticVerifyEvidence.commandLabels,
|
|
3268
3235
|
behaviorLabels: behaviorVerifyEvidence.commandLabels,
|
|
3269
|
-
mockLabels: [],
|
|
3236
|
+
mockLabels: mockVerifyEvidence?.commandLabels ?? [],
|
|
3270
3237
|
staticCommandTexts: staticVerifyEvidence.commandTexts,
|
|
3271
3238
|
behaviorCommandTexts: behaviorVerifyEvidence.commandTexts,
|
|
3272
|
-
mockCommandTexts: [],
|
|
3239
|
+
mockCommandTexts: mockVerifyEvidence?.commandTexts ?? [],
|
|
3273
3240
|
});
|
|
3274
3241
|
const fixedVerificationContext = [
|
|
3275
3242
|
"## Fixed frontend verification entrypoints",
|
|
3276
|
-
"
|
|
3243
|
+
"These shell entrypoints are fixed at DAG generation and are the only commands the static and behavior shell nodes execute. A strategy or plan may add tests behind an existing entrypoint inside writeSet, but must not invent or replace commands or assume subtask_prompt executes a command.",
|
|
3277
3244
|
"Reference frozen commands ONLY by commandId (record_plan_verification_target entry.commandId). The runtime resolves mode and label; never invent a mode or type.",
|
|
3278
3245
|
...frontendVerifyDirectory.map((entry) => ` - ${entry.commandId} [${entry.mode}]: ${JSON.stringify(entry.label)}`),
|
|
3279
3246
|
`- Static command source: ${staticVerifyEvidence.commandSource}`,
|
|
3280
3247
|
`- Behavior command source: ${behaviorVerifyEvidence.commandSource}`,
|
|
3281
3248
|
].join("\n");
|
|
3282
3249
|
const advisories = [];
|
|
3250
|
+
if (!hasDeclaredFrontendVerification &&
|
|
3251
|
+
fallbackVerifyCommands.staticCommands.length === 0 &&
|
|
3252
|
+
fallbackVerifyCommands.behaviorCommands.length === 0) {
|
|
3253
|
+
advisories.push("未发现可用的前端验证命令:目标项目没有可读取的 package.json scripts,也未探测到本地 TypeScript,verify 节点将没有静态/行为命令可执行。请在 task.json --verify 或执行约束.md 的验证约束中显式声明命令(例如 node --check src/app.js),然后重新生成 DAG。");
|
|
3254
|
+
}
|
|
3283
3255
|
if (frontendSourceMentionsMock(frontendSources) &&
|
|
3284
3256
|
frontendMockStrategyMustBeNotNeeded(frontendSources)) {
|
|
3285
|
-
advisories.push("auto 模式已将 Mock 策略收窄为 not-needed:任务源提到接口/API/Mock 需求,但仓库无确认 Mock
|
|
3257
|
+
advisories.push("auto 模式已将 Mock 策略收窄为 not-needed:任务源提到接口/API/Mock 需求,但仓库无确认 Mock 能力或无确定性 Mock 验证命令。若项目规范要求 Mock,请声明 frontendMock.verifyCommands 或 policy:required 后重新生成 DAG。");
|
|
3286
3258
|
}
|
|
3287
3259
|
const openspecGate = await resolveFrontendOpenspecGateConfig(sources);
|
|
3288
3260
|
const requiresOpenspecClassification = openspecGate.openspecPolicy === "cited" &&
|
|
@@ -3388,13 +3360,13 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3388
3360
|
allowedPaths: readOnlyPaths,
|
|
3389
3361
|
forbiddenPaths,
|
|
3390
3362
|
skills: FRONTEND_CONTRACT_SKILLS,
|
|
3391
|
-
outputContract: "Incremental typed requirement facts; narrative is display-only. Submit through the incremental typed tools record_requirement / record_constraint / record_evidence_expectation / record_handoff_intent / record_open_question / record_split_proposal / record_ui_state / record_required_deliverables / record_openspec_selection, then complete each input scope with complete_contract_scope, then call finalize_contract; correct rejected calls until one successful terminal. record_requirement takes the canonical ledger requirement id and optional execution:{groupId,kind,summary} — the runtime owns the authoritative text, source spans, fragment bindings, and disposition. UI-visible or interactive requirements register a non-blocking frontend-test handoff intent, and any source-declared UI-state table is extracted verbatim through record_ui_state. End finalize_contract with a single contract disposition of ready | ready-with-assumptions | blocked. Cover scope, non-goals, acceptance criteria, UI states, target runtime environment, risks, and verification expectations; do not fix target files, components, or implementation methods as requirements. When OpenSpec candidates exist, classify only the ones you actually use: call record_openspec_selection once per required/relevant path; never enumerate irrelevant candidates (unmentioned defaults to irrelevant) and never emit a fenced selection JSON. No file writes.",
|
|
3363
|
+
outputContract: "Incremental typed requirement facts; narrative is display-only and should be omitted. Submit through the incremental typed tools record_requirement / record_constraint / record_evidence_expectation / record_handoff_intent / record_open_question / record_split_proposal / record_ui_state / record_required_deliverables / record_openspec_selection, then complete each input scope with complete_contract_scope, then call finalize_contract; correct rejected calls until one successful terminal. record_requirement takes the canonical ledger requirement id and optional execution:{groupId,kind,summary} — the runtime owns the authoritative text, source spans, fragment bindings, and disposition. UI-visible or interactive requirements register a non-blocking frontend-test handoff intent, and any source-declared UI-state table is extracted verbatim through record_ui_state. End finalize_contract with a single contract disposition of ready | ready-with-assumptions | blocked. Cover scope, non-goals, acceptance criteria, UI states, target runtime environment, risks, and verification expectations; do not fix target files, components, or implementation methods as requirements. When OpenSpec candidates exist, classify only the ones you actually use: call record_openspec_selection once per required/relevant path; never enumerate irrelevant candidates (unmentioned defaults to irrelevant) and never emit a fenced selection JSON. After a successful typed terminal, stop without a Markdown summary. No file writes.",
|
|
3392
3364
|
subtask_prompt: [
|
|
3393
3365
|
"OUTPUT BUDGET DISCIPLINE: provider capacity is discovered at runtime; use small records — NEVER attempt to emit the whole contract in one response; a single large JSON dump will be truncated and rejected. Incremental submission through the typed tools is the ONLY supported output mode. Start submitting with the FIRST tool call: after each read, call record_requirement for the requirements you have already confirmed, one or a few per call. Every tool-call round MUST make progress by submitting at least one record_* fact. Do not re-read the same source file that is already materialized in this session; read each file at most once.",
|
|
3394
3366
|
"Consume the complete injected input scope and produce a concise frontend implementation contract as typed requirement facts from complete injected scopes.",
|
|
3395
3367
|
"Confirm each requirement by the SAME id as the ledger canonical requirement it covers (sourceBinding.requirementIds, e.g. AC-001) — do NOT invent new REQ/BR prefixed ids for canonical requirements: the compiled contract must match the ledger canonical requirement ids exactly or schema validation rejects it (unknown requirement id). record_requirement takes the canonical id and optional execution:{groupId,kind,summary}; the runtime commits the authoritative text and sourceFragmentIds from the frozen ledger. Never pass text/statement/sourceFragmentIds yourself — model rewrites and JSON-stringified fragment arrays are rejected.",
|
|
3396
3368
|
"Requirement semantics, source spans, dispositions, and fragment bindings are ledger/runtime-owned. If a canonical requirement is genuinely blocked, say so in the Markdown contract narrative and finalize with the matching disposition instead of trying to encode it in the requirement fact.",
|
|
3397
|
-
"
|
|
3369
|
+
"Register evidence expectations for each requirement across static, behavior, Mock, and real integration as required | optional | not-applicable; required must follow from user requirements, task risk, or project governance, never from model convenience. For UI-visible or interactive requirements, register a non-blocking frontend-test handoff intent.",
|
|
3398
3370
|
'Use record_evidence_expectation with {requirementId,evidence:{static,behavior,mock,"real-integration"}}; every lane is required | optional | not-applicable. Requirement text and provenance remain runtime-owned.',
|
|
3399
3371
|
'Before finalize_contract ready, call record_required_deliverables once with the complete {items:[{path,requirementId,sourceFragmentId}]} inventory, or {items:[]} when no file delivery is mandatory. Interpret the original source, including lists and tables: allowedPaths/only-allowed-to-modify is permission, not an obligation; do not promote prohibited files, examples or references into deliverables. Paths must occur exactly in a frozen source fragment bound to that canonical requirement. Correct the whole inventory before finalizing if needed.',
|
|
3400
3372
|
"Authoritative UI states: when the task source declares a UI-state table (state id / trigger / observable outcome), extract it VERBATIM through record_ui_state, one call per state, using the source's own state ids. The planner must bind these ids later — do not rename, merge, or invent states.",
|
|
@@ -3454,10 +3426,10 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3454
3426
|
},
|
|
3455
3427
|
outputContract: "Typed decision patch only: map frozen requirements to implementation/verification targets and select the needed component, state, data/Mock, styling, and dependency decisions. Use only the record_* tools needed to express those decisions, then call finalize_plan; correct rejected facts until one successful terminal. Contract owns requirement semantics; Scout owns repository discovery; deterministic runtime owns schema, protected fields, path containment, and command validation. No Markdown narrative or file writes.",
|
|
3456
3428
|
subtask_prompt: [
|
|
3457
|
-
"
|
|
3429
|
+
"Plan only the delta between the frozen frontend-contract-pi facts and frontend-scout-pi target surface. Do not reinterpret the task, repeat requirements, search the repository, or choose implementation order.",
|
|
3458
3430
|
"Record only: requirement-to-file/verification coverage; component/styling choices; applicable UI state and interaction behavior; data/Mock strategy; and a dependency policy or genuine evidence gap. Reuse Scout paths. If scope is missing, record a blocking gap instead of inventing a path.",
|
|
3459
3431
|
"Use the typed tool schemas as the field contract. Runtime owns schemaVersion, sourceBinding, riskLevel, targets.files, mockApi.productionDefaultOff, aliases, command allowlisting, path containment, and final validation; do not restate those rules or emit a full JSON contract.",
|
|
3460
|
-
`Cover each frozen requirement ID exactly once: ${requirementIds.join(", ") || "(none)"}. Bind every verification target to a frozen commandId from the directory above plus a Scout-confirmed file.
|
|
3432
|
+
`Cover each frozen requirement ID exactly once: ${requirementIds.join(", ") || "(none)"}. Bind every verification target to a frozen commandId from the directory above plus a Scout-confirmed file. Behavior commands prove observable behavior: one target may cover multiple related requirementIds when one test behavior proves them together; do not mechanically create one target per requirement. A behavior target id is the stable identifier of that contract entry and its file must be a test file. Static commands are project-wide checks traced by file and command only.`,
|
|
3461
3433
|
"UX vocabulary protocol: record_state_registry FIRST with the full global vocabulary — one stable kebab-case behavior-domain name per UI state/interaction (e.g. planner-task-edit, focus-queue-move), never one name per AC number and never a rename of an already-recorded concept. Details consume complete execution-group scopes and reuse the same global names across scopes. Constraints/exclusions must not manufacture UI. Then record_state_flow entries whose names all come from that registry; uiState names must use the contract's declared authoritative ids (declaredUiStates in the plan input) when present. Retry attempts see committedUx in this input — reuse those exact names. Components: one choice may cover many state/interaction ids via covers; reuse-existing requires evidencePath naming an existing repo file (greenfield must be decision=new).",
|
|
3462
3434
|
...(requiresOpenspecClassification ? ["When a component choice uses an OpenSpec selection, cite that selection; otherwise do not classify unrelated candidates."] : []),
|
|
3463
3435
|
"Call finalize_plan; correct rejected facts until one successful terminal after the necessary typed facts. Return no Markdown narrative.",
|
|
@@ -3502,11 +3474,20 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3502
3474
|
"request-adapter",
|
|
3503
3475
|
"not-needed",
|
|
3504
3476
|
],
|
|
3505
|
-
mockCommandLabels: [],
|
|
3477
|
+
mockCommandLabels: mockVerifyEvidence?.commandLabels ?? [],
|
|
3506
3478
|
artifactName: "frontend-implementation-contract.json",
|
|
3507
3479
|
outputDir: "contracts",
|
|
3508
3480
|
requireSourceFreshness: true,
|
|
3509
3481
|
implementationWriteSet: implementPaths.writeSet,
|
|
3482
|
+
...(taskConfig.requirementOwnership?.length
|
|
3483
|
+
? { requirementOwnership: taskConfig.requirementOwnership }
|
|
3484
|
+
: {}),
|
|
3485
|
+
...(taskConfig.capabilityBoundary
|
|
3486
|
+
? { capabilityBoundary: taskConfig.capabilityBoundary }
|
|
3487
|
+
: {}),
|
|
3488
|
+
...(taskConfig.interactionIds?.length
|
|
3489
|
+
? { interactionIds: taskConfig.interactionIds }
|
|
3490
|
+
: {}),
|
|
3510
3491
|
openspecPolicy: openspecGate.openspecPolicy,
|
|
3511
3492
|
openspecSpecRoots: taskConfig.frontendOpenspec?.specRoots ?? [
|
|
3512
3493
|
...DEFAULT_FRONTEND_SPEC_ROOTS,
|
|
@@ -3546,15 +3527,16 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3546
3527
|
skills: FRONTEND_DESIGN_REVIEW_SKILLS,
|
|
3547
3528
|
outputContract: "Authoritative typed design terminal via approve_design / request_design_changes tools. No JSON verdict; the committed typed design fact is the only authority. No file writes.",
|
|
3548
3529
|
subtask_prompt: [
|
|
3549
|
-
"Submit findings individually with record_design_finding and stable IDs; terminal tools aggregate saved findings. Correct rejected calls, stop after a successful terminal. Check execution groups against every member source outcome, including permission, threshold and failure-path differences;
|
|
3530
|
+
"Submit findings individually with record_design_finding and stable IDs; terminal tools aggregate saved findings. Correct rejected calls, stop after a successful terminal. Check execution groups against every member source outcome, including permission, threshold and failure-path differences; shared verification is valid only when it proves each independent AC.",
|
|
3531
|
+
"After the typed design terminal succeeds, stop immediately; do not emit a Markdown recap.",
|
|
3550
3532
|
"Audit the frontend plan before implementation. frontend-plan-pi is emitted to you as canonical full-contract JSON after the runtime applied and validated the planner's editable patch against its protected skeleton; there is no separate plan prose.",
|
|
3551
3533
|
"Your authoritative terminal verdict is exactly one committed typed tool call: approve_design or request_design_changes. Call exactly one of them; after calling one, do not call the other.",
|
|
3552
3534
|
"request_design_changes must carry a typed issueCategory, at least one evidenceRef, and non-empty findings.",
|
|
3553
3535
|
"Your verdict is consumed as deterministic data input by frontend-writer-admission-shell. approve_design permits admission; request_design_changes blocks writer admission until a recovery plan incorporates every Critical/Important finding.",
|
|
3554
|
-
"Request design changes
|
|
3555
|
-
"
|
|
3556
|
-
"Component selection conformance is a hard blocking condition: request_design_changes when the frontend spec (component/theme/rule.components bucket) already defines a component for a purpose but the plan selects another or self-invents one without a declared deviation; when uiComponentChoices is missing/empty for UI-visible work while the frozen component/theme bucket is non-empty; when a decision=specified specReference.path is missing a ledger OpenSpec reference or successful read event; or when a decision=new component lacks a traceable task-source/PRD specReference. A PRD reference for decision=new is not an OpenSpec citation and must not be rejected merely for lacking an OpenSpec read event. For uiComponentChoices,
|
|
3557
|
-
"
|
|
3536
|
+
"Request design changes when the Mock strategy is blocked, missing, unsupported by repository evidence, inconsistent with the API contract, outside authorized paths/dependencies, unable to prove production-default-off behavior with the fixed production/default-real-path static check, or missing deterministic behavior verification for a declared behavior target or selected Mock strategy. Mock strategies require Mock-backed evidence; a static-only contract is allowed only when every verification target is static and maps to a declared static entrypoint; not-needed requires applicable real/no-remote behavior evidence unless auto mode explicitly skipped Mock because no project Mock capability exists, in which case the plan must preserve the real request path and record the Real Integration Gap.",
|
|
3537
|
+
"Also request design changes for missing applicable UI states, unsupported dependency additions, design-system drift without reason, weak interaction coverage, broad scope, inline fake data, schema drift, or missing deterministic verification commands.",
|
|
3538
|
+
"Component selection conformance is a hard blocking condition: request_design_changes when the frontend spec (component/theme/rule.components bucket) already defines a component for a purpose but the plan selects another or self-invents one without a declared deviation; when uiComponentChoices is missing/empty for UI-visible work while the frozen component/theme bucket is non-empty; when a decision=specified specReference.path is missing a ledger OpenSpec reference or successful read event; or when a decision=new component lacks a traceable task-source/PRD specReference. A PRD reference for decision=new is not an OpenSpec citation and must not be rejected merely for lacking an OpenSpec read event. For uiComponentChoices, coverage is judged by the covers list: every applicable UI state and interaction must appear in some choice's purpose or covers, and the runtime already pre-checks this deterministically at plan finalize — do not re-litigate it. purpose is a human-readable description of what the choice is for and must NOT be rejected merely for not matching an interaction or uiState name; judge responsibility semantics from the covered ids' expectedBehavior plus the choice rationale.",
|
|
3539
|
+
"You must NOT make authoritative assertions about the execution result of frozen verification commands: command results are deterministically established by frontend-verify-shell. Record a verification-feasibility concern only as a non-blocking finding (severity must not be Critical, and it must never be the sole fatal basis for request_design_changes). Only semantic design defects (component selection, state flow, interaction contract, or conflicts with the specification) may be Critical; a pure command-will-fail prediction must not be classified as contract-requirement-gap.",
|
|
3558
3540
|
"Read-only: do not modify repository files.",
|
|
3559
3541
|
"LARGE-FILE AUDIT (avoid full reads): style/theme audit files can be large (e.g. styles.css is often hundreds of KB). Prefer grep to locate the exact rules/variables you must verify (e.g. grep the oc- class, is-* modifier, or --oc- theme variables with their line numbers), then read only the narrow line range when surrounding context is needed. Do not read a large style/test file in full — a single full read can exhaust the read budget and fail the attempt.",
|
|
3560
3542
|
"Canonical contract reading: frontend-design-policy-shell prints absolute paths for Contract, Contract index, and the non-blocking Capacity diagnostic. Read the capacity diagnostic first. When it recommends full-contract, read the exact Contract path. When it recommends indexed-sections, read the Contract index and its hash-bound section files instead of opening the full contract. Never resolve a bare contracts/... path against the repository root or hunt for substitutes. Implementation target files inside the writeSet are created later by the implement node: do not read them and do not treat their absence as a design defect.",
|
|
@@ -3580,7 +3562,11 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3580
3562
|
frontendWriterAdmission: {
|
|
3581
3563
|
schemaVersion: 1,
|
|
3582
3564
|
designReviewFromNodeId: "frontend-design-review-pi",
|
|
3583
|
-
frozenCommandLabels:
|
|
3565
|
+
frozenCommandLabels: [
|
|
3566
|
+
...staticVerifyEvidence.commandLabels,
|
|
3567
|
+
...behaviorVerifyEvidence.commandLabels,
|
|
3568
|
+
...(mockVerifyEvidence?.commandLabels ?? []),
|
|
3569
|
+
],
|
|
3584
3570
|
allowedMockStrategies: taskConfig.frontendMock?.policy === "disabled" ||
|
|
3585
3571
|
frontendMockStrategyMustBeNotNeeded(frontendSources)
|
|
3586
3572
|
? ["not-needed"]
|
|
@@ -3596,6 +3582,12 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3596
3582
|
"request-adapter",
|
|
3597
3583
|
"not-needed",
|
|
3598
3584
|
],
|
|
3585
|
+
...(lintShellCommands.length > 0 && lintVerifyEvidence
|
|
3586
|
+
? {
|
|
3587
|
+
lintCommands: lintShellCommands,
|
|
3588
|
+
lintEvidence: lintVerifyEvidence,
|
|
3589
|
+
}
|
|
3590
|
+
: {}),
|
|
3599
3591
|
},
|
|
3600
3592
|
cwd: ".",
|
|
3601
3593
|
timeoutMs: 60000,
|
|
@@ -3611,16 +3603,18 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3611
3603
|
forbiddenPaths,
|
|
3612
3604
|
writerOutcomePolicyType: "frontend-facts-v1",
|
|
3613
3605
|
}),
|
|
3614
|
-
outputContract: "The
|
|
3606
|
+
outputContract: "The implementation status is derived by the executor from mechanical facts (write-tool events, run delta, write guard, requirement coverage, focused-check), not from any IMPLEMENTATION_OUTCOME first line. Deliver a Markdown 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.",
|
|
3615
3607
|
subtask_prompt: [
|
|
3616
3608
|
"Implement against the validated run-owned Frontend Implementation Contract materialized by frontend-design-policy-shell (path/schema/hash) and authorized by frontend-writer-admission-shell. Do not rebuild the contract from Markdown alone.",
|
|
3617
3609
|
"WRITER TOOL PROTOCOL (hard): the response text is not delivery. Never paste source code, test code, or full file contents into chat. After reading the canonical contract, make the first implementation change with the structured write/edit tool (one file per call); continue writing through those tools until the writeSet is complete. If a write/edit tool is unavailable, stop and report the blocked capability instead of drafting code in the response.",
|
|
3618
3610
|
"The canonical contract already contains the approved requirement, target-file, UI-state, verification, design, and Mock/API decisions. Do not re-open task sources, OpenSpec, AI workspace, plan/revision, or design-review prose, and do not repeat broad repository research. Inspect only contract target files and directly related local code needed to implement them.",
|
|
3619
|
-
"
|
|
3620
|
-
"Map every requirement id, expectedOutcome, interaction and applicable UI state to concrete
|
|
3611
|
+
"Execute in fixed stages and report each in the delivery summary: (1) Contract confirm, (2) Tests sync, (3) Component/UI state implementation, (4) API/Mock wiring per contract.mockApi, (5) Focused checks behind frozen entrypoints only, (6) Diff cleanup.",
|
|
3612
|
+
"Map every requirement id, expectedOutcome, interaction trigger/expectedBehavior, and applicable UI state from the contract to concrete files. Do not invent shell verification commands; only frozen static/behavior entrypoints will run.",
|
|
3613
|
+
"A behavior verification target's target.id is only the contract's identifier for that entry; it does not need to appear in test titles. Never add tests, rename describe/it/test titles, or restructure files just to carry generated ids — reuse affected existing test files and their names. The requirement ↔ verification-target association lives in the contract (requirementIds / verificationTargetIds), not in title strings.",
|
|
3614
|
+
"Tests must genuinely prove the behavior each target maps to; a passing test-file execution does not by itself prove every mapped behavior is covered — keep assertions aligned with the contract's expectedBehavior, and state any residual gap honestly in Tests Changed.",
|
|
3621
3615
|
"Begin implementation after the contract and its target files are confirmed. Do not spend the turn collecting optional context. If the canonical contract lacks behavior needed to edit safely, stop and state the blocking reason in the summary instead of reopening broad discovery.",
|
|
3622
3616
|
"Your implementation status is derived by the executor from mechanical facts (persisted write-tool events, run delta, write guard, requirement coverage, focused-check failures), never from any IMPLEMENTATION_OUTCOME first line. Do not emit an IMPLEMENTATION_OUTCOME first line.",
|
|
3623
|
-
"The node
|
|
3617
|
+
"The node runs a bounded micro-loop: after each write attempt the executor re-runs frozen focused checks and records a per-round diff checkpoint; the write guard stays active every round. Only repair local issues attributable to the current diff (syntax/type/import/format/unit-assert/obvious omission). Never change requirements, design, writeSet, or verification strictness inside the loop.",
|
|
3624
3618
|
"Implement only the approved Mock strategy carried by the validated contract. Preserve the real request path as the default, require explicit test/dev activation, and never comment out or replace the real request with inline data.",
|
|
3625
3619
|
"frontend-design-policy-shell materialized and validated the canonical contract; frontend-writer-admission-shell authorized the writeSet. Stay within writeSet and preserve unrelated files.",
|
|
3626
3620
|
"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.",
|
|
@@ -3628,7 +3622,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3628
3622
|
"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.",
|
|
3629
3623
|
...(implementPaths.docIndexCompanions.length > 0
|
|
3630
3624
|
? [
|
|
3631
|
-
`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).
|
|
3625
|
+
`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.`,
|
|
3632
3626
|
]
|
|
3633
3627
|
: []),
|
|
3634
3628
|
writerDeliveryContract(taskConfig),
|
|
@@ -3645,7 +3639,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3645
3639
|
writePolicy: "read-only",
|
|
3646
3640
|
allowedPaths: readOnlyPaths,
|
|
3647
3641
|
forbiddenPaths,
|
|
3648
|
-
outputContract: "Run
|
|
3642
|
+
outputContract: "Run frozen Mock/static/behavior commands and materialize the verification trace. Any failure is terminal: no same-run repair branch, failure ownership facts are materialized for recovery.",
|
|
3649
3643
|
subtask_prompt: "Execute the frontend verification bundle. Preserve per-command evidence; a failure fails this node (terminal) and routes to recovery.",
|
|
3650
3644
|
shell: {
|
|
3651
3645
|
commands: [],
|
|
@@ -3655,6 +3649,8 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3655
3649
|
lintCommands: lintShellCommands,
|
|
3656
3650
|
staticCommands: staticShellCommands,
|
|
3657
3651
|
behaviorCommands: behaviorShellCommands,
|
|
3652
|
+
mockEvidence: mockVerifyEvidence,
|
|
3653
|
+
lintEvidence: lintVerifyEvidence,
|
|
3658
3654
|
staticEvidence: staticVerifyEvidence,
|
|
3659
3655
|
behaviorEvidence: behaviorVerifyEvidence,
|
|
3660
3656
|
lintBaselineNodeId: lintShellCommands.length > 0
|
|
@@ -3698,20 +3694,21 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3698
3694
|
skills: FRONTEND_REVIEW_SKILLS,
|
|
3699
3695
|
outputContract: 'Authoritative typed review terminal via approve_review / request_review_changes tools. No JSON verdict is required in the response text; the typed terminal fact is the only authority. No file writes.',
|
|
3700
3696
|
subtask_prompt: [
|
|
3701
|
-
"Submit findings individually with record_review_finding and stable IDs; terminal tools aggregate saved findings. Correct rejected calls, stop after a successful terminal. Check execution groups against every member source outcome, including permission, threshold and failure-path differences;
|
|
3702
|
-
"
|
|
3697
|
+
"Submit findings individually with record_review_finding and stable IDs; terminal tools aggregate saved findings. Correct rejected calls, stop after a successful terminal. Check execution groups against every member source outcome, including permission, threshold and failure-path differences; shared verification is valid only when it proves each independent AC.",
|
|
3698
|
+
"After the typed review terminal succeeds, stop immediately; do not emit a Markdown recap.",
|
|
3699
|
+
"Review the frontend implementation and verification evidence.",
|
|
3703
3700
|
"Your authoritative terminal verdict is exactly one committed typed tool call: approve_review or request_review_changes. Call it once and do not call the other afterwards.",
|
|
3704
3701
|
"approve_review means the implementation passes; it must not carry Critical or Important findings. request_review_changes must carry a typed issueCategory, at least one evidenceRef, and non-empty findings.",
|
|
3705
3702
|
"Do NOT emit an equivalent JSON verdict in the response text: the committed typed terminal fact is the only authority and no branch or gate reads response-text JSON verdicts.",
|
|
3706
|
-
"Read contracts/frontend-review-context.json from frontend-review-context-shell. It binds a hash-verified canonical contract reference, a field-to-section index, frontend lint assessment when configured, the effective verification trace, and the run-owned actual diff. Read contractRef.capacityDiagnosticPath first: use contractRef.path only when full-contract is recommended; otherwise read only the hash-bound contractRef.sections needed for the changed surface and verification claims. Then read diff.reviewSummaryPath. The full artifacts/diff_patch.patch is retained only as audit evidence: do NOT read it in full. For semantic review, read only the named per-file diff fragment in the summary/index (in part order when needed) and then the current source file when necessary. Do not claim actual diff is missing when those artifacts exist; do not invent a diff from the implementation summary alone. Trace proves command/file/stable-target-id binding only—not semantic correctness.",
|
|
3707
|
-
"
|
|
3703
|
+
"Read contracts/frontend-review-context.json from frontend-review-context-shell. It binds a hash-verified canonical contract reference, a field-to-section index, frontend lint assessment when configured, the per-command verification evidence (verificationEvidence.commands: commandId/label/lane/exitCode/ok plus allPassed, and lintStatus with lintConfigured) , the effective verification trace, and the run-owned actual diff. Read contractRef.capacityDiagnosticPath first: use contractRef.path only when full-contract is recommended; otherwise read only the hash-bound contractRef.sections needed for the changed surface and verification claims. Then read diff.reviewSummaryPath. The full artifacts/diff_patch.patch is retained only as audit evidence: do NOT read it in full. For semantic review, read only the named per-file diff fragment in the summary/index (in part order when needed) and then the current source file when necessary. Do not claim actual diff is missing when those artifacts exist; do not invent a diff from the implementation summary alone. Trace proves command/file/stable-target-id binding only—not semantic correctness.",
|
|
3704
|
+
"Treat lint status exactly as passed | baseline-debt | failed | unavailable, and read lintConfigured: when it is false the task declares no lint commands, so lintStatus unavailable means it is not part of this task — report no lint finding either way. baseline-debt may continue only with intact evidence and zero diagnostics on writer-changed files; report the tolerated debt count and never rewrite it as lint passed. Typecheck, build, and test still require successful final exits, judged from verificationEvidence.commands exit codes rather than from the binding trace.",
|
|
3708
3705
|
"Flag .skip/.only, deleted or weakened tests, unauthorized config changes, Mock-only evidence claimed as real integration, and Browser/visual claims (always not-run in this workflow).",
|
|
3709
3706
|
"The contract referenced and hash-bound by frontend-review-context.json is the effective plan materialized by frontend-design-policy-shell. Do not re-open task sources, OpenSpec, AI workspace, design-review, writer summary, or verification node prose. Inspect only the canonical review context, its indexed contract sections, its bound diff, and diff-referenced files when semantic review requires source code.",
|
|
3710
|
-
"For uiComponentChoices,
|
|
3711
|
-
"
|
|
3707
|
+
"For uiComponentChoices, coverage is judged by the covers list and is deterministically pre-checked at plan finalize; purpose is a human-readable description and must not be rejected merely for not matching an interaction or uiState name. Responsibility is expressed by the covered ids' expectedBehavior plus the choice rationale.",
|
|
3708
|
+
"Treat a commented-out real request, default-enabled Mock, production entrypoint importing test mocks, API/fixture contract drift, unauthorized Mock dependency/path, or missing behavior evidence for the selected strategy as at least Important. Mock strategies require Mock-backed evidence. not-needed requires applicable real/no-remote behavior evidence unless auto mode explicitly skipped Mock because no project Mock capability exists; in that case verify that the real request remains the default and the Real Integration Gap is preserved.",
|
|
3712
3709
|
"Inspect the frontend-verify-shell evidence in the review context directly, including the production/default-real-path static check, and require Mock activation to be off for that check.",
|
|
3713
3710
|
"Distinguish Mock-backed evidence from real API integration evidence and preserve the Real Integration Gap when the backend was not exercised.",
|
|
3714
|
-
"Review
|
|
3711
|
+
"Review implementation quality, behavior/state coverage, verification evidence, and maintainability. Read-only: do not modify files.",
|
|
3715
3712
|
].join("\n\n"),
|
|
3716
3713
|
},
|
|
3717
3714
|
{
|
|
@@ -4469,11 +4466,11 @@ const section=allLines.slice(start,end).join('\\n');
|
|
|
4469
4466
|
const raw=[];
|
|
4470
4467
|
const lines=section.split('\\n').filter(l=>l.includes('|'));
|
|
4471
4468
|
const cells=line=>line.split('|').slice(1,-1).map(value=>stripBackticks(value).trim());
|
|
4472
|
-
const expectedHeader
|
|
4469
|
+
const expectedHeader=${JSON.stringify(BACKEND_TEST_MODULE_INDEX_HEADER)};
|
|
4473
4470
|
const headerIndex=lines.findIndex(line=>{const row=cells(line);return expectedHeader.every((value,index)=>row[index]===value);});
|
|
4474
4471
|
if(headerIndex<0){process.stderr.write('invalid-module-index-header: require exact business ownership and path columns\\n');process.exit(2);}
|
|
4475
4472
|
const dataRows=lines.slice(headerIndex+2).map(cells).filter(row=>row.length>=8&&row[0]&&row[0]!=='Module Stem');
|
|
4476
|
-
const allowedSplit=new Set(
|
|
4473
|
+
const allowedSplit=new Set(${JSON.stringify(BACKEND_TEST_MODULE_SPLIT_REASONS)});
|
|
4477
4474
|
const operationOwners=new Map();const canonicalSeen=new Set();const declaredModules=[];let planRepairApplied=headingRepair||partitionRepair;
|
|
4478
4475
|
for(const row of dataRows){
|
|
4479
4476
|
const rawStem=String(row[0]||'').trim(),resource=String(row[1]||'').trim(),operations=String(row[2]||'').split(';').map(value=>value.trim()).filter(Boolean),split=String(row[5]||'').trim();
|
|
@@ -5161,7 +5158,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
5161
5158
|
`STRICT_BACKEND_TEST_MODULE_LAYOUT=${JSON.stringify(taskConfig.backendTest.moduleLayout)}`,
|
|
5162
5159
|
]
|
|
5163
5160
|
: []),
|
|
5164
|
-
|
|
5161
|
+
`Include exactly one \`## Module Index\` table with this exact header: \`${backendTestModuleIndexHeaderMarkdown()}\`. The Markdown Path cell must contain exactly one resolved repository-relative path such as \`${canonicalBackendTestModuleMarkdownPath(layout.markdownDir, "health")}\`; do not emit a Markdown link or repeat the path. Split Reason is exactly one of \`${BACKEND_TEST_MODULE_SPLIT_REASONS.join("\`, \`")}\`. Group by stable business resource/domain, not by CRUD operation, AC, parameter/field axis, scenario type or regression purpose: one resource's list/detail/create/update/delete and its filters/response assertions/regression floor belong in one module. Multiple modules owning the same exact \`METHOD /path\` are forbidden unless every such row is \`explicit-user-layout\` from primary-requirement path pairs or has a documented \`output-budget\` proof. Keep the total module count at the smallest safe value and never exceed 8 modules. Name model-derived modules with stable lowercase business stems such as \`health\` or \`resource_notes\`; explicit-user-layout preserves the primary requirement filename stem even when it is more specific. Do not use priority-only stems \`p0\`, \`p1\` or \`p2\`; Priority belongs only in the Coverage Matrix. Pure hexadecimal/hash-like opaque stems and test-purpose-only stems are forbidden. Do not use Case-ID-like module filenames. Markdown Path, Pytest Path and downstream automation mapping must be one-to-one and exact; for model-derived modules the default pair remains \`${layout.markdownDir}/<module>.md\` and \`${layout.scriptDir}/test_<module>.py\`, while explicit-user-layout preserves the primary requirement paths. Do not hand-write a conflicting module count in prose; the Module Index row count is the only count truth.`,
|
|
5165
5162
|
"Scenario Partitions (query/filter axes): inspect every affected GET/list operation for query/path parameters whose bound source documents a finite enum or classification domain. If at least one such axis exists, add exactly one machine-readable `## Scenario Partitions` section after the Coverage Matrix using exactly `| Partition ID | Operation | Axis | Domain | Required Slots | Expected by Slot | Bind Rule |` with the separator row and one row per eligible axis. If no affected axis has a source-backed finite domain, omit the entire `## Scenario Partitions` heading and section; do not emit an explanatory prose-only section. Partition ID is a stable `SP-<OPERATION>-<AXIS>` token; Domain must copy the legal values verbatim from the bound OpenAPI enum or requirement sentence (never guess), using bare semicolon-separated identifier values inside the single table cell (for example `ACTIVE; ARCHIVED`, with no Markdown backticks or prose); Required Slots must contain `each-value` and exactly one `not-in-set`, plus `omitted` only when the parameter is optional. Before returning, expand every declared partition into its complete deterministic exact slot ID set: one `TP-<Partition ID>-<VALUE-TOKEN>` per Domain value, `TP-<Partition ID>-OMITTED` only for an optional axis, and exactly one `TP-<Partition ID>-NOT-IN-SET`. Every expanded slot ID must appear verbatim in the binding Rule's `Required Test Points` cell and be assigned to concrete Case IDs in that same Coverage Matrix row; ordinary alias/family Test Points do not replace this inventory. Scheme A: Case count may be smaller than the enum count, but every exact slot still needs an independent variant Test Point and pytest.param id; never use SINGLE/MULTIPLE aliases as coverage. Expected by Slot states the documented expectation per slot kind (`domain-value`, `default-behavior`, `empty-result`/`excluded-result` when documented, or `GAP` when the source does not document the complement expectation — never invent 空列表/400). POST/PUT body field-validation enums stay in the Coverage Matrix as `TP-<FIELD>-ENUM-*` and MUST NOT get a Scenario Partition row. Do not create partitions for axes without a documented legal-value domain. Only GET/list query or path parameters whose bound source documents a finite enum or classification set may become a Scenario Partition. Do not create partitions for free-form strings, primary keys, required-or-optional-only parameters, or boundary/format-only axes. If an axis has no finite legal-value domain, do not declare a Partition row and do not invent NOT-IN-SET cases. Cross-axis combinations stay as ONE nominal Case; never declare a cross-axis cartesian partition.",
|
|
5166
5163
|
"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.",
|
|
5167
5164
|
...(sharedSetupPrompt ? [sharedSetupPrompt] : []),
|
|
@@ -5401,7 +5398,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
5401
5398
|
"Output budget protocol (hard, max output <=16K per turn): Write exactly the frozen `{{item.pytestPath}}`. Never paste full Python modules into assistant chat. Do not merge or split modules. Do not reduce params/assertions/skips to fit. If OUTPUT_LIMIT_RECOVERY is injected, continue only listed missing/broken scripts.",
|
|
5402
5399
|
"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.",
|
|
5403
5400
|
"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.",
|
|
5404
|
-
'Ensure every automatable final Markdown Case ID in this module appears in exactly one primary pytest test function or pytest test class method region, using the exact `primary symbol` declared by Markdown. Skip evidence-only meta Cases that declare `脚本/primary symbol=无` with empty variants; do not invent a business pytest symbol for them. The symbol must start with `test_BE_<MODULE>_<NNN>_` so every parameterized collected item remains associated with its Case. Module-level functions and class-based pytest methods are both supported. Only `变体测试点` may use stable `pytest.param(..., id="TP-...")` IDs, and every atomic variant ID must appear exactly once with a genuine input/state/outcome change. A Case with exactly one variant Test Point still needs one literal `pytest.param(..., id="TP-...")` row; never leave a single-variant Case as a bare function with the TP only in the docstring. Use a literal direct `pytest.param(..., id=...)` expression for every row; never hide or wrap it behind `_post_case`, `_put_case`, row-factory functions, comprehensions, generators, or dynamically returned parameter lists; do not use decorator-level `ids=[...]`, generated suffixes, or IDs that extend/shorten the exact Markdown TP. Do not parameterize `场景断言测试点` or `横切证据测试点`; execute all assertion checkpoints within the same business journey/item and use shared helpers for cross-cutting evidence. The primary symbol docstring
|
|
5401
|
+
'Ensure every automatable final Markdown Case ID in this module appears in exactly one primary pytest test function or pytest test class method region, using the exact `primary symbol` declared by Markdown. Skip evidence-only meta Cases that declare `脚本/primary symbol=无` with empty variants; do not invent a business pytest symbol for them. The symbol must start with `test_BE_<MODULE>_<NNN>_` so every parameterized collected item remains associated with its Case. Module-level functions and class-based pytest methods are both supported. Only `变体测试点` may use stable `pytest.param(..., id="TP-...")` IDs, and every atomic variant ID must appear exactly once with a genuine input/state/outcome change. A Case with exactly one variant Test Point still needs one literal `pytest.param(..., id="TP-...")` row; never leave a single-variant Case as a bare function with the TP only in the docstring. Use a literal direct `pytest.param(..., id=...)` expression for every row; never hide or wrap it behind `_post_case`, `_put_case`, row-factory functions, comprehensions, generators, or dynamically returned parameter lists; do not use decorator-level `ids=[...]`, generated suffixes, or IDs that extend/shorten the exact Markdown TP. Do not parameterize `场景断言测试点` or `横切证据测试点`; execute all assertion checkpoints within the same business journey/item and use shared helpers for cross-cutting evidence. Governance-only cross-cutting bindings such as writeSet compliance, execution count, report existence, or orchestration state are metadata-only in business pytest: preserve their IDs in `Cross-Cutting-Test-Points`, but never assert `__file__`, filesystem placement, pytest invocation count, Harness state, or report artifacts inside the business test. Harness-owned evidence verifies those bindings. The first statement inside every primary symbol must be a triple-quoted docstring containing exact lines `Case-ID: BE-...`, `Assertion-Test-Points: TP-...;TP-...` and `Cross-Cutting-Test-Points: TP-...;TP-...` (use `none` when empty), for example `def test_BE_X_001():\n """\n Case-ID: BE-X-001\n Assertion-Test-Points: TP-X-ASSERT\n Cross-Cutting-Test-Points: none\n """`. Module/class docstrings, comments before `def`, and singular `Assertion-Test-Point:` comments never bind a Test Point. Implement request dictionaries so their direct and nested key paths and enum literals exactly satisfy the Case `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum`; for `Payload Contract: none`, do not invent a JSON/body DTO. GET/list filters still declare query fields in those payload labels when the Case varies `params=`/`query=` keys. Python `True`/`False` may implement JSON/OpenAPI `true`/`false` query or body booleans. GET/DELETE setup journeys may create resources, but their setup DTO must not change the target operation\'s no-body payload contract. No Test Point may be invented, renamed, omitted or bound in two modes. The generated pytest collection shape must equal the Markdown prediction `sum(max(1, variant count per Case))`; keep it at or below the task\'s explicit budget by removing duplicate execution, never by collapsing multiple parameter rows under a coarse family TP. Assertions come only from 预期结果 and setup comes only from 前置条件/测试数据/自动化映射.',
|
|
5405
5402
|
"Name the generated pytest file so it corresponds one-to-one with its source Markdown module file: this module stem `{{item.stem}}` maps to exactly the frozen `{{item.pytestPath}}`. The <module> stem is the Markdown filename without the `.md` extension, lowercased and with non-alphanumeric characters replaced by underscores. For example, `resource_notes` → `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 frozen manifest pytest path and do not invent prefixes. Never merge multiple Markdown modules into one pytest file, never split one module across several files, and never invent pytest filenames unrelated to the Markdown modules.",
|
|
5406
5403
|
"Scenario Partition slots: every `TP-<Partition ID>-...` variant Test Point declared by this module's Markdown MUST become exactly one literal direct `pytest.param(..., id=\"TP-<Partition ID>-...\")` row with the exact slot ID; the not-in-set slot passes a concrete literal absent from the documented Domain (e.g. `UNKNOWN_TYPE`) — never `_OMIT`, never a descriptive token. Never split one slot into multiple params or merge several slots under a family TP id. Slot filtering requests hit the documented list endpoint with the slot value as the query/path filter.",
|
|
5407
5404
|
"Keep this module self-contained: define module-local fixtures and helpers directly in `{{item.pytestPath}}`, so pytest discovers every fixture dependency without external plugin registration. The request log must include method, URL/path, and request parameters (query plus JSON/body/payload summary). The response log must include status code and response result (JSON/text/body summary), and both records must be visible in pytest stdout/stderr without changing assertions. Recursively redact sensitive values and apply bounded truncation before logging.",
|
|
@@ -5459,7 +5456,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
5459
5456
|
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.",
|
|
5460
5457
|
subtask_prompt: [
|
|
5461
5458
|
"Repair the generated backend pytest asset as one bounded program using the direct upstream collection assessment. This is the only repair attempt and happens before any business test body execution. The direct upstream JSON includes authoritative `repairPaths` and bounded `repairFindings`; treat both as the complete mandatory checklist without searching for a run directory or report file. Treat any upstream line such as `Repair paths: testcase/test_x.py` as equivalent 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.",
|
|
5462
|
-
"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. Use this deterministic repair map instead of reading analyzer implementation: findings about `Case-ID`, `Assertion-Test-Points`, or `Cross-Cutting-Test-Points` are fixed by
|
|
5459
|
+
"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. Use this deterministic repair map instead of reading analyzer implementation: findings about `Case-ID`, `Assertion-Test-Points`, or `Cross-Cutting-Test-Points` are fixed by making the first statement in the declared primary symbol a triple-quoted docstring with the exact plural metadata lines; these are the primary symbol docstring metadata lines. Module/class docstrings, comments before `def`, and singular `Assertion-Test-Point:` comments are invalid. Variant binding findings are fixed in the literal direct `pytest.param(..., id=\"TP-...\")` row; primary-symbol cardinality/name findings are fixed in the function name or duplicate primary symbols; script mismatch is fixed only on the authoritative assessment repairPaths; payload findings are fixed in request payload construction. Do not read controller `src/**` or inspect JS/TS analyzer code. Do not search for `testcase/**/README.md`. Never invent a business pytest symbol for evidence-only Markdown Cases that declare `脚本/primary symbol=无` with empty variants. For fixture defects inspect both provider and importer listed by repairPaths; fix ScopeMismatch by aligning fixture scopes or inlining request-scoped values so module fixtures never depend on function fixtures; when a shared fixture depends on sibling fixtures, register the whole provider module through an exact pytest_plugins declaration rather than importing only the outer fixture. Do not create unrelated pytest scripts.",
|
|
5463
5460
|
"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.",
|
|
5464
5461
|
"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.",
|
|
5465
5462
|
"Use local edit only on assessment-listed paths; keep summaries short; never rewrite unrelated modules.",
|