@tea-agent/loop-agent 0.40.0-next.9 → 0.41.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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +130 -3
- package/README.md +2 -2
- package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
- package/dist/adapters/context-transfer/pi-session.js +61 -0
- package/dist/application/dag/generate-task-dag.js +6 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +21 -5
- package/dist/cli/program.js +61 -4
- package/dist/commands/client-recovery.js +8 -36
- package/dist/commands/dag-artifact.js +284 -0
- package/dist/commands/dag-context.js +184 -0
- package/dist/commands/dag-rerun.js +206 -1
- package/dist/commands/init-model-catalog.js +22 -0
- package/dist/commands/task-advance.js +23 -1
- package/dist/commands/task-source-prepare.js +5 -0
- package/dist/commands/task-status.js +1 -0
- package/dist/executors/dag-pi-executor.js +39 -12
- package/dist/executors/pi-sdk-executor.js +36 -5
- package/dist/executors/shell-executor.js +64 -17
- package/dist/governance/checks.js +1 -0
- package/dist/{worker → infrastructure}/console/app-data.js +4 -0
- package/dist/infrastructure/console/artifact-revision-store.js +430 -0
- package/dist/infrastructure/console/context-export-store.js +160 -0
- package/dist/infrastructure/console/dir-lock.js +132 -0
- package/dist/{worker → infrastructure}/console/operation-store.js +28 -0
- package/dist/infrastructure/harness/artifact-store.js +10 -1
- package/dist/infrastructure/harness/atomic-write.js +12 -2
- package/dist/shared/context-transfer/artifact-revision.js +172 -0
- package/dist/shared/context-transfer.js +418 -0
- package/dist/shared/operator/capabilities.js +23 -1
- package/dist/shared/operator/safe-run-summary.js +1 -0
- package/dist/shared/path-safety.js +93 -0
- package/dist/shared/pi-context-pressure/checkpoint.js +116 -0
- package/dist/shared/pi-context-pressure/compaction-policy.js +151 -0
- package/dist/shared/pi-context-pressure/env.js +58 -0
- package/dist/shared/pi-context-pressure/extension.js +100 -0
- package/dist/shared/pi-context-pressure/index.js +7 -0
- package/dist/shared/pi-context-pressure/overflow.js +252 -0
- package/dist/shared/pi-context-pressure/sift-bridge.js +386 -0
- package/dist/shared/pi-context-pressure/telemetry.js +51 -0
- package/dist/shared/preview.js +28 -4
- package/dist/task/config-types.js +2 -1
- package/dist/task/task-demand-routing.js +3 -0
- package/dist/worker/console/chat/assistant-content.js +7 -0
- package/dist/worker/console/chat/chat-event-store.js +84 -13
- package/dist/worker/console/chat/goal-round-driver.js +72 -0
- package/dist/worker/console/chat/goals.js +300 -0
- package/dist/worker/console/chat/pi-runtime.js +285 -8
- package/dist/worker/console/chat/provider-error.js +2 -1
- package/dist/worker/console/chat/repo-browser.js +94 -12
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +487 -30
- package/dist/worker/console/chat/session-catalog.js +48 -0
- package/dist/worker/console/chat/session-stats.js +96 -0
- package/dist/worker/console/chat/session-store.js +28 -19
- package/dist/worker/console/chat/shortcuts.js +7 -0
- package/dist/worker/console/chat/sift-bridge.js +1 -0
- package/dist/worker/console/chat/turn-process.js +32 -3
- package/dist/worker/console/chat/user-questions.js +1 -1
- package/dist/worker/console/console-update-runtime.js +1 -1
- package/dist/worker/console/context-transfer-diagnostics.js +198 -0
- package/dist/worker/console/dag-confirmation.js +1 -1
- package/dist/worker/console/dag-execution-receipt.js +21 -3
- package/dist/worker/console/doctor.js +1 -1
- package/dist/worker/console/draft-store.js +1 -1
- package/dist/worker/console/human-gate-token.js +1 -1
- package/dist/worker/console/index.js +3 -3
- package/dist/worker/console/interview/assessment.js +1 -1
- package/dist/worker/console/interview/session.js +1 -1
- package/dist/worker/console/operation-runner.js +45 -4
- package/dist/worker/console/operation-sse.js +1 -1
- package/dist/worker/console/operation-wait.js +1 -1
- package/dist/worker/console/operator-actions.js +124 -8
- package/dist/worker/console/operator-selection.js +6 -2
- package/dist/worker/console/operator-surface-health.js +1 -1
- package/dist/worker/console/operator-user-error.js +169 -0
- package/dist/worker/console/pi-plugins.catalog.json +73658 -0
- package/dist/worker/console/pi-plugins.js +179 -0
- package/dist/worker/console/routes.js +864 -13
- package/dist/worker/console/security.js +35 -0
- package/dist/worker/console/server.js +177 -44
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bs-CDAXM.js → abnfDiagram-N423BO3Z-DSVcg1YO.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CAkA3We3.js → arc-BtXhIqui.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DEmI_zqr.js → architectureDiagram-T3A2C74G-B-nh-ilJ.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-uDHANebE.js → blockDiagram-VBNYF7ZC-BIUSVdZI.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Bv97b4_B.js → c4Diagram-5PPSVZJV-Cmw3L4qM.js} +1 -1
- package/dist/worker/console/static/assets/channel-BhZFNefx.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-shmQKsFz.js → chunk-2GRJ4B5K-CGpMMjGm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-BAraNvcN.js → chunk-2Q5K7J3B-BNBkEpEJ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BirhxUop.js → chunk-5RXB4S5H-ubln3eaU.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C9LgUJpR.js → chunk-5VM5RSS4-_XcBTt1i.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DrU-s-t7.js → chunk-6Q2QTUOP-Ln30Pgrh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Tx-H_FA2.js → chunk-GF5L2VYU-ESH3lhSx.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BjLtNFrR.js → chunk-JWPE2WC7-DAJt-qXB.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-UNLVwSsy.js → chunk-KBJHAD2P-Cx10LIcH.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CBEUX8XD.js → chunk-RYQCIY6F-CENeOxoJ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-WTL7IZOq.js → chunk-XXDRQBXY-DXqK-D38.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Sbv-NJni.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Sbv-NJni.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CkHG6W3s.js → cose-bilkent-JH36ORCC-CE4dmvGY.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BRzFqTaL.js → cynefin-VYW2F7L2-CRacN1zK.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2_CFp_i7.js → cynefinDiagram-MW4NZA55-Cneukjrt.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJtsmdkE.js → dagre-VZM6K2ZE-DPFboRoL.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DNEFFo6w.js → diagram-7IWD3JNH-DIAQgcOP.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B_IWV7eo.js → diagram-B4RE2ZJO-VrYMKxuy.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DvvRx2dz.js → diagram-LBJQPF4R-CLHvBghn.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CsibsWLg.js → diagram-Q27KOJAE-DVCWBif8.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-les_djBQ.js → diagram-UB23O5K3-BTMmbBzJ.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BEm2n7qJ.js → ebnfDiagram-BXEA7PRR-BUZsKJ56.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BI7vHHVm.js → erDiagram-JOGREHBK-BsBZ5NXC.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CUFCERrB.js → flowDiagram-UKHOOZJN-D7fH_kDa.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-wxyLiGWt.js → ganttDiagram-PKOTCBZU-Bza5OohY.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bt897K8Z.js → gitGraphDiagram-DS77QQ5N-DpkoGR_Y.js} +1 -1
- package/dist/worker/console/static/assets/index-Ius3vxhf.css +1 -0
- package/dist/worker/console/static/assets/index-yASzcbBr.js +451 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DYNx_IPv.js → infoDiagram-6WML65LV-BYm3g6LT.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CzpLNEu6.js → ishikawaDiagram-WSZJBQD7-C2RS_H4v.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-97f9owNU.js → journeyDiagram-NVQOT4AX-B4slKpCo.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BxM9hyBn.js → kanban-definition-27J2QSJJ-B7orRNpI.js} +1 -1
- package/dist/worker/console/static/assets/{linear-ByuxcHvp.js → linear-D1BeBxLQ.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DwPGvpCv.js → mermaid.core-0TtMP8k4.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DCoqG9RT.js → mindmap-definition-FAOFIHXS-XL5EbCtT.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BLSBmBMx.js → pegDiagram-VL7TDLO6-CwF7uas5.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B0t_0oJW.js → pieDiagram-7S7Q4E2Y-A_Jo-72f.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js → quadrantDiagram-CIZ2JOQS-CxOa4gKL.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-B3jRdATC.js → railroadDiagram-AXF67PYL-DoAoswIH.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-cTtXEN.js → requirementDiagram-LRYGKXZP-C1Lc1MlD.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B25igJRE.js → sankeyDiagram-W5VNT64P-DFXOkq0U.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-jW_lM1_D.js → sequenceDiagram-SI44F4Z6-B0ufUCuW.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CrzTgsHE.js → sizeCapture-X5ZJPWSS-iPbW55g3.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-B01MvlqE.js → stateDiagram-OKZ733FA-CHiXzEKP.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BtMgzo_H.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-YcF12FiU.js → swimlanes-SLNWSIFB-LyFs7TpX.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cb-OVd9Y.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ClnRcg2s.js → timeline-definition-Z64GVDOM-tNkJnb6z.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-B6gr_z-q.js → vennDiagram-T6HMQDX7-DFm7Oo8o.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CvSgguGH.js → wardleyDiagram-T6FBY63Y-tpblTp9i.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-23OtSZCb.js → xychartDiagram-ELKLHX3M-BCDLWR_D.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +95 -25
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +35 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +3 -1
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +26 -19
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -1
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
- package/dist/worker/console/static-src/operator-chat/workspace-reorder.js +18 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
- package/dist/worker/console/static-src/shell/useWorkspaces.js +150 -34
- package/dist/worker/console/static-src/shell/workspace-route.js +3 -1
- package/dist/worker/console/workspace-context.js +56 -5
- package/dist/worker/console/workspace-registry.js +335 -62
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/observe/health.js +1 -1
- package/dist/worker/observe/node-transparency.js +572 -0
- package/dist/worker/observe/routes.js +51 -1
- package/dist/worker/observe/static/api.js +69 -5
- package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
- package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
- package/dist/worker/observe/static/inspect-bootstrap.d.ts +8 -0
- package/dist/worker/observe/static/inspect-bootstrap.js +57 -0
- package/dist/worker/observe/static/inspect-workspace.js +54 -10
- package/dist/worker/observe/static/operator-chrome.css +23 -4
- package/dist/worker/observe/static/operator-chrome.js +7 -2
- package/dist/worker/observe/static/state.js +3 -1
- package/dist/worker/observe/static/styles.css +1108 -135
- package/dist/worker/observe/static/views/dag-inspector.js +1257 -59
- package/dist/worker/observe/static/views/dags.js +7 -5
- package/dist/worker/observe/static/views/session-timeline.js +210 -10
- package/dist/worker/outcomes/adapters.js +19 -0
- package/dist/worker/outcomes/types.js +1 -0
- package/dist/worker/pool/attempt-lease.js +97 -66
- package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
- package/dist/worker/pool/reconcile.js +46 -1
- package/dist/worker/pool/run-store.js +2 -0
- package/dist/worker/pool/state-projection.js +2 -0
- package/dist/worker/task-spec/workflow-routing.js +8 -3
- package/dist/workflows/dag/artifact-bindings.js +149 -0
- package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +162 -8
- package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
- package/dist/workflows/dag/backend-test-result-contract.js +4 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
- package/dist/workflows/dag/backend-test-writer-completeness.js +11 -0
- package/dist/workflows/dag/budget-enforcement.js +10 -0
- package/dist/workflows/dag/context-receipt.js +305 -0
- package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
- package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
- package/dist/workflows/dag/context-transfer/renderers.js +93 -0
- package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
- package/dist/workflows/dag/init-hybrid.js +151 -4
- package/dist/workflows/dag/node-execution.js +112 -27
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +29 -4
- package/dist/workflows/dag/rerun-plan.js +555 -1
- package/dist/workflows/dag/rerun-run.js +601 -15
- package/dist/workflows/dag/runner.js +20 -1
- package/dist/workflows/dag/skill-snapshot.js +17 -0
- package/dist/workflows/dag/types.js +183 -7
- package/docs/architecture/runtime-boundaries.md +8 -7
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/templates/agent-dag.schema.json +32 -0
- package/docs/templates/backend-test-dag.json +6 -5
- package/docs/templates/init-managed-agents.md +1 -1
- package/docs/templates/product-line/task.yaml +1 -1
- package/package.json +5 -2
- package/skills/loop-agent/references/command-reference.md +21 -1
- package/dist/worker/console/static/assets/channel-amxUpk7o.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +0 -1
- package/dist/worker/console/static/assets/index-BHhUOTri.css +0 -1
- package/dist/worker/console/static/assets/index-DqJbO3-p.js +0 -407
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +0 -8
- /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
|
@@ -45,6 +45,60 @@ const CONSTRAINT_FILE = "执行约束.md";
|
|
|
45
45
|
const REFERENCE_DIRECTORY = "references";
|
|
46
46
|
const MAX_SOURCE_EXCERPT_CHARS = 2000;
|
|
47
47
|
const MAX_INLINE_SOURCE_REFERENCE_DOCUMENTS = 8;
|
|
48
|
+
/**
|
|
49
|
+
* Generation-time compiler for machine-declared structured artifacts. The
|
|
50
|
+
* frozen spec remains the sole runtime authority: this compiler never reads
|
|
51
|
+
* prompt/outputContract prose or workspace filenames, and historical specs
|
|
52
|
+
* loaded outside init-hybrid are left untouched.
|
|
53
|
+
*/
|
|
54
|
+
function stampGeneratedArtifactBindings(spec) {
|
|
55
|
+
const producedByNode = new Map();
|
|
56
|
+
for (const task of spec.tasks) {
|
|
57
|
+
if ((task.producesArtifacts?.length ?? 0) > 0) {
|
|
58
|
+
for (const artifact of task.producesArtifacts ?? []) {
|
|
59
|
+
producedByNode.set(task.id, artifact.artifactId);
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const gate = task.shell?.jsonArtifactGate;
|
|
64
|
+
const structured = task.structuredContractOutput;
|
|
65
|
+
if (!gate && !structured)
|
|
66
|
+
continue;
|
|
67
|
+
const artifactId = `${task.id}.structured-output`;
|
|
68
|
+
const path = gate
|
|
69
|
+
? `${gate.outputDir}/${gate.artifactName}`
|
|
70
|
+
: `contracts/candidates/${task.id}/canonical-contract.json`;
|
|
71
|
+
task.producesArtifacts = [
|
|
72
|
+
{
|
|
73
|
+
artifactId,
|
|
74
|
+
kind: "structured",
|
|
75
|
+
path,
|
|
76
|
+
mediaType: "application/json",
|
|
77
|
+
schemaId: gate?.schemaId ?? FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID,
|
|
78
|
+
revisionPolicy: "replace-file",
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
producedByNode.set(task.id, artifactId);
|
|
82
|
+
}
|
|
83
|
+
for (const task of spec.tasks) {
|
|
84
|
+
const existing = new Set((task.consumesArtifacts ?? []).map((binding) => binding.artifactId));
|
|
85
|
+
const generated = task.depends_on.flatMap((parentNodeId) => {
|
|
86
|
+
const artifactId = producedByNode.get(parentNodeId);
|
|
87
|
+
if (!artifactId || existing.has(artifactId))
|
|
88
|
+
return [];
|
|
89
|
+
existing.add(artifactId);
|
|
90
|
+
return [
|
|
91
|
+
{
|
|
92
|
+
artifactId,
|
|
93
|
+
required: task.dependsPolicy !== "all-or-condition-skip",
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
});
|
|
97
|
+
if (generated.length > 0) {
|
|
98
|
+
task.consumesArtifacts = [...(task.consumesArtifacts ?? []), ...generated];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
48
102
|
const INTERACTIVE_UI_DELIVERY_CONTRACT = [
|
|
49
103
|
"[INTERACTIVE_UI_DELIVERY_CONTRACT]",
|
|
50
104
|
"This is an interactive UI delivery task.",
|
|
@@ -2074,6 +2128,7 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
2074
2128
|
applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
|
|
2075
2129
|
stampTargetTemplateTransientRetryProfile(spec);
|
|
2076
2130
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
2131
|
+
stampGeneratedArtifactBindings(spec);
|
|
2077
2132
|
parseDagSpec(spec);
|
|
2078
2133
|
assertValidDagSpec(spec);
|
|
2079
2134
|
return spec;
|
|
@@ -2279,6 +2334,7 @@ function buildBlockedFrontendMockDag(sources, readOnlyPaths, forbiddenPaths, glo
|
|
|
2279
2334
|
],
|
|
2280
2335
|
};
|
|
2281
2336
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
2337
|
+
stampGeneratedArtifactBindings(spec);
|
|
2282
2338
|
parseDagSpec(spec);
|
|
2283
2339
|
assertValidDagSpec(spec);
|
|
2284
2340
|
return spec;
|
|
@@ -3353,6 +3409,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3353
3409
|
};
|
|
3354
3410
|
spec.tasks = pruneFrontendTasksForRisk(spec.tasks, frontendRisk);
|
|
3355
3411
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
3412
|
+
stampGeneratedArtifactBindings(spec);
|
|
3356
3413
|
parseDagSpec(spec);
|
|
3357
3414
|
assertValidDagSpec(spec);
|
|
3358
3415
|
return spec;
|
|
@@ -4020,6 +4077,84 @@ const BACKEND_TEST_SKILLS_BY_ROLE = {
|
|
|
4020
4077
|
verifier: ["verification-before-completion", "systematic-debugging"],
|
|
4021
4078
|
closeout: ["loop-agent", "verification-before-completion"],
|
|
4022
4079
|
};
|
|
4080
|
+
/**
|
|
4081
|
+
* Read-only Worker workflow for producing a revision-eligible Backend Test
|
|
4082
|
+
* Analysis v2 contract without crossing into test generation or execution.
|
|
4083
|
+
* The explicit consumer keeps artifact lineage meaningful for safe
|
|
4084
|
+
* continuation: replacing the contract reruns review + deterministic closeout
|
|
4085
|
+
* only.
|
|
4086
|
+
*/
|
|
4087
|
+
function buildBackendTestAnalysisHybridDag(sources) {
|
|
4088
|
+
const analysis = buildAnalyzeInputsNode(sources);
|
|
4089
|
+
const contract = buildBackendTestAnalysisContractGateNode(sources);
|
|
4090
|
+
const review = {
|
|
4091
|
+
id: "review-backend-test-analysis-pi",
|
|
4092
|
+
depends_on: [contract.id],
|
|
4093
|
+
role: "reviewer",
|
|
4094
|
+
executor: "pi",
|
|
4095
|
+
complexity: "LOW",
|
|
4096
|
+
writePolicy: "read-only",
|
|
4097
|
+
allowedPaths: commonReadOnlyPaths(sources),
|
|
4098
|
+
forbiddenPaths: commonForbiddenPaths(sources),
|
|
4099
|
+
outputContract: "Concise Markdown review of the validated Backend Test Analysis v2 contract, covering source binding, explicit requirement coverage, evidence gaps, risks, and downstream readiness. No file writes.",
|
|
4100
|
+
subtask_prompt: [
|
|
4101
|
+
"Review the validated run-owned `contracts/backend-test-analysis.json` artifact.",
|
|
4102
|
+
"Treat that structured artifact as the authoritative analysis contract; do not substitute the producer's assistant prose.",
|
|
4103
|
+
"Check that every explicit AC/REQ/BR is represented by acceptanceCriteria or an evidenceGap, source references are precise, and unknown behavior remains an evidence gap rather than an invented requirement.",
|
|
4104
|
+
"Summarize source-binding integrity, coverage, risks, evidence gaps, and whether the contract is ready for a later backend-test generation workflow.",
|
|
4105
|
+
"Read-only: do not modify code, docs, artifacts, or repository files.",
|
|
4106
|
+
].join("\n\n"),
|
|
4107
|
+
};
|
|
4108
|
+
const closeout = {
|
|
4109
|
+
id: "backend-test-analysis-closeout-static",
|
|
4110
|
+
depends_on: [review.id],
|
|
4111
|
+
role: "closeout",
|
|
4112
|
+
executor: "static",
|
|
4113
|
+
complexity: "LOW",
|
|
4114
|
+
writePolicy: "none",
|
|
4115
|
+
allowedPaths: [],
|
|
4116
|
+
forbiddenPaths: commonForbiddenPaths(sources),
|
|
4117
|
+
outputContract: "Deterministic handoff pointing to the validated analysis contract and its independent read-only review.",
|
|
4118
|
+
subtask_prompt: "Close the read-only analysis workflow after the structured contract and review both succeed.",
|
|
4119
|
+
static: {
|
|
4120
|
+
resultMarkdown: [
|
|
4121
|
+
"# Backend-test analysis closeout",
|
|
4122
|
+
"",
|
|
4123
|
+
"Validated contract: `contracts/backend-test-analysis.json`.",
|
|
4124
|
+
"Independent review: `review-backend-test-analysis-pi`.",
|
|
4125
|
+
].join("\n"),
|
|
4126
|
+
},
|
|
4127
|
+
};
|
|
4128
|
+
const spec = {
|
|
4129
|
+
version: 3,
|
|
4130
|
+
title: `Backend test analysis DAG: ${sources.taskConfig.title}`,
|
|
4131
|
+
runtimeContract: GENERATED_DAG_RUNTIME_CONTRACT,
|
|
4132
|
+
outputLanguage: sources.outputLanguage ?? DEFAULT_DAG_OUTPUT_LANGUAGE,
|
|
4133
|
+
objective: extractObjective(sources.requirementMarkdown, sources.taskConfig.title),
|
|
4134
|
+
successCriteria: [
|
|
4135
|
+
"A strict Backend Test Analysis v2 contract is materialized with immutable source binding",
|
|
4136
|
+
"Every explicit requirement is covered or preserved as an evidence gap",
|
|
4137
|
+
"An independent read-only review consumes the validated structured artifact",
|
|
4138
|
+
],
|
|
4139
|
+
globalConstraints: [
|
|
4140
|
+
...sources.taskConfig.hardConstraints,
|
|
4141
|
+
"This workflow is analysis-only: no repository writer, dynamic expansion, test generation, or test execution is allowed.",
|
|
4142
|
+
],
|
|
4143
|
+
defaults: {
|
|
4144
|
+
...BACKEND_TEST_DEFAULTS,
|
|
4145
|
+
contextProfile: sources.taskConfig.contextProfile,
|
|
4146
|
+
},
|
|
4147
|
+
skillsByRole: BACKEND_TEST_SKILLS_BY_ROLE,
|
|
4148
|
+
executorModels: sources.executorModelMatrix ?? DEFAULT_DAG_EXECUTOR_MODELS,
|
|
4149
|
+
verifyStrategy: resolveDagVerifyStrategy(sources.taskConfig),
|
|
4150
|
+
tasks: [analysis, contract, review, closeout],
|
|
4151
|
+
};
|
|
4152
|
+
applyDefaultReadOnlyRetryPolicy(spec);
|
|
4153
|
+
stampGeneratedArtifactBindings(spec);
|
|
4154
|
+
parseDagSpec(spec);
|
|
4155
|
+
assertValidDagSpec(spec);
|
|
4156
|
+
return spec;
|
|
4157
|
+
}
|
|
4023
4158
|
/**
|
|
4024
4159
|
* Plan D: gap-fill incremental DAG (mode=gap-fill).
|
|
4025
4160
|
*
|
|
@@ -4421,6 +4556,7 @@ async function buildBackendTestGapFillDag(sources) {
|
|
|
4421
4556
|
spec.backendTestSharedSetup = { ...sharedSetup };
|
|
4422
4557
|
}
|
|
4423
4558
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
4559
|
+
stampGeneratedArtifactBindings(spec);
|
|
4424
4560
|
parseDagSpec(spec);
|
|
4425
4561
|
assertValidDagSpec(spec);
|
|
4426
4562
|
return spec;
|
|
@@ -4520,7 +4656,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
4520
4656
|
"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.",
|
|
4521
4657
|
"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.",
|
|
4522
4658
|
"Mandatory module index: include a `## Module Index` table in the plan artifact 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.",
|
|
4523
|
-
"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), using bare semicolon-separated identifier values inside the single table cell (for example `ACTIVE; ARCHIVED`, with no Markdown backticks or prose); Required Slots
|
|
4659
|
+
"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), using bare semicolon-separated identifier values inside the single table cell (for example `ACTIVE; ARCHIVED`, with no Markdown backticks or prose); Required Slots must contain `each-value` and exactly one `not-in-set`, plus `omitted` only when the parameter is optional. Before returning, expand every declared partition into its complete deterministic exact slot ID set: one `TP-<Partition ID>-<VALUE-TOKEN>` per Domain value, `TP-<Partition ID>-OMITTED` only for an optional axis, and exactly one `TP-<Partition ID>-NOT-IN-SET`. Every expanded slot ID must appear verbatim in the binding Rule's `Required Test Points` cell and be assigned to concrete Case IDs in that same Coverage Matrix row; ordinary alias/family Test Points do not replace this inventory. 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.",
|
|
4524
4660
|
"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.",
|
|
4525
4661
|
...(sharedSetupPrompt ? [sharedSetupPrompt] : []),
|
|
4526
4662
|
intake.boundedSourceContext,
|
|
@@ -4603,7 +4739,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
4603
4739
|
"Write human-readable content in Simplified Chinese by default. Keep English only for machine-readable IDs and technical literals such as Case/AC/REQ/BR IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and exact source citations.",
|
|
4604
4740
|
'Write the module {{item.stem}} as readable case cards covering every in-scope rule/Test Point the README Coverage Matrix assigns to this module. Every case starts with `## BE-<MODULE>-<NNN>|<中文用例名称>`. `<NNN>` is exactly three zero-padded digits (`001`, `002`, ...), never two digits (`01`), a bare number, or an alphabetic suffix such as `011A`. Every case must include `### 覆盖规则`, `### 测试点`, `### 场景类型`, `### 前置条件`, `### 操作步骤`, `### 预期结果`, and `### 自动化映射` Do not group cases under "## 测试类 ..." (or any h2 grouping) headings that force Cases down to h3; each Case must be a direct h2 (`##`), and its seven sections must be h3 (`###`) children of that Case. If you need to convey a pytest class, state it inside the Case\'s `### 自动化映射` instead. Forbidden: `## 测试类 X` then `### BE-PD-001` and `### 覆盖规则` at the same h3 level. Required: `## BE-PD-001` then `### 覆盖规则`.; `覆盖规则` and `测试点` must reference exact Matrix Rule Keys/Test Points. Add `测试目的`, `验收标准`, `需求依据`, and `测试数据` for readable evidence. The `验收标准` section must list the exact applicable `AC-...` IDs, and every explicit task AC must appear in at least one Case. Every automatable case explicitly names its target pytest script and exactly one primary symbol so traceability scans only that script/symbol. Evidence-only meta cases that exist solely for non-executable assertion/cross-cutting process evidence may declare `脚本:无` and `primary symbol:无` with empty `变体测试点`, and must not invent a business pytest item.',
|
|
4605
4741
|
"Name this module file with the stable lowercase business stem `{{item.stem}}` (filename `testcase/md/{{item.stem}}.md`). Priority-only stems `p0`, `p1` and `p2` are forbidden and must never produce `p0.md` or `test_p0.py`. Pure hexadecimal/hash-like opaque stems such as `a401606` and `deadbeef` are also forbidden. Do not use Case-ID-like module filenames. For every automatable case, `自动化映射` must name exactly `testcase/test_{{item.stem}}.py`, where the module stem is this Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `health` → `testcase/test_health.py`; `resource_notes` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.",
|
|
4606
|
-
"Scenario Partition slots: when README declares `## Scenario Partitions`, every slot of each declared partition MUST appear in this module's Cases as exactly one variant Test Point with the deterministic ID `TP-<Partition ID>-<VALUE-TOKEN>` (each-value), `TP-<Partition ID>-OMITTED` (optional axis only) and exactly one `TP-<Partition ID>-NOT-IN-SET` complement slot with `intent=enum-invalid`. Example: Partition ID `SP-GET-API-RESOURCE-NOTES-STATUS` → `TP-SP-GET-API-RESOURCE-NOTES-STATUS-ACTIVE`. Slot IDs copy the declared Partition ID exactly; never drop the HTTP method, invent, merge, renumber or split slot IDs. Prefer ONE Case per partition with a parameter table over duplicated Cases per value. The not-in-set slot value must be a concrete literal absent from the Domain (e.g. `UNKNOWN_TYPE`) and its expected result must come from the bound source — when Expected by Slot is GAP, the Case states the expectation as GAP evidence, never a guessed 空列表/400. Never create cross-axis combination variants beyond the single documented nominal.",
|
|
4742
|
+
"Scenario Partition slots: when README declares `## Scenario Partitions`, every slot of each declared partition MUST appear in this module's Cases as exactly one variant Test Point with the deterministic ID `TP-<Partition ID>-<VALUE-TOKEN>` (each-value), `TP-<Partition ID>-OMITTED` (optional axis only) and exactly one `TP-<Partition ID>-NOT-IN-SET` complement slot with `intent=enum-invalid`. Example: Partition ID `SP-GET-API-RESOURCE-NOTES-STATUS` → `TP-SP-GET-API-RESOURCE-NOTES-STATUS-ACTIVE`. Slot IDs copy the declared Partition ID exactly; never drop the HTTP method, invent, merge, renumber or split slot IDs. Before returning, derive the complete exact slot set from every applicable Scenario Partitions row and verify that the module Cases declare and bind every slot assigned by the Coverage Matrix; ordinary alias Test Points do not satisfy a partition slot, and aggregate aliases such as `SINGLE`/`MULTIPLE` are forbidden. Prefer ONE Case per partition with a parameter table over duplicated Cases per value. The not-in-set slot value must be a concrete literal absent from the Domain (e.g. `UNKNOWN_TYPE`) and its expected result must come from the bound source — when Expected by Slot is GAP, the Case states the expectation as GAP evidence, never a guessed 空列表/400. Never create cross-axis combination variants beyond the single documented nominal.",
|
|
4607
4743
|
"For every variant Test Point, write its machine-checkable `场景意图: <TP-ID>; operation=...; target=...; intent=...` line inside that same Case body/自动化映射. Never collect Scenario Intent lines in a file-level appendix, implementation-details block, or another Case; local TP ownership is mandatory.",
|
|
4608
4744
|
"Every Case must keep at least one numbered executable line under `### 操作步骤`; a compact variant/result table may follow but must not replace the numbered action anchor. Keep numbered/bulleted independently assertable results under `### 预期结果`. The exact `### 操作步骤` and `### 预期结果` headings must remain present for every Case, including compact/table-based Cases; never compress later Cases by dropping required headings. Every result must name the observable HTTP status, response field/value, state transition or membership condition, never vague wording such as ‘符合预期’.",
|
|
4609
4745
|
"In every `自动化映射`, use exactly these machine-readable list labels: `脚本`, `primary symbol`, `变体测试点`, `场景断言测试点`, `横切证据测试点`, plus a deterministic payload contract. For operations without a request body write `Payload Contract: none`. Otherwise write `Payload Required Paths`, `Payload Allowed Paths`, and `Payload Enum` (write `none` when there is no enum); nested fields use dot paths such as `approver.name`. Each Case describes exactly one target request payload contract: put every payload label on its own list line, never concatenate multiple operations or setup POST/PUT contracts into one label line, and never repeat a `Payload Contract:` token inside explanatory prose/details after the machine-readable line. Values must come only from bound API/DTO evidence, never guesses. Each Test Point from `### 测试点` must appear in exactly one binding list, and every Test Point named in any binding list must also be declared in that Case's `### 测试点`; write `无` for an empty list. A variant Test Point is atomic: one exact endpoint/input/precondition/outcome row equals one exact pytest item and one exact TP ID. If a parameter table has five rows, declare five distinct variant TP IDs in Markdown; never declare one family TP and append row suffixes only in pytest. Classify as `variant` only when endpoint, request input, precondition business state, or expected outcome genuinely changes and therefore needs an independent pytest parameter item. Classify CRUD checkpoints, status/body/header/schema assertions and multiple checks over the same response/journey as `assertion`; classify shared HTTP logging/redaction/truncation evidence as `cross-cutting`. Never create a Test Point merely to parameterize a checkpoint. Every non-cross-cutting TP ID is owned by exactly one Case; when the same response/schema/error assertion is needed in different Cases, use distinct Case-specific TP IDs instead of reusing one assertion TP across Cases. Keep the script path identical to the module one-to-one path and declare exactly one primary symbol named with the canonical Case prefix, for example `BE-RN-003` → `test_BE_RN_003_<description>`; non-Case-prefixed primary symbols are forbidden because parameterized item association must remain deterministic. For evidence-only meta Cases with no executable business journey, write `脚本:无` and `primary symbol:无`, keep `变体测试点:无`, and place process evidence only in assertion/cross-cutting lists. If the bound contract only says an identifier is returned/present, do not declare a concrete identifier type. If a 404 Case needs a nonexistent path identifier but its syntax/type is unspecified, define a create-delete-derived valid identifier journey instead of an arbitrary UUID/text placeholder. For redaction scenarios, list sensitive header/field key names only. Never write any header-name-and-value pair, credential placeholder, fake token, anti-example, or other secret-shaped literal in Markdown; state only that a test-only value is supplied at runtime and omitted. Put implementation-only restrictions in a concise `<details>` block rather than dominating the main case flow. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.",
|
|
@@ -4648,7 +4784,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
4648
4784
|
"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.",
|
|
4649
4785
|
"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.",
|
|
4650
4786
|
"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.",
|
|
4651
|
-
"Scenario Partitions synchronization: when the run-owned plan declares `## Scenario Partitions`, verify each declared partition's slots are fully materialized as variant Test Points with exact `TP-<Partition ID>-...` IDs (each-value per Domain value, OMITTED only for optional axes, exactly one NOT-IN-SET with intent=enum-invalid). Directly add missing slot rows/Cases. Record an illegal plan Partition row that has no source-backed finite domain as GAP/CONFLICT and remove only its derived `TP-SP-*` slots/Cases from target modules; never modify the immutable plan artifact. Never delete a legal source-backed partition or drop its complement slot to force coverage green. When the bound source does not document the complement expectation, keep the slot with GAP expected instead of guessing. Body-field validation enums (`TP-<FIELD>-ENUM-*`) are NOT partitions — do not add partition rows for them.",
|
|
4787
|
+
"Scenario Partitions synchronization: when the run-owned plan declares `## Scenario Partitions`, verify each declared partition's slots are fully materialized as variant Test Points with exact `TP-<Partition ID>-...` IDs (each-value per Domain value, OMITTED only for optional axes, exactly one NOT-IN-SET with intent=enum-invalid). Before returning, derive the complete exact slot set from every legal Scenario Partitions row and compare it with both the binding Coverage Matrix Rule's Required Test Points and the final Case `### 测试点`/`变体测试点` sets; directly add every missing exact slot to the already-assigned Case IDs; ordinary alias Test Points do not satisfy a partition slot, and aggregate aliases such as `SINGLE`/`MULTIPLE` are forbidden. Directly add missing slot rows/Cases. Record an illegal plan Partition row that has no source-backed finite domain as GAP/CONFLICT and remove only its derived `TP-SP-*` slots/Cases from target modules; never modify the immutable plan artifact. Never delete a legal source-backed partition or drop its complement slot to force coverage green. When the bound source does not document the complement expectation, keep the slot with GAP expected instead of guessing. Body-field validation enums (`TP-<FIELD>-ENUM-*`) are NOT partitions — do not add partition rows for them.",
|
|
4652
4788
|
"Before returning, verify that every explicit source AC/REQ/BR, error code and strong DTO field token appears in the run-owned plan 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.",
|
|
4653
4789
|
"Read only indexed source paths. Do not scan the repository, modify source/**, generate pytest, execute tests, or emit JSON.",
|
|
4654
4790
|
...(sharedSetupPrompt ? [sharedSetupPrompt] : []),
|
|
@@ -4776,6 +4912,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
4776
4912
|
// intentional repair as an out-of-bounds mutation.
|
|
4777
4913
|
collectionAssess.writePolicy = "exclusive";
|
|
4778
4914
|
collectionAssess.writeSet = [applyLayout("testcase/**/test_*.py")];
|
|
4915
|
+
collectionAssess.allowedPaths = Array.from(new Set([...ro, ...collectionAssess.writeSet]));
|
|
4779
4916
|
const repairPytest = {
|
|
4780
4917
|
id: "repair-backend-pytest-collection-pi",
|
|
4781
4918
|
depends_on: [collectionAssess.id],
|
|
@@ -4814,7 +4951,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
4814
4951
|
writerOutcomePolicy: { type: "implementation-outcome-v1", requireChangedFiles: true },
|
|
4815
4952
|
outputContract: "First non-empty line is IMPLEMENTATION_OUTCOME: changed|blocked, followed by a concise repair summary. This node runs only for REPAIRABLE initial facts, so already-satisfied is invalid and a successful outcome requires a non-empty bounded diff. Modify only generated pytest scripts/helpers/factories and preserve every Markdown Case, Test Point, primary symbol and assertion meaning.",
|
|
4816
4953
|
subtask_prompt: [
|
|
4817
|
-
"Repair the generated backend pytest asset as one bounded program using the direct upstream collection assessment. This is the only repair attempt and happens before any business test body execution. Treat any upstream line such as `Repair paths: testcase/test_x.py` as
|
|
4954
|
+
"Repair the generated backend pytest asset as one bounded program using the direct upstream collection assessment. This is the only repair attempt and happens before any business test body execution. The direct upstream JSON includes authoritative `repairPaths` and bounded `repairFindings`; treat both as the complete mandatory checklist without searching for a run directory or report file. Treat any upstream line such as `Repair paths: testcase/test_x.py` as equivalent authoritative repairPaths evidence. Directly read and edit that testcase path; do not search for separate root-level `contracts/**`, guess a DAG run directory, or require another report artifact. If the read tool successfully returns the testcase file, the path exists—continue the bounded repair and never later claim that file is absent.",
|
|
4818
4955
|
"Initial status REPAIRABLE means at least one listed finding remains: `already-satisfied` is forbidden, and you must produce a non-empty bounded diff on repairPaths before returning `IMPLEMENTATION_OUTCOME: changed`. Fix only readiness-proven generated testcase-local defects on initial facts repairPaths: create exact safe missing mapped test_*.py paths, repair syntax/import/symbol/decorator/parameterization, close generated fixture dependencies/plugin registration, and repair initial Markdown-to-pytest correspondence findings. Use this deterministic repair map instead of reading analyzer implementation: findings about `Case-ID`, `Assertion-Test-Points`, or `Cross-Cutting-Test-Points` are fixed by editing the declared primary symbol docstring metadata lines; variant binding findings are fixed in the literal direct `pytest.param(..., id=\"TP-...\")` row; primary-symbol cardinality/name findings are fixed in the function name or duplicate primary symbols; script mismatch is fixed only on the authoritative assessment repairPaths; payload findings are fixed in request payload construction. Do not read controller `src/**` or inspect JS/TS analyzer code. Do not search for `testcase/**/README.md`. Never invent a business pytest symbol for evidence-only Markdown Cases that declare `脚本/primary symbol=无` with empty variants. For fixture defects inspect both provider and importer listed by repairPaths; fix ScopeMismatch by aligning fixture scopes or inlining request-scoped values so module fixtures never depend on function fixtures; when a shared fixture depends on sibling fixtures, register the whole provider module through an exact pytest_plugins declaration rather than importing only the outer fixture. Do not create unrelated pytest scripts.",
|
|
4819
4956
|
"This is the single pytest incremental synchronization round. The `Findings` in `reports/backend-test-pytest-collection-initial.md` are the mandatory repair checklist: resolve every repairable listed finding on every authoritative `Repair paths` file before considering any other advisory evidence, and never substitute an unrelated scenario-param cleanup for a listed correspondence/collection defect. For every assessment-listed path, compare the effective Markdown Case/Test Points/test data and its `Payload Contract`/`Payload Required Paths`/`Payload Allowed Paths`/`Payload Enum` labels with the generated module. Incrementally add or repair only missing symbols, params, assertions and payload builders. Repair every assessment-listed missing nested path, unexpected key and enum mismatch; preserve exact DTO keys, nested shapes, enum/boundary literals, operation transport and business preconditions; remove guessed replacement keys only when the effective Markdown proves the exact contract. Keep path/query/header identifiers and scenario-control metadata separate from DTO patches and JSON bodies; an `id` used for a path target must be passed to the request path/helper, never inserted into a body patch unless `id` is explicitly listed in Payload Allowed Paths. Flatten every variant into a literal direct `pytest.param(..., id=\"TP-...\")` row; replace `_post_case`/`_put_case` or other parameter-row factories because correspondence and scenario readiness require the actual row values and IDs to be statically visible. Also repair helper call sites to match their defined return signatures; do not tuple-unpack a helper that returns one scalar value.",
|
|
4820
4957
|
"Preserve final testcase/md/** semantics, every Case ID, Rule/Test Point binding, primary symbol, parameter ID, expected status/body/schema assertion, HTTP logging, redaction and truncation behavior.",
|
|
@@ -4922,6 +5059,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
4922
5059
|
spec.backendTestSharedSetup = { ...intake.sharedSetup };
|
|
4923
5060
|
}
|
|
4924
5061
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
5062
|
+
stampGeneratedArtifactBindings(spec);
|
|
4925
5063
|
parseDagSpec(spec);
|
|
4926
5064
|
assertValidDagSpec(spec);
|
|
4927
5065
|
return spec;
|
|
@@ -5676,6 +5814,7 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
5676
5814
|
};
|
|
5677
5815
|
applyFrontendTestLayoutToDagSpec(spec, layout);
|
|
5678
5816
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
5817
|
+
stampGeneratedArtifactBindings(spec);
|
|
5679
5818
|
parseDagSpec(spec);
|
|
5680
5819
|
assertValidDagSpec(spec);
|
|
5681
5820
|
return spec;
|
|
@@ -6178,6 +6317,7 @@ function buildKnowledgeSyncHybridDag(sources) {
|
|
|
6178
6317
|
buildKnowledgeSyncPointerNode(sources, featureId),
|
|
6179
6318
|
],
|
|
6180
6319
|
};
|
|
6320
|
+
stampGeneratedArtifactBindings(spec);
|
|
6181
6321
|
parseDagSpec(spec);
|
|
6182
6322
|
assertValidDagSpec(spec);
|
|
6183
6323
|
return spec;
|
|
@@ -6624,6 +6764,7 @@ function buildKnowledgeGraphBootstrapHybridDag(sources) {
|
|
|
6624
6764
|
buildKgBootstrapMaterializeNode(sources),
|
|
6625
6765
|
],
|
|
6626
6766
|
};
|
|
6767
|
+
stampGeneratedArtifactBindings(spec);
|
|
6627
6768
|
parseDagSpec(spec);
|
|
6628
6769
|
assertValidDagSpec(spec);
|
|
6629
6770
|
return spec;
|
|
@@ -6721,6 +6862,8 @@ async function buildHybridDagForTemplate(sources, template, options = {}) {
|
|
|
6721
6862
|
}
|
|
6722
6863
|
else if (template === "frontend-test-dag")
|
|
6723
6864
|
spec = buildFrontendTestHybridDag(sources);
|
|
6865
|
+
else if (template === "backend-test-analysis-dag")
|
|
6866
|
+
spec = buildBackendTestAnalysisHybridDag(sources);
|
|
6724
6867
|
else if (template === "backend-test-dag")
|
|
6725
6868
|
spec = await buildBackendTestHybridDag(sources);
|
|
6726
6869
|
else if (template === "knowledge-sync-dag")
|
|
@@ -6757,6 +6900,7 @@ async function buildHybridDagForTemplate(sources, template, options = {}) {
|
|
|
6757
6900
|
template === "supervised-implementation") {
|
|
6758
6901
|
stampTargetTemplateTransientRetryProfile(spec);
|
|
6759
6902
|
}
|
|
6903
|
+
stampGeneratedArtifactBindings(spec);
|
|
6760
6904
|
spec = parseDagSpec(spec);
|
|
6761
6905
|
assertValidDagSpec(spec);
|
|
6762
6906
|
return spec;
|
|
@@ -6764,6 +6908,7 @@ async function buildHybridDagForTemplate(sources, template, options = {}) {
|
|
|
6764
6908
|
const GOVERNANCE_DISALLOWED_TEMPLATES = new Set([
|
|
6765
6909
|
"frontend-implementation",
|
|
6766
6910
|
"frontend-test-dag",
|
|
6911
|
+
"backend-test-analysis-dag",
|
|
6767
6912
|
"backend-test-dag",
|
|
6768
6913
|
"knowledge-sync-dag",
|
|
6769
6914
|
"knowledge-graph-bootstrap-dag",
|
|
@@ -7124,6 +7269,7 @@ function buildReviewGatedHybridDag(standard, sources) {
|
|
|
7124
7269
|
applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
|
|
7125
7270
|
stampTargetTemplateTransientRetryProfile(spec);
|
|
7126
7271
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
7272
|
+
stampGeneratedArtifactBindings(spec);
|
|
7127
7273
|
parseDagSpec(spec);
|
|
7128
7274
|
assertValidDagSpec(spec);
|
|
7129
7275
|
return spec;
|
|
@@ -7629,6 +7775,7 @@ async function buildSupervisedHybridDag(standard, sources) {
|
|
|
7629
7775
|
applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
|
|
7630
7776
|
stampTargetTemplateTransientRetryProfile(spec);
|
|
7631
7777
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
7778
|
+
stampGeneratedArtifactBindings(spec);
|
|
7632
7779
|
parseDagSpec(spec);
|
|
7633
7780
|
assertValidDagSpec(spec);
|
|
7634
7781
|
return spec;
|
|
@@ -10,6 +10,7 @@ import { writeNodeRecord, writeNodeSkillArtifacts } from "./run-store.js";
|
|
|
10
10
|
import { resolveContextPolicy } from "./context-policy.js";
|
|
11
11
|
import { buildDagNodePromptEnvelope, formatConvergenceFeedbackBlock, } from "./prompt.js";
|
|
12
12
|
import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
|
|
13
|
+
import { materializeDeclaredArtifactFacts } from "./artifact-bindings.js";
|
|
13
14
|
import { buildOutputLimitRecoverySection, loadBackendTestWriterProgressForRetry, } from "./backend-test-writer-completeness.js";
|
|
14
15
|
import { computeBackoffDelayMs, isCanonicalFinalVerifyShellRetryCandidate, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isTargetTemplateTransientRetryNode, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, VERIFY_SHELL_RETRY_POLICY, } from "./retry-policy.js";
|
|
15
16
|
import { applyNodeActivity, evaluateNodeLiveness, resolveLivenessPolicy, } from "./liveness-policy.js";
|
|
@@ -42,7 +43,11 @@ function canonicalApprovalJson(value) {
|
|
|
42
43
|
export function parseAndValidateFinalWriteSetApproval(input) {
|
|
43
44
|
const binding = input.task.finalWriteSetApproval;
|
|
44
45
|
if (!binding) {
|
|
45
|
-
return {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
reason: "missing final write-set approval binding",
|
|
49
|
+
approvalSourceNodeId: "(missing)",
|
|
50
|
+
};
|
|
46
51
|
}
|
|
47
52
|
const source = input.state.nodes[binding.approvalSourceNodeId];
|
|
48
53
|
if (source?.status !== "FINISHED" || parseProcessVerdict(source) !== "pass") {
|
|
@@ -53,20 +58,44 @@ export function parseAndValidateFinalWriteSetApproval(input) {
|
|
|
53
58
|
};
|
|
54
59
|
}
|
|
55
60
|
const raw = canonicalNodeOutput(source);
|
|
56
|
-
const blocks = [
|
|
61
|
+
const blocks = [
|
|
62
|
+
...raw.matchAll(/```FINAL_WRITE_SET_APPROVAL_JSON\s*\r?\n([\s\S]*?)\r?\n```/g),
|
|
63
|
+
];
|
|
57
64
|
if (blocks.length !== 1) {
|
|
58
|
-
return {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
reason: `expected exactly one FINAL_WRITE_SET_APPROVAL_JSON block, found ${blocks.length}`,
|
|
68
|
+
approvalSourceNodeId: binding.approvalSourceNodeId,
|
|
69
|
+
};
|
|
59
70
|
}
|
|
60
71
|
let approval;
|
|
61
72
|
try {
|
|
62
73
|
approval = JSON.parse(blocks[0][1]);
|
|
63
74
|
}
|
|
64
75
|
catch {
|
|
65
|
-
return {
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
reason: "final write-set approval is not valid JSON",
|
|
79
|
+
approvalSourceNodeId: binding.approvalSourceNodeId,
|
|
80
|
+
};
|
|
66
81
|
}
|
|
67
|
-
const required = [
|
|
68
|
-
|
|
69
|
-
|
|
82
|
+
const required = [
|
|
83
|
+
"schemaVersion",
|
|
84
|
+
"writerNodeId",
|
|
85
|
+
"approvalSourceNodeId",
|
|
86
|
+
"auditedPlanNodeId",
|
|
87
|
+
"approvedWriteSet",
|
|
88
|
+
"taskContractSha256",
|
|
89
|
+
"auditedPlanSha256",
|
|
90
|
+
"approvalDigest",
|
|
91
|
+
];
|
|
92
|
+
if (Object.keys(approval).length !== required.length ||
|
|
93
|
+
required.some((key) => !(key in approval))) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
reason: "final write-set approval has an invalid schema",
|
|
97
|
+
approvalSourceNodeId: binding.approvalSourceNodeId,
|
|
98
|
+
};
|
|
70
99
|
}
|
|
71
100
|
const approved = approval.approvedWriteSet;
|
|
72
101
|
if (approval.schemaVersion !== 1 ||
|
|
@@ -79,12 +108,20 @@ export function parseAndValidateFinalWriteSetApproval(input) {
|
|
|
79
108
|
typeof approval.taskContractSha256 !== "string" ||
|
|
80
109
|
typeof approval.auditedPlanSha256 !== "string" ||
|
|
81
110
|
typeof approval.approvalDigest !== "string") {
|
|
82
|
-
return {
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
reason: "final write-set approval binding or field types are invalid",
|
|
114
|
+
approvalSourceNodeId: binding.approvalSourceNodeId,
|
|
115
|
+
};
|
|
83
116
|
}
|
|
84
117
|
if (!/^[a-f0-9]{64}$/.test(approval.taskContractSha256) ||
|
|
85
118
|
!/^[a-f0-9]{64}$/.test(approval.auditedPlanSha256) ||
|
|
86
119
|
!/^[a-f0-9]{64}$/.test(approval.approvalDigest)) {
|
|
87
|
-
return {
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
reason: "final write-set approval digest fields are invalid",
|
|
123
|
+
approvalSourceNodeId: binding.approvalSourceNodeId,
|
|
124
|
+
};
|
|
88
125
|
}
|
|
89
126
|
const canonicalPayload = { ...approval };
|
|
90
127
|
delete canonicalPayload.approvalDigest;
|
|
@@ -92,33 +129,70 @@ export function parseAndValidateFinalWriteSetApproval(input) {
|
|
|
92
129
|
.update(canonicalApprovalJson(canonicalPayload))
|
|
93
130
|
.digest("hex");
|
|
94
131
|
if (approval.approvalDigest !== expectedDigest) {
|
|
95
|
-
return {
|
|
132
|
+
return {
|
|
133
|
+
ok: false,
|
|
134
|
+
reason: "final write-set approval digest mismatch",
|
|
135
|
+
approvalSourceNodeId: binding.approvalSourceNodeId,
|
|
136
|
+
};
|
|
96
137
|
}
|
|
97
138
|
const expectedTaskDigest = input.spec.taskContractBinding?.canonicalHash;
|
|
98
139
|
const plan = input.state.nodes[binding.auditedPlanNodeId];
|
|
99
140
|
const expectedPlanDigest = createHash("sha256")
|
|
100
141
|
.update(canonicalNodeOutput(plan))
|
|
101
142
|
.digest("hex");
|
|
102
|
-
if (!expectedTaskDigest ||
|
|
103
|
-
|
|
143
|
+
if (!expectedTaskDigest ||
|
|
144
|
+
approval.taskContractSha256 !== expectedTaskDigest ||
|
|
145
|
+
approval.auditedPlanSha256 !== expectedPlanDigest) {
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
reason: "final write-set approval is stale for the task contract or audited plan",
|
|
149
|
+
approvalSourceNodeId: binding.approvalSourceNodeId,
|
|
150
|
+
};
|
|
104
151
|
}
|
|
105
152
|
const effectiveWriteSet = approved;
|
|
106
|
-
if (effectiveWriteSet.length === 0 ||
|
|
107
|
-
|
|
153
|
+
if (effectiveWriteSet.length === 0 ||
|
|
154
|
+
new Set(effectiveWriteSet).size !== effectiveWriteSet.length) {
|
|
155
|
+
return {
|
|
156
|
+
ok: false,
|
|
157
|
+
reason: "final write-set approval must contain a non-empty ordered unique path set",
|
|
158
|
+
approvalSourceNodeId: binding.approvalSourceNodeId,
|
|
159
|
+
};
|
|
108
160
|
}
|
|
109
161
|
for (const entry of effectiveWriteSet) {
|
|
110
162
|
const normalized = entry.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
111
|
-
if (!normalized ||
|
|
112
|
-
|
|
163
|
+
if (!normalized ||
|
|
164
|
+
normalized === "." ||
|
|
165
|
+
normalized === ".." ||
|
|
166
|
+
normalized.includes("*") ||
|
|
167
|
+
normalized.includes("?") ||
|
|
168
|
+
normalized.includes("REPLACE/") ||
|
|
169
|
+
normalized.includes("PLACEHOLDER")) {
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
reason: `final write-set approval contains a broad or placeholder path: ${entry}`,
|
|
173
|
+
approvalSourceNodeId: binding.approvalSourceNodeId,
|
|
174
|
+
};
|
|
113
175
|
}
|
|
114
176
|
if (!input.task.allowedPaths.some((allowed) => pathMatchesPattern(normalized, allowed))) {
|
|
115
|
-
return {
|
|
177
|
+
return {
|
|
178
|
+
ok: false,
|
|
179
|
+
reason: `final write-set approval exceeds allowedPaths: ${entry}`,
|
|
180
|
+
approvalSourceNodeId: binding.approvalSourceNodeId,
|
|
181
|
+
};
|
|
116
182
|
}
|
|
117
183
|
if (input.task.forbiddenPaths.some((forbidden) => pathMatchesPattern(normalized, forbidden))) {
|
|
118
|
-
return {
|
|
184
|
+
return {
|
|
185
|
+
ok: false,
|
|
186
|
+
reason: `final write-set approval overlaps forbiddenPaths: ${entry}`,
|
|
187
|
+
approvalSourceNodeId: binding.approvalSourceNodeId,
|
|
188
|
+
};
|
|
119
189
|
}
|
|
120
190
|
}
|
|
121
|
-
return {
|
|
191
|
+
return {
|
|
192
|
+
ok: true,
|
|
193
|
+
effectiveWriteSet,
|
|
194
|
+
approvalDigest: approval.approvalDigest,
|
|
195
|
+
};
|
|
122
196
|
}
|
|
123
197
|
export function buildNodePrompt(spec, task, upstream, options) {
|
|
124
198
|
const policy = resolveContextPolicy(spec);
|
|
@@ -126,6 +200,7 @@ export function buildNodePrompt(spec, task, upstream, options) {
|
|
|
126
200
|
spec,
|
|
127
201
|
task,
|
|
128
202
|
upstream,
|
|
203
|
+
runDir: options?.runDir,
|
|
129
204
|
resolvedSkills: policy.resolveSkills(spec, task),
|
|
130
205
|
maxUpstreamChars: policy.resolveMaxUpstreamChars(task),
|
|
131
206
|
projectGovernanceContext: options?.projectGovernanceContext,
|
|
@@ -314,10 +389,7 @@ function promptRestartCandidateForAttempt(input) {
|
|
|
314
389
|
}
|
|
315
390
|
const appendedPrefix = `${input.baseRuntimePrompt}\n\n`;
|
|
316
391
|
if (input.attemptPrompt.startsWith(appendedPrefix)) {
|
|
317
|
-
return [
|
|
318
|
-
input.taskPrompt,
|
|
319
|
-
input.attemptPrompt.slice(appendedPrefix.length),
|
|
320
|
-
]
|
|
392
|
+
return [input.taskPrompt, input.attemptPrompt.slice(appendedPrefix.length)]
|
|
321
393
|
.filter(Boolean)
|
|
322
394
|
.join("\n\n");
|
|
323
395
|
}
|
|
@@ -353,6 +425,7 @@ export async function buildNodePromptWithResolvedSkillInstructions(spec, task, u
|
|
|
353
425
|
spec,
|
|
354
426
|
task,
|
|
355
427
|
upstream,
|
|
428
|
+
runDir: options?.runDir,
|
|
356
429
|
resolvedSkills: skillNames,
|
|
357
430
|
resolvedSkillInstructions,
|
|
358
431
|
maxUpstreamChars: policy.resolveMaxUpstreamChars(task),
|
|
@@ -533,7 +606,11 @@ export async function executeDagNode(input) {
|
|
|
533
606
|
await notifyNodeObserver(input.observer, "onNodeFinish", nodeId, state);
|
|
534
607
|
};
|
|
535
608
|
if (task.finalWriteSetApproval) {
|
|
536
|
-
const authorization = parseAndValidateFinalWriteSetApproval({
|
|
609
|
+
const authorization = parseAndValidateFinalWriteSetApproval({
|
|
610
|
+
task,
|
|
611
|
+
spec,
|
|
612
|
+
state,
|
|
613
|
+
});
|
|
537
614
|
if (!authorization.ok) {
|
|
538
615
|
node.runtimeWriteAuthorization = {
|
|
539
616
|
schemaVersion: 1,
|
|
@@ -622,6 +699,7 @@ export async function executeDagNode(input) {
|
|
|
622
699
|
spec,
|
|
623
700
|
task,
|
|
624
701
|
upstream: state.nodes,
|
|
702
|
+
runDir,
|
|
625
703
|
snapshot: skillSnapshot,
|
|
626
704
|
projectGovernanceContext,
|
|
627
705
|
});
|
|
@@ -712,6 +790,7 @@ export async function executeDagNode(input) {
|
|
|
712
790
|
else {
|
|
713
791
|
({ prompt, resolvedSkills } =
|
|
714
792
|
await buildNodePromptWithResolvedSkillInstructions(spec, task, state.nodes, cwd, {
|
|
793
|
+
runDir,
|
|
715
794
|
projectGovernanceContext,
|
|
716
795
|
convergenceFeedback: deriveConvergenceFeedback(state),
|
|
717
796
|
}));
|
|
@@ -875,6 +954,7 @@ export async function executeDagNode(input) {
|
|
|
875
954
|
model,
|
|
876
955
|
...(thinking ? { thinking } : {}),
|
|
877
956
|
prompt: attemptPrompt,
|
|
957
|
+
resolvedSkills: resolvedSkills.map((skill) => skill.name),
|
|
878
958
|
attempt: attemptNumber,
|
|
879
959
|
reportActivity,
|
|
880
960
|
timeoutMs: livenessPolicy.absoluteMaxWallClockMs,
|
|
@@ -912,9 +992,7 @@ export async function executeDagNode(input) {
|
|
|
912
992
|
...result,
|
|
913
993
|
ok: false,
|
|
914
994
|
failureCategory: GOVERNANCE_BLOCKED_CATEGORY,
|
|
915
|
-
stderr: [result.stderr, audit.reason]
|
|
916
|
-
.filter(Boolean)
|
|
917
|
-
.join("\n"),
|
|
995
|
+
stderr: [result.stderr, audit.reason].filter(Boolean).join("\n"),
|
|
918
996
|
};
|
|
919
997
|
}
|
|
920
998
|
}
|
|
@@ -1237,6 +1315,13 @@ export async function executeDagNode(input) {
|
|
|
1237
1315
|
node.structuredArtifactSha256 = pendingStructuredArtifact.sha256;
|
|
1238
1316
|
node.structuredArtifactSchemaId = pendingStructuredArtifact.schemaId;
|
|
1239
1317
|
}
|
|
1318
|
+
if ((task.producesArtifacts?.length ?? 0) > 0) {
|
|
1319
|
+
node.declaredArtifacts = await materializeDeclaredArtifactFacts({
|
|
1320
|
+
runDir,
|
|
1321
|
+
task,
|
|
1322
|
+
node,
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1240
1325
|
}
|
|
1241
1326
|
const finishedAt = new Date().toISOString();
|
|
1242
1327
|
node.finishedAt = finishedAt;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { assertReadableRegularFile, assertRealPathInsideBoundary, readFileBoundarySafe, } from "../../shared/path-safety.js";
|