@tea-agent/loop-agent 0.42.0-next.7 → 0.42.0-next.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/application/dag/run-dag.js +40 -0
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +313 -56
- package/dist/executors/shell-executor.js +50 -20
- package/dist/worker/console/chat/pi-runtime.js +20 -4
- package/dist/worker/console/chat/routes.js +44 -22
- package/dist/worker/console/chat/session-store.js +16 -0
- package/dist/worker/console/server.js +19 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BQBHX31n.js → abnfDiagram-N423BO3Z-C6n9_m0P.js} +1 -1
- package/dist/worker/console/static/assets/{arc-MSJ199eR.js → arc-DQh-IfZ1.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DiRq2qMX.js → architectureDiagram-T3A2C74G-54NnrwUC.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-rlaxFOvi.js → blockDiagram-VBNYF7ZC-pivRALGK.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BSpi9ump.js → c4Diagram-5PPSVZJV-BR7OV2NJ.js} +1 -1
- package/dist/worker/console/static/assets/channel-DsZxrgqe.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BN8VNeh5.js → chunk-2GRJ4B5K-B1Aq6BcK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CDa1OVRA.js → chunk-2Q5K7J3B-CbdU0rjo.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-zxMQ5b8E.js → chunk-5RXB4S5H-Dqi2GJeD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-XTqNlzH4.js → chunk-5VM5RSS4-BYn1Hu4R.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DpZQfFTq.js → chunk-6Q2QTUOP-DRLjDL0k.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-DPv743zu.js → chunk-GF5L2VYU-CY9Xx2jV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CFsCFoF4.js → chunk-JWPE2WC7-BNCZs7_z.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CV__aJTt.js → chunk-KBJHAD2P-DZ8AStLL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BshKlNir.js → chunk-RYQCIY6F-DsxjYrzz.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-B-A3ErBP.js → chunk-XXDRQBXY-Ddx1KC1l.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuToPeGV.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuToPeGV.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-25xj8928.js → cose-bilkent-JH36ORCC-W9TveCnK.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DFmv38cl.js → cynefin-VYW2F7L2-l9j_ztZH.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-hReuWDQP.js → cynefinDiagram-MW4NZA55-BMJMKi4G.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C9X3YPUI.js → dagre-VZM6K2ZE-Bb4mG9pH.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CAVmqjg9.js → diagram-7IWD3JNH-BMgL_Qv0.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DYNlLsIm.js → diagram-B4RE2ZJO-Bvo2T4OQ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BP5mlbfb.js → diagram-LBJQPF4R-_5kWRN9c.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-KWOHK5ZM.js → diagram-Q27KOJAE-DqdMrltM.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-D25Ro5y-.js → diagram-UB23O5K3-CDDYsNkp.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-kLaahtbh.js → ebnfDiagram-BXEA7PRR-c4nfnZUV.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-1tgKuLez.js → erDiagram-JOGREHBK-DnkUcNMs.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DARr-Cvn.js → flowDiagram-UKHOOZJN-Dz0KGLZE.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BgwjBv_V.js → ganttDiagram-PKOTCBZU-Cj1t1uka.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DneIlYSI.js → gitGraphDiagram-DS77QQ5N-tp2FrBHd.js} +1 -1
- package/dist/worker/console/static/assets/{index-Dkv0ezuE.js → index-D9Sc0f0y.js} +84 -84
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DA7ATqY6.js → infoDiagram-6WML65LV-DQwJS8DH.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DnOS-E1Z.js → ishikawaDiagram-WSZJBQD7-eimCQWD4.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-C8JnwJ_s.js → journeyDiagram-NVQOT4AX-Dd9Gbwgv.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Cw0_OjEC.js → kanban-definition-27J2QSJJ-Qve3cyft.js} +1 -1
- package/dist/worker/console/static/assets/{linear-mrxq3fyn.js → linear-UXKSe36Z.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Bagus6zF.js → mermaid.core-CWhj4JXN.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-Bf95bLuW.js → mindmap-definition-FAOFIHXS-yTcwf4SJ.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BvtUW6w2.js → pegDiagram-VL7TDLO6-_7qToaZP.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CbWtT-Ry.js → pieDiagram-7S7Q4E2Y-CXmrKmDm.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-ctnxxIdD.js → quadrantDiagram-CIZ2JOQS-hMmrJyaS.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BZ7Z3X0-.js → railroadDiagram-AXF67PYL-BXw8tCBe.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BMX1xnIy.js → requirementDiagram-LRYGKXZP-BmjgnLZl.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-MShOQznz.js → sankeyDiagram-W5VNT64P-Bj77SPpN.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CTGOazaB.js → sequenceDiagram-SI44F4Z6-B2FDVysL.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-zGbfM4nV.js → sizeCapture-X5ZJPWSS-UChNY9ZZ.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Brf8Wwzr.js → stateDiagram-OKZ733FA-wbAEkbd7.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-yPoT19ft.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CvNw3ZqF.js → swimlanes-SLNWSIFB-DCEkU8HR.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-VWdarchG.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-z2LyZpDt.js → timeline-definition-Z64GVDOM-BGp7H06U.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-Bc_coprH.js → vennDiagram-T6HMQDX7-DsCIQuWR.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-M1NSmWq-.js → wardleyDiagram-T6FBY63Y-cRZLP4Xe.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-Ct0rjGXW.js → xychartDiagram-ELKLHX3M-CP0xLR9Y.js} +1 -1
- package/dist/worker/console/static/index.html +1 -1
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +31 -2
- package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -3
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
- package/dist/worker/observe/routes.js +4 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +58 -9
- package/dist/workflows/dag/backend-test-scenario-param.js +156 -20
- package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
- package/dist/workflows/dag/backend-test-writer-completeness.js +7 -3
- package/dist/workflows/dag/frontend-recovery-plan.js +2 -1
- package/dist/workflows/dag/frontend-recovery-run.js +33 -5
- package/dist/workflows/dag/frontend-writer-admission.js +44 -10
- package/dist/workflows/dag/init-hybrid.js +26 -5
- package/dist/workflows/dag/node-execution.js +76 -0
- package/dist/workflows/dag/rerun-feedback.js +59 -0
- package/dist/workflows/dag/rerun-task.js +8 -2
- package/dist/workflows/dag/runner.js +18 -11
- package/dist/workflows/dag/scheduler.js +21 -6
- package/docs/templates/backend-test-dag.json +1 -1
- package/package.json +1 -1
- package/dist/worker/console/static/assets/channel-BdTaMnYP.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cid7CSRK.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cid7CSRK.js +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CIkoculx.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-pJ-V5EdT.js +0 -8
|
@@ -22,8 +22,9 @@ export const frontendWriterAdmissionClassificationSchema = z.enum([
|
|
|
22
22
|
"requires-human-approval",
|
|
23
23
|
"blocked",
|
|
24
24
|
"stale",
|
|
25
|
-
// Legacy
|
|
26
|
-
//
|
|
25
|
+
// Legacy artifacts remain parseable for diagnostics, but `admitted` is never
|
|
26
|
+
// accepted by the runtime authorization gate. Canonical producers always
|
|
27
|
+
// emit one of the four values above.
|
|
27
28
|
"admitted",
|
|
28
29
|
]);
|
|
29
30
|
/** A+B (AC-006): Micro topology uses `requirement-to-target-and-evidence`
|
|
@@ -78,16 +79,22 @@ export const frontendWriterAdmissionResultV1Schema = z
|
|
|
78
79
|
path: z.string().optional(),
|
|
79
80
|
})
|
|
80
81
|
.strict()),
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
// deterministic gates (prewrite, write guard) and the final code
|
|
85
|
-
// review.
|
|
82
|
+
// Design findings are preserved as a bounded, structured capsule. A
|
|
83
|
+
// request_design_changes verdict is a blocking admission decision; the
|
|
84
|
+
// capsule is consumed by recovery and by any explicitly authorized retry.
|
|
86
85
|
designReviewAdvisory: z
|
|
87
86
|
.object({
|
|
88
87
|
verdict: z.enum(["approve_design", "request_design_changes"]),
|
|
89
88
|
restartPhase: z.string().min(1).optional(),
|
|
90
89
|
findingCount: z.number().int().nonnegative(),
|
|
90
|
+
findings: z.array(z.object({
|
|
91
|
+
severity: z.enum(["Critical", "Important", "Minor", "Info"]),
|
|
92
|
+
file: z.string().min(1).optional(),
|
|
93
|
+
line: z.number().int().positive().optional(),
|
|
94
|
+
issue: z.string().min(1),
|
|
95
|
+
requiredChange: z.string().min(1).optional(),
|
|
96
|
+
}).strict()),
|
|
97
|
+
evidenceRefs: z.array(z.string().min(1)),
|
|
91
98
|
})
|
|
92
99
|
.strict()
|
|
93
100
|
.optional(),
|
|
@@ -115,7 +122,7 @@ export function isConcreteWriteSetPath(value) {
|
|
|
115
122
|
const path = value.trim();
|
|
116
123
|
if (path === "" || path === "." || path === "./")
|
|
117
124
|
return false;
|
|
118
|
-
if (path.startsWith("/") || path.includes("\\"))
|
|
125
|
+
if (path.startsWith("/") || path.includes("\\") || path.includes("*") || path.includes("?"))
|
|
119
126
|
return false;
|
|
120
127
|
const segments = path.split("/");
|
|
121
128
|
if (segments.some((segment) => segment === ".." || segment === "**" || segment === "")) {
|
|
@@ -123,6 +130,32 @@ export function isConcreteWriteSetPath(value) {
|
|
|
123
130
|
}
|
|
124
131
|
return true;
|
|
125
132
|
}
|
|
133
|
+
export function normalizeConcreteWriteSetPath(value) {
|
|
134
|
+
const normalized = value.trim();
|
|
135
|
+
return isConcreteWriteSetPath(normalized) ? normalized : undefined;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Deterministically extract repository file paths that frozen verification
|
|
139
|
+
* commands operate on (`--config <file>`, `node --check <file>`). The plan's
|
|
140
|
+
* verification targets do not always name verification infrastructure (a
|
|
141
|
+
* vitest config, fixtures), yet verify-shell cannot run without it and the
|
|
142
|
+
* writer cannot create it unless the effective writeSet authorizes the file.
|
|
143
|
+
*/
|
|
144
|
+
export function collectVerificationCommandFiles(commands) {
|
|
145
|
+
const files = new Set();
|
|
146
|
+
const configRe = /--config\s+([\w@./-]+\.(?:js|mjs|cjs|ts|json))/g;
|
|
147
|
+
const checkRe = /node\s+--check\s+([\w@./-]+\.(?:js|mjs|cjs))/g;
|
|
148
|
+
for (const command of commands) {
|
|
149
|
+
for (const re of [configRe, checkRe]) {
|
|
150
|
+
for (const match of command.matchAll(re)) {
|
|
151
|
+
const file = match[1];
|
|
152
|
+
if (file && file.includes("/"))
|
|
153
|
+
files.add(file);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return [...files].sort();
|
|
158
|
+
}
|
|
126
159
|
/** Derive the concrete writeSet from implementation targets ∪ non-static
|
|
127
160
|
* verification target files. Entries are de-duplicated and lexicographically
|
|
128
161
|
* sorted (frozen). */
|
|
@@ -146,7 +179,8 @@ export function deriveWriteSet(contract, options) {
|
|
|
146
179
|
: candidates;
|
|
147
180
|
const writeSet = new Set();
|
|
148
181
|
for (const candidate of expanded) {
|
|
149
|
-
|
|
182
|
+
const normalized = normalizeConcreteWriteSetPath(candidate);
|
|
183
|
+
if (!normalized) {
|
|
150
184
|
findings.push({
|
|
151
185
|
code: "write-set-path-not-concrete",
|
|
152
186
|
message: `writeSet entry is not a concrete path: ${candidate}`,
|
|
@@ -154,7 +188,7 @@ export function deriveWriteSet(contract, options) {
|
|
|
154
188
|
});
|
|
155
189
|
continue;
|
|
156
190
|
}
|
|
157
|
-
writeSet.add(
|
|
191
|
+
writeSet.add(normalized);
|
|
158
192
|
}
|
|
159
193
|
return { writeSet: [...writeSet].sort(), findings };
|
|
160
194
|
}
|
|
@@ -3473,7 +3473,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3473
3473
|
"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.",
|
|
3474
3474
|
"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.",
|
|
3475
3475
|
"request_design_changes must carry a typed issueCategory, at least one evidenceRef, and non-empty findings.",
|
|
3476
|
-
"Your verdict is consumed
|
|
3476
|
+
"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.",
|
|
3477
3477
|
"Request design changes when the Mock strategy is MOCK_STRATEGY: 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 otherwise requires applicable real/no-remote behavior evidence unless auto mode explicitly skipped Mock because no project Mock capability exists; in that case the plan must preserve the real request path and record the Real Integration Gap.",
|
|
3478
3478
|
"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.",
|
|
3479
3479
|
"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.",
|
|
@@ -3554,6 +3554,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3554
3554
|
"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.",
|
|
3555
3555
|
"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.",
|
|
3556
3556
|
"For every non-static verification target, treat target.id as a stable trace token and include that exact token in a real describe/it/test literal title (for example, it('[VT-DASHBOARD-SHELL] renders the dashboard', ...)). One test title may carry multiple target ids when it proves multiple grouped behaviors; comments and ordinary strings do not count as trace evidence.",
|
|
3557
|
+
"Before reporting Tests Changed as done, self-audit with the executor's rule: collect ONLY the string literals passed directly to describe(/it(/test( calls in each test file and confirm every non-static target id for that file appears inside one of those literals. A token in a comment, a variable, or a non-title string does not satisfy the trace check; if any id is missing from the literal titles, edit the title strings before finishing.",
|
|
3557
3558
|
"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.",
|
|
3558
3559
|
"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.",
|
|
3559
3560
|
"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.",
|
|
@@ -4375,7 +4376,27 @@ const headingAlias={'\u6a21\u5757\u7d22\u5f15':'Module Index','\u8986\u76d6\u830
|
|
|
4375
4376
|
const allLines=readme.replace(/\\r\\n/g,'\\n').replace(/\\r/g,'\\n').split('\\n');
|
|
4376
4377
|
let headingRepair=false;
|
|
4377
4378
|
for(let i=0;i<allLines.length;i++){const alias=/^##\\s+(模块索引|覆盖范围|覆盖矩阵|场景分区)\\s*$/.exec(allLines[i].trim());if(alias){allLines[i]='## '+headingAlias[alias[1]];headingRepair=true;}}
|
|
4378
|
-
|
|
4379
|
+
let partitionRepair=false;
|
|
4380
|
+
const partHeadTrim=[];for(let i=0;i<allLines.length;i++){if(allLines[i].trim()==='## Scenario Partitions')partHeadTrim.push(i);}
|
|
4381
|
+
if(partHeadTrim.length===1){
|
|
4382
|
+
const pStart=partHeadTrim[0]+1;let pEnd=allLines.length;for(let i=pStart;i<allLines.length;i++){if(/^##\\s+\\S/.test(allLines[i].trim())){pEnd=i;break;}}
|
|
4383
|
+
const pCells=line=>line.split('|').slice(1,-1).map(v=>String(v||'').trim());
|
|
4384
|
+
const pHeader=['Partition ID','Operation','Axis','Domain','Required Slots','Expected by Slot','Bind Rule'];
|
|
4385
|
+
for(let i=pStart;i<pEnd;i++){
|
|
4386
|
+
if(!allLines[i].includes('|')) continue;
|
|
4387
|
+
const row=pCells(allLines[i]);
|
|
4388
|
+
if(row.length<=7) continue;
|
|
4389
|
+
if(pHeader.every((v,idx)=>row[idx]===v)) continue;
|
|
4390
|
+
const pid=String(row[0]||'').trim();
|
|
4391
|
+
const extras=row.slice(7).join(';').split(/[;,,;]/).map(v=>v.trim().split(bt).join('')).filter(Boolean);
|
|
4392
|
+
const prefix='TP-'+pid.toUpperCase()+'-';
|
|
4393
|
+
if(extras.length && extras.every(t=>/^TP-[A-Z0-9-]+$/i.test(t)&&(t.toUpperCase().startsWith(prefix)||t.toUpperCase().startsWith('TP-SP-')))){
|
|
4394
|
+
allLines[i]='| '+row.slice(0,7).join(' | ')+' |';
|
|
4395
|
+
partitionRepair=true;
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
if(headingRepair||partitionRepair)readme=allLines.join('\\n');
|
|
4379
4400
|
const headings=[];for(let i=0;i<allLines.length;i++){if(allLines[i].trim()==='## Module Index')headings.push(i);}
|
|
4380
4401
|
if(headings.length!==1){process.stderr.write((headings.length===0?'missing-module-index':'duplicate-module-index')+'; require exactly one exact ## Module Index section\\n');process.exit(2);}
|
|
4381
4402
|
const start=headings[0]+1;let end=allLines.length;for(let i=start;i<allLines.length;i++){if(/^##\\s+\\S/.test(allLines[i].trim())){end=i;break;}}
|
|
@@ -4388,7 +4409,7 @@ const headerIndex=lines.findIndex(line=>{const row=cells(line);return expectedHe
|
|
|
4388
4409
|
if(headerIndex<0){process.stderr.write('invalid-module-index-header: require exact business ownership and path columns\\n');process.exit(2);}
|
|
4389
4410
|
const dataRows=lines.slice(headerIndex+2).map(cells).filter(row=>row.length>=8&&row[0]&&row[0]!=='Module Stem');
|
|
4390
4411
|
const allowedSplit=new Set(['explicit-user-layout','primary-business-resource','independent-business-resource','output-budget']);
|
|
4391
|
-
const operationOwners=new Map();const canonicalSeen=new Set();const declaredModules=[];let planRepairApplied=headingRepair;
|
|
4412
|
+
const operationOwners=new Map();const canonicalSeen=new Set();const declaredModules=[];let planRepairApplied=headingRepair||partitionRepair;
|
|
4392
4413
|
for(const row of dataRows){
|
|
4393
4414
|
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();
|
|
4394
4415
|
const mdMatches=[...String(row[6]||'').matchAll(rxMdPath)];
|
|
@@ -4430,7 +4451,7 @@ if(planRepairApplied){
|
|
|
4430
4451
|
const table=['| '+expectedHeader.join(' | ')+' |','|'+expectedHeader.map(()=>'---').join('|')+'|',...declaredModules.map(item=>'| '+[item.stem,item.businessResource,item.ownedOperations.join('; '),item.ownedRuleKeys.join('; '),item.caseIds.join('; '),item.splitReason,'['+item.stem+'](./'+item.stem+'.md) '+item.markdownPath,item.pytestPath].join(' | ')+' |')].join('\\n');
|
|
4431
4452
|
readme=[...allLines.slice(0,headings[0]+1),table,...allLines.slice(end)].join('\\n');
|
|
4432
4453
|
fs.writeFileSync(planPath,readme,'utf8');
|
|
4433
|
-
} else if(headingRepair){
|
|
4454
|
+
} else if(headingRepair||partitionRepair){
|
|
4434
4455
|
fs.writeFileSync(planPath,readme,'utf8');
|
|
4435
4456
|
}
|
|
4436
4457
|
for(const [operation,owners] of operationOwners){if(owners.length>1&&!owners.every(owner=>owner.split==='explicit-user-layout'||owner.split==='output-budget')){process.stderr.write('overlapping-operation-modules: '+operation+' -> '+owners.map(owner=>owner.stem).join(',')+'; merge by business resource or use an authoritative explicit-user-layout\\n');process.exit(2);}}
|
|
@@ -5075,7 +5096,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
5075
5096
|
]
|
|
5076
5097
|
: []),
|
|
5077
5098
|
"Include exactly one `## Module Index` table with this exact header: `| Module Stem | Business Resource | Owned Operations | Owned Rule Keys | Case IDs | Split Reason | Markdown Path | Pytest Path |`. Use canonical relative links `[label](./<stem>.md)` inside the Markdown Path cell followed by the resolved `${layout.markdownDir}/<stem>.md` path. Split Reason is exactly one of `explicit-user-layout`, `primary-business-resource`, `independent-business-resource`, or `output-budget`. 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. The relative link target, Markdown Path, Pytest Path and downstream automation mapping must be one-to-one and exact; for model-derived modules the default pair remains `testcase/md/<module>.md` and `testcase/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.",
|
|
5078
|
-
"Scenario Partitions (query/filter axes):
|
|
5099
|
+
"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.",
|
|
5079
5100
|
"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.",
|
|
5080
5101
|
...(sharedSetupPrompt ? [sharedSetupPrompt] : []),
|
|
5081
5102
|
intake.boundedSourceContext,
|
|
@@ -6,6 +6,7 @@ import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
|
6
6
|
import { redactSecrets, truncateUtf8Preview } from "../../shared/preview.js";
|
|
7
7
|
import { recordDecisionEnvelopeForNode, shouldPauseOnHumanEscalation, writeHumanEscalationArtifacts, } from "./decision-envelope.js";
|
|
8
8
|
import { FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT, FRONTEND_WRITER_NODE_IDS, isFrontendWriterAuthorized, readFrontendPrewriteResult, } from "./scheduler.js";
|
|
9
|
+
import { collectVerificationCommandFiles, } from "./frontend-writer-admission.js";
|
|
9
10
|
import { writeNodeRecord, writeNodeSkillArtifacts } from "./run-store.js";
|
|
10
11
|
import { resolveContextPolicy } from "./context-policy.js";
|
|
11
12
|
import { buildDagNodePromptEnvelope, formatConvergenceFeedbackBlock, } from "./prompt.js";
|
|
@@ -1282,6 +1283,52 @@ export async function executeDagNode(input) {
|
|
|
1282
1283
|
await skipFrontendWriter(record);
|
|
1283
1284
|
return;
|
|
1284
1285
|
}
|
|
1286
|
+
// The admission artifact is the effective authorization boundary. Never
|
|
1287
|
+
// leave the writer using the broad task glob after the shell has frozen a
|
|
1288
|
+
// concrete set: doing so makes the receipt auditable but unenforceable.
|
|
1289
|
+
// Files referenced by frozen verification commands are unioned in: the
|
|
1290
|
+
// plan's verification targets do not always name verification
|
|
1291
|
+
// infrastructure, yet verify-shell cannot run without it and the writer
|
|
1292
|
+
// must be authorized to create it.
|
|
1293
|
+
const admissionWriteSetEntries = admission.result.writeSet.map((entry) => entry.trim().replace(/\\/g, "/").replace(/^\.\//, ""));
|
|
1294
|
+
const frozenVerificationBundle = spec.tasks.find((specTask) => specTask.shell?.frontendVerificationBundle)?.shell?.frontendVerificationBundle;
|
|
1295
|
+
const verificationCommandFiles = frozenVerificationBundle
|
|
1296
|
+
? collectVerificationCommandFiles([
|
|
1297
|
+
...(frozenVerificationBundle.staticCommands ?? []),
|
|
1298
|
+
...(frozenVerificationBundle.behaviorCommands ?? []),
|
|
1299
|
+
...(frozenVerificationBundle.mockCommands ?? []),
|
|
1300
|
+
...(frozenVerificationBundle.lintCommands ?? []),
|
|
1301
|
+
])
|
|
1302
|
+
: [];
|
|
1303
|
+
const extraVerificationFiles = verificationCommandFiles.filter((file) => !admissionWriteSetEntries.includes(file) &&
|
|
1304
|
+
file &&
|
|
1305
|
+
!file.includes("*") &&
|
|
1306
|
+
!file.includes("?") &&
|
|
1307
|
+
!file.split("/").some((segment) => segment === "..") &&
|
|
1308
|
+
task.allowedPaths.some((allowed) => pathMatchesPattern(file, allowed)) &&
|
|
1309
|
+
!task.forbiddenPaths.some((forbidden) => pathMatchesPattern(file, forbidden)));
|
|
1310
|
+
const admittedWriteSet = [
|
|
1311
|
+
...new Set([...admissionWriteSetEntries, ...extraVerificationFiles]),
|
|
1312
|
+
];
|
|
1313
|
+
if (admittedWriteSet.length === 0 ||
|
|
1314
|
+
new Set(admittedWriteSet).size !== admittedWriteSet.length ||
|
|
1315
|
+
admittedWriteSet.some((entry) => !entry ||
|
|
1316
|
+
entry.includes("*") ||
|
|
1317
|
+
entry.includes("?") ||
|
|
1318
|
+
entry.split("/").some((segment) => segment === "..") ||
|
|
1319
|
+
!task.allowedPaths.some((allowed) => pathMatchesPattern(entry, allowed)) ||
|
|
1320
|
+
task.forbiddenPaths.some((forbidden) => pathMatchesPattern(entry, forbidden)))) {
|
|
1321
|
+
await failBeforePrompt(new Error("frontend writer admission contains an invalid effective writeSet"), "final-write-set-approval-invalid");
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
task = { ...task, writeSet: admittedWriteSet };
|
|
1325
|
+
node.runtimeWriteAuthorization = {
|
|
1326
|
+
schemaVersion: 1,
|
|
1327
|
+
status: "validated",
|
|
1328
|
+
approvalSourceNodeId: FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT,
|
|
1329
|
+
approvalDigest: admission.result.admissionDigest,
|
|
1330
|
+
effectiveWriteSet: [...admittedWriteSet],
|
|
1331
|
+
};
|
|
1285
1332
|
node.frontendWriterAdmission = record;
|
|
1286
1333
|
}
|
|
1287
1334
|
let projectGovernanceContext;
|
|
@@ -1457,6 +1504,26 @@ export async function executeDagNode(input) {
|
|
|
1457
1504
|
return;
|
|
1458
1505
|
}
|
|
1459
1506
|
}
|
|
1507
|
+
if (FRONTEND_WRITER_NODE_IDS.includes(nodeId)) {
|
|
1508
|
+
// Design-review findings are not reliable in the provider's prose output
|
|
1509
|
+
// (typed terminal nodes commonly return an empty assistant message). Inject
|
|
1510
|
+
// the bounded admission capsule explicitly so an authorized retry has the
|
|
1511
|
+
// reviewer's concrete issue/evidence context.
|
|
1512
|
+
try {
|
|
1513
|
+
const admission = await readFrontendPrewriteResult(runDir);
|
|
1514
|
+
const advisory = admission.ok ? admission.result.designReviewAdvisory : undefined;
|
|
1515
|
+
if (advisory?.findings?.length || advisory?.evidenceRefs?.length) {
|
|
1516
|
+
prompt = `${prompt}\n\n<design_review_findings>\n${JSON.stringify({
|
|
1517
|
+
verdict: advisory.verdict,
|
|
1518
|
+
findings: advisory.findings.slice(0, 16),
|
|
1519
|
+
evidenceRefs: advisory.evidenceRefs.slice(0, 16),
|
|
1520
|
+
})}\n</design_review_findings>\nAddress every Critical/Important finding before writing.`;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
catch {
|
|
1524
|
+
// Admission is already enforced above; prompt enrichment is best effort.
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1460
1527
|
node.resolvedSkills = resolvedSkills;
|
|
1461
1528
|
await writeNodeSkillArtifacts(runDir, nodeId, resolvedSkills);
|
|
1462
1529
|
let model = resolveModelForTask(task, spec.executorModels);
|
|
@@ -1568,6 +1635,9 @@ export async function executeDagNode(input) {
|
|
|
1568
1635
|
return acc;
|
|
1569
1636
|
}, {});
|
|
1570
1637
|
let attemptPrompt = buildAttemptPrompt(task, prompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics, frontendPlanRetryStep);
|
|
1638
|
+
if (task.id === "frontend-scout-pi" && attemptNumber > 1) {
|
|
1639
|
+
attemptPrompt = `${attemptPrompt}\n\nSCOUT RETRY (reuse existing evidence): preserve all committed target-surface/design-evidence facts and do not re-read files already covered by the prior attempt. Inspect only unresolvedPaths or missing target-surface fields, then commit the minimal correction. If the existing facts are complete, commit the same canonical facts without broad rediscovery.`;
|
|
1640
|
+
}
|
|
1571
1641
|
if (attemptNumber > 1 &&
|
|
1572
1642
|
// The plan node (frontend-plan-pi) is a typed-facts ladder task:
|
|
1573
1643
|
// its retry is driven by the §5.1 ladder (compact-terminal-first
|
|
@@ -1793,6 +1863,9 @@ export async function executeDagNode(input) {
|
|
|
1793
1863
|
sdkAttempted: result.sdkAttempted,
|
|
1794
1864
|
tokensUsed: result.tokensUsed,
|
|
1795
1865
|
parsedEvents: result.parsedEvents,
|
|
1866
|
+
stopReason: result.stopReason,
|
|
1867
|
+
thinkingObserved: result.thinkingObserved,
|
|
1868
|
+
writeToolCallCount: result.writeToolCallCount,
|
|
1796
1869
|
artifactPath: `${nodeId}/attempt-${attemptNumber}.json`,
|
|
1797
1870
|
};
|
|
1798
1871
|
if (retryPolicy !== undefined) {
|
|
@@ -1874,6 +1947,9 @@ export async function executeDagNode(input) {
|
|
|
1874
1947
|
retryPolicy === undefined
|
|
1875
1948
|
? result.parsedEvents
|
|
1876
1949
|
: sumAttemptMetric(attempts, (attempt) => attempt.parsedEvents);
|
|
1950
|
+
node.stopReason = result.stopReason;
|
|
1951
|
+
node.thinkingObserved = result.thinkingObserved;
|
|
1952
|
+
node.writeToolCallCount = result.writeToolCallCount;
|
|
1877
1953
|
node.lastActivityAt = attemptFinishedAt;
|
|
1878
1954
|
if (result.failureCategory === "termination-unconfirmed") {
|
|
1879
1955
|
node.needsAttentionReason = "attempt-termination-unconfirmed";
|
|
@@ -399,6 +399,65 @@ export async function deriveDagRerunFeedback(input) {
|
|
|
399
399
|
},
|
|
400
400
|
});
|
|
401
401
|
}
|
|
402
|
+
// Preserve actionable writer failures even when the run stopped before a
|
|
403
|
+
// final review node could commit typed findings. This is especially
|
|
404
|
+
// important for provider length exhaustion: the next run must see the
|
|
405
|
+
// stop reason/tool statistics instead of repeating the same prompt.
|
|
406
|
+
const writerNodeId = ["frontend-implement-pi", "implement-pi"].find((nodeId) => {
|
|
407
|
+
const node = input.state.nodes[nodeId];
|
|
408
|
+
return (node?.status === "ERROR" &&
|
|
409
|
+
[
|
|
410
|
+
"empty-output",
|
|
411
|
+
"invalid-output",
|
|
412
|
+
"writer-thinking-exhausted",
|
|
413
|
+
"writer-budget-exhausted",
|
|
414
|
+
"incomplete-write-set",
|
|
415
|
+
"partial-success-with-context-overflow",
|
|
416
|
+
].includes(node.failureCategory ?? ""));
|
|
417
|
+
});
|
|
418
|
+
if (writerNodeId) {
|
|
419
|
+
const writerNode = await readNodeRecord(input.runDir, pass, undefined, writerNodeId, input.state.nodes[writerNodeId]);
|
|
420
|
+
if (writerNode) {
|
|
421
|
+
const writerFeedback = [
|
|
422
|
+
`writer failure category: ${writerNode.failureCategory ?? "unknown"}`,
|
|
423
|
+
writerNode.stopReason ? `provider stopReason: ${writerNode.stopReason}` : "",
|
|
424
|
+
typeof writerNode.writeToolCallCount === "number"
|
|
425
|
+
? `write tool calls: ${writerNode.writeToolCallCount}`
|
|
426
|
+
: "",
|
|
427
|
+
writerNode.stderr?.trim() ?? "",
|
|
428
|
+
canonicalNodeOutput(writerNode),
|
|
429
|
+
]
|
|
430
|
+
.filter(Boolean)
|
|
431
|
+
.join("\n\n");
|
|
432
|
+
const parentSourceBindingHash = sourceBindingHash(input.spec);
|
|
433
|
+
return withDigest({
|
|
434
|
+
schemaVersion: 1,
|
|
435
|
+
kind: "unresolved-terminal-feedback",
|
|
436
|
+
parentRunId: input.parentRunId,
|
|
437
|
+
taskId: input.taskId,
|
|
438
|
+
sourceNodeId: writerNodeId,
|
|
439
|
+
verdict: "request-revision",
|
|
440
|
+
failureCategory: writerNode.failureCategory,
|
|
441
|
+
feedbackText: scrubAndBoundFeedbackText(writerFeedback, input.runDir, input.state.cwd),
|
|
442
|
+
evidenceRefs: [
|
|
443
|
+
{
|
|
444
|
+
nodeId: writerNodeId,
|
|
445
|
+
preservedNodeRecordPath: `${writerNodeId}.json`,
|
|
446
|
+
},
|
|
447
|
+
],
|
|
448
|
+
attemptSummary: { completedPasses: 1, maxPasses: 1 },
|
|
449
|
+
binding: {
|
|
450
|
+
...(parentSourceBindingHash
|
|
451
|
+
? { sourceCanonicalHash: parentSourceBindingHash }
|
|
452
|
+
: {}),
|
|
453
|
+
...(input.spec.taskContractBinding?.canonicalHash
|
|
454
|
+
? { taskContractCanonicalHash: input.spec.taskContractBinding.canonicalHash }
|
|
455
|
+
: {}),
|
|
456
|
+
status: "unknown",
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
402
461
|
return undefined;
|
|
403
462
|
}
|
|
404
463
|
const completedPasses = pass?.pass ?? Math.max(1, input.state.convergence?.currentPass ?? 1);
|
|
@@ -309,7 +309,10 @@ export async function rerunStandaloneTask(input) {
|
|
|
309
309
|
state,
|
|
310
310
|
});
|
|
311
311
|
}
|
|
312
|
-
catch {
|
|
312
|
+
catch (error) {
|
|
313
|
+
if (state.terminalReason === "design-request-changes") {
|
|
314
|
+
throw new Error(`frontend design recovery feedback unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
315
|
+
}
|
|
313
316
|
// Feedback is additive recovery context. A corrupt/missing historical
|
|
314
317
|
// artifact must be reported honestly but must not block an otherwise
|
|
315
318
|
// eligible full rerun.
|
|
@@ -378,7 +381,10 @@ export async function rerunStandaloneTask(input) {
|
|
|
378
381
|
kind: "standalone-task-rerun",
|
|
379
382
|
})
|
|
380
383
|
: undefined;
|
|
381
|
-
|
|
384
|
+
const nestedAutoRecoveryLease = recoveryLease?.kind === "held" &&
|
|
385
|
+
recoveryLease.lease.kind === "auto-frontend-recovery" &&
|
|
386
|
+
recoveryLease.lease.requestId === input.requestId;
|
|
387
|
+
if (recoveryLease?.kind === "held" && !nestedAutoRecoveryLease) {
|
|
382
388
|
return blockedResult({
|
|
383
389
|
parentRunId: input.parentRunId,
|
|
384
390
|
reason: input.reason,
|
|
@@ -1087,7 +1087,7 @@ export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd, f
|
|
|
1087
1087
|
if (hasFrontendWriter && !skipFrontendRecovery) {
|
|
1088
1088
|
const admission = await readFrontendPrewriteResult(runDir);
|
|
1089
1089
|
if (admission.ok) {
|
|
1090
|
-
// M8: admission
|
|
1090
|
+
// M8: admission uses the accepted/blocked state machine. A blocked admission whose
|
|
1091
1091
|
// failureSource is frontend-plan-pi is the contract-invalid analog of
|
|
1092
1092
|
// the old retryable-invalid path (candidate continuation may re-plan);
|
|
1093
1093
|
// every other blocked admission is terminal.
|
|
@@ -1130,7 +1130,10 @@ export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd, f
|
|
|
1130
1130
|
if (continuationCount < frontendRecoveryQuota) {
|
|
1131
1131
|
recoveryTrigger = {
|
|
1132
1132
|
requestId: state.runId,
|
|
1133
|
-
|
|
1133
|
+
// The rejected design is a plan input defect. Reset from the
|
|
1134
|
+
// planner so the next child can incorporate the committed
|
|
1135
|
+
// findings before design review runs again.
|
|
1136
|
+
failureSource: "frontend-plan-pi",
|
|
1134
1137
|
failureOwner: classifyReviewIssueCategoryFailureOwner(designRequestFact.issueCategory),
|
|
1135
1138
|
protocolFailureReason: `frontend design review request_design_changes (issueCategory=${designRequestFact.issueCategory})`,
|
|
1136
1139
|
recoveryMode: "new-dag",
|
|
@@ -1295,10 +1298,12 @@ export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd, f
|
|
|
1295
1298
|
* A parent-run atomic lease is acquired before `OperationStore.create`, then
|
|
1296
1299
|
* its operation id is persisted into that lease before the controller may run.
|
|
1297
1300
|
* `clientRequestId` remains a useful store-level replay key, but is not a
|
|
1298
|
-
* cross-process recovery admission boundary. The
|
|
1299
|
-
*
|
|
1300
|
-
*
|
|
1301
|
-
*
|
|
1301
|
+
* cross-process recovery admission boundary. The reset partition is frozen via
|
|
1302
|
+
* `computeFrontendRecoveryPlanForSource` and serialized under the run dir's
|
|
1303
|
+
* bounded `.runtime/` namespace for audit. The operation invokes
|
|
1304
|
+
* `dag rerun-task`, which regenerates a valid child DagSpec and carries typed
|
|
1305
|
+
* parent feedback through the managed rerun path. `planHash` is the second
|
|
1306
|
+
* idempotency dimension of `actionParams`.
|
|
1302
1307
|
*/
|
|
1303
1308
|
async function createRecoveryOperation(input) {
|
|
1304
1309
|
const { cwd, runDir, spec, state, trigger } = input;
|
|
@@ -1344,11 +1349,13 @@ async function createRecoveryOperation(input) {
|
|
|
1344
1349
|
},
|
|
1345
1350
|
cliArgs: [
|
|
1346
1351
|
"dag",
|
|
1347
|
-
"
|
|
1348
|
-
"--
|
|
1349
|
-
|
|
1350
|
-
"--
|
|
1351
|
-
|
|
1352
|
+
"rerun-task",
|
|
1353
|
+
"--run-id",
|
|
1354
|
+
state.runId,
|
|
1355
|
+
"--reason",
|
|
1356
|
+
trigger.protocolFailureReason ?? "frontend automatic recovery",
|
|
1357
|
+
"--request-id",
|
|
1358
|
+
trigger.requestId,
|
|
1352
1359
|
"--json",
|
|
1353
1360
|
],
|
|
1354
1361
|
});
|
|
@@ -4,7 +4,7 @@ import { isPauseOnHumanDecisionGate } from "./decision-envelope.js";
|
|
|
4
4
|
import { evaluateConditionExpression } from "./dynamic-runtime/condition.js";
|
|
5
5
|
import { sha256Hex } from "./frontend-implementation-contract.js";
|
|
6
6
|
import { boundaryForTargetNode, computeFrontendShapeNodeDigest, evaluateFrontendShapeBoundary, } from "./frontend-shape-facts.js";
|
|
7
|
-
import { FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT, frontendWriterAdmissionResultV1Schema, } from "./frontend-writer-admission.js";
|
|
7
|
+
import { FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT, frontendWriterAdmissionResultV1Schema, normalizeConcreteWriteSetPath, } from "./frontend-writer-admission.js";
|
|
8
8
|
import { FRONTEND_RECOVERY_IMPORT_MANIFEST_REL_PATH, FRONTEND_RECOVERY_INTENT_REL_DIR, } from "./frontend-recovery-plan.js";
|
|
9
9
|
export function isConditionSkippedReason(reason) {
|
|
10
10
|
return Boolean(reason?.startsWith("condition "));
|
|
@@ -174,10 +174,7 @@ export async function readFrontendWriterAdmissionResult(runDir) {
|
|
|
174
174
|
/** Authorize only the `accepted` classification (four-state admission).
|
|
175
175
|
* `requires-human-approval`, `blocked`, and `stale` are all denied. */
|
|
176
176
|
export function isFrontendWriterAuthorized(result) {
|
|
177
|
-
return result.classification === "accepted"
|
|
178
|
-
result.classification === "admitted"
|
|
179
|
-
? "authorized"
|
|
180
|
-
: "denied";
|
|
177
|
+
return result.classification === "accepted" ? "authorized" : "denied";
|
|
181
178
|
}
|
|
182
179
|
/** Fail-closed: leave no PENDING nodes that look "still scheduled" after abort. */
|
|
183
180
|
export function markPendingNodesControllerInterrupted(state, reason = "run aborted by controller (abortSignal)") {
|
|
@@ -417,6 +414,12 @@ export async function executeDagRanksOnce(input) {
|
|
|
417
414
|
continue;
|
|
418
415
|
}
|
|
419
416
|
const decision = isFrontendWriterAuthorized(admission.result);
|
|
417
|
+
const normalizedWriteSet = admission.result.writeSet
|
|
418
|
+
.map(normalizeConcreteWriteSetPath)
|
|
419
|
+
.filter((entry) => entry !== undefined);
|
|
420
|
+
const concreteWriteSetValid = (admission.result.writeSet.length > 0 &&
|
|
421
|
+
normalizedWriteSet.length === admission.result.writeSet.length &&
|
|
422
|
+
new Set(normalizedWriteSet).size === normalizedWriteSet.length);
|
|
420
423
|
node.frontendWriterAdmission = {
|
|
421
424
|
schemaVersion: 1,
|
|
422
425
|
writerNodeId: id,
|
|
@@ -428,12 +431,24 @@ export async function executeDagRanksOnce(input) {
|
|
|
428
431
|
? `classification: ${admission.result.classification}`
|
|
429
432
|
: null,
|
|
430
433
|
};
|
|
431
|
-
if (decision === "denied") {
|
|
434
|
+
if (decision === "denied" || !concreteWriteSetValid) {
|
|
435
|
+
node.frontendWriterAdmission.reason = decision === "denied"
|
|
436
|
+
? `classification: ${admission.result.classification}`
|
|
437
|
+
: "admission writeSet is not a concrete unique path set";
|
|
432
438
|
node.status = "SKIPPED";
|
|
433
439
|
node.skippedReason = "frontend-prewrite-not-authorized";
|
|
434
440
|
node.finishedAt = checkedAt;
|
|
435
441
|
frontendAdmissionSettled.push(id);
|
|
436
442
|
}
|
|
443
|
+
else {
|
|
444
|
+
node.runtimeWriteAuthorization = {
|
|
445
|
+
schemaVersion: 1,
|
|
446
|
+
status: "validated",
|
|
447
|
+
approvalSourceNodeId: FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT,
|
|
448
|
+
approvalDigest: admission.result.admissionDigest,
|
|
449
|
+
effectiveWriteSet: normalizedWriteSet,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
437
452
|
}
|
|
438
453
|
if (frontendAdmissionSettled.length > 0)
|
|
439
454
|
await input.persistState();
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
"validate-backend-test-environment-shell"
|
|
149
149
|
],
|
|
150
150
|
"complexity": "MED",
|
|
151
|
-
"subtask_prompt": "This is a required plan-generation node. Read only the strict read set and return the complete Markdown plan in the assistant response. The runtime persists the response as a run-owned Harness artifact named generate-backend-md-plan-pi/plan.md. Do not write testcase/md/README.md or any project file; module case cards are written by downstream sharded nodes.\n\nOutput budget protocol (hard, max output <=16K per turn): Never paste full Matrix, case bodies, or source text into assistant chat. README holds only Scope+Matrix+module index; never inline full case bodies. If a Completeness Gate / OUTPUT_LIMIT_RECOVERY retry is injected, continue only listed target paths.\n\nReturn the complete plan as plain Markdown. Do not emit JSON or code fences. The plan must contain the exact English protocol headings ## Coverage Scope, ## Coverage Matrix, ## Scenario Partitions when applicable, and ## Module Index. Never translate those headings into 覆盖范围/覆盖矩阵/场景分区/模块索引.\n\nRead the upstream environment report only through the strict read set. Generate the Markdown-first backend test plan; it will be persisted under the current DAG run's Harness artifacts, not under testcase/md/.\n\nWrite human-readable content in Simplified Chinese by default. Keep English protocol literals exact: section headings, table headers, Partition IDs, TP IDs, Case IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and source citations. Never translate ## Module Index into ## 模块索引.\n\nCreate the concise plan entry page: test objective, target/environment, isolation/cleanup, module summary and a linked case index table with Case ID, Chinese case name, scenario type, endpoint and expected status/result. Avoid repeating every case body in the plan artifact.\n\nBefore the Coverage Matrix, write a mandatory machine-readable `## Coverage Scope` section in the plan artifact using exactly `| Field | Value |`, immediately followed by the separator row `|---|---|`, and these six unique rows: `Change Classification`, `Coverage Policy`, `Affected Operations`, `Affected Rule Keys`, `Regression Floor`, `Scope Evidence`. Always set `Change Classification` to `new-operation` and `Coverage Policy` to `full-contract`; do NOT reason about whether operations are new or existing. Cover all in-scope rules from the requirement document at full depth; treat the product requirement as the coverage baseline and use API contract evidence (fields/status/enum/boundary/format) to supplement scenario dimensions. Scope is limited to operations/rules the requirement document (or its referenced API contract) explicitly describes; do not expand to unrelated operations that the requirement does not mention. List affected operations exactly as `METHOD /path`, stable rule keys separated by semicolons, and precise source pointers as Scope Evidence.\n\nCoverage depth is full over the in-scope rules: fully cover every documented status, request/response field rule, requiredness, enum, boundary, format, auth and business state of each affected operation the requirement describes, but do not re-test unrelated operations the requirement does not mention. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same affected path can affect them; unresolved impact stays visible as GAP/CONFLICT.\n\nBefore writing cases, build the mandatory machine-readable Coverage Matrix inside the plan artifact itself. Its section heading line must be exactly `## Coverage Matrix` with no numeric prefix/suffix; never place the canonical Matrix only in a module file. Use this exact header: `| Rule Key | Priority | Source | Endpoint/Field | Dimension | Rule | Required Test Points | Case IDs | Status |`. Every data row must contain exactly 9 pipe-delimited cells and must never omit `Dimension`; use concise dimensions such as requirement, operation, response-status, requiredness, enum, boundary, format, business-state or error. Use only P0/P1/P2 and COVERED/PARTIAL/GAP/CONFLICT. Use stable `TP-<UPPERCASE-HYPHENATED-ID>` test points separated by semicolons.\n\nEach Rule Key must appear in exactly one Matrix row. Preserve each AC/REQ/BR Rule Key as one row; if one product rule spans multiple dimensions, use a concise composite Dimension in that single row instead of duplicating the key. Derive OpenAPI Rule Keys exactly as the deterministic analyzer does: operation token is `<HTTP-METHOD>-<PATH>` with braces removed and every non-alphanumeric run replaced by a hyphen, uppercase (for example POST `/api/resource-notes` → `POST-API-RESOURCE-NOTES`); response statuses use `API-<OPERATION>-RESPONSE-STATUS`; body/parameter fields use `API-<OPERATION>-<FIELD>-REQUIRED|ENUM|MIN-LENGTH|MAX-LENGTH|MINIMUM|MAXIMUM|PATTERN|FORMAT`. Do not invent aliases such as API-CREATE-FIELDS when a deterministic key applies.\n\nCoverage priority is strict inside the declared scope: P0 product requirements/task hard constraints always remain in scope; P1 exhaustively supplements documented operations, fields, business rules, statuses and errors only for Affected Operations; P2 adds bounded protocol robustness only when it is relevant to the change and does not invent product behavior. Coverage percentages describe the declared affected scope, never whole-API completeness unless every operation is explicitly listed. Conflicts or undefined expectations must stay visible as GAP/CONFLICT with precise source pointers, never guessed.\n\nFor uniqueness/lifecycle rules cover absent, active-existing, deleted-existing, create-delete-recreate, restore-then-recreate and documented scope/case-normalization states. For every enum cover every valid value plus bounded invalid equivalence classes (unknown, case variant, whitespace, empty, null/missing and wrong types as applicable). For every length/number rule cover min-1, min, nominal, max and max+1. For format rules cover each allowed class separately plus a valid mixed value, and representative forbidden classes including uppercase, internal/leading/trailing whitespace, tab/newline, unsupported punctuation, slash, emoji or control characters when the source contract supports that expectation.\n\nMandatory module layout contract: first inspect the PRIMARY requirement for an explicit list of required Markdown/Python output path pairs. When explicit paths are present, they are authoritative `explicit-user-layout`: reproduce their exact filenames, count and one-to-one pairs in Module Index; do not rename, merge, split, omit or add a module from reference/example scripts. Only when the primary requirement has no explicit file layout may you derive the smallest `business-resource-layout`. Existing examples, historical regression functions and shared setup may add evidence/assertions to an existing required module, but never create an extra physical module by themselves. A reference-only `resp_regression`, positive/negative/boundary/error/response module is forbidden. The Module Stem cell must contain only the plain filename stem; never put Markdown link syntax or a path in that cell.\n\nInclude exactly one `## Module Index` table with this exact header: `| Module Stem | Business Resource | Owned Operations | Owned Rule Keys | Case IDs | Split Reason | Markdown Path | Pytest Path |`. Use canonical relative links `[label](./<stem>.md)` inside the Markdown Path cell followed by the resolved `${layout.markdownDir}/<stem>.md` path. Split Reason is exactly one of `explicit-user-layout`, `primary-business-resource`, `independent-business-resource`, or `output-budget`. 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. The relative link target, Markdown Path, Pytest Path and downstream automation mapping must be one-to-one and exact; for model-derived modules the default pair remains `testcase/md/<module>.md` and `testcase/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.\n\nScenario Partitions (query/filter axes): for every affected GET/list operation, declare one row per enum or classification axis used for filtering (query/path parameters such as type/status/category). Add a mandatory 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. 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.\n\nBefore 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.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor each index entry, use `readPath` for Pi read-tool calls and copy `path` exactly into Markdown Source References. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.\n\nRead only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text.",
|
|
151
|
+
"subtask_prompt": "This is a required plan-generation node. Read only the strict read set and return the complete Markdown plan in the assistant response. The runtime persists the response as a run-owned Harness artifact named generate-backend-md-plan-pi/plan.md. Do not write testcase/md/README.md or any project file; module case cards are written by downstream sharded nodes.\n\nOutput budget protocol (hard, max output <=16K per turn): Never paste full Matrix, case bodies, or source text into assistant chat. README holds only Scope+Matrix+module index; never inline full case bodies. If a Completeness Gate / OUTPUT_LIMIT_RECOVERY retry is injected, continue only listed target paths.\n\nReturn the complete plan as plain Markdown. Do not emit JSON or code fences. The plan must contain the exact English protocol headings ## Coverage Scope, ## Coverage Matrix, ## Scenario Partitions when applicable, and ## Module Index. Never translate those headings into 覆盖范围/覆盖矩阵/场景分区/模块索引.\n\nRead the upstream environment report only through the strict read set. Generate the Markdown-first backend test plan; it will be persisted under the current DAG run's Harness artifacts, not under testcase/md/.\n\nWrite human-readable content in Simplified Chinese by default. Keep English protocol literals exact: section headings, table headers, Partition IDs, TP IDs, Case IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and source citations. Never translate ## Module Index into ## 模块索引.\n\nCreate the concise plan entry page: test objective, target/environment, isolation/cleanup, module summary and a linked case index table with Case ID, Chinese case name, scenario type, endpoint and expected status/result. Avoid repeating every case body in the plan artifact.\n\nBefore the Coverage Matrix, write a mandatory machine-readable `## Coverage Scope` section in the plan artifact using exactly `| Field | Value |`, immediately followed by the separator row `|---|---|`, and these six unique rows: `Change Classification`, `Coverage Policy`, `Affected Operations`, `Affected Rule Keys`, `Regression Floor`, `Scope Evidence`. Always set `Change Classification` to `new-operation` and `Coverage Policy` to `full-contract`; do NOT reason about whether operations are new or existing. Cover all in-scope rules from the requirement document at full depth; treat the product requirement as the coverage baseline and use API contract evidence (fields/status/enum/boundary/format) to supplement scenario dimensions. Scope is limited to operations/rules the requirement document (or its referenced API contract) explicitly describes; do not expand to unrelated operations that the requirement does not mention. List affected operations exactly as `METHOD /path`, stable rule keys separated by semicolons, and precise source pointers as Scope Evidence.\n\nCoverage depth is full over the in-scope rules: fully cover every documented status, request/response field rule, requiredness, enum, boundary, format, auth and business state of each affected operation the requirement describes, but do not re-test unrelated operations the requirement does not mention. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same affected path can affect them; unresolved impact stays visible as GAP/CONFLICT.\n\nBefore writing cases, build the mandatory machine-readable Coverage Matrix inside the plan artifact itself. Its section heading line must be exactly `## Coverage Matrix` with no numeric prefix/suffix; never place the canonical Matrix only in a module file. Use this exact header: `| Rule Key | Priority | Source | Endpoint/Field | Dimension | Rule | Required Test Points | Case IDs | Status |`. Every data row must contain exactly 9 pipe-delimited cells and must never omit `Dimension`; use concise dimensions such as requirement, operation, response-status, requiredness, enum, boundary, format, business-state or error. Use only P0/P1/P2 and COVERED/PARTIAL/GAP/CONFLICT. Use stable `TP-<UPPERCASE-HYPHENATED-ID>` test points separated by semicolons.\n\nEach Rule Key must appear in exactly one Matrix row. Preserve each AC/REQ/BR Rule Key as one row; if one product rule spans multiple dimensions, use a concise composite Dimension in that single row instead of duplicating the key. Derive OpenAPI Rule Keys exactly as the deterministic analyzer does: operation token is `<HTTP-METHOD>-<PATH>` with braces removed and every non-alphanumeric run replaced by a hyphen, uppercase (for example POST `/api/resource-notes` → `POST-API-RESOURCE-NOTES`); response statuses use `API-<OPERATION>-RESPONSE-STATUS`; body/parameter fields use `API-<OPERATION>-<FIELD>-REQUIRED|ENUM|MIN-LENGTH|MAX-LENGTH|MINIMUM|MAXIMUM|PATTERN|FORMAT`. Do not invent aliases such as API-CREATE-FIELDS when a deterministic key applies.\n\nCoverage priority is strict inside the declared scope: P0 product requirements/task hard constraints always remain in scope; P1 exhaustively supplements documented operations, fields, business rules, statuses and errors only for Affected Operations; P2 adds bounded protocol robustness only when it is relevant to the change and does not invent product behavior. Coverage percentages describe the declared affected scope, never whole-API completeness unless every operation is explicitly listed. Conflicts or undefined expectations must stay visible as GAP/CONFLICT with precise source pointers, never guessed.\n\nFor uniqueness/lifecycle rules cover absent, active-existing, deleted-existing, create-delete-recreate, restore-then-recreate and documented scope/case-normalization states. For every enum cover every valid value plus bounded invalid equivalence classes (unknown, case variant, whitespace, empty, null/missing and wrong types as applicable). For every length/number rule cover min-1, min, nominal, max and max+1. For format rules cover each allowed class separately plus a valid mixed value, and representative forbidden classes including uppercase, internal/leading/trailing whitespace, tab/newline, unsupported punctuation, slash, emoji or control characters when the source contract supports that expectation.\n\nMandatory module layout contract: first inspect the PRIMARY requirement for an explicit list of required Markdown/Python output path pairs. When explicit paths are present, they are authoritative `explicit-user-layout`: reproduce their exact filenames, count and one-to-one pairs in Module Index; do not rename, merge, split, omit or add a module from reference/example scripts. Only when the primary requirement has no explicit file layout may you derive the smallest `business-resource-layout`. Existing examples, historical regression functions and shared setup may add evidence/assertions to an existing required module, but never create an extra physical module by themselves. A reference-only `resp_regression`, positive/negative/boundary/error/response module is forbidden. The Module Stem cell must contain only the plain filename stem; never put Markdown link syntax or a path in that cell.\n\nInclude exactly one `## Module Index` table with this exact header: `| Module Stem | Business Resource | Owned Operations | Owned Rule Keys | Case IDs | Split Reason | Markdown Path | Pytest Path |`. Use canonical relative links `[label](./<stem>.md)` inside the Markdown Path cell followed by the resolved `${layout.markdownDir}/<stem>.md` path. Split Reason is exactly one of `explicit-user-layout`, `primary-business-resource`, `independent-business-resource`, or `output-budget`. 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. The relative link target, Markdown Path, Pytest Path and downstream automation mapping must be one-to-one and exact; for model-derived modules the default pair remains `testcase/md/<module>.md` and `testcase/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.\n\nScenario 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.\n\nBefore 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.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor each index entry, use `readPath` for Pi read-tool calls and copy `path` exactly into Markdown Source References. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.\n\nRead only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text.",
|
|
152
152
|
"executor": "pi",
|
|
153
153
|
"role": "planner",
|
|
154
154
|
"toolProfile": "read-only",
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{ag as o,ah as n}from"./mermaid.core-Bagus6zF.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-DPv743zu.js";import{_ as i}from"./mermaid.core-Bagus6zF.js";import"./chunk-5VM5RSS4-XTqNlzH4.js";import"./chunk-XXDRQBXY-B-A3ErBP.js";import"./chunk-KBJHAD2P-CV__aJTt.js";import"./chunk-2GRJ4B5K-BN8VNeh5.js";import"./index-Dkv0ezuE.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-DPv743zu.js";import{_ as i}from"./mermaid.core-Bagus6zF.js";import"./chunk-5VM5RSS4-XTqNlzH4.js";import"./chunk-XXDRQBXY-B-A3ErBP.js";import"./chunk-KBJHAD2P-CV__aJTt.js";import"./chunk-2GRJ4B5K-BN8VNeh5.js";import"./index-Dkv0ezuE.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as r,b as e,a,S as s}from"./chunk-5RXB4S5H-zxMQ5b8E.js";import{_ as i}from"./mermaid.core-Bagus6zF.js";import"./chunk-XXDRQBXY-B-A3ErBP.js";import"./chunk-KBJHAD2P-CV__aJTt.js";import"./chunk-2GRJ4B5K-BN8VNeh5.js";import"./index-Dkv0ezuE.js";var u={parser:a,get db(){return new s(2)},renderer:e,styles:r,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{u as diagram};
|