@tea-agent/loop-agent 0.38.2 → 0.39.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +2 -0
  3. package/dist/application/task-lifecycle/advance.js +4 -0
  4. package/dist/build-stamp.json +3 -3
  5. package/dist/commands/task-advance.js +31 -0
  6. package/dist/executors/pi-event-serializer.js +42 -1
  7. package/dist/executors/pi-executor.js +51 -0
  8. package/dist/executors/pi-sdk-executor.js +7 -26
  9. package/dist/executors/shell-executor.js +2 -2
  10. package/dist/task/contract/constants.js +1 -0
  11. package/dist/task/contract/diff.js +26 -0
  12. package/dist/task/contract/project.js +5 -0
  13. package/dist/task/contract/schema.js +6 -1
  14. package/dist/task/source-prepare/build-draft.js +15 -0
  15. package/dist/worker/console/pi-readiness.js +4 -4
  16. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4bmo3iY.js → abnfDiagram-N423BO3Z-C4kVYweA.js} +1 -1
  17. package/dist/worker/console/static/assets/{arc-SAD7McOS.js → arc-Bj2M8iO5.js} +1 -1
  18. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DAVLUdK1.js → architectureDiagram-T3A2C74G-CLlXe4-6.js} +1 -1
  19. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CPPdv4vj.js → blockDiagram-VBNYF7ZC-psEp5xH0.js} +1 -1
  20. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-D1GRGZax.js → c4Diagram-5PPSVZJV-DeKcOCGJ.js} +1 -1
  21. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +1 -0
  22. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D_Bq2ZqK.js → chunk-2GRJ4B5K-K0C744p1.js} +1 -1
  23. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CPLVtamK.js → chunk-2Q5K7J3B-xbNw4J9-.js} +1 -1
  24. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DnaZWcL3.js → chunk-5RXB4S5H-DjLaSDUO.js} +1 -1
  25. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BRTmFgnq.js → chunk-5VM5RSS4-CtiPlKel.js} +1 -1
  26. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BRlfYZoH.js → chunk-6Q2QTUOP-DZtolir7.js} +1 -1
  27. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CjNybD4u.js → chunk-GF5L2VYU-CPcdNeew.js} +1 -1
  28. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D3e-74fG.js → chunk-JWPE2WC7-NgHc6V37.js} +1 -1
  29. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Br3ir6f2.js → chunk-KBJHAD2P-DQ_T-hg8.js} +1 -1
  30. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-1NgVuRbh.js → chunk-RYQCIY6F-DgLsxcVP.js} +1 -1
  31. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BTYuvuWN.js → chunk-XXDRQBXY-UrVoM9z1.js} +1 -1
  32. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +1 -0
  33. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +1 -0
  34. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BC6Bgosf.js → cose-bilkent-JH36ORCC-CLkYvTb3.js} +1 -1
  35. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DV-HypfL.js → cynefin-VYW2F7L2-BwT_xKrE.js} +1 -1
  36. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DhTaddO-.js → cynefinDiagram-MW4NZA55-Bi9tuzif.js} +1 -1
  37. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BnEcOmiI.js → dagre-VZM6K2ZE-BCIqkWBV.js} +1 -1
  38. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-xphGRVKp.js → diagram-7IWD3JNH-Dip6l9_Z.js} +1 -1
  39. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CrdD1mRG.js → diagram-B4RE2ZJO-B-xkh_wK.js} +1 -1
  40. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-D4YdLsaF.js → diagram-LBJQPF4R-DZO0kTt_.js} +1 -1
  41. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-LCKyGZ5U.js → diagram-Q27KOJAE-D6ZplNbZ.js} +1 -1
  42. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BeiTe7t0.js → diagram-UB23O5K3-BPekbhoS.js} +1 -1
  43. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BO2cq8GS.js → ebnfDiagram-BXEA7PRR-BtmaJpK5.js} +1 -1
  44. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CmWww3Nt.js → erDiagram-JOGREHBK-6DS0Xc44.js} +1 -1
  45. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-C2kkUYg5.js → flowDiagram-UKHOOZJN-CihnROSm.js} +1 -1
  46. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DY_JZ2iQ.js → ganttDiagram-PKOTCBZU-CZC4zOE2.js} +1 -1
  47. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BizhCSw9.js → gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js} +1 -1
  48. package/dist/worker/console/static/assets/{index-BXN89VPX.js → index-DTZOKgAn.js} +142 -63
  49. package/dist/worker/console/static/assets/index-Ya5FE7cD.css +1 -0
  50. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Dlo2eK_d.js → infoDiagram-6WML65LV-DlSl4BGx.js} +1 -1
  51. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-3EIlIIuZ.js → ishikawaDiagram-WSZJBQD7-snnzFl_U.js} +1 -1
  52. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DiMDe5Yh.js → journeyDiagram-NVQOT4AX-BDE7qpMx.js} +1 -1
  53. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-sN553oIN.js → kanban-definition-27J2QSJJ-CD2Ci04G.js} +1 -1
  54. package/dist/worker/console/static/assets/{linear-D9Py31Ld.js → linear-DdnapKIH.js} +1 -1
  55. package/dist/worker/console/static/assets/{mermaid.core-D7Occzk_.js → mermaid.core-BVjAT9b8.js} +5 -5
  56. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DjDRboiR.js → mindmap-definition-FAOFIHXS-D0yJk-zA.js} +1 -1
  57. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Cgc4qlzp.js → pegDiagram-VL7TDLO6-BVo9tFP-.js} +1 -1
  58. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DxZj8pKd.js → pieDiagram-7S7Q4E2Y-DnOV-mZ_.js} +1 -1
  59. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CJ50MngR.js → quadrantDiagram-CIZ2JOQS-CzUJo58i.js} +1 -1
  60. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BY-OD8Tr.js → railroadDiagram-AXF67PYL-Bvikrolh.js} +1 -1
  61. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CQFButfK.js → requirementDiagram-LRYGKXZP-DtaSVCap.js} +1 -1
  62. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BB_QUK_b.js → sankeyDiagram-W5VNT64P-C2c9A0wW.js} +1 -1
  63. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CqhXZjyx.js → sequenceDiagram-SI44F4Z6-DmXJcU7r.js} +1 -1
  64. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-2l1U4rgJ.js → sizeCapture-X5ZJPWSS-B4GUFW92.js} +1 -1
  65. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BX1FbrhJ.js → stateDiagram-OKZ733FA-Dsad2MXf.js} +1 -1
  66. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +1 -0
  67. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DOSnT7hb.js → swimlanes-SLNWSIFB-DGq48Fbi.js} +2 -2
  68. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +8 -0
  69. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DVJQA7AN.js → timeline-definition-Z64GVDOM-CSSi6OFf.js} +1 -1
  70. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-74uxl9gx.js → vennDiagram-T6HMQDX7-BqyzPrWv.js} +1 -1
  71. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DsBxlcg_.js → wardleyDiagram-T6FBY63Y-DlznVb6S.js} +1 -1
  72. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-snuW9Mjw.js → xychartDiagram-ELKLHX3M-CfBcgJ_K.js} +1 -1
  73. package/dist/worker/console/static/index.html +2 -2
  74. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
  75. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +56 -4
  76. package/dist/workflows/dag/backend-test-scenario-partitions.js +73 -1
  77. package/dist/workflows/dag/frontend-implementation-contract.js +79 -17
  78. package/dist/workflows/dag/frontend-prewrite-gate.js +104 -27
  79. package/dist/workflows/dag/init-hybrid.js +59 -31
  80. package/dist/workflows/dag/node-execution.js +52 -2
  81. package/dist/workflows/dag/prompt.js +23 -1
  82. package/dist/workflows/dag/types.js +6 -0
  83. package/dist/workflows/dag/upstream-artifacts.js +4 -0
  84. package/docs/operations/local-development-environment.md +1 -5
  85. package/docs/skills/vetted-skill-registry.md +14 -0
  86. package/docs/templates/README.md +1 -1
  87. package/docs/templates/backend-test-dag.json +2 -2
  88. package/package.json +8 -4
  89. package/skills/analyze-product-requirements/SKILL.md +8 -5
  90. package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
  91. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
  92. package/skills/analyze-product-requirements/references/example.md +50 -0
  93. package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
  94. package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
  95. package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
  96. package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
  97. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
  98. package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
  99. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
  100. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
  101. package/skills/improve-codebase-architecture/SKILL.md +81 -0
  102. package/skills/improve-codebase-architecture/deepening.md +37 -0
  103. package/skills/improve-codebase-architecture/html-report.md +123 -0
  104. package/skills/improve-codebase-architecture/interface-design.md +44 -0
  105. package/skills/improve-codebase-architecture/language.md +53 -0
  106. package/dist/worker/console/static/assets/channel-R6oQIDkw.js +0 -1
  107. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BPXlmPWI.js +0 -1
  108. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BPXlmPWI.js +0 -1
  109. package/dist/worker/console/static/assets/index-CuH5CNFu.css +0 -1
  110. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DJKi0Qel.js +0 -1
  111. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-YYe7EEm5.js +0 -8
@@ -2518,6 +2518,13 @@ async function buildFrontendHybridDagFromTask(sources) {
2518
2518
  "- uiComponentChoices[]: purpose, component, decision (specified|reuse-existing|new), specReference { path, section, line } | null, rationale",
2519
2519
  "Do not require or read a separate plan prose section; the contract JSON is the only plan surface.",
2520
2520
  ].join("\n");
2521
+ const frontendPlanFieldGuide = [
2522
+ "## Compact planner field guide",
2523
+ "Emit only editable fields: requirements[], implementationSteps[], targets.routes/publicApiChanges, uiStates[], interactions[], mockApi.strategy/activation/endpoints, verificationTargets[], designEvidence, evidenceGaps[], stylingStrategy, uiComponentChoices[], dependencyPolicy, residualRisks[], realIntegrationGap.",
2524
+ "Protected and forbidden: schemaVersion, sourceBinding, riskLevel, targets.files, mockApi.productionDefaultOff, schemaId, targetFiles, requirementCoverage.",
2525
+ "Invariants: every requirement has expectedOutcome; every interaction has trigger + expectedBehavior; applicable uiStates have expectedBehavior + implementationTargets + verificationTargetIds; non-applicable uiStates have notApplicableReason; commandLabel is frozen; mockApi production default remains off.",
2526
+ "Enums: mock strategy native|browser-intercept|request-adapter|not-needed; uiComponentChoices decision specified|reuse-existing|new. Omit optional empty strings and unchanged revision fields.",
2527
+ ].join("\n");
2521
2528
  const sourceContext = [
2522
2529
  buildSourceContextBlock(sources),
2523
2530
  capabilityContextBlock,
@@ -2529,19 +2536,9 @@ async function buildFrontendHybridDagFromTask(sources) {
2529
2536
  const requirementIds = frontendSourceBinding.requirementIds;
2530
2537
  const requirementCoverageInstruction = requirementIds.length > 0
2531
2538
  ? [
2532
- `## Requirement Coverage (per-AC echo with bad/good examples)`,
2533
- `For each requirement ID below, echo the ID verbatim and confirm: expectedOutcome (user-observable or logic-observable), implementation targets (files), and verification targets (commandLabel + file).`,
2534
- `Do not skip any ID. Use the bad/good patterns below as reference for each field.`,
2535
- ``,
2536
- `Bad example (empty expectedOutcome, empty targets -- REJECTED at contract materialization):`,
2537
- `- AC-001: expectedOutcome="" implementationTargets=[] verificationTargets=[]`,
2538
- ``,
2539
- `Good example (concrete expectedOutcome, real files, real verification targets):`,
2540
- `- AC-001: expectedOutcome="TypeScript compilation exits with code 0 and produces no errors in dist/" implementationTargets=["src/app.tsx"] verificationTargets=["vt-typecheck":"npm run typecheck","tsconfig.json"]`,
2541
- ``,
2542
- ...requirementIds.map((id) => `- ${id}: [expectedOutcome] [implementation files] [verification targets]`),
2543
- ``,
2544
- `Every requirement MUST have a non-empty expectedOutcome. Every interaction MUST have non-empty trigger and expectedBehavior. UI states with applicable=true MUST have non-empty expectedBehavior. Empty strings or omitted fields for these will cause contract rejection.`,
2539
+ "## Requirement Coverage",
2540
+ `Cover each frozen ID exactly once in requirements[]: ${requirementIds.join(", ")}. Each item needs a concrete expectedOutcome, implementationTargets, and verificationTargetIds.`,
2541
+ "Interactions need trigger + expectedBehavior; applicable uiStates need expectedBehavior + implementationTargets + verificationTargetIds. Do not use empty strings.",
2545
2542
  ].join("\n")
2546
2543
  : "";
2547
2544
  const verificationTargetFileInstruction = [
@@ -2555,6 +2552,11 @@ async function buildFrontendHybridDagFromTask(sources) {
2555
2552
  "Before producing this contract/plan, use the Pi read tool to read the FULL bound source files (需求.md, 执行约束.md, and every `references/*` Bound readPath listed above) — the inline copies above may be truncated excerpts, and requirement/acceptance references are authoritative only in their full form.",
2556
2553
  "Do not drop scope fields, acceptance criteria, non-goals, UI states, or column/field definitions that exist in the full sources but are absent from the inline excerpts; if a field appears in the full source, it belongs in the contract.",
2557
2554
  ].join("\n");
2555
+ const plannerSourceReadInstruction = [
2556
+ "## Source-read policy",
2557
+ "Use the injected task sources plus frontend-contract-pi and frontend-scout-pi as the primary evidence. Do not repeat reads that those facts already settle.",
2558
+ "Read a bound source only to resolve a missing or conflicting field; read every applicable OpenSpec candidate directly before citing it. Never infer omitted task fields from a preview.",
2559
+ ].join("\n");
2558
2560
  const strategy = resolveDagVerifyStrategy(taskConfig);
2559
2561
  const readOnlyPaths = taskConfig.allowedPaths.length > 0 ? taskConfig.allowedPaths : ["**"];
2560
2562
  const behaviorPaths = deriveFrontendBehaviorPaths(taskConfig);
@@ -2791,8 +2793,8 @@ async function buildFrontendHybridDagFromTask(sources) {
2791
2793
  },
2792
2794
  allowedPaths: readOnlyPaths,
2793
2795
  forbiddenPaths,
2794
- skills: FRONTEND_IMPLEMENTATION_SKILLS,
2795
- outputContract: "JSON-only patch output: one-line lead-in, then exactly ONE fenced json object (```json ... ```) containing only the editable RFC 7386 plan patch for the runtime contract skeleton. Omit protected fields: schemaVersion, sourceBinding, riskLevel, targets.files, and mockApi.productionDefaultOff. Immediately after it, append exactly one ```openspec-citations``` fenced citation block. The runtime applies the patch, validates it, and promotes canonical full-contract JSON for downstream review. Do NOT emit a full contract, Markdown plan explanation, raw JSON, or any other fenced block. No file writes.",
2796
+ skills: [],
2797
+ outputContract: "JSON-only patch output: exactly ONE fenced json object (```json ... ```) containing only the editable RFC 7386 plan patch for the runtime contract skeleton, immediately followed by exactly one ```openspec-citations``` fenced citation block. Omit protected fields: schemaVersion, sourceBinding, riskLevel, targets.files, and mockApi.productionDefaultOff. The runtime applies the patch, validates it, and promotes canonical full-contract JSON for downstream review. Do NOT emit a full contract, Markdown plan explanation, raw JSON, or any other fenced block. No file writes.",
2796
2798
  subtask_prompt: [
2797
2799
  "Use frontend-contract-pi, frontend-scout-pi, task sources, and the generation-time Mock capability evidence to fill the runtime-owned frontend contract skeleton. Return JSON-only output containing only an editable RFC 7386 plan patch. The runtime already owns schemaVersion, sourceBinding, riskLevel, targets.files, and mockApi.productionDefaultOff; omit those protected paths even when their values look obvious.",
2798
2800
  "The patch fields become the complete implementation plan after deterministic merge. Do not produce a separate plan document, prose mirror, or full contract.",
@@ -2800,17 +2802,17 @@ async function buildFrontendHybridDagFromTask(sources) {
2800
2802
  "Encode ordered steps (implementationSteps), target files, UI state handling, styling/component strategy (stylingStrategy), interaction notes, Mock/API strategy, dependency policy (dependencyPolicy), deterministic verification entrypoints, Real Integration Gap (realIntegrationGap), and residual risks (residualRisks) into the contract JSON fields. Use only the fixed entrypoints below; implementation may add tests behind them but cannot replace them.",
2801
2803
  "Every target file and verification target must be selected from the current target workspace and task scope. Do not reuse paths or symbols from examples, prior tasks, or loop-agent itself; if the project uses app/, packages/, spec/, __tests__, or another layout, preserve that layout.",
2802
2804
  "Consume the Scout TARGET_SURFACE evidence before selecting files. Preserve the discovered existing entrypoint and data source. If implementationPaths or testPaths are outside task allowedPaths, record a blocking scope conflict; do not substitute a new page or silently broaden the writeSet.",
2803
- "Output in this exact order: (1) exactly one fenced json object containing the editable plan patch; (2) exactly one openspec-citations citation fenced block appended immediately after it. Do NOT emit protected skeleton fields, a full contract, Markdown plan explanation, raw JSON, or any other fenced block.",
2805
+ "Output exactly two adjacent blocks: (1) one fenced json object containing the editable plan patch; (2) one openspec-citations citation block. Do NOT emit protected skeleton fields, a full contract, Markdown plan explanation, raw JSON, or any other fenced block.",
2804
2806
  "Each requirement must state its user-observable or logic-observable expectedOutcome. Each interaction must state its trigger and expectedBehavior. IDs plus file paths are not sufficient behavior semantics.",
2805
2807
  requirementCoverageInstruction,
2806
2808
  "verificationTargets[].commandLabel MUST be one of the frozen command labels listed above. Any other value will be rejected at contract materialization.",
2807
2809
  verificationTargetFileInstruction,
2808
2810
  "Read-only: do not modify code, docs, artifacts, or repository files.",
2809
- mandatorySourceReadInstruction,
2811
+ plannerSourceReadInstruction,
2810
2812
  fixedVerificationContext,
2811
2813
  sourceContext,
2812
2814
  mockContextBlock,
2813
- frontendContractSchemaBlock,
2815
+ frontendPlanFieldGuide,
2814
2816
  frontendComponentConformanceInstruction,
2815
2817
  openspecCitationInstruction,
2816
2818
  ].join("\n\n"),
@@ -2822,6 +2824,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2822
2824
  executor: "pi",
2823
2825
  complexity: "MED",
2824
2826
  writePolicy: "read-only",
2827
+ artifactFirstUpstreamNodeIds: ["frontend-plan-pi"],
2825
2828
  allowedPaths: readOnlyPaths,
2826
2829
  forbiddenPaths,
2827
2830
  skills: FRONTEND_DESIGN_REVIEW_SKILLS,
@@ -2848,6 +2851,7 @@ async function buildFrontendHybridDagFromTask(sources) {
2848
2851
  executor: "pi",
2849
2852
  complexity: "MED",
2850
2853
  writePolicy: "read-only",
2854
+ artifactFirstUpstreamNodeIds: ["frontend-plan-pi"],
2851
2855
  outputMode: "structured-required",
2852
2856
  retryPolicy: STRUCTURED_REQUIRED_PI_RETRY_POLICY,
2853
2857
  structuredContractOutput: {
@@ -2856,22 +2860,22 @@ async function buildFrontendHybridDagFromTask(sources) {
2856
2860
  },
2857
2861
  allowedPaths: readOnlyPaths,
2858
2862
  forbiddenPaths,
2859
- skills: FRONTEND_IMPLEMENTATION_SKILLS,
2860
- outputContract: "When the initial design review requests revision, return a one-line lead-in followed by exactly ONE fenced json object (```json ... ```) containing an RFC 7386 merge-patch delta against the original frontend-implementation-contract-v1 (only the fields you change; null deletes a key; arrays and scalars replace; plain objects merge recursively). Immediately after it, append exactly one ```openspec-citations``` fenced citation block. Do NOT emit a full contract, Markdown explanation, or prose — the output is JSON-only; the gate applies the patch on the original contract and renders plan.md deterministically. Apart from the patch JSON fenced block and the openspec-citations block, do not emit any other fenced block or raw JSON. No file writes.",
2863
+ skills: [],
2864
+ outputContract: "When the initial design review requests revision, return exactly ONE fenced json object (```json ... ```) containing an RFC 7386 merge-patch delta against the original frontend-implementation-contract-v1 (only the fields you change; null deletes a key; arrays and scalars replace; plain objects merge recursively), immediately followed by exactly one ```openspec-citations``` fenced citation block. Do NOT emit a full contract, Markdown explanation, or prose — the output is JSON-only; the gate applies the patch on the original contract and renders plan.md deterministically. Apart from the patch JSON fenced block and the openspec-citations block, do not emit any other fenced block or raw JSON. No file writes.",
2861
2865
  subtask_prompt: [
2862
- "Consume frontend-plan-pi (original contract JSON) and frontend-design-review-pi (first design review findings).",
2866
+ "Consume the hash-bound frontend-plan-pi contract artifact and frontend-design-review-pi findings. Read the artifact only for fields affected by a Required Plan Correction.",
2863
2867
  "This node runs only when frontend-design-review-pi emitted VERDICT: request-revision. Produce an RFC 7386 merge-patch delta against the original contract JSON that addresses every Required Plan Correction from the design findings.",
2864
2868
  "The patch delta may update editable contract fields such as requirements, implementationSteps, targets.routes/publicApiChanges, uiStates, interactions, mockApi.strategy/activation/endpoints, dependencyPolicy, stylingStrategy, uiComponentChoices, verificationTargets, evidenceGaps, residualRisks, and realIntegrationGap. It must not modify protected schemaVersion, sourceBinding, riskLevel, targets.files, or mockApi.productionDefaultOff. Only include fields you change; omit unchanged fields (the gate applies the patch on the original contract). null deletes a key; arrays and scalars replace; plain objects merge recursively.",
2865
- requirementCoverageInstruction,
2869
+ "Only include changed fields. For a changed requirement, preserve its expectedOutcome, implementationTargets, and verificationTargetIds; do not resend unchanged requirements.",
2866
2870
  "Do not turn MOCK_STRATEGY: blocked into an implementable strategy without new repository or contract evidence that resolves every blocker.",
2867
2871
  "Read-only: do not modify code, docs, artifacts, or repository files. This node revises the plan only.",
2868
- "Output in this exact order: (1) exactly one fenced json object containing the merge-patch delta — this is the only plan output the prewrite gate applies on the original contract; (2) exactly one openspec-citations citation fenced block appended immediately after it. Do NOT emit a full contract, Markdown explanation, or prose — the output is JSON-only. Do not emit any raw JSON or JSON objects in prose. Apart from the patch JSON fenced block and the openspec-citations block, do not emit any other fenced block. Do not include secrets or unsafe paths.",
2872
+ "Output exactly two adjacent blocks: (1) one fenced json object containing the merge-patch delta; (2) one openspec-citations citation block. Do NOT emit a full contract, Markdown explanation, or prose. Do not emit raw JSON or JSON objects in prose, secrets, or unsafe paths.",
2869
2873
  "Preserve each requirement expectedOutcome and each interaction trigger/expectedBehavior in the effective (merged) contract; do not reduce behavior semantics to IDs and paths.",
2870
2874
  "verificationTargets[].commandLabel MUST be one of the frozen command labels listed above. Any other value will be rejected at contract materialization.",
2871
2875
  verificationTargetFileInstruction,
2872
2876
  fixedVerificationContext,
2873
2877
  sourceContext,
2874
- frontendContractSchemaBlock,
2878
+ frontendPlanFieldGuide,
2875
2879
  mockContextBlock,
2876
2880
  frontendComponentConformanceInstruction,
2877
2881
  openspecCitationInstruction,
@@ -2890,6 +2894,10 @@ async function buildFrontendHybridDagFromTask(sources) {
2890
2894
  executor: "pi",
2891
2895
  complexity: "MED",
2892
2896
  writePolicy: "read-only",
2897
+ artifactFirstUpstreamNodeIds: [
2898
+ "frontend-plan-revision-pi",
2899
+ "frontend-plan-pi",
2900
+ ],
2893
2901
  allowedPaths: readOnlyPaths,
2894
2902
  forbiddenPaths,
2895
2903
  skills: FRONTEND_DESIGN_REVIEW_SKILLS,
@@ -3799,11 +3807,31 @@ const BACKEND_TEST_DEFAULTS = {
3799
3807
  export function applyBackendTestLayoutToText(text, layout) {
3800
3808
  if (layout.isDefault)
3801
3809
  return text;
3802
- return text
3803
- .replaceAll("testcase/md/", `${layout.markdownDir}/`)
3804
- .replaceAll("testcase/test_", `${layout.scriptDir}/test_`)
3805
- .replaceAll("testcase/**", `${layout.testRoot}/**`)
3806
- .replaceAll("testcase/", `${layout.testRoot}/`);
3810
+ const replacements = [
3811
+ ["testcase/test_", `${layout.scriptDir}/test_`],
3812
+ ["testcase/md/", `${layout.markdownDir}/`],
3813
+ ["testcase/**", `${layout.testRoot}/**`],
3814
+ ["testcase/", `${layout.testRoot}/`],
3815
+ ];
3816
+ replacements.sort((a, b) => b[0].length - a[0].length);
3817
+ let output = "";
3818
+ const resolvedPrefix = `${layout.testRoot}/`;
3819
+ for (let i = 0; i < text.length;) {
3820
+ if (text.startsWith(resolvedPrefix, i)) {
3821
+ output += resolvedPrefix;
3822
+ i += resolvedPrefix.length;
3823
+ continue;
3824
+ }
3825
+ const hit = replacements.find(([token]) => text.startsWith(token, i));
3826
+ if (hit) {
3827
+ output += hit[1];
3828
+ i += hit[0].length;
3829
+ continue;
3830
+ }
3831
+ output += text[i];
3832
+ i += 1;
3833
+ }
3834
+ return output;
3807
3835
  }
3808
3836
  /**
3809
3837
  * Builds the `node -e` command for the backend-test module manifest shell.
@@ -4369,7 +4397,7 @@ async function buildBackendTestHybridDag(sources) {
4369
4397
  "Coverage priority is strict inside the declared scope: P0 product requirements/task hard constraints always remain in scope; P1 exhaustively supplements documented operations, fields, business rules, statuses and errors only for Affected Operations; P2 adds bounded protocol robustness only when it is relevant to the change and does not invent product behavior. Coverage percentages describe the declared affected scope, never whole-API completeness unless every operation is explicitly listed. Conflicts or undefined expectations must stay visible as GAP/CONFLICT with precise source pointers, never guessed.",
4370
4398
  "For uniqueness/lifecycle rules cover absent, active-existing, deleted-existing, create-delete-recreate, restore-then-recreate and documented scope/case-normalization states. For every enum cover every valid value plus bounded invalid equivalence classes (unknown, case variant, whitespace, empty, null/missing and wrong types as applicable). For every length/number rule cover min-1, min, nominal, max and max+1. For format rules cover each allowed class separately plus a valid mixed value, and representative forbidden classes including uppercase, internal/leading/trailing whitespace, tab/newline, unsupported punctuation, slash, emoji or control characters when the source contract supports that expectation.",
4371
4399
  "Mandatory module index: include a `## Module Index` table in README that lists every planned module as a canonical relative link of the exact form `[label](./<stem>.md)` plus a `testcase/md/<stem>.md` path cell, so a downstream deterministic manifest can parse the module list. Group by stable business resource/domain, not by CRUD operation: one resource's list/detail/create/update/delete cases belong in one module such as `resource_notes`; split only when a single module would exceed the per-child 16K output protocol, keep the total module count at the smallest safe value, and never exceed 8 modules. Name each module file with a stable lowercase business stem such as `health` or `resource_notes`. Pure hexadecimal/hash-like opaque stems such as `a401606` or `deadbeef` are forbidden. Do not use priority-only stems `p0`, `p1` or `p2`; Priority belongs only in the Coverage Matrix and never defines module files. Do not use Case-ID-like module filenames such as `BE-HEALTH.md` or `BE-NOTES.md`. The relative link target MUST equal the on-disk filename stem the sharded writer will create. For every automatable case, `自动化映射` must name exactly `testcase/test_<module>.py`, where <module> is that Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `testcase/md/health.md` → `testcase/test_health.py`; `testcase/md/resource_notes.md` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.",
4372
- "Scenario 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); Required Slots writes `each-value` plus `omitted` only when the parameter is optional; 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. Cross-axis combinations stay as ONE nominal Case; never declare a cross-axis cartesian partition.",
4400
+ "Scenario 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); Required Slots writes `each-value` plus `omitted` only when the parameter is optional; 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.",
4373
4401
  "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.",
4374
4402
  ...(sharedSetupPrompt ? [sharedSetupPrompt] : []),
4375
4403
  intake.boundedSourceContext,
@@ -4479,7 +4507,7 @@ async function buildBackendTestHybridDag(sources) {
4479
4507
  "Correct testcase/md/** directly: add documented omissions, remove unsupported cases, rename module files to stable lowercase stems when needed, normalize every Case ID to hyphen-separated module segments plus exactly three zero-padded digits (`BE-RESOURCE_NOTES-01` → `BE-RESOURCE-NOTES-001`; `BE-RN-011A` must be renumbered or merged) consistently across headings/index/mappings, fix automation mappings so each automatable case points at `testcase/test_<module>.py` derived from that module filename and declares exactly one primary symbol (evidence-only meta cases may keep `脚本/primary symbol=无` with empty variants), assign every Test Point exactly one of `变体测试点`/`场景断言测试点`/`横切证据测试点`, then perform an exact-set check: each Case's `### 测试点` set must equal (not merely contain) the union of those three binding lists; delete stale/legacy aliases and ensure every binding-list Test Point is present, expand every variant parameter row into its own atomic TP ID, make every non-cross-cutting TP Case-specific and owned by exactly one Case, require every primary symbol to start with the canonical Case prefix, ensure every explicit AC ID appears in an applicable Case `验收标准`, merge execution duplicates, improve navigation/tables/Chinese wording, or record gaps in Chinese. Remove every credential/header value, placeholder, fake token and anti-example from Markdown. Sensitive key names may remain only as a plain list; values must be described as runtime-only and omitted, with no colon/value pair or literal example anywhere, including details blocks and explanatory text. Keep Case IDs, AC/REQ/BR IDs, HTTP methods, paths, fields, enum values, filenames, code symbols and source citations as exact machine-readable identifiers; only normalize Case ID separator/sequence formatting as specified above. Recalculate predicted collected items as `sum(max(1, variant count per Case))`; when the task declares a budget, directly merge redundant journeys/reclassify same-request checkpoints until the prediction is within budget, while preserving all required coverage. The validator accepts Chinese and legacy English section aliases; retain or converge to the Chinese human-readable headings without losing structure.",
4480
4508
  "This is the single Markdown incremental synchronization round. Read every authoritative reference index entry whose role hints include acceptance-criteria, api-contract, data-contract or business-rule; do not rely on the derived PRD as a complete inventory. Preserve every explicit AC/REQ/BR ID, every documented HTTP/business error code, every DTO/JSON field, enum value, boundary, format, nested shape, transaction/state/idempotency/uniqueness/auth/tenant/cross-field rule. For each natural-language normative business rule preserved as required scope, include its exact source sentence without paraphrase together with source path and line/heading anchor so the deterministic ledger can verify quote/hash provenance. Ensure every Case declares exactly `Payload Contract: none` or the three labels `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum`; every label must occupy its own machine-readable list line, and a Case must never concatenate target/setup operations or multiple `Payload Contract` tokens onto one line, and explanatory prose/details must not repeat any `Payload Contract:` token; never infer missing keys or enum values. A target GET/DELETE operation with no request body must remain `Payload Contract: none` even when its setup journey performs POST/PUT with a DTO; setup payloads never redefine the target Case payload contract. Add only missing Matrix rows/Test Points/Cases/assertions or repair exact drift; do not rewrite already-valid unrelated modules. Work gap-targeted: inspect source anchors and affected modules first, leave unrelated valid modules byte-stable, and return `already-satisfied` without restating the full suite when no gap exists.",
4481
4509
  "For affected API fields, use one valid nominal payload plus atomic required/missing/null/empty/wrong-type, every documented enum value plus bounded invalid classes, documented min-1/min/nominal/max/max+1, formats and nested object/array constraints. Do not generate a Cartesian product or invent undocumented constraints. Do not invent a concrete identifier type when the source only requires presence; for a missing-resource 404 path with unspecified identifier syntax/type, synchronize the Case to a create-delete-derived valid identifier journey rather than an arbitrary UUID/text placeholder.",
4482
- "Scenario Partitions synchronization: when README declares `## Scenario Partitions`, verify each declared partition's slots are fully materialized as variant Test Points with exact `TP-<Partition ID>-...` IDs (each-value per Domain value, OMITTED only for optional axes, exactly one NOT-IN-SET with intent=enum-invalid). Directly add missing slot rows/Cases; never delete a declared partition or drop its complement slot to force coverage green. When the bound source does not document the complement expectation, keep the slot with GAP expected instead of guessing. Body-field validation enums (`TP-<FIELD>-ENUM-*`) are NOT partitions — do not add partition rows for them.",
4510
+ "Scenario Partitions synchronization: when README declares `## Scenario Partitions`, verify each declared partition's slots are fully materialized as variant Test Points with exact `TP-<Partition ID>-...` IDs (each-value per Domain value, OMITTED only for optional axes, exactly one NOT-IN-SET with intent=enum-invalid). Directly add missing slot rows/Cases. You may delete an illegal Partition row that has no source-backed finite domain, together with its derived `TP-SP-*` slots/Cases. Never delete a legal source-backed partition or drop its complement slot to force coverage green. When the bound source does not document the complement expectation, keep the slot with GAP expected instead of guessing. Body-field validation enums (`TP-<FIELD>-ENUM-*`) are NOT partitions — do not add partition rows for them.",
4483
4511
  "Before returning, verify that every explicit source AC/REQ/BR, error code and strong DTO field token appears in README or an applicable module Case. If a fact cannot be safely automated, retain it as GAP/CONFLICT with its exact source pointer instead of dropping it. Return already-satisfied only when no target file needs an incremental edit.",
4484
4512
  "Read only indexed source paths. Do not scan the repository, modify source/**, generate pytest, execute tests, or emit JSON.",
4485
4513
  ...(sharedSetupPrompt ? [sharedSetupPrompt] : []),
@@ -132,14 +132,14 @@ export function buildNodePrompt(spec, task, upstream, options) {
132
132
  function frontendStructuredArtifactRetryGuidance(schemaId) {
133
133
  if (schemaId === "frontend-implementation-contract-plan-patch-v1") {
134
134
  return [
135
- "Return exactly this artifact and nothing else: one short lead-in line, then exactly one fenced json block containing only the editable RFC 7386 plan patch, immediately followed by exactly one fenced openspec-citations block.",
135
+ "Return exactly two blocks and nothing else: one fenced json block containing only the editable RFC 7386 plan patch, immediately followed by one fenced openspec-citations block.",
136
136
  "The runtime applies your patch to its protected contract skeleton. Do not emit schemaVersion, sourceBinding, riskLevel, targets.files, or mockApi.productionDefaultOff; do not emit the full contract.",
137
137
  "Do not emit Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
138
138
  ];
139
139
  }
140
140
  if (schemaId === "frontend-implementation-contract-revision-patch-v1") {
141
141
  return [
142
- "Return exactly this artifact and nothing else: one short lead-in line, then exactly one fenced json block containing the RFC 7386 merge-patch delta, immediately followed by exactly one fenced openspec-citations block.",
142
+ "Return exactly two blocks and nothing else: one fenced json block containing the RFC 7386 merge-patch delta, immediately followed by one fenced openspec-citations block.",
143
143
  "The delta contains only the fields you change; null deletes a key, arrays and scalars replace, and plain objects merge recursively. Do not emit a full contract or a schemaVersion/targets section.",
144
144
  "Do not emit Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
145
145
  ];
@@ -149,6 +149,27 @@ function frontendStructuredArtifactRetryGuidance(schemaId) {
149
149
  "The contract JSON fields are the complete implementation plan. Do not emit a separate plan document, Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
150
150
  ];
151
151
  }
152
+ function isFrontendPlanArtifactSchema(schemaId) {
153
+ return (schemaId === "frontend-implementation-contract-plan-patch-v1" ||
154
+ schemaId === "frontend-implementation-contract-revision-patch-v1");
155
+ }
156
+ function buildFrontendPlanArtifactRecoveryPrompt(options) {
157
+ const cause = options.failure === "truncated"
158
+ ? "Previous response was truncated by the provider (stopReason=length)."
159
+ : options.failure === "too-large"
160
+ ? "Previous response could not be retained by the provider."
161
+ : "Previous response failed structured validation.";
162
+ return [
163
+ "<retry_instruction>",
164
+ cause,
165
+ options.reason ? `Validation error: ${options.reason}` : "",
166
+ ...frontendStructuredArtifactRetryGuidance(options.schemaId),
167
+ "Use concise values and reference paths/IDs instead of copying source prose. Do not omit required semantics.",
168
+ "</retry_instruction>",
169
+ ]
170
+ .filter(Boolean)
171
+ .join("\n");
172
+ }
152
173
  function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics) {
153
174
  if (attemptNumber <= 1)
154
175
  return basePrompt;
@@ -164,6 +185,13 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
164
185
  if (previousFailureCategory === "invalid-output" &&
165
186
  task.structuredContractOutput &&
166
187
  previousProtocolReason) {
188
+ if (isFrontendPlanArtifactSchema(task.structuredContractOutput.schemaId)) {
189
+ return buildFrontendPlanArtifactRecoveryPrompt({
190
+ failure: "invalid",
191
+ reason: previousProtocolReason,
192
+ schemaId: task.structuredContractOutput.schemaId,
193
+ });
194
+ }
167
195
  return [
168
196
  basePrompt,
169
197
  "",
@@ -177,6 +205,14 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
177
205
  }
178
206
  if (previousFailureCategory === "structured-output-truncated" &&
179
207
  task.structuredContractOutput) {
208
+ const schemaId = task.structuredContractOutput.schemaId;
209
+ if (isFrontendPlanArtifactSchema(schemaId)) {
210
+ return buildFrontendPlanArtifactRecoveryPrompt({
211
+ failure: "truncated",
212
+ reason: previousProtocolReason,
213
+ schemaId,
214
+ });
215
+ }
180
216
  return [
181
217
  basePrompt,
182
218
  "",
@@ -253,6 +289,12 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
253
289
  }
254
290
  if (task.outputMode === "structured-required") {
255
291
  if (task.structuredContractOutput) {
292
+ if (isFrontendPlanArtifactSchema(task.structuredContractOutput.schemaId)) {
293
+ return buildFrontendPlanArtifactRecoveryPrompt({
294
+ failure: "too-large",
295
+ schemaId: task.structuredContractOutput.schemaId,
296
+ });
297
+ }
256
298
  return [
257
299
  basePrompt,
258
300
  "",
@@ -831,6 +873,14 @@ export async function executeDagNode(input) {
831
873
  previousProtocolReason = contractCheck.reason;
832
874
  }
833
875
  else {
876
+ if (contractCheck.structuredArtifact) {
877
+ node.structuredArtifactPath = contractCheck.structuredArtifact.path;
878
+ node.structuredArtifactSha256 = contractCheck.structuredArtifact.sha256;
879
+ node.structuredArtifactSchemaId = contractCheck.structuredArtifact.schemaId;
880
+ }
881
+ if (contractCheck.frontendPlanOutput) {
882
+ node.frontendPlanOutput = contractCheck.frontendPlanOutput;
883
+ }
834
884
  if (contractCheck.normalizedText) {
835
885
  result = {
836
886
  ...result,
@@ -67,6 +67,7 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
67
67
  const toleratedFailures = new Set(task.failureAwareDependsOn ?? []);
68
68
  for (const depId of task.depends_on) {
69
69
  const record = upstream[depId];
70
+ const artifactFirst = task.artifactFirstUpstreamNodeIds?.includes(depId);
70
71
  const stdout = record?.stdout?.trim() ? record.stdout : "";
71
72
  const assistantText = record?.assistantText?.trim()
72
73
  ? record.assistantText
@@ -116,7 +117,28 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
116
117
  ].join("\n"));
117
118
  continue;
118
119
  }
119
- if (!record || record.status !== "FINISHED" || !upstreamText)
120
+ if (!record || record.status !== "FINISHED")
121
+ continue;
122
+ if (artifactFirst) {
123
+ const lines = [
124
+ `## Upstream artifact: ${depId}`,
125
+ "Artifact-first input: do not infer fields from a preview. Read the hash-bound payload only when a field is required; do not edit runner evidence.",
126
+ ];
127
+ if (record.structuredArtifactPath &&
128
+ record.structuredArtifactSha256 &&
129
+ record.structuredArtifactSchemaId) {
130
+ lines.push(`- payload: ${record.structuredArtifactPath}`, `- schema: ${record.structuredArtifactSchemaId}`, `- sha256: ${record.structuredArtifactSha256}`);
131
+ if (record.frontendPlanOutput) {
132
+ lines.push(`- payload kind: ${record.frontendPlanOutput.payloadKind}`, `- citations: ${record.frontendPlanOutput.citationsArtifactPath}`, `- citations sha256: ${record.frontendPlanOutput.citationsArtifactSha256}`);
133
+ }
134
+ }
135
+ else {
136
+ lines.push("- artifact: unavailable (do not reconstruct the contract from node prose; treat this as an upstream artifact integrity failure)");
137
+ }
138
+ sections.push(lines.join("\n"));
139
+ continue;
140
+ }
141
+ if (!upstreamText)
120
142
  continue;
121
143
  const artifactKind = stdout ? "stdout" : "assistant";
122
144
  const artifactPath = stdout
@@ -796,6 +796,12 @@ export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/
796
796
  }
797
797
  })
798
798
  .optional(),
799
+ /**
800
+ * Opt-in upstream context mode for nodes that must consume a validated,
801
+ * hash-bound artifact rather than a potentially truncated text preview.
802
+ * The default remains preview mode for every existing DAG.
803
+ */
804
+ artifactFirstUpstreamNodeIds: z.array(z.string()).optional(),
799
805
  /**
800
806
  * Fail-closed outcome/diff consistency contract for bounded Pi writers.
801
807
  * The writer must begin with IMPLEMENTATION_OUTCOME: changed,
@@ -38,6 +38,10 @@ export function relocateNodeArtifactPaths(node, oldRunDir, newRunDir) {
38
38
  node.stdoutArtifactPath = rewrite(node.stdoutArtifactPath);
39
39
  node.assistantArtifactPath = rewrite(node.assistantArtifactPath);
40
40
  node.structuredArtifactPath = rewrite(node.structuredArtifactPath);
41
+ if (node.frontendPlanOutput) {
42
+ node.frontendPlanOutput.citationsArtifactPath = rewrite(node.frontendPlanOutput.citationsArtifactPath);
43
+ node.frontendPlanOutput.rawCandidatePath = rewrite(node.frontendPlanOutput.rawCandidatePath);
44
+ }
41
45
  node.nodeRecordPath = rewrite(node.nodeRecordPath);
42
46
  }
43
47
  export function relocateConvergenceArtifactPaths(convergence, oldRunDir, newRunDir) {
@@ -29,11 +29,7 @@ Cursor Cloud VM 当前有两个需要特别注意的环境问题。
29
29
 
30
30
  ### Node.js 版本
31
31
 
32
- VM 默认 `node`(`/exec-daemon/node`)可能是 v22.14.0,但可选依赖 `@earendil-works/pi-ai` / `@earendil-works/pi-coding-agent` 要求 Node.js `>=22.19.0`。版本过低时,`npm install` / `npm ci` 可能跳过这些依赖,随后 `npm run typecheck``npm run build` 会报告:
33
-
34
- ```text
35
- Cannot find module '@earendil-works/...'
36
- ```
32
+ VM 默认 `node`(`/exec-daemon/node`)可能是 v22.14.0,但根包与运行时依赖 `@earendil-works/pi-ai` / `@earendil-works/pi-coding-agent` 都要求 Node.js `>=22.19.0`。版本过低时,npm 默认模式会报告 `EBADENGINE` 警告;启用 `engine-strict` 时,`npm install` / `npm ci` 会直接失败。即使默认模式完成安装,也不应在不受支持的 Node.js 版本上继续执行 typecheck、buildruntime 命令。
37
33
 
38
34
  在 Cursor Cloud 中执行安装或验证前,先切换到已配置的 Node.js 22:
39
35
 
@@ -16,10 +16,22 @@ The entries below are local wrappers or existing local skills. They are not whol
16
16
  | `code-review-core` | local wrapper inspired by code review practice | `skills/code-review-core/SKILL.md` | reviewer | reviewer | No external tools or network by default. |
17
17
  | `codebase-scout` | local wrapper | `skills/codebase-scout/SKILL.md` | scout | scout | Read-only reconnaissance guidance. |
18
18
  | `init-capability-evolution` | local wrapper | `skills/init-capability-evolution/SKILL.md` | supervisor, maintenance | optional | Used only when changes may affect target-project initialization, package surface, or init projection rules. |
19
+ | `frontend-implementation` | local existing (frontend hybrid DAG) | `skills/frontend-implementation/SKILL.md` | frontend plan / contract / scout / mock nodes (spec-level `skillsByRole`) | frontend-implementation DAG only | Required refs node-contracts / design-spec / code-standards; injected by `init-hybrid.ts` spec generation and `src/adapters/loop-agent.ts`; projected via init-surface manifest. Not in `DEFAULT_SKILLS_BY_ROLE`. |
20
+ | `frontend-review` | local existing (frontend hybrid DAG) | `skills/frontend-review/SKILL.md` | reviewer (frontend review nodes, spec-level) | frontend-implementation / repair DAGs | Consumed by `init-hybrid.ts` / `frontend-repair.ts` / `shell-executor.ts`; required ref review-findings (maxChars 2800). |
21
+ | `frontend-verification` | local existing (frontend hybrid DAG) | `skills/frontend-verification/SKILL.md` | verifier / closeout (frontend evidence, spec-level) | frontend DAG closeout | Consumed by `shell-executor.ts` / `frontend-repair.ts` / `frontend-review-context.ts`; required ref verification-checklist. |
22
+ | `frontend-design-review` | local existing (frontend hybrid DAG) | `skills/frontend-design-review/SKILL.md` | design-gate reviewer (before any writer) | frontend-implementation DAG design gate | Injected by `init-hybrid.ts`; runs before the prewrite gate authorizes writers; required ref review-checklist. |
23
+ | `frontend-bounded-implement` | local existing (frontend hybrid DAG) | `skills/frontend-bounded-implement/SKILL.md` | implementer (writer nodes, spec-level) | frontend writers after canonical contract gate | Injected by `init-hybrid.ts`; writers run only after the canonical contract gate accepts and only inside the frozen writeSet (ADR 0015/0016 discipline). |
19
24
  | `grill-with-docs` | local operator skill adapted from domain grilling + ADR/glossary discipline | `skills/grill-with-docs/SKILL.md` | explicit interactive operator only | never a default DAG role | Resolves decisions via `harness.json.governanceRoot`; required refs `context-format.md` / `adr-format.md`; respects writeSet; not in `DEFAULT_SKILLS_BY_ROLE`. |
25
+ | `grill-me` | local interview question engine wrapper | `skills/grill-me/SKILL.md` | interview runtime dependency (not a DAG role) | never a default DAG role | Question engine lives in `src/worker/console/interview/grill-me.ts` (Console interview / operator-actions); intentionally NOT projected by init-surface manifest — runs in this repo's Console only. |
26
+ | `analyze-product-requirements` | local org-internal Product Analysis V4 skill | `skills/analyze-product-requirements/SKILL.md` | source-prepare dependency (not a DAG role) | never a default DAG role | Loaded by `src/task/source-prepare/prepare.ts` during 任务源 preparation; IRON-LAW freeze semantics on `product-analysis.md`; intentionally NOT projected by init-surface manifest. |
20
27
  | `webapp-testing` | local wrapper inspired by frontend/browser testing practice | `skills/webapp-testing/SKILL.md` | verifier, reviewer | optional | Only applies when task explicitly involves browser-rendered behavior; no default Playwright/Semgrep execution. |
21
28
  | `playwright-cli` | repo-local Playwright CLI instructions | `skills/playwright-cli/SKILL.md` | FE-test case executor | FE-test only | Direct browser commands require isolated test environments, per-case evidence paths, and explicit credential/data handling. |
22
29
  | `playwright-cli-case-generator` | adapted from the repo-local playwright CLI case-generator contract | `skills/playwright-cli-case-generator/SKILL.md` | FE-test case generator | FE-test only | Generates Markdown cases and a compact manifest from RAG facts; does not execute browsers, create test code, or invent API/data constraints. |
30
+ | `analyze-product-dependencies` | local org-internal product dependency analysis | `skills/analyze-product-dependencies/SKILL.md` | explicit interactive operator only | never a default DAG role | PRD → code/API mapping analysis; read-only; no runtime references; not projected. |
31
+ | `browser-tools` | local operator skill (CDP automation) | `skills/browser-tools/SKILL.md` | explicit interactive operator only | never a default DAG role | Requires user-visible Chrome with remote debugging (:9222); credential/data handling reviewed per use; not projected. |
32
+ | `local-jacoco-coverage` | local operator orchestration skill | `skills/local-jacoco-coverage/SKILL.md` | explicit interactive operator only | never a default DAG role | Orchestrates backend-test DAGs with a JaCoCo agent — it drives DAGs, so it must never be loaded by one (no recursion); not projected. |
33
+ | `using-git-worktrees` | local wrapper | `skills/using-git-worktrees/SKILL.md` | explicit interactive operator only | never a default DAG role | Workspace isolation guidance only; no runtime references; not projected. |
34
+ | `improve-codebase-architecture` | local operator skill (copied from shared agent platform 2026-08-21) | `skills/improve-codebase-architecture/SKILL.md` | explicit interactive operator only | never a default DAG role | Interactive architecture review producing a temp HTML report; reads CONTEXT.md glossary + `docs/decisions/` ADRs; cross-directory refs `../grill-with-docs/{context,adr}-format.md` are exempt (see Vetting Rules); not projected. |
23
35
 
24
36
  ## Verification placement taxonomy
25
37
 
@@ -42,6 +54,8 @@ Authoring vocabulary for where a check or verification skill should live. Prefer
42
54
  - Default role mappings may reference only repo-local skills that resolve cleanly under `dag validate --strict-skills`.
43
55
  - `agent-worker` is explicitly outside default role mappings. Its trigger description must cover `agent-worker`, Feature Packet, TaskSpec, Task Pool, self-host/candidate and the `loop-agent` routing boundary; `scripts/check-skill-entry.sh` enforces this public entry contract.
44
56
  - `grill-with-docs` is an explicit interactive operator skill only; it must stay outside `DEFAULT_SKILLS_BY_ROLE`.
57
+ - Registry ↔ disk sync: every `skills/*/SKILL.md` in the repo must have a row above. Operator-local skills are recorded with `never a default DAG role` instead of being omitted, so an audit cannot mistake them for drift.
58
+ - `improve-codebase-architecture` is exempt from the "references stay within the skill directory" rule: it is operator-only, never resolved by DAG skill snapshots, and reuses `grill-with-docs` context/ADR formats by relative path.
45
59
  - Optional/security/web skills remain task- or profile-specific until their tool, network, credential, and write behavior is reviewed.
46
60
  - This registry records source inspiration, not license clearance for vendored third-party content. Vendoring requires a separate license/security review.
47
61
  - `SKILL.md` is the entry point. References must be declared in frontmatter and stay within the skill directory.
@@ -30,7 +30,7 @@
30
30
 
31
31
  ## Backend-test
32
32
 
33
- - `backend-test-dag.json` — backend-test DAG 模板;其中 `generate-backend-md-cases-pi` 是唯一允许 `writer-empty-diff` 重试的 writer(总共两次,仅限 post-write-guard attribution 确认的空 diff)。
33
+ - `backend-test-dag.json` — backend-test DAG 模板;其中 `generate-backend-md-cases-pi` 是唯一允许 `writer-empty-diff` 重试的 writer(总共两次,仅限 post-write-guard attribution 确认的空 diff);N2/N5 合同限定 Scenario Partition 必须有源有限域。
34
34
  - `backend-test-dag.classify.prompt.md`、`backend-test-dag.generate-pytest.prompt.md`、`backend-test-dag.review-cases.prompt.md`、`backend-test-dag.retrospect.prompt.md` — 分类、生成、审查和复盘提示。
35
35
  - `backend-test-analysis.schema.json`、`backend-test-execution.schema.json`、`backend-test-result.schema.json`、`backend-test-case-manifest.schema.json` — 分析、执行、结果与用例清单 schema。
36
36
 
@@ -145,7 +145,7 @@
145
145
  ]
146
146
  },
147
147
  "outputContract": "Write a Chinese, human-readable testcase/md/README.md as the single Markdown-first entry page with Coverage Scope, Coverage Matrix and a machine-parseable module index. Do not write module case cards here; do not execute pytest or modify production code/config.",
148
- "subtask_prompt": "This is a required file-generation node. After reading the bounded inputs, immediately use write tools to create testcase/md/README.md. Do not end after analysis or planning, and do not return before a non-empty bounded diff exists. Write ONLY testcase/md/README.md in this node; 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\nThe first non-empty response line must be exactly IMPLEMENTATION_OUTCOME: changed after the README has been written, or IMPLEMENTATION_OUTCOME: blocked when precise missing evidence prevents safe generation. already-satisfied is not valid for this node.\n\nRead the upstream environment report. Generate the Markdown-first backend test README under testcase/md/README.md.\n\nWrite human-readable content in Simplified Chinese by default. Keep English only for machine-readable IDs and technical literals such as Case/AC/REQ/BR IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and exact source citations.\n\nCreate testcase/md/README.md as the concise 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 README.\n\nBefore the Coverage Matrix, write a mandatory machine-readable `## Coverage Scope` section in README 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 `testcase/md/README.md` 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 index: include a `## Module Index` table in README that lists every planned module as a canonical relative link of the exact form `[label](./<stem>.md)` plus a `testcase/md/<stem>.md` path cell, so a downstream deterministic manifest can parse the module list. Group by stable business resource/domain, not by CRUD operation: one resource's list/detail/create/update/delete cases belong in one module such as `resource_notes`; split only when a single module would exceed the per-child 16K output protocol, keep the total module count at the smallest safe value, and never exceed 8 modules. Name each module file with a stable lowercase business stem such as `health` or `resource_notes`. Pure hexadecimal/hash-like opaque stems such as `a401606` or `deadbeef` are forbidden. Do not use priority-only stems `p0`, `p1` or `p2`; Priority belongs only in the Coverage Matrix and never defines module files. Do not use Case-ID-like module filenames such as `BE-HEALTH.md` or `BE-NOTES.md`. The relative link target MUST equal the on-disk filename stem the sharded writer will create. For every automatable case, `自动化映射` must name exactly `testcase/test_<module>.py`, where <module> is that Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `testcase/md/health.md` → `testcase/test_health.py`; `testcase/md/resource_notes.md` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.\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); Required Slots writes `each-value` plus `omitted` only when the parameter is optional; 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. 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."
148
+ "subtask_prompt": "This is a required file-generation node. After reading the bounded inputs, immediately use write tools to create testcase/md/README.md. Do not end after analysis or planning, and do not return before a non-empty bounded diff exists. Write ONLY testcase/md/README.md in this node; 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\nThe first non-empty response line must be exactly IMPLEMENTATION_OUTCOME: changed after the README has been written, or IMPLEMENTATION_OUTCOME: blocked when precise missing evidence prevents safe generation. already-satisfied is not valid for this node.\n\nRead the upstream environment report. Generate the Markdown-first backend test README under testcase/md/README.md.\n\nWrite human-readable content in Simplified Chinese by default. Keep English only for machine-readable IDs and technical literals such as Case/AC/REQ/BR IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and exact source citations.\n\nCreate testcase/md/README.md as the concise 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 README.\n\nBefore the Coverage Matrix, write a mandatory machine-readable `## Coverage Scope` section in README 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 `testcase/md/README.md` 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 index: include a `## Module Index` table in README that lists every planned module as a canonical relative link of the exact form `[label](./<stem>.md)` plus a `testcase/md/<stem>.md` path cell, so a downstream deterministic manifest can parse the module list. Group by stable business resource/domain, not by CRUD operation: one resource's list/detail/create/update/delete cases belong in one module such as `resource_notes`; split only when a single module would exceed the per-child 16K output protocol, keep the total module count at the smallest safe value, and never exceed 8 modules. Name each module file with a stable lowercase business stem such as `health` or `resource_notes`. Pure hexadecimal/hash-like opaque stems such as `a401606` or `deadbeef` are forbidden. Do not use priority-only stems `p0`, `p1` or `p2`; Priority belongs only in the Coverage Matrix and never defines module files. Do not use Case-ID-like module filenames such as `BE-HEALTH.md` or `BE-NOTES.md`. The relative link target MUST equal the on-disk filename stem the sharded writer will create. For every automatable case, `自动化映射` must name exactly `testcase/test_<module>.py`, where <module> is that Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `testcase/md/health.md` → `testcase/test_health.py`; `testcase/md/resource_notes.md` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.\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); Required Slots writes `each-value` plus `omitted` only when the parameter is optional; 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."
149
149
  },
150
150
  {
151
151
  "id": "materialize-backend-md-module-manifest-shell",
@@ -274,7 +274,7 @@
274
274
  "type": "implementation-outcome-v1"
275
275
  },
276
276
  "outputContract": "First non-empty line is IMPLEMENTATION_OUTCOME: changed|already-satisfied|blocked. Perform exactly one bounded incremental synchronization of testcase/md/** against all bound source references; preserve valid Cases and report a concise summary.",
277
- "subtask_prompt": "Perform one gap-targeted synchronization, not a full-suite rewrite or stylistic review. Start from explicit bound source IDs/error codes/DTO fields/normative quoted rules and the README Matrix; open and edit only modules that own a missing or conflicting rule. Preserve unrelated valid modules byte-for-byte and avoid optional wording cleanup.\n\nOutput budget protocol: never dump full Matrix/case bodies into assistant chat. Inspect README first, build a concise target list, then read/write only target modules one file per tool call. Do not traverse every module when the Matrix and source token inventory show no gap; return `already-satisfied`. When adding omitted in-scope cases, keep every required section. Do not bulk-delete in-scope cases to save tokens.\n\nFor every variant Test Point, ensure the Markdown scenario intent is machine-checkable and located inside that same Case body/自动化映射, never in a file-level appendix, implementation-details block, or another Case. Use an exact transport target: `场景意图: <TP-ID>; operation=<METHOD /path>; target=<body.field|query.field|path.field|header.field|request>; intent=<empty|missing|null|min-1|min|max|max+1|pattern-invalid|enum-invalid|wrong-type|nominal-operation|custom-literal:V>; bound=<n optional>; example=<optional>; expectedCode=<optional>`. Never use vague targets such as field=resource/health. Keep pytest params aligned to the exact target. For intent=missing/empty/default-omit, pytest may use `_OMIT` or delete the key; for intent=enum-invalid use a concrete invalid enum literal (for example `UNKNOWN_STATUS`), never `_OMIT`/missing-key; for trim/padded samples use `custom-literal:trim` or a real padded string, not a bare token like `filter-active` when the intent is `custom-literal:ACTIVE`.\n\nTreat the requirement document as the coverage baseline; scope is limited to operations/rules it (or its referenced API contract) describes, and API contract evidence supplements scenario dimensions. For every in-scope operation, check applicable lifecycle/uniqueness states (including deleted-existing when in scope), valid enum values, bounded invalid classes, min-1/min/nominal/max/max+1, allowed/forbidden format classes, required/null/missing/wrong-type semantics, status/error codes, auth and state transitions. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same affected path can affect them; unresolved impact stays visible as GAP/CONFLICT. Directly add in-scope omissions; reject scope expansion to operations absent from the requirement document; undefined impact remains GAP/CONFLICT rather than invented behavior.\n\nCheck AC completeness/meaning, endpoint, fields/shape, status/error codes, rules, states, documented boundaries/auth, positive/negative coverage, executable steps and assertable results. Require the exact `## Coverage Scope` Field/Value table with the `|---|---|` separator row, a valid classification-policy pair, non-empty Affected Operations/Rule Keys/Scope Evidence, and the classification-specific Regression Floor. Require the exact unnumbered `## Coverage Matrix` heading in `testcase/md/README.md`, exact headers, exactly 9 cells in every data row (including a non-empty Dimension), deterministic OpenAPI Rule Keys for every in-scope affected operation, exactly one Matrix row per Rule Key (merge multi-dimension product rows), and bidirectional Matrix Rule/Test Point ↔ Case bindings. Never describe affected-scope coverage as whole-API completeness. Every explicit AC ID must appear in at least one Case `验收标准`; every explicit in-scope AC/REQ/BR Rule Key cited by a Case must have exactly one Coverage Matrix row, and no Case may cite a source Rule Key omitted from the Matrix. Every Matrix Case ID must share at least one of that row's Required Test Points and the Case must cite that Rule Key. Perform an explicit execution-redundancy review: merge checkpoint-only parameter rows, repeated default/read-back assertions, DELETE status/body/follow-up-read checks, response schema/Content-Type checks, PUT full-update/timestamp checks, repeated list setup and identical null/empty inputs when endpoint, input partition, precondition state and expected outcome are the same. Preserve separate POST/PUT, boundary, enum, wrong-type, role/tenant and distinct business-state variants. Directly repair malformed headings/rows/keys and binding modes rather than merely commenting on them. Reject avoidable English prose, duplicated bilingual wording, repeated boilerplate, oversized unstructured sections, a `### 操作步骤` section that contains only a table without any numbered executable line, vague results such as ‘符合预期’, Case-ID-like module filenames (for example `BE-HEALTH.md`), dropped exact `### 操作步骤`/`### 预期结果` headings, and missing or drifted script/function mapping where it can be derived.\n\nCorrect testcase/md/** directly: add documented omissions, remove unsupported cases, rename module files to stable lowercase stems when needed, normalize every Case ID to hyphen-separated module segments plus exactly three zero-padded digits (`BE-RESOURCE_NOTES-01` → `BE-RESOURCE-NOTES-001`; `BE-RN-011A` must be renumbered or merged) consistently across headings/index/mappings, fix automation mappings so each automatable case points at `testcase/test_<module>.py` derived from that module filename and declares exactly one primary symbol (evidence-only meta cases may keep `脚本/primary symbol=无` with empty variants), assign every Test Point exactly one of `变体测试点`/`场景断言测试点`/`横切证据测试点`, then perform an exact-set check: each Case's `### 测试点` set must equal (not merely contain) the union of those three binding lists; delete stale/legacy aliases and ensure every binding-list Test Point is present, expand every variant parameter row into its own atomic TP ID, make every non-cross-cutting TP Case-specific and owned by exactly one Case, require every primary symbol to start with the canonical Case prefix, ensure every explicit AC ID appears in an applicable Case `验收标准`, merge execution duplicates, improve navigation/tables/Chinese wording, or record gaps in Chinese. Remove every credential/header value, placeholder, fake token and anti-example from Markdown. Sensitive key names may remain only as a plain list; values must be described as runtime-only and omitted, with no colon/value pair or literal example anywhere, including details blocks and explanatory text. Keep Case IDs, AC/REQ/BR IDs, HTTP methods, paths, fields, enum values, filenames, code symbols and source citations as exact machine-readable identifiers; only normalize Case ID separator/sequence formatting as specified above. Recalculate predicted collected items as `sum(max(1, variant count per Case))`; when the task declares a budget, directly merge redundant journeys/reclassify same-request checkpoints until the prediction is within budget, while preserving all required coverage. The validator accepts Chinese and legacy English section aliases; retain or converge to the Chinese human-readable headings without losing structure.\n\nThis is the single Markdown incremental synchronization round. Read every authoritative reference index entry whose role hints include acceptance-criteria, api-contract, data-contract or business-rule; do not rely on the derived PRD as a complete inventory. Preserve every explicit AC/REQ/BR ID, every documented HTTP/business error code, every DTO/JSON field, enum value, boundary, format, nested shape, transaction/state/idempotency/uniqueness/auth/tenant/cross-field rule. For each natural-language normative business rule preserved as required scope, include its exact source sentence without paraphrase together with source path and line/heading anchor so the deterministic ledger can verify quote/hash provenance. Ensure every Case declares exactly `Payload Contract: none` or the three labels `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum`; every label must occupy its own machine-readable list line, and a Case must never concatenate target/setup operations or multiple `Payload Contract` tokens onto one line, and explanatory prose/details must not repeat any `Payload Contract:` token; never infer missing keys or enum values. A target GET/DELETE operation with no request body must remain `Payload Contract: none` even when its setup journey performs POST/PUT with a DTO; setup payloads never redefine the target Case payload contract. Add only missing Matrix rows/Test Points/Cases/assertions or repair exact drift; do not rewrite already-valid unrelated modules. Work gap-targeted: inspect source anchors and affected modules first, leave unrelated valid modules byte-stable, and return `already-satisfied` without restating the full suite when no gap exists.\n\nFor affected API fields, use one valid nominal payload plus atomic required/missing/null/empty/wrong-type, every documented enum value plus bounded invalid classes, documented min-1/min/nominal/max/max+1, formats and nested object/array constraints. Do not generate a Cartesian product or invent undocumented constraints. Do not invent a concrete identifier type when the source only requires presence; for a missing-resource 404 path with unspecified identifier syntax/type, synchronize the Case to a create-delete-derived valid identifier journey rather than an arbitrary UUID/text placeholder.\n\nScenario Partitions synchronization: when README declares `## Scenario Partitions`, verify each declared partition's slots are fully materialized as variant Test Points with exact `TP-<Partition ID>-...` IDs (each-value per Domain value, OMITTED only for optional axes, exactly one NOT-IN-SET with intent=enum-invalid). Directly add missing slot rows/Cases; never delete a declared partition or drop its complement slot to force coverage green. When the bound source does not document the complement expectation, keep the slot with GAP expected instead of guessing. Body-field validation enums (`TP-<FIELD>-ENUM-*`) are NOT partitions — do not add partition rows for them.\n\nBefore returning, verify that every explicit source AC/REQ/BR, error code and strong DTO field token appears in README or an applicable module Case. If a fact cannot be safely automated, retain it as GAP/CONFLICT with its exact source pointer instead of dropping it. Return already-satisfied only when no target file needs an incremental edit.\n\nRead only indexed source paths. Do not scan the repository, modify source/**, generate pytest, execute tests, or emit JSON.\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 keep `path` as the exact Markdown Source References citation. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.",
277
+ "subtask_prompt": "Perform one gap-targeted synchronization, not a full-suite rewrite or stylistic review. Start from explicit bound source IDs/error codes/DTO fields/normative quoted rules and the README Matrix; open and edit only modules that own a missing or conflicting rule. Preserve unrelated valid modules byte-for-byte and avoid optional wording cleanup.\n\nOutput budget protocol: never dump full Matrix/case bodies into assistant chat. Inspect README first, build a concise target list, then read/write only target modules one file per tool call. Do not traverse every module when the Matrix and source token inventory show no gap; return `already-satisfied`. When adding omitted in-scope cases, keep every required section. Do not bulk-delete in-scope cases to save tokens.\n\nFor every variant Test Point, ensure the Markdown scenario intent is machine-checkable and located inside that same Case body/自动化映射, never in a file-level appendix, implementation-details block, or another Case. Use an exact transport target: `场景意图: <TP-ID>; operation=<METHOD /path>; target=<body.field|query.field|path.field|header.field|request>; intent=<empty|missing|null|min-1|min|max|max+1|pattern-invalid|enum-invalid|wrong-type|nominal-operation|custom-literal:V>; bound=<n optional>; example=<optional>; expectedCode=<optional>`. Never use vague targets such as field=resource/health. Keep pytest params aligned to the exact target. For intent=missing/empty/default-omit, pytest may use `_OMIT` or delete the key; for intent=enum-invalid use a concrete invalid enum literal (for example `UNKNOWN_STATUS`), never `_OMIT`/missing-key; for trim/padded samples use `custom-literal:trim` or a real padded string, not a bare token like `filter-active` when the intent is `custom-literal:ACTIVE`.\n\nTreat the requirement document as the coverage baseline; scope is limited to operations/rules it (or its referenced API contract) describes, and API contract evidence supplements scenario dimensions. For every in-scope operation, check applicable lifecycle/uniqueness states (including deleted-existing when in scope), valid enum values, bounded invalid classes, min-1/min/nominal/max/max+1, allowed/forbidden format classes, required/null/missing/wrong-type semantics, status/error codes, auth and state transitions. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same affected path can affect them; unresolved impact stays visible as GAP/CONFLICT. Directly add in-scope omissions; reject scope expansion to operations absent from the requirement document; undefined impact remains GAP/CONFLICT rather than invented behavior.\n\nCheck AC completeness/meaning, endpoint, fields/shape, status/error codes, rules, states, documented boundaries/auth, positive/negative coverage, executable steps and assertable results. Require the exact `## Coverage Scope` Field/Value table with the `|---|---|` separator row, a valid classification-policy pair, non-empty Affected Operations/Rule Keys/Scope Evidence, and the classification-specific Regression Floor. Require the exact unnumbered `## Coverage Matrix` heading in `testcase/md/README.md`, exact headers, exactly 9 cells in every data row (including a non-empty Dimension), deterministic OpenAPI Rule Keys for every in-scope affected operation, exactly one Matrix row per Rule Key (merge multi-dimension product rows), and bidirectional Matrix Rule/Test Point ↔ Case bindings. Never describe affected-scope coverage as whole-API completeness. Every explicit AC ID must appear in at least one Case `验收标准`; every explicit in-scope AC/REQ/BR Rule Key cited by a Case must have exactly one Coverage Matrix row, and no Case may cite a source Rule Key omitted from the Matrix. Every Matrix Case ID must share at least one of that row's Required Test Points and the Case must cite that Rule Key. Perform an explicit execution-redundancy review: merge checkpoint-only parameter rows, repeated default/read-back assertions, DELETE status/body/follow-up-read checks, response schema/Content-Type checks, PUT full-update/timestamp checks, repeated list setup and identical null/empty inputs when endpoint, input partition, precondition state and expected outcome are the same. Preserve separate POST/PUT, boundary, enum, wrong-type, role/tenant and distinct business-state variants. Directly repair malformed headings/rows/keys and binding modes rather than merely commenting on them. Reject avoidable English prose, duplicated bilingual wording, repeated boilerplate, oversized unstructured sections, a `### 操作步骤` section that contains only a table without any numbered executable line, vague results such as ‘符合预期’, Case-ID-like module filenames (for example `BE-HEALTH.md`), dropped exact `### 操作步骤`/`### 预期结果` headings, and missing or drifted script/function mapping where it can be derived.\n\nCorrect testcase/md/** directly: add documented omissions, remove unsupported cases, rename module files to stable lowercase stems when needed, normalize every Case ID to hyphen-separated module segments plus exactly three zero-padded digits (`BE-RESOURCE_NOTES-01` → `BE-RESOURCE-NOTES-001`; `BE-RN-011A` must be renumbered or merged) consistently across headings/index/mappings, fix automation mappings so each automatable case points at `testcase/test_<module>.py` derived from that module filename and declares exactly one primary symbol (evidence-only meta cases may keep `脚本/primary symbol=无` with empty variants), assign every Test Point exactly one of `变体测试点`/`场景断言测试点`/`横切证据测试点`, then perform an exact-set check: each Case's `### 测试点` set must equal (not merely contain) the union of those three binding lists; delete stale/legacy aliases and ensure every binding-list Test Point is present, expand every variant parameter row into its own atomic TP ID, make every non-cross-cutting TP Case-specific and owned by exactly one Case, require every primary symbol to start with the canonical Case prefix, ensure every explicit AC ID appears in an applicable Case `验收标准`, merge execution duplicates, improve navigation/tables/Chinese wording, or record gaps in Chinese. Remove every credential/header value, placeholder, fake token and anti-example from Markdown. Sensitive key names may remain only as a plain list; values must be described as runtime-only and omitted, with no colon/value pair or literal example anywhere, including details blocks and explanatory text. Keep Case IDs, AC/REQ/BR IDs, HTTP methods, paths, fields, enum values, filenames, code symbols and source citations as exact machine-readable identifiers; only normalize Case ID separator/sequence formatting as specified above. Recalculate predicted collected items as `sum(max(1, variant count per Case))`; when the task declares a budget, directly merge redundant journeys/reclassify same-request checkpoints until the prediction is within budget, while preserving all required coverage. The validator accepts Chinese and legacy English section aliases; retain or converge to the Chinese human-readable headings without losing structure.\n\nThis is the single Markdown incremental synchronization round. Read every authoritative reference index entry whose role hints include acceptance-criteria, api-contract, data-contract or business-rule; do not rely on the derived PRD as a complete inventory. Preserve every explicit AC/REQ/BR ID, every documented HTTP/business error code, every DTO/JSON field, enum value, boundary, format, nested shape, transaction/state/idempotency/uniqueness/auth/tenant/cross-field rule. For each natural-language normative business rule preserved as required scope, include its exact source sentence without paraphrase together with source path and line/heading anchor so the deterministic ledger can verify quote/hash provenance. Ensure every Case declares exactly `Payload Contract: none` or the three labels `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum`; every label must occupy its own machine-readable list line, and a Case must never concatenate target/setup operations or multiple `Payload Contract` tokens onto one line, and explanatory prose/details must not repeat any `Payload Contract:` token; never infer missing keys or enum values. A target GET/DELETE operation with no request body must remain `Payload Contract: none` even when its setup journey performs POST/PUT with a DTO; setup payloads never redefine the target Case payload contract. Add only missing Matrix rows/Test Points/Cases/assertions or repair exact drift; do not rewrite already-valid unrelated modules. Work gap-targeted: inspect source anchors and affected modules first, leave unrelated valid modules byte-stable, and return `already-satisfied` without restating the full suite when no gap exists.\n\nFor affected API fields, use one valid nominal payload plus atomic required/missing/null/empty/wrong-type, every documented enum value plus bounded invalid classes, documented min-1/min/nominal/max/max+1, formats and nested object/array constraints. Do not generate a Cartesian product or invent undocumented constraints. Do not invent a concrete identifier type when the source only requires presence; for a missing-resource 404 path with unspecified identifier syntax/type, synchronize the Case to a create-delete-derived valid identifier journey rather than an arbitrary UUID/text placeholder.\n\nScenario Partitions synchronization: when README declares `## Scenario Partitions`, verify each declared partition's slots are fully materialized as variant Test Points with exact `TP-<Partition ID>-...` IDs (each-value per Domain value, OMITTED only for optional axes, exactly one NOT-IN-SET with intent=enum-invalid). Directly add missing slot rows/Cases. You may delete an illegal Partition row that has no source-backed finite domain, together with its derived `TP-SP-*` slots/Cases. Never delete a legal source-backed partition or drop its complement slot to force coverage green. When the bound source does not document the complement expectation, keep the slot with GAP expected instead of guessing. Body-field validation enums (`TP-<FIELD>-ENUM-*`) are NOT partitions — do not add partition rows for them.\n\nBefore returning, verify that every explicit source AC/REQ/BR, error code and strong DTO field token appears in README or an applicable module Case. If a fact cannot be safely automated, retain it as GAP/CONFLICT with its exact source pointer instead of dropping it. Return already-satisfied only when no target file needs an incremental edit.\n\nRead only indexed source paths. Do not scan the repository, modify source/**, generate pytest, execute tests, or emit JSON.\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 keep `path` as the exact Markdown Source References citation. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.",
278
278
  "retryPolicy": {
279
279
  "maxAttempts": 2,
280
280
  "backoff": "exponential",