@tea-agent/loop-agent 0.41.1-beta.0 → 0.42.0-next.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/CHANGELOG.md +61 -207
- 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 +19 -75
- package/dist/application/task-lifecycle/advance.js +5 -24
- package/dist/application/task-lifecycle/observe.js +17 -171
- package/dist/application/task-lifecycle/plan-transitions.js +7 -42
- package/dist/application/task-lifecycle/recommendations.js +2 -13
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +12 -7
- package/dist/cli/program.js +23 -6
- package/dist/commands/client-recovery.js +0 -3
- 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.js +1 -27
- package/dist/commands/task-advance.js +0 -19
- package/dist/executors/dag-pi-executor.js +266 -3792
- package/dist/executors/pi-executor.js +4 -15
- package/dist/executors/pi-sdk-executor.js +3 -0
- package/dist/executors/shell-executor.js +217 -945
- package/dist/executors/shell-write-guard.js +0 -7
- 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/infrastructure/console/operation-store.js +197 -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/dag-failure-category.js +0 -12
- package/dist/shared/openspec-spec.js +4 -70
- package/dist/shared/operator/capabilities.js +14 -7
- package/dist/shared/operator/safe-run-summary.js +1 -0
- package/dist/shared/path-safety.js +93 -0
- package/dist/shared/preview.js +28 -4
- package/dist/task/config-types.js +9 -35
- package/dist/task/contract/adopt.js +0 -4
- package/dist/task/contract/import-revision.js +0 -4
- package/dist/task/contract/project.js +0 -3
- package/dist/task/contract/schema.js +1 -2
- package/dist/task/frontend-project-capability.js +20 -203
- package/dist/task/runtime.js +2 -5
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +9 -15
- package/dist/task/source-prepare/prepare.js +1 -89
- package/dist/task/source-prepare/semantic-intake.js +2 -6
- package/dist/task/source-references.js +1 -22
- package/dist/task/task-demand-routing.js +3 -0
- package/dist/worker/console/chat/chat-event-store.js +2 -2
- package/dist/worker/console/chat/pi-runtime.js +3 -2
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +13 -4
- package/dist/worker/console/chat/semantic-activity.js +12 -11
- package/dist/worker/console/chat/session-stats.js +96 -0
- package/dist/worker/console/chat/session-store.js +1 -1
- package/dist/worker/console/chat/turn-process.js +28 -4
- 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 +1 -1
- 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 -6
- 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 +107 -168
- 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.js +60 -1
- package/dist/worker/console/routes.js +637 -8
- package/dist/worker/console/security.js +35 -0
- package/dist/worker/console/server.js +6 -6
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bho4we4c.js → abnfDiagram-N423BO3Z-DZZ8m3PO.js} +1 -1
- package/dist/worker/console/static/assets/{arc-TBDkTeK0.js → arc-D6PvaVd-.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BF_fZrn2.js → architectureDiagram-T3A2C74G-B_OTOiI8.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CCnPgUe2.js → blockDiagram-VBNYF7ZC-Bv6rqHBg.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCYZJLAT.js → c4Diagram-5PPSVZJV-B8eHr0oz.js} +1 -1
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D8K6adhU.js → chunk-2GRJ4B5K-DYwR0im2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DkT1Q78b.js → chunk-2Q5K7J3B-D2WPGqXt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DuhvpIr7.js → chunk-5RXB4S5H-CQISJ_I7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BnK6dC49.js → chunk-5VM5RSS4-C0o2Du1e.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DPz__z7r.js → chunk-6Q2QTUOP-4f8kr-U7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-0kkppM2j.js → chunk-GF5L2VYU-D_OWvXzX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BB41iK5t.js → chunk-JWPE2WC7-CasdPz5X.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CiVrxKHU.js → chunk-KBJHAD2P-Cj8lRrla.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C2h9Xje1.js → chunk-RYQCIY6F-CUvD1FSd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-3iOSHdbM.js → chunk-XXDRQBXY-CqLw_eqB.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BN3A7tkk.js → cose-bilkent-JH36ORCC-ku-WqJPx.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CTuBy0Or.js → cynefin-VYW2F7L2-Bq_PYDhl.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-wb6tUlMN.js → cynefinDiagram-MW4NZA55-BiGZV641.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-GP1Ivx8B.js → dagre-VZM6K2ZE-C4JRlglF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ejrcK6L0.js → diagram-7IWD3JNH-B0dNeWYG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-YFkCCvUU.js → diagram-B4RE2ZJO-Cj35YvYM.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-B4rrvIaL.js → diagram-LBJQPF4R-uzQoJ2-8.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C-YIZcUf.js → diagram-Q27KOJAE-D1a-Buoz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPF5wcL1.js → diagram-UB23O5K3-XjRrLRSs.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CU5SzXjO.js → ebnfDiagram-BXEA7PRR-Da_O24cW.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CpL414DE.js → erDiagram-JOGREHBK-BLJ8jrYU.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-MrhLzNau.js → flowDiagram-UKHOOZJN-B9GrLjM0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Bg4qFuUo.js → ganttDiagram-PKOTCBZU-CeJ0TqiK.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-ColxSXLR.js → gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js} +1 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +1 -0
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +451 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czvtm6gc.js → infoDiagram-6WML65LV-DB26i3d8.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js → ishikawaDiagram-WSZJBQD7-BOGRyeZT.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DWYZcvph.js → journeyDiagram-NVQOT4AX-CZRoBO_6.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-KTLZRUjx.js → kanban-definition-27J2QSJJ-qlWhJyoB.js} +1 -1
- package/dist/worker/console/static/assets/{linear-_Exn7bfl.js → linear-CfUiDDB3.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Cxg8kX8V.js → mermaid.core-BQe6fpqj.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-oKrlo2Bt.js → mindmap-definition-FAOFIHXS-zFzWHw64.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-XAeteiNd.js → pegDiagram-VL7TDLO6-DFGUqjZO.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BTf1K-lp.js → pieDiagram-7S7Q4E2Y-BB5i0l8Z.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js → quadrantDiagram-CIZ2JOQS-obbVZ_X5.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BVbKtZhW.js → railroadDiagram-AXF67PYL-BwdaNzc1.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C5e4JFcr.js → requirementDiagram-LRYGKXZP-CkTJaugh.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cz30hc29.js → sankeyDiagram-W5VNT64P-BwJ-hIgq.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DS33n6d0.js → sequenceDiagram-SI44F4Z6-BzSgmm7w.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-46w7v-9T.js → sizeCapture-X5ZJPWSS-Bn3oj5Q1.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-pbcD5SsZ.js → stateDiagram-OKZ733FA-dxhGtL8K.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DuivYBSj.js → swimlanes-SLNWSIFB-BLpEInya.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cb9VZVIl.js → timeline-definition-Z64GVDOM-ChJGYcXy.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-D2-mfOgR.js → vennDiagram-T6HMQDX7-DK-qjRer.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DeS2aC76.js → wardleyDiagram-T6FBY63Y-3qeaWAg-.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-WGsrFjxH.js → xychartDiagram-ELKLHX3M-CootlyP9.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/app/useRunProgress.js +9 -2
- package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +22 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +106 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
- package/dist/worker/console/workspace-context.js +28 -112
- package/dist/worker/console/workspace-registry.js +2 -2
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/observability/read-model.js +4 -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/dag-node-purpose.js +0 -5
- package/dist/worker/observe/static/state.js +3 -1
- package/dist/worker/observe/static/styles.css +1037 -126
- package/dist/worker/observe/static/views/dag-inspector.js +1217 -65
- package/dist/worker/observe/static/views/session-timeline.js +121 -25
- 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-result-contract.js +1 -1
- 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/contract-validator-registrations.js +2 -1
- package/dist/workflows/dag/frontend-implementation-contract.js +162 -932
- package/dist/workflows/dag/frontend-plan-render.js +1 -2
- package/dist/workflows/dag/frontend-prewrite-gate.js +349 -255
- package/dist/workflows/dag/frontend-recovery-plan.js +10 -17
- package/dist/workflows/dag/frontend-recovery-run.js +20 -33
- package/dist/workflows/dag/frontend-repair.js +432 -1
- package/dist/workflows/dag/frontend-review-context.js +15 -261
- package/dist/workflows/dag/frontend-verification-trace.js +24 -249
- package/dist/workflows/dag/frontend-worktree-diff.js +17 -250
- package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
- package/dist/workflows/dag/init-hybrid.js +640 -685
- package/dist/workflows/dag/interrupt-request.js +0 -7
- package/dist/workflows/dag/node-execution.js +116 -759
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +30 -38
- package/dist/workflows/dag/report.js +1 -37
- package/dist/workflows/dag/rerun-feedback.js +1 -256
- package/dist/workflows/dag/rerun-plan.js +557 -11
- package/dist/workflows/dag/rerun-run.js +601 -38
- package/dist/workflows/dag/rerun-task.js +0 -29
- package/dist/workflows/dag/retry-policy.js +104 -214
- package/dist/workflows/dag/runner.js +142 -430
- package/dist/workflows/dag/scheduler.js +16 -114
- package/dist/workflows/dag/skill-snapshot.js +17 -0
- package/dist/workflows/dag/types.js +195 -246
- package/dist/workflows/dag/validate.js +12 -28
- package/docs/architecture/runtime-boundaries.md +6 -6
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/init-surface.manifest.json +12 -30
- package/docs/skills/vetted-skill-registry.md +2 -4
- package/docs/templates/README.md +0 -2
- package/docs/templates/agent-dag-report.schema.json +2 -8
- package/docs/templates/agent-dag.schema.json +33 -1
- package/docs/templates/frontend-implementation-contract.schema.json +1 -4
- package/docs/templates/product-line/task.yaml +1 -1
- package/harness.json +3 -3
- package/package.json +3 -1
- package/skills/frontend-bounded-implement/SKILL.md +14 -15
- package/skills/frontend-design-review/SKILL.md +41 -22
- package/skills/frontend-implementation/SKILL.md +52 -0
- package/skills/frontend-implementation/references/code-standards.md +33 -0
- package/skills/frontend-implementation/references/design-spec.md +56 -0
- package/skills/frontend-implementation/references/node-contracts.md +31 -0
- package/skills/frontend-review/SKILL.md +15 -20
- package/skills/frontend-review/references/review-findings.md +7 -6
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +6 -1
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/commands/dag-follow-up.js +0 -138
- package/dist/executors/pi-read-budget-policy.js +0 -239
- package/dist/worker/console/frontend-human-decision-adapter.js +0 -19
- package/dist/worker/console/frontend-split-operation-adapter.js +0 -20
- package/dist/worker/console/operation-store.js +0 -389
- package/dist/worker/console/static/assets/channel-bnXW0U3C.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +0 -1
- package/dist/worker/console/static/assets/index-D3CC4eUz.css +0 -1
- package/dist/worker/console/static/assets/index-DLt4ZFvD.js +0 -437
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +0 -8
- package/dist/worker/console/static-src/active-run-badge.js +0 -17
- package/dist/worker/materialize/frontend-split-task-materializer.js +0 -72
- package/dist/workflows/dag/dag-retry-schema.js +0 -138
- package/dist/workflows/dag/frontend-closeout.js +0 -221
- package/dist/workflows/dag/frontend-design-policy.js +0 -400
- package/dist/workflows/dag/frontend-human-decision.js +0 -182
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +0 -159
- package/dist/workflows/dag/frontend-read-budgets.js +0 -12
- package/dist/workflows/dag/frontend-recovery-capsule.js +0 -455
- package/dist/workflows/dag/frontend-recovery-controller.js +0 -202
- package/dist/workflows/dag/frontend-recovery-lineage.js +0 -178
- package/dist/workflows/dag/frontend-review-findings.js +0 -270
- package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -914
- package/dist/workflows/dag/frontend-shape-capsule-store.js +0 -191
- package/dist/workflows/dag/frontend-shape-facts.js +0 -409
- package/dist/workflows/dag/frontend-shape.js +0 -427
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +0 -108
- package/dist/workflows/dag/frontend-split-application-service.js +0 -203
- package/dist/workflows/dag/frontend-split-orchestrator.js +0 -899
- package/dist/workflows/dag/frontend-typed-event-store.js +0 -452
- package/dist/workflows/dag/frontend-typed-event-transaction.js +0 -180
- package/dist/workflows/dag/frontend-writer-admission.js +0 -285
- package/dist/workflows/dag/frontend-writer-status.js +0 -256
- package/dist/workflows/dag/recovery-lease.js +0 -80
- package/docs/templates/frontend-implementation-dag.json +0 -89
- package/docs/templates/spec-registry.schema.json +0 -45
- package/skills/frontend-bounded-implement/references/code-standards.md +0 -19
- package/skills/frontend-contract/SKILL.md +0 -23
- package/skills/frontend-contract/references/contract-protocol.md +0 -34
- package/skills/frontend-plan/SKILL.md +0 -26
- package/skills/frontend-plan/references/decision-contract.md +0 -37
- package/skills/frontend-plan/references/design-decisions.md +0 -17
- package/skills/frontend-scout/SKILL.md +0 -25
- package/skills/frontend-scout/references/design-evidence.md +0 -16
- package/skills/frontend-scout/references/scout-evidence.md +0 -23
- /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
|
@@ -2,19 +2,16 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { access, readdir, readFile, realpath } from "node:fs/promises";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
5
|
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
7
6
|
import { assertValidDagSpec } from "./validate.js";
|
|
8
7
|
import { DAG_AGENT_RUNTIME_PI_ONLY, DAG_REPAIR_WRITER_PROTOCOL_EXPLICIT_NODE_V1, DAG_RUNTIME_CONTRACT_SCHEMA_VERSION, DEFAULT_DAG_OUTPUT_LANGUAGE, DEFAULT_DAG_EXECUTOR_MODELS, parseDagSpec, } from "./types.js";
|
|
9
8
|
import { bindDagRerunFeedback } from "./rerun-feedback.js";
|
|
10
9
|
import { planMavenVerification, } from "../../verification/maven/index.js";
|
|
11
10
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
12
|
-
import {
|
|
13
|
-
import { readFrontendSpecRegistry, scoreFrontendSpecCandidate, } from "../../task/frontend-project-capability.js";
|
|
11
|
+
import { extractTaskSourceOpenspecPaths } from "../../shared/openspec-spec.js";
|
|
14
12
|
import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
|
|
15
|
-
import { FRONTEND_READ_BUDGETS } from "./frontend-read-budgets.js";
|
|
16
13
|
import { buildDecisionEnvelopePromptContract } from "./decision-envelope.js";
|
|
17
|
-
import { DEFAULT_READ_ONLY_PI_RETRY_POLICY,
|
|
14
|
+
import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, PLANNER_OUTPUT_LIMIT_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY, STRUCTURED_REQUIRED_PI_RETRY_POLICY, BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY, WRITER_TRANSPORT_RETRY_POLICY, TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE, TARGET_TEMPLATE_WRITER_TRANSPORT_RETRY_POLICY, isCanonicalFinalVerifyShellRetryCandidate, isSafeReadOnlyPiRetryCandidate, isTargetTemplateImplementPi, isWriterTransportRetryCandidate, } from "./retry-policy.js";
|
|
18
15
|
import { REVIEW_JSON_VERDICT_OUTPUT_PROTOCOL, REVIEW_VERDICT_OUTPUT_PROTOCOL, } from "./output-protocol.js";
|
|
19
16
|
import { resolveAdapter } from "../../adapters/index.js";
|
|
20
17
|
import { loadHarnessManifest } from "../../governance/harness.js";
|
|
@@ -22,7 +19,7 @@ import { mergeDocumentIndexCompanions } from "../../governance/document-index-cl
|
|
|
22
19
|
import { buildAuthoritySurfaceAuditNode, buildAuthoritySurfaceGateNode, resolveAuthoritySurfaceAudit, } from "./authority-surface.js";
|
|
23
20
|
import { applySddEmbeddedEnhancements, probeRepoLocalSddSkills, } from "./sdd-embedded.js";
|
|
24
21
|
import { discoverProjectGovernancePresence } from "./project-governance-context.js";
|
|
25
|
-
import {
|
|
22
|
+
import { getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
|
|
26
23
|
import { materializeTaskReferenceDocs } from "../../task/source-references.js";
|
|
27
24
|
import { observeTaskContract } from "../../task/contract/observe.js";
|
|
28
25
|
import { extractRequirementFactsFromMarkdown } from "../../task/source-prepare/parse-intent.js";
|
|
@@ -39,10 +36,8 @@ import { buildBackendTestOutcomeGateShellSnippet } from "./backend-test-result-c
|
|
|
39
36
|
import { buildBackendTestIntakeContext } from "./backend-test-intake-context.js";
|
|
40
37
|
import { buildFrontendTestOutcomeGateShellSnippet } from "./frontend-test-result-contract.js";
|
|
41
38
|
import { classifyFrontendRisk, } from "./frontend-risk.js";
|
|
42
|
-
import { discoverFrontendProjectCapability,
|
|
43
|
-
import { buildFrontendImplementationContractSkeleton, } from "./frontend-implementation-contract.js";
|
|
44
|
-
import { computeFrontendShapeSourceDigest, parseFrontendShapeTransitionCapsule, resolveFrontendTaskShape, } from "./frontend-shape.js";
|
|
45
|
-
import { discoverLatestCommittedFrontendShapeCapsule } from "./frontend-shape-capsule-store.js";
|
|
39
|
+
import { discoverFrontendProjectCapability, } from "./frontend-project-capability.js";
|
|
40
|
+
import { buildFrontendImplementationContractSkeleton, FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, FRONTEND_IMPLEMENTATION_CONTRACT_PLAN_PATCH_SCHEMA_ID, loadFrontendImplementationContractJsonSchema, } from "./frontend-implementation-contract.js";
|
|
46
41
|
import { FRONTEND_NO_VERIFICATION_MARKER_TEXT } from "./frontend-verification-trace.js";
|
|
47
42
|
import { serializeDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
48
43
|
const REQUIREMENT_FILE = "需求.md";
|
|
@@ -50,6 +45,60 @@ const CONSTRAINT_FILE = "执行约束.md";
|
|
|
50
45
|
const REFERENCE_DIRECTORY = "references";
|
|
51
46
|
const MAX_SOURCE_EXCERPT_CHARS = 2000;
|
|
52
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
|
+
}
|
|
53
102
|
const INTERACTIVE_UI_DELIVERY_CONTRACT = [
|
|
54
103
|
"[INTERACTIVE_UI_DELIVERY_CONTRACT]",
|
|
55
104
|
"This is an interactive UI delivery task.",
|
|
@@ -104,9 +153,7 @@ const FRONTEND_SKILLS_BY_ROLE = {
|
|
|
104
153
|
verifier: [],
|
|
105
154
|
closeout: [],
|
|
106
155
|
};
|
|
107
|
-
const
|
|
108
|
-
const FRONTEND_SCOUT_SKILLS = ["frontend-scout"];
|
|
109
|
-
const FRONTEND_PLAN_SKILLS = ["frontend-plan"];
|
|
156
|
+
const FRONTEND_IMPLEMENTATION_SKILLS = ["frontend-implementation"];
|
|
110
157
|
const FRONTEND_BOUNDED_IMPLEMENT_SKILLS = ["frontend-bounded-implement"];
|
|
111
158
|
const FRONTEND_DESIGN_REVIEW_SKILLS = ["frontend-design-review"];
|
|
112
159
|
const FRONTEND_REVIEW_SKILLS = ["frontend-review"];
|
|
@@ -227,28 +274,6 @@ async function hasDirectDependency(repoRoot, depName) {
|
|
|
227
274
|
return false;
|
|
228
275
|
}
|
|
229
276
|
}
|
|
230
|
-
/**
|
|
231
|
-
* Collect the project's declared direct dependencies (dependencies +
|
|
232
|
-
* devDependencies names) from package.json. Used to freeze the frontend
|
|
233
|
-
* design-policy allowedDependencies set: the model's dependency policy must
|
|
234
|
-
* not be rejected for mentioning an already-declared dependency, and a
|
|
235
|
-
* dependency not present in the manifest is a genuine unauthorized addition.
|
|
236
|
-
* Returns [] when package.json is unreadable (no allowlist → the dependency
|
|
237
|
-
* check is skipped rather than rejecting prose tokens).
|
|
238
|
-
*/
|
|
239
|
-
async function collectDeclaredDependencies(repoRoot) {
|
|
240
|
-
try {
|
|
241
|
-
const raw = await readFile(path.join(repoRoot, "package.json"), "utf-8");
|
|
242
|
-
const pkg = JSON.parse(raw);
|
|
243
|
-
return [
|
|
244
|
-
...Object.keys(pkg.dependencies ?? {}),
|
|
245
|
-
...Object.keys(pkg.devDependencies ?? {}),
|
|
246
|
-
];
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
return [];
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
277
|
/** Check whether handler/fixture/bootstrap files exist for known mock frameworks. */
|
|
253
278
|
async function discoverMockHandlerFiles(repoRoot, serviceRoot) {
|
|
254
279
|
const exactCandidates = [
|
|
@@ -1678,10 +1703,7 @@ function buildBackendTestAnalysisSourceBindingContract(sources) {
|
|
|
1678
1703
|
requirementIds: binding.requirementIds,
|
|
1679
1704
|
};
|
|
1680
1705
|
}
|
|
1681
|
-
function buildSourceContextBlock(sources
|
|
1682
|
-
const includeRequirementExcerpt = options.includeRequirementExcerpt ?? true;
|
|
1683
|
-
const includeConstraintExcerpt = options.includeConstraintExcerpt ?? true;
|
|
1684
|
-
const includeReferenceDocuments = options.includeReferenceDocuments ?? true;
|
|
1706
|
+
function buildSourceContextBlock(sources) {
|
|
1685
1707
|
const requirementRef = toDagSourcePath(sources, sources.requirementPath);
|
|
1686
1708
|
const requirementExcerpt = excerptMarkdown(sources.requirementMarkdown, {
|
|
1687
1709
|
sourceRef: requirementRef,
|
|
@@ -1690,7 +1712,7 @@ function buildSourceContextBlock(sources, options = {}) {
|
|
|
1690
1712
|
const parts = [
|
|
1691
1713
|
`## Task source: 需求.md`,
|
|
1692
1714
|
`Bound readPath (use for Pi read-tool calls): ${requirementRef}`,
|
|
1693
|
-
|
|
1715
|
+
requirementExcerpt.text,
|
|
1694
1716
|
];
|
|
1695
1717
|
if (sources.constraintMarkdown) {
|
|
1696
1718
|
const constraintRef = toDagSourcePath(sources, sources.constraintPath);
|
|
@@ -1698,11 +1720,9 @@ function buildSourceContextBlock(sources, options = {}) {
|
|
|
1698
1720
|
sourceRef: constraintRef,
|
|
1699
1721
|
});
|
|
1700
1722
|
boundReadPaths.push(`- constraints: ${constraintRef}`);
|
|
1701
|
-
parts.push("## Task source: 执行约束.md", `Bound readPath (use for Pi read-tool calls): ${constraintRef}`,
|
|
1723
|
+
parts.push("## Task source: 执行约束.md", `Bound readPath (use for Pi read-tool calls): ${constraintRef}`, constraintExcerpt.text);
|
|
1702
1724
|
}
|
|
1703
|
-
for (const reference of (
|
|
1704
|
-
? sources.referenceDocuments ?? []
|
|
1705
|
-
: []).slice(0, MAX_INLINE_SOURCE_REFERENCE_DOCUMENTS)) {
|
|
1725
|
+
for (const reference of (sources.referenceDocuments ?? []).slice(0, MAX_INLINE_SOURCE_REFERENCE_DOCUMENTS)) {
|
|
1706
1726
|
const relativePath = path
|
|
1707
1727
|
.relative(path.join(sources.taskDir, "source"), reference.path)
|
|
1708
1728
|
.replaceAll(path.sep, "/");
|
|
@@ -1833,16 +1853,6 @@ export async function loadTaskHybridSources(repoRoot, taskId) {
|
|
|
1833
1853
|
catch (error) {
|
|
1834
1854
|
throw new Error(`failed to load verification commands for task "${taskId}": ${error instanceof Error ? error.message : String(error)}`);
|
|
1835
1855
|
}
|
|
1836
|
-
const frontendShapeTransitionCapsule = taskConfig.taskKind === "frontend-implementation" && CANONICAL_TASK_ID_PATTERN.test(taskId)
|
|
1837
|
-
? await discoverLatestCommittedFrontendShapeCapsule({
|
|
1838
|
-
repoRoot,
|
|
1839
|
-
taskId,
|
|
1840
|
-
sourceDigest: computeFrontendShapeSourceDigest({
|
|
1841
|
-
requirementMarkdown,
|
|
1842
|
-
constraintMarkdown: constraintMarkdown ?? "",
|
|
1843
|
-
}),
|
|
1844
|
-
})
|
|
1845
|
-
: undefined;
|
|
1846
1856
|
const sources = {
|
|
1847
1857
|
taskId,
|
|
1848
1858
|
repoRoot,
|
|
@@ -1859,7 +1869,6 @@ export async function loadTaskHybridSources(repoRoot, taskId) {
|
|
|
1859
1869
|
verifyCommands,
|
|
1860
1870
|
sddEmbeddedSkills: await probeRepoLocalSddSkills(repoRoot),
|
|
1861
1871
|
projectGovernancePresent: await discoverProjectGovernancePresence(repoRoot),
|
|
1862
|
-
...(frontendShapeTransitionCapsule ? { autoloadedFrontendShapeTransitionCapsule: frontendShapeTransitionCapsule } : {}),
|
|
1863
1872
|
};
|
|
1864
1873
|
return sources;
|
|
1865
1874
|
}
|
|
@@ -1877,9 +1886,7 @@ async function prepareFrontendMockSources(sources, discoveredProjectCapability)
|
|
|
1877
1886
|
}
|
|
1878
1887
|
}
|
|
1879
1888
|
const projectCapability = discoveredProjectCapability ??
|
|
1880
|
-
(await discoverFrontendProjectCapability(repoRoot
|
|
1881
|
-
specRoots: sources.taskConfig.frontendOpenspec?.specRoots,
|
|
1882
|
-
}));
|
|
1889
|
+
(await discoverFrontendProjectCapability(repoRoot));
|
|
1883
1890
|
const frontendRisk = classifyFrontendRisk({
|
|
1884
1891
|
title: sources.taskConfig.title,
|
|
1885
1892
|
requirementMarkdown: sources.requirementMarkdown,
|
|
@@ -1990,7 +1997,6 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
1990
1997
|
executor: "pi",
|
|
1991
1998
|
complexity: "MED",
|
|
1992
1999
|
writePolicy: "read-only",
|
|
1993
|
-
readBudget: FRONTEND_READ_BUDGETS.contract,
|
|
1994
2000
|
allowedPaths: taskConfig.allowedPaths.length > 0 ? taskConfig.allowedPaths : ["**"],
|
|
1995
2001
|
forbiddenPaths,
|
|
1996
2002
|
outputContract: "Plain Markdown implementation contract (10 lines or fewer); no file writes.",
|
|
@@ -2007,7 +2013,6 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
2007
2013
|
executor: "pi",
|
|
2008
2014
|
complexity: scoutComplexity,
|
|
2009
2015
|
writePolicy: "read-only",
|
|
2010
|
-
readBudget: FRONTEND_READ_BUDGETS.scout,
|
|
2011
2016
|
allowedPaths: scoutPaths.srcPaths,
|
|
2012
2017
|
forbiddenPaths,
|
|
2013
2018
|
outputContract: "Plain Markdown source reconnaissance summary; no file writes.",
|
|
@@ -2024,7 +2029,6 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
2024
2029
|
executor: "pi",
|
|
2025
2030
|
complexity: scoutComplexity,
|
|
2026
2031
|
writePolicy: "read-only",
|
|
2027
|
-
readBudget: FRONTEND_READ_BUDGETS.plan,
|
|
2028
2032
|
allowedPaths: scoutPaths.testPaths,
|
|
2029
2033
|
forbiddenPaths,
|
|
2030
2034
|
outputContract: "Plain Markdown test coverage reconnaissance summary; no file writes.",
|
|
@@ -2124,6 +2128,7 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
2124
2128
|
applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
|
|
2125
2129
|
stampTargetTemplateTransientRetryProfile(spec);
|
|
2126
2130
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
2131
|
+
stampGeneratedArtifactBindings(spec);
|
|
2127
2132
|
parseDagSpec(spec);
|
|
2128
2133
|
assertValidDagSpec(spec);
|
|
2129
2134
|
return spec;
|
|
@@ -2144,7 +2149,7 @@ function buildFrontendMockAssessNode(sources, sourceContext, mockContextBlock, f
|
|
|
2144
2149
|
writePolicy: "read-only",
|
|
2145
2150
|
allowedPaths: readOnlyPaths,
|
|
2146
2151
|
forbiddenPaths,
|
|
2147
|
-
skills:
|
|
2152
|
+
skills: FRONTEND_IMPLEMENTATION_SKILLS,
|
|
2148
2153
|
firstProtocolLine: "MOCK_STRATEGY:",
|
|
2149
2154
|
outputContract: "Plain Markdown whose first line is MOCK_STRATEGY: native|browser-intercept|request-adapter|not-needed|blocked, followed by Mock Decision, API Contract Evidence, Specification Evidence, Service Evidence, Backend Readiness, Selection Evidence, Endpoint / Fixture Matrix, Activation, Target Files, Production Safety, Verification Plan, Real Integration Gap, and Blocking Issues. No file writes.",
|
|
2150
2155
|
subtask_prompt: [
|
|
@@ -2329,6 +2334,7 @@ function buildBlockedFrontendMockDag(sources, readOnlyPaths, forbiddenPaths, glo
|
|
|
2329
2334
|
],
|
|
2330
2335
|
};
|
|
2331
2336
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
2337
|
+
stampGeneratedArtifactBindings(spec);
|
|
2332
2338
|
parseDagSpec(spec);
|
|
2333
2339
|
assertValidDagSpec(spec);
|
|
2334
2340
|
return spec;
|
|
@@ -2391,181 +2397,40 @@ function frontendMockStrategyMustBeNotNeeded(sources) {
|
|
|
2391
2397
|
capabilityStatus === "ambiguous" ||
|
|
2392
2398
|
!hasDeterministicMockVerification));
|
|
2393
2399
|
}
|
|
2394
|
-
|
|
2395
|
-
* 候选规范懒加载:prompt 只内联与任务相关的候选,不全部塞给模型。
|
|
2396
|
-
*
|
|
2397
|
-
* - mandatory(任务源显式声明/引用)始终保留——模型必须知道它们存在。
|
|
2398
|
-
* - scan-strict 候选按「路径段是否命中任务源关键词」过滤:列表页任务只
|
|
2399
|
-
* 带出列表相关组件/页面规范,创建页规范不进 prompt。
|
|
2400
|
-
* - runtime 的选型/read 门禁仍消费完整 openspecCandidatePaths(prewrite
|
|
2401
|
-
* gate 用全量);这里只缩小 prompt 体积,不改变门禁语义。未提及的候选
|
|
2402
|
-
* 由 runtime 默认 irrelevant,模型无需枚举。
|
|
2403
|
-
* - 真正读取发生在 plan 侧:模型对 required 选型调用 read 工具,prewrite
|
|
2404
|
-
* 门禁验证 read 事件——按需读取,不预加载正文。
|
|
2405
|
-
*/
|
|
2406
|
-
export function filterRelevantOpenspecCandidates(input) {
|
|
2407
|
-
const mandatory = new Set(input.mandatoryPaths);
|
|
2408
|
-
const relevant = [];
|
|
2409
|
-
for (const candidate of input.candidates) {
|
|
2410
|
-
if (mandatory.has(candidate)) {
|
|
2411
|
-
relevant.push(candidate);
|
|
2412
|
-
continue;
|
|
2413
|
-
}
|
|
2414
|
-
if (openspecCandidateMatchesSource(candidate, input.sourceMarkdown)) {
|
|
2415
|
-
relevant.push(candidate);
|
|
2416
|
-
}
|
|
2417
|
-
}
|
|
2418
|
-
// 保持候选发现顺序(确定性);mandatory 与相关候选都按原始顺序出现。
|
|
2419
|
-
const maxCandidates = input.maxCandidates ?? 24;
|
|
2420
|
-
if (relevant.length <= maxCandidates)
|
|
2421
|
-
return relevant;
|
|
2422
|
-
const mandatoryRelevant = relevant.filter((candidate) => mandatory.has(candidate));
|
|
2423
|
-
const optionalRelevant = relevant.filter((candidate) => !mandatory.has(candidate));
|
|
2424
|
-
return [
|
|
2425
|
-
...mandatoryRelevant,
|
|
2426
|
-
...optionalRelevant.slice(0, Math.max(0, maxCandidates - mandatoryRelevant.length)),
|
|
2427
|
-
];
|
|
2428
|
-
}
|
|
2429
|
-
function buildFrontendSpecCandidateSummaries(input) {
|
|
2430
|
-
const mandatory = new Set(input.mandatoryPaths);
|
|
2431
|
-
return input.paths
|
|
2432
|
-
.map((candidate) => scoreFrontendSpecCandidate(candidate, "", {
|
|
2433
|
-
registryMode: input.registryModes.get(candidate),
|
|
2434
|
-
taskRelated: mandatory.has(candidate) || openspecCandidateMatchesSource(candidate, input.sourceMarkdown),
|
|
2435
|
-
}))
|
|
2436
|
-
.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
|
|
2437
|
-
}
|
|
2438
|
-
/**
|
|
2439
|
-
* 候选路径是否与任务源相关:从任务源提取文件名/路径 token(去扩展名、
|
|
2440
|
-
* 去连字符/下划线),若候选的路径段包含任一 token 即视为相关。纯字符串
|
|
2441
|
-
* 判定,无 IO;找不到 token 时保守保留(避免漏掉模型可能需要的规范)。
|
|
2442
|
-
*/
|
|
2443
|
-
function openspecCandidateMatchesSource(candidate, sourceMarkdown) {
|
|
2444
|
-
const sourceTokens = extractOpenspecSourceTokens(sourceMarkdown);
|
|
2445
|
-
if (sourceTokens.size === 0)
|
|
2446
|
-
return true;
|
|
2447
|
-
const candidateLower = candidate.toLowerCase().replace(/\\/g, "/");
|
|
2448
|
-
const candidateSegments = candidateLower.split("/");
|
|
2449
|
-
const candidateBasename = candidateSegments[candidateSegments.length - 1] ?? "";
|
|
2450
|
-
for (const token of sourceTokens) {
|
|
2451
|
-
if (candidateBasename.includes(token) ||
|
|
2452
|
-
candidateLower.includes(`/${token}/`) ||
|
|
2453
|
-
candidateLower.includes(`${token}.`)) {
|
|
2454
|
-
return true;
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
return false;
|
|
2458
|
-
}
|
|
2459
|
-
/** 从任务源 markdown 提取显著 token:反引号代码段、路径、组件/页面名。 */
|
|
2460
|
-
function extractOpenspecSourceTokens(markdown) {
|
|
2461
|
-
const tokens = new Set();
|
|
2462
|
-
const push = (raw) => {
|
|
2463
|
-
const cleaned = raw
|
|
2464
|
-
.trim()
|
|
2465
|
-
.replace(/[.*+?^${}()|[\]\\]/g, "")
|
|
2466
|
-
.toLowerCase();
|
|
2467
|
-
if (cleaned.length >= 2 && cleaned.length <= 40)
|
|
2468
|
-
tokens.add(cleaned);
|
|
2469
|
-
};
|
|
2470
|
-
// 反引号内联代码(文件名/组件名)
|
|
2471
|
-
for (const match of markdown.matchAll(/`([^`\n]+)`/g)) {
|
|
2472
|
-
push(match[1] ?? "");
|
|
2473
|
-
}
|
|
2474
|
-
// markdown 链接文本
|
|
2475
|
-
for (const match of markdown.matchAll(/\[([^\]]+)\]\([^)]+\)/g)) {
|
|
2476
|
-
push(match[1] ?? "");
|
|
2477
|
-
}
|
|
2478
|
-
// 路径 token(openspec/ 或 xxx/xxx.md)
|
|
2479
|
-
for (const match of markdown.matchAll(/(?:[A-Za-z0-9_-]+\/)+[A-Za-z0-9_.-]+/g)) {
|
|
2480
|
-
const segments = (match[0] ?? "").split("/");
|
|
2481
|
-
const basename = segments[segments.length - 1] ?? "";
|
|
2482
|
-
push(basename.replace(/\.[a-z0-9]+$/i, ""));
|
|
2483
|
-
for (const segment of segments)
|
|
2484
|
-
push(segment);
|
|
2485
|
-
}
|
|
2486
|
-
return tokens;
|
|
2487
|
-
}
|
|
2488
|
-
async function resolveFrontendOpenspecGateConfig(sources) {
|
|
2400
|
+
function resolveFrontendOpenspecGateConfig(sources) {
|
|
2489
2401
|
const taskConfig = sources.taskConfig;
|
|
2490
2402
|
const policy = taskConfig.frontendOpenspec?.policy ?? "cited";
|
|
2491
2403
|
const declared = taskConfig.frontendOpenspec?.requiredReadPaths ?? [];
|
|
2492
|
-
const
|
|
2493
|
-
DEFAULT_OPENSPEC_GOVERNANCE_ROOT;
|
|
2494
|
-
const specRoots = taskConfig.frontendOpenspec?.specRoots ?? DEFAULT_FRONTEND_SPEC_ROOTS;
|
|
2495
|
-
const rootAliases = sources.repoRoot
|
|
2496
|
-
? await resolveFrontendSpecRootAliases(sources.repoRoot, [...specRoots])
|
|
2497
|
-
: [];
|
|
2498
|
-
const remappedSourceMarkdown = rootAliases.reduce((markdown, { alias, logicalRoot }) => markdown.replace(new RegExp(`(^|[^A-Za-z0-9_.-])${alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/`, "g"), `$1${logicalRoot}/`), [sources.requirementMarkdown, sources.constraintMarkdown ?? ""].join("\n"));
|
|
2499
|
-
const taskSourceCited = extractTaskSourceFrontendSpecPaths(remappedSourceMarkdown, specRoots, governanceRoot);
|
|
2404
|
+
const taskSourceCited = extractTaskSourceOpenspecPaths([sources.requirementMarkdown, sources.constraintMarkdown ?? ""].join("\n"));
|
|
2500
2405
|
const scanStrict = sources.frontendProjectCapability?.designEvidence.normativePaths ?? [];
|
|
2501
|
-
const registry = sources.repoRoot
|
|
2502
|
-
? await readFrontendSpecRegistry(sources.repoRoot)
|
|
2503
|
-
: null;
|
|
2504
|
-
const registryModes = new Map();
|
|
2505
|
-
for (const root of registry?.roots ?? []) {
|
|
2506
|
-
if (root.scope && !root.scope.includes("frontend"))
|
|
2507
|
-
continue;
|
|
2508
|
-
if (!root.mode)
|
|
2509
|
-
continue;
|
|
2510
|
-
for (const candidate of scanStrict) {
|
|
2511
|
-
if (candidate === root.path || candidate.startsWith(`${root.path}/`)) {
|
|
2512
|
-
registryModes.set(candidate, root.mode);
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
}
|
|
2516
2406
|
const dedupeSorted = (paths) => [...new Set(paths)].sort();
|
|
2517
2407
|
const openspecCandidateSources = {
|
|
2518
2408
|
declared: dedupeSorted(declared),
|
|
2519
2409
|
taskSourceCited: dedupeSorted(taskSourceCited),
|
|
2520
2410
|
scanStrict: dedupeSorted(scanStrict),
|
|
2521
2411
|
};
|
|
2522
|
-
// Candidate discovery is frozen independently from the eventual must-read
|
|
2523
|
-
// set. The selector may mark candidates irrelevant, while explicit task
|
|
2524
|
-
// declarations/source citations are never allowed to be downgraded.
|
|
2525
|
-
const openspecMandatoryPaths = dedupeSorted([
|
|
2526
|
-
...declared,
|
|
2527
|
-
...taskSourceCited,
|
|
2528
|
-
]);
|
|
2529
|
-
// `cited` must stay demand-driven: a normative registry makes a path
|
|
2530
|
-
// discoverable, not implicitly task-mandatory. Only scan-strict may offer
|
|
2531
|
-
// the global discovery set to the bounded task-relevance selector.
|
|
2532
2412
|
const openspecCandidatePaths = policy === "cited"
|
|
2533
|
-
?
|
|
2534
|
-
:
|
|
2535
|
-
|
|
2536
|
-
...openspecMandatoryPaths,
|
|
2537
|
-
]);
|
|
2538
|
-
const result = {
|
|
2413
|
+
? dedupeSorted([...declared, ...taskSourceCited])
|
|
2414
|
+
: openspecCandidateSources.scanStrict;
|
|
2415
|
+
return {
|
|
2539
2416
|
openspecPolicy: policy,
|
|
2540
2417
|
openspecCandidatePaths,
|
|
2541
2418
|
openspecCandidateSources,
|
|
2542
|
-
openspecMandatoryPaths,
|
|
2543
|
-
openspecCandidateSummaries: buildFrontendSpecCandidateSummaries({
|
|
2544
|
-
paths: openspecCandidatePaths,
|
|
2545
|
-
mandatoryPaths: openspecMandatoryPaths,
|
|
2546
|
-
registryModes,
|
|
2547
|
-
sourceMarkdown: [sources.requirementMarkdown, sources.constraintMarkdown ?? ""].join("\n"),
|
|
2548
|
-
}),
|
|
2549
2419
|
};
|
|
2550
|
-
if (sources.repoRoot && openspecCandidatePaths.length > 0) {
|
|
2551
|
-
result.openspecCandidateSnapshots = await Promise.all(openspecCandidatePaths.map(async (candidate) => ({
|
|
2552
|
-
path: candidate,
|
|
2553
|
-
sha256: createHash("sha256")
|
|
2554
|
-
.update(await readFile(path.join(sources.repoRoot, candidate)))
|
|
2555
|
-
.digest("hex"),
|
|
2556
|
-
})));
|
|
2557
|
-
}
|
|
2558
|
-
return result;
|
|
2559
2420
|
}
|
|
2421
|
+
const openspecCitationInstruction = [
|
|
2422
|
+
"OpenSpec 引用块(citation block):在 fenced json 契约块之后,追加**恰好一个** ```openspec-citations 围栏代码块(三反引号 + openspec-citations)。",
|
|
2423
|
+
'该块内每行一个 JSON 对象 {"path":"<repo 相对 openspec 路径>","section":"<命中章节或空串>","line":<int 或 null>},必须逐条列出你在本计划中实际读取并应用的每个 openspec 规范文件。',
|
|
2424
|
+
"prewrite gate 会用真实 read 事件核验每条引用:引用存在但无成功 read 事件 → openspec-citation-not-read;契约冻结的必读候选未被引用 → openspec-not-cited;两者都 fail-closed。不要引用未读取的路径。",
|
|
2425
|
+
].join("\n");
|
|
2560
2426
|
const frontendComponentConformanceInstruction = [
|
|
2561
2427
|
"## Component Selection conformance (uiComponentChoices; hard rule)",
|
|
2562
2428
|
"每个 UI 用途必须在契约的 uiComponentChoices[] 中声明组件选型:{ purpose, component, decision, specReference, rationale }。",
|
|
2563
|
-
"purpose is the stable coverage key:它应精确匹配 interaction.name 或 uiState.name;职责语义由对应 interaction.expectedBehavior / uiState.expectedBehavior 与 rationale 表达。不得仅因 purpose 与 interaction 或 component 标识符相同而判缺陷。",
|
|
2564
2429
|
"- decision=specified:前端规范(候选组件/主题桶 + 任务源显式引用)已定义该用途组件 → 必须使用该组件,并给精确 specReference { path, section, line }(path 必须是 openspec/ai_workspace 受支持规范路径)。",
|
|
2565
|
-
"- decision=reuse-existing
|
|
2566
|
-
"- decision=new
|
|
2430
|
+
"- decision=reuse-existing:复用仓库既有组件/惯例(规范未点名)→ specReference 可为 null,rationale 说明复用的现有组件与依据。",
|
|
2431
|
+
"- decision=new:规范与既有代码均无合适组件 → specReference 必须为 null,rationale 必须说明偏差理由(design-review 审,最终 review 复核)。",
|
|
2567
2432
|
"不得静默替换规范组件或自创组件而无偏差声明;spec 已定义该用途组件时不得改选其它组件。",
|
|
2568
|
-
"prewrite gate
|
|
2433
|
+
"prewrite gate 确定性交叉校验:specReference.path 非法 → component-spec-reference-invalid;未在 openspec-citations 引用块中引用或未真实读取 → component-spec-not-cited;候选桶非空且契约有 UI 可见工作而 uiComponentChoices 缺失/空 → component-choices-missing。",
|
|
2569
2434
|
].join("\n");
|
|
2570
2435
|
function resolveFrontendCapabilityContextBlock(sources) {
|
|
2571
2436
|
const risk = sources.frontendRisk;
|
|
@@ -2579,7 +2444,34 @@ function resolveFrontendCapabilityContextBlock(sources) {
|
|
|
2579
2444
|
}
|
|
2580
2445
|
if (capability) {
|
|
2581
2446
|
parts.push("", capability.adapterGuidance);
|
|
2582
|
-
|
|
2447
|
+
const classified = capability.designEvidence.classified;
|
|
2448
|
+
const bucketLines = [];
|
|
2449
|
+
const pushBucket = (label, paths) => {
|
|
2450
|
+
if (paths.length > 0)
|
|
2451
|
+
bucketLines.push(`${label}: ${paths.join(", ")}`);
|
|
2452
|
+
};
|
|
2453
|
+
pushBucket("schemas", classified.schemas);
|
|
2454
|
+
pushBucket("code-template", classified.codeTemplate);
|
|
2455
|
+
pushBucket("rule.api", classified.rule.api);
|
|
2456
|
+
pushBucket("rule.mock", classified.rule.mock);
|
|
2457
|
+
pushBucket("rule.router", classified.rule.router);
|
|
2458
|
+
pushBucket("rule.hooks", classified.rule.hooks);
|
|
2459
|
+
pushBucket("rule.utils", classified.rule.utils);
|
|
2460
|
+
pushBucket("rule.components", classified.rule.components);
|
|
2461
|
+
pushBucket("rule.other", classified.rule.other);
|
|
2462
|
+
pushBucket("theme", classified.theme);
|
|
2463
|
+
pushBucket("component", classified.component);
|
|
2464
|
+
pushBucket("ui-other", classified.uiOther);
|
|
2465
|
+
pushBucket("advisory-other", classified.advisoryOther);
|
|
2466
|
+
parts.push("## Classified openspec specification paths (role semantics)");
|
|
2467
|
+
if (bucketLines.length > 0) {
|
|
2468
|
+
parts.push(...bucketLines);
|
|
2469
|
+
}
|
|
2470
|
+
else {
|
|
2471
|
+
parts.push("(no openspec specification paths discovered — greenfield)");
|
|
2472
|
+
}
|
|
2473
|
+
parts.push("component / theme / rule.components 是「组件/主题规范」必读语义桶:规范已定义某用途组件时必须使用它(uiComponentChoices 用 decision=specified + 精确 path/section/line),不得静默替换为自认更合适的组件;无规范定义时才允许 reuse-existing 或 new(new 必须声明偏差 rationale)。这些路径在生成期冻结为 prewrite gate 的 componentSpecCandidatePaths。");
|
|
2474
|
+
parts.push("Each consuming node MUST report in its output: applicable rules, the hit path/section/line number for every applied specification, and any conflicts or missing specifications. Missing or conflicting required specifications must fail closed rather than silently substituting nearby repository conventions.");
|
|
2583
2475
|
parts.push(`A11y capability: ${capability.a11y.status}` +
|
|
2584
2476
|
(capability.a11y.tools.length
|
|
2585
2477
|
? ` (${capability.a11y.tools.join(", ")})`
|
|
@@ -2588,102 +2480,17 @@ function resolveFrontendCapabilityContextBlock(sources) {
|
|
|
2588
2480
|
}
|
|
2589
2481
|
return parts.join("\n");
|
|
2590
2482
|
}
|
|
2591
|
-
function pruneFrontendTasksForMicro(tasks) {
|
|
2592
|
-
// Micro topology (7 nodes): contract-import-shell → writer-admission →
|
|
2593
|
-
// implement → verify → review-context → review → closeout. Drops scout/plan/
|
|
2594
|
-
// design-policy/design-review and replaces the model contract node with a
|
|
2595
|
-
// deterministic contract-import shell (§7.3: no model, no requirement
|
|
2596
|
-
// rewrites — micro consumes a pre-validated managed Contract).
|
|
2597
|
-
const drop = new Set([
|
|
2598
|
-
"frontend-contract-pi",
|
|
2599
|
-
"frontend-scout-pi",
|
|
2600
|
-
"frontend-plan-pi",
|
|
2601
|
-
"frontend-design-policy-shell",
|
|
2602
|
-
"frontend-design-review-pi",
|
|
2603
|
-
]);
|
|
2604
|
-
const contractPi = tasks.find((task) => task.id === "frontend-contract-pi");
|
|
2605
|
-
const contractImportShell = contractPi
|
|
2606
|
-
? {
|
|
2607
|
-
id: "frontend-contract-import-shell",
|
|
2608
|
-
role: "verifier",
|
|
2609
|
-
executor: "shell",
|
|
2610
|
-
complexity: "LOW",
|
|
2611
|
-
writePolicy: "read-only",
|
|
2612
|
-
depends_on: [],
|
|
2613
|
-
allowedPaths: contractPi.allowedPaths,
|
|
2614
|
-
forbiddenPaths: contractPi.forbiddenPaths,
|
|
2615
|
-
subtask_prompt: "Deterministic managed-Contract import + source/binding/schema freshness re-validation; no model, no requirement rewrite.",
|
|
2616
|
-
shell: {
|
|
2617
|
-
commands: [],
|
|
2618
|
-
frontendContractImport: {
|
|
2619
|
-
schemaVersion: 1,
|
|
2620
|
-
artifactName: "frontend-task-contract.json",
|
|
2621
|
-
outputDir: "contracts",
|
|
2622
|
-
requireSourceFreshness: true,
|
|
2623
|
-
},
|
|
2624
|
-
cwd: ".",
|
|
2625
|
-
timeoutMs: 60000,
|
|
2626
|
-
},
|
|
2627
|
-
}
|
|
2628
|
-
: undefined;
|
|
2629
|
-
const filtered = [
|
|
2630
|
-
...(contractImportShell ? [contractImportShell] : []),
|
|
2631
|
-
...tasks.filter((task) => !drop.has(task.id)),
|
|
2632
|
-
];
|
|
2633
|
-
const byId = new Map(filtered.map((task) => [task.id, task]));
|
|
2634
|
-
const remap = (deps) => {
|
|
2635
|
-
if (!deps)
|
|
2636
|
-
return [];
|
|
2637
|
-
const next = [];
|
|
2638
|
-
for (const dep of deps) {
|
|
2639
|
-
if (drop.has(dep))
|
|
2640
|
-
continue;
|
|
2641
|
-
if (byId.has(dep))
|
|
2642
|
-
next.push(dep);
|
|
2643
|
-
}
|
|
2644
|
-
return [...new Set(next)];
|
|
2645
|
-
};
|
|
2646
|
-
return filtered.map((task) => {
|
|
2647
|
-
const depends_on = remap(task.depends_on);
|
|
2648
|
-
if (task.id === "frontend-writer-admission-shell") {
|
|
2649
|
-
const admission = task.shell?.frontendWriterAdmission;
|
|
2650
|
-
return {
|
|
2651
|
-
...task,
|
|
2652
|
-
depends_on: ["frontend-contract-import-shell"],
|
|
2653
|
-
shell: admission
|
|
2654
|
-
? {
|
|
2655
|
-
...task.shell,
|
|
2656
|
-
commands: task.shell?.commands ?? [],
|
|
2657
|
-
frontendWriterAdmission: {
|
|
2658
|
-
...admission,
|
|
2659
|
-
designReviewFromNodeId: undefined,
|
|
2660
|
-
},
|
|
2661
|
-
}
|
|
2662
|
-
: task.shell,
|
|
2663
|
-
};
|
|
2664
|
-
}
|
|
2665
|
-
return { ...task, depends_on };
|
|
2666
|
-
});
|
|
2667
|
-
}
|
|
2668
|
-
function pruneFrontendTasksForSplitRequired(tasks) {
|
|
2669
|
-
const writerChain = new Set([
|
|
2670
|
-
"frontend-writer-admission-shell",
|
|
2671
|
-
"frontend-implement-pi",
|
|
2672
|
-
"frontend-verify-shell",
|
|
2673
|
-
"frontend-review-context-shell",
|
|
2674
|
-
"frontend-review-pi",
|
|
2675
|
-
"frontend-closeout-shell",
|
|
2676
|
-
]);
|
|
2677
|
-
return tasks.filter((task) => !writerChain.has(task.id));
|
|
2678
|
-
}
|
|
2679
2483
|
function pruneFrontendTasksForRisk(tasks, risk) {
|
|
2680
2484
|
if (risk.forceFullGates || risk.selectedRisk !== "small") {
|
|
2681
2485
|
return tasks;
|
|
2682
2486
|
}
|
|
2683
|
-
// Small topology keeps one design review and removes only the
|
|
2684
|
-
//
|
|
2685
|
-
//
|
|
2686
|
-
const drop = new Set([
|
|
2487
|
+
// Small topology keeps one design review and removes only the conditional
|
|
2488
|
+
// revision/final-review branch. The deterministic prewrite gate consumes the
|
|
2489
|
+
// surviving plan and design review directly.
|
|
2490
|
+
const drop = new Set([
|
|
2491
|
+
"frontend-plan-revision-pi",
|
|
2492
|
+
"frontend-final-design-review-pi",
|
|
2493
|
+
]);
|
|
2687
2494
|
const filtered = tasks.filter((task) => !drop.has(task.id));
|
|
2688
2495
|
const byId = new Map(filtered.map((task) => [task.id, task]));
|
|
2689
2496
|
const remap = (deps) => {
|
|
@@ -2691,8 +2498,11 @@ function pruneFrontendTasksForRisk(tasks, risk) {
|
|
|
2691
2498
|
return [];
|
|
2692
2499
|
const next = [];
|
|
2693
2500
|
for (const dep of deps) {
|
|
2694
|
-
if (dep === "frontend-
|
|
2501
|
+
if (dep === "frontend-plan-revision-pi") {
|
|
2502
|
+
if (byId.has("frontend-plan-pi"))
|
|
2503
|
+
next.push("frontend-plan-pi");
|
|
2695
2504
|
continue;
|
|
2505
|
+
}
|
|
2696
2506
|
if (byId.has(dep) || dep === "frontend-implement-pi")
|
|
2697
2507
|
next.push(dep);
|
|
2698
2508
|
}
|
|
@@ -2700,27 +2510,30 @@ function pruneFrontendTasksForRisk(tasks, risk) {
|
|
|
2700
2510
|
};
|
|
2701
2511
|
return filtered.map((task) => {
|
|
2702
2512
|
const depends_on = remap(task.depends_on);
|
|
2703
|
-
if (task.id === "frontend-
|
|
2704
|
-
|
|
2705
|
-
// the design-review dependency and the verdict requirement.
|
|
2706
|
-
const admission = task.shell?.frontendWriterAdmission;
|
|
2513
|
+
if (task.id === "frontend-prewrite-gate-shell") {
|
|
2514
|
+
const gate = task.shell?.frontendPrewriteGate;
|
|
2707
2515
|
return {
|
|
2708
2516
|
...task,
|
|
2709
|
-
depends_on: ["frontend-design-
|
|
2710
|
-
|
|
2517
|
+
depends_on: ["frontend-plan-pi", "frontend-design-review-pi"],
|
|
2518
|
+
dependsPolicy: "all",
|
|
2519
|
+
shell: gate
|
|
2711
2520
|
? {
|
|
2712
2521
|
...task.shell,
|
|
2713
2522
|
commands: task.shell?.commands ?? [],
|
|
2714
|
-
|
|
2715
|
-
...
|
|
2716
|
-
|
|
2523
|
+
frontendPrewriteGate: {
|
|
2524
|
+
...gate,
|
|
2525
|
+
planFromNodeId: "frontend-plan-pi",
|
|
2526
|
+
planFallbackFromNodeIds: [],
|
|
2527
|
+
reviewFromNodeId: "frontend-design-review-pi",
|
|
2528
|
+
reviewFallbackFromNodeIds: [],
|
|
2529
|
+
revisionPatch: false,
|
|
2717
2530
|
},
|
|
2718
2531
|
}
|
|
2719
2532
|
: task.shell,
|
|
2720
2533
|
};
|
|
2721
2534
|
}
|
|
2722
2535
|
if (task.id === "frontend-implement-pi") {
|
|
2723
|
-
for (const need of ["frontend-
|
|
2536
|
+
for (const need of ["frontend-prewrite-gate-shell"]) {
|
|
2724
2537
|
if (byId.has(need) && !depends_on.includes(need))
|
|
2725
2538
|
depends_on.push(need);
|
|
2726
2539
|
}
|
|
@@ -2745,54 +2558,9 @@ function buildFrontendWriterNodeDefaults(input) {
|
|
|
2745
2558
|
allowedPaths: input.allowedPaths,
|
|
2746
2559
|
forbiddenPaths: input.forbiddenPaths,
|
|
2747
2560
|
skills: FRONTEND_BOUNDED_IMPLEMENT_SKILLS,
|
|
2748
|
-
writerOutcomePolicy: {
|
|
2749
|
-
type: input.writerOutcomePolicyType ?? "implementation-outcome-v1",
|
|
2750
|
-
},
|
|
2561
|
+
writerOutcomePolicy: { type: "implementation-outcome-v1" },
|
|
2751
2562
|
};
|
|
2752
2563
|
}
|
|
2753
|
-
function resolveFrontendShapeCapsuleGenerationInput(sources) {
|
|
2754
|
-
const explicit = sources.frontendShapeTransitionCapsule == null
|
|
2755
|
-
? undefined
|
|
2756
|
-
: parseFrontendShapeTransitionCapsule(sources.frontendShapeTransitionCapsule);
|
|
2757
|
-
const autoloaded = sources.autoloadedFrontendShapeTransitionCapsule == null
|
|
2758
|
-
? undefined
|
|
2759
|
-
: parseFrontendShapeTransitionCapsule(sources.autoloadedFrontendShapeTransitionCapsule);
|
|
2760
|
-
if (explicit && autoloaded && explicit.capsuleDigest !== autoloaded.capsuleDigest) {
|
|
2761
|
-
throw new Error("explicit frontend shape transition capsule conflicts with runtime autoload");
|
|
2762
|
-
}
|
|
2763
|
-
return explicit ?? autoloaded;
|
|
2764
|
-
}
|
|
2765
|
-
/** Standard frontend DAG template (docs/templates/frontend-implementation-dag.json).
|
|
2766
|
-
* It is the topology source of truth: node set, execution order, and depends_on
|
|
2767
|
-
* come from the template; the runtime generator assembles each node's full
|
|
2768
|
-
* configuration (budgets, retry, skeleton, skills, dynamic prompt sections).
|
|
2769
|
-
* Resolution order: repoRoot copy (dev workspace) → bundled package copy. */
|
|
2770
|
-
export async function loadFrontendDagTemplate(repoRoot) {
|
|
2771
|
-
const candidates = [];
|
|
2772
|
-
if (repoRoot) {
|
|
2773
|
-
candidates.push(path.join(repoRoot, "docs", "templates", "frontend-implementation-dag.json"));
|
|
2774
|
-
}
|
|
2775
|
-
candidates.push(fileURLToPath(new URL("../../../docs/templates/frontend-implementation-dag.json", import.meta.url)));
|
|
2776
|
-
for (const candidate of candidates) {
|
|
2777
|
-
try {
|
|
2778
|
-
const parsed = JSON.parse(await readFile(candidate, "utf8"));
|
|
2779
|
-
if (!Array.isArray(parsed.tasks))
|
|
2780
|
-
continue;
|
|
2781
|
-
const tasks = parsed.tasks.filter((item) => typeof item === "object" &&
|
|
2782
|
-
item !== null &&
|
|
2783
|
-
typeof item.id === "string" &&
|
|
2784
|
-
Array.isArray(item.depends_on) &&
|
|
2785
|
-
typeof item.executor === "string");
|
|
2786
|
-
if (tasks.length === 0)
|
|
2787
|
-
continue;
|
|
2788
|
-
return { tasks };
|
|
2789
|
-
}
|
|
2790
|
-
catch {
|
|
2791
|
-
// try next candidate
|
|
2792
|
-
}
|
|
2793
|
-
}
|
|
2794
|
-
return null;
|
|
2795
|
-
}
|
|
2796
2564
|
async function buildFrontendHybridDagFromTask(sources) {
|
|
2797
2565
|
const { taskConfig } = sources;
|
|
2798
2566
|
const mockCapability = sources.frontendMockCapability ?? {
|
|
@@ -2817,11 +2585,6 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2817
2585
|
const implementId = frontendImplementationNodeId();
|
|
2818
2586
|
const mockContextBlock = resolveFrontendMockContextBlock(frontendSources);
|
|
2819
2587
|
const capabilityContextBlock = resolveFrontendCapabilityContextBlock(frontendSources);
|
|
2820
|
-
// Freeze the design-policy dependency allowlist from the project manifest:
|
|
2821
|
-
// already-declared deps are authorized; anything else in the model's
|
|
2822
|
-
// dependency policy that looks like a package name is an unauthorized new
|
|
2823
|
-
// dependency (fail-closed at the policy shell and the plan pre-check).
|
|
2824
|
-
const declaredDependencies = await collectDeclaredDependencies(sources.repoRoot ?? process.cwd());
|
|
2825
2588
|
const frontendRisk = frontendSources.frontendRisk ??
|
|
2826
2589
|
classifyFrontendRisk({
|
|
2827
2590
|
title: taskConfig.title,
|
|
@@ -2830,63 +2593,138 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2830
2593
|
allowedPaths: taskConfig.allowedPaths,
|
|
2831
2594
|
complexity: taskConfig.complexity,
|
|
2832
2595
|
});
|
|
2833
|
-
const frontendTaskShape = resolveFrontendTaskShape({
|
|
2834
|
-
complexity: taskConfig.complexity,
|
|
2835
|
-
allowedPaths: taskConfig.allowedPaths,
|
|
2836
|
-
requirementMarkdown: sources.requirementMarkdown,
|
|
2837
|
-
constraintMarkdown: sources.constraintMarkdown ?? undefined,
|
|
2838
|
-
splitSignal: { splitRequired: false, runtimeSupportsSplit: true },
|
|
2839
|
-
taskId: sources.taskId,
|
|
2840
|
-
sourceDigest: computeFrontendShapeSourceDigest({
|
|
2841
|
-
requirementMarkdown: sources.requirementMarkdown,
|
|
2842
|
-
constraintMarkdown: sources.constraintMarkdown ?? "",
|
|
2843
|
-
}),
|
|
2844
|
-
shapeTransitionCapsule: resolveFrontendShapeCapsuleGenerationInput(sources),
|
|
2845
|
-
});
|
|
2846
2596
|
const frontendSourceBinding = buildDagSourceBinding(sources, taskConfig.taskKind);
|
|
2847
2597
|
const frontendContractSkeleton = buildFrontendImplementationContractSkeleton({
|
|
2848
2598
|
sourceBinding: frontendSourceBinding,
|
|
2849
2599
|
riskLevel: frontendRisk.selectedRisk,
|
|
2850
2600
|
targetFiles: implementPaths.writeSet,
|
|
2851
2601
|
});
|
|
2602
|
+
const frontendContractSchemaBlock = (() => {
|
|
2603
|
+
const schema = loadFrontendImplementationContractJsonSchema();
|
|
2604
|
+
return [
|
|
2605
|
+
`## Final ${FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID} JSON Schema (authoritative after runtime merge)`,
|
|
2606
|
+
schema,
|
|
2607
|
+
"",
|
|
2608
|
+
"## Runtime contract skeleton (deterministic and protected)",
|
|
2609
|
+
JSON.stringify(frontendContractSkeleton),
|
|
2610
|
+
"",
|
|
2611
|
+
"The initial planner emits an editable RFC 7386 patch against this skeleton. It MUST omit schemaVersion, sourceBinding, riskLevel, targets.files, and mockApi.productionDefaultOff. The runtime merges and validates the final contract, then writes a hash-bound canonical JSON artifact; downstream review and prewrite consume that artifact path, not planner stdout.",
|
|
2612
|
+
"",
|
|
2613
|
+
"## Forbidden fields (these are NOT in the schema; do not emit)",
|
|
2614
|
+
"- schemaId",
|
|
2615
|
+
"- targetFiles",
|
|
2616
|
+
"- requirementCoverage",
|
|
2617
|
+
"",
|
|
2618
|
+
"## Critical rules",
|
|
2619
|
+
"- verificationTargets is a TOP-LEVEL required array",
|
|
2620
|
+
"- uiStates items use name/applicable/expectedBehavior/implementationTargets/verificationTargetIds/notApplicableReason",
|
|
2621
|
+
"- Use uiStates: [] for frontend logic changes with no user-visible UI state. Do not invent UI states.",
|
|
2622
|
+
"- For applicable=true, provide non-empty expectedBehavior plus non-empty implementationTargets and verificationTargetIds. For applicable=false, provide non-empty notApplicableReason and omit expectedBehavior instead of emitting an empty string.",
|
|
2623
|
+
"- mockApi.productionDefaultOff must always be true (including strategy: not-needed)",
|
|
2624
|
+
"- All implementation files, verification files, symbols, and commands must be discovered from the current target workspace and current task. Never copy paths, symbols, or commands from the loop-agent repository, an example task, or prior run output.",
|
|
2625
|
+
"- Use relative POSIX paths rooted at the target workspace. Do not assume a particular src/test directory layout; preserve the target project's actual app/, packages/, spec/, __tests__, or other layout.",
|
|
2626
|
+
"",
|
|
2627
|
+
"## Bad / Good contract field examples",
|
|
2628
|
+
"",
|
|
2629
|
+
"### verificationTargets - BAD (invented commandLabel, missing file):",
|
|
2630
|
+
'{"id":"vt-1","type":"static","commandLabel":"lint","file":"","requirementIds":["AC-001"],"uiStates":[]} <-- REJECTED: commandLabel not in frozen command set; empty file path',
|
|
2631
|
+
"",
|
|
2632
|
+
'### verificationTargets - GOOD (real frozen label, real file):',
|
|
2633
|
+
'{"id":"vt-1","type":"static","commandLabel":"npm run typecheck","file":"tsconfig.json","requirementIds":["AC-001"],"uiStates":[]} <-- Matches frozen command set; real file path',
|
|
2634
|
+
"",
|
|
2635
|
+
"### requirements - BAD (missing expectedOutcome):",
|
|
2636
|
+
'{"id":"AC-001","expectedOutcome":"","implementationTargets":["src/app.tsx"],"verificationTargetIds":["vt-1"]} <-- REJECTED: empty expectedOutcome',
|
|
2637
|
+
"",
|
|
2638
|
+
"### requirements - GOOD (concrete expectedOutcome):",
|
|
2639
|
+
'{"id":"AC-001","expectedOutcome":"TypeScript compilation exits with code 0 and produces no errors in dist/","implementationTargets":["src/app.tsx"],"verificationTargetIds":["vt-1"]}',
|
|
2640
|
+
"",
|
|
2641
|
+
"### interactions - BAD (empty trigger/expectedBehavior):",
|
|
2642
|
+
'{"name":"save-click","trigger":"","expectedBehavior":"","implementationTargets":["src/button.tsx"],"verificationTargetIds":["vt-3"]} <-- REJECTED: empty trigger and expectedBehavior',
|
|
2643
|
+
"",
|
|
2644
|
+
"### interactions - GOOD:",
|
|
2645
|
+
'{"name":"save-click","trigger":"User clicks the Save button in the editor toolbar","expectedBehavior":"POST /api/save is called with editor content; success toast appears; button enters disabled+spinner state until response","implementationTargets":["src/editor/save-button.tsx"],"verificationTargetIds":["vt-3"]}',
|
|
2646
|
+
"",
|
|
2647
|
+
"### uiStates - BAD (applicable=true but missing expectedBehavior):",
|
|
2648
|
+
'{"name":"loading","applicable":true,"expectedBehavior":"","implementationTargets":[],"verificationTargetIds":[]} <-- REJECTED: applicable UI state requires non-empty expectedBehavior, implementationTargets, and verificationTargetIds',
|
|
2649
|
+
"",
|
|
2650
|
+
"### uiStates - GOOD (applicable=true with complete fields):",
|
|
2651
|
+
'{"name":"loading","applicable":true,"expectedBehavior":"Skeleton placeholder visible while data fetches; aria-busy=true on the list container","implementationTargets":["src/dashboard/list-view.tsx"],"verificationTargetIds":["vt-3"]}',
|
|
2652
|
+
"",
|
|
2653
|
+
"### uiStates - BAD (applicable=false without notApplicableReason):",
|
|
2654
|
+
'{"name":"dark-mode","applicable":false} <-- REJECTED: non-applicable UI state requires notApplicableReason',
|
|
2655
|
+
"",
|
|
2656
|
+
"### uiStates - GOOD (applicable=false with reason):",
|
|
2657
|
+
'{"name":"dark-mode","applicable":false,"notApplicableReason":"Dark mode toggle is out of scope for this task; only light theme is targeted"}',
|
|
2658
|
+
"",
|
|
2659
|
+
"### mockApi.endpoints - BAD (strategy=native but empty endpoints):",
|
|
2660
|
+
'{"strategy":"native","productionDefaultOff":true,"activation":"env flag","endpoints":[]} <-- REJECTED: native strategy requires at least one endpoint with method, path, fixture, and consumer',
|
|
2661
|
+
"",
|
|
2662
|
+
"### mockApi.endpoints - GOOD (strategy=native with complete endpoint):",
|
|
2663
|
+
'{"strategy":"native","productionDefaultOff":true,"activation":"VITE_ENABLE_MOCK=true","endpoints":[{"method":"GET","path":"/api/users","fixture":"mocks/fixtures/users.json","consumer":"src/api/users.ts"}]}',
|
|
2664
|
+
"",
|
|
2665
|
+
"### optional plan fields - GOOD (all optional; omit when absent):",
|
|
2666
|
+
'{"implementationSteps":["confirm contract","sync tests"],"stylingStrategy":"reuse existing design tokens","dependencyPolicy":"no new runtime deps","residualRisks":["browser a11y not-run"],"realIntegrationGap":"FE-TEST owns live HTTP"}',
|
|
2667
|
+
"",
|
|
2668
|
+
"### optional plan fields - BAD (present-but-empty strings are rejected):",
|
|
2669
|
+
'{"stylingStrategy":"","dependencyPolicy":""} <-- REJECTED: optional string fields must be non-empty when present; omit them instead',
|
|
2670
|
+
"",
|
|
2671
|
+
"### uiComponentChoices - GOOD (specified with precise spec hit):",
|
|
2672
|
+
'{"purpose":"primary action button","component":"Button","decision":"specified","specReference":{"path":"openspec/schemas/button.md","section":"Variants","line":12},"rationale":"spec mandates Button for primary actions"}',
|
|
2673
|
+
"",
|
|
2674
|
+
"### uiComponentChoices - GOOD (new with deviation rationale; specReference null):",
|
|
2675
|
+
'{"purpose":"loading skeleton","component":"SkeletonCard","decision":"new","specReference":null,"rationale":"no spec or existing component covers skeleton; deviation pending design-review approval"}',
|
|
2676
|
+
"",
|
|
2677
|
+
"### uiComponentChoices - BAD (specified without specReference, or new with specReference):",
|
|
2678
|
+
'{"purpose":"primary action","component":"MyButton","decision":"specified","specReference":null,"rationale":"..."} <-- REJECTED: specified requires specReference',
|
|
2679
|
+
'{"purpose":"primary action","component":"MyButton","decision":"new","specReference":{"path":"openspec/schemas/button.md","section":"","line":null},"rationale":"..."} <-- REJECTED: new must not carry specReference',
|
|
2680
|
+
].join("\n");
|
|
2681
|
+
})();
|
|
2852
2682
|
const frontendContractFieldSummary = [
|
|
2853
2683
|
"## Contract field summary (authoritative JSON; no plan prose)",
|
|
2854
|
-
"
|
|
2684
|
+
"The plan/revision node emits only a fenced json contract — there is no Markdown plan explanation to read. Review these fields:",
|
|
2855
2685
|
"- requirements[]: id, expectedOutcome, implementationTargets, verificationTargetIds, evidenceGap",
|
|
2856
2686
|
"- uiStates[]: name, applicable, expectedBehavior, implementationTargets, verificationTargetIds, notApplicableReason",
|
|
2857
2687
|
"- interactions[]: name, trigger, expectedBehavior, implementationTargets, verificationTargetIds",
|
|
2858
|
-
"- targets: routes, publicApiChanges
|
|
2688
|
+
"- targets: files, routes, publicApiChanges",
|
|
2859
2689
|
"- mockApi: strategy, productionDefaultOff, activation, endpoints[]",
|
|
2860
|
-
"- verificationTargets[]: id
|
|
2861
|
-
"- designEvidence: source, paths, conflicts; evidenceGaps[]
|
|
2862
|
-
"- optional: stylingStrategy, uiComponentChoices[], dependencyPolicy, residualRisks[], realIntegrationGap",
|
|
2690
|
+
"- verificationTargets[]: id, type, commandLabel, file, symbol, requirementIds, uiStates",
|
|
2691
|
+
"- designEvidence: source, paths, conflicts; evidenceGaps[]",
|
|
2692
|
+
"- optional: implementationSteps[], stylingStrategy, uiComponentChoices[], dependencyPolicy, residualRisks[], realIntegrationGap",
|
|
2863
2693
|
"- uiComponentChoices[]: purpose, component, decision (specified|reuse-existing|new), specReference { path, section, line } | null, rationale",
|
|
2864
2694
|
"Do not require or read a separate plan prose section; the contract JSON is the only plan surface.",
|
|
2865
2695
|
].join("\n");
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
contract: [buildSourceContextBlock(sources), capabilityContextBlock]
|
|
2873
|
-
.filter(Boolean)
|
|
2874
|
-
.join("\n\n"),
|
|
2875
|
-
scout: [
|
|
2876
|
-
buildSourceContextBlock(sources, { includeReferenceDocuments: false }),
|
|
2877
|
-
capabilityContextBlock,
|
|
2878
|
-
]
|
|
2879
|
-
.filter(Boolean)
|
|
2880
|
-
.join("\n\n"),
|
|
2881
|
-
designReview: buildSourceContextBlock(sources, {
|
|
2882
|
-
includeRequirementExcerpt: false,
|
|
2883
|
-
includeConstraintExcerpt: false,
|
|
2884
|
-
includeReferenceDocuments: false,
|
|
2885
|
-
}),
|
|
2886
|
-
};
|
|
2696
|
+
const sourceContext = [
|
|
2697
|
+
buildSourceContextBlock(sources),
|
|
2698
|
+
capabilityContextBlock,
|
|
2699
|
+
]
|
|
2700
|
+
.filter(Boolean)
|
|
2701
|
+
.join("\n\n");
|
|
2887
2702
|
const hasMockVerifyCommands = (taskConfig.frontendMock?.verifyCommands.length ?? 0) > 0 ||
|
|
2888
2703
|
mockCapability.verifyCommands.length > 0;
|
|
2889
2704
|
const requirementIds = frontendSourceBinding.requirementIds;
|
|
2705
|
+
const requirementCoverageInstruction = requirementIds.length > 0
|
|
2706
|
+
? [
|
|
2707
|
+
`## Requirement Coverage (per-AC echo with bad/good examples)`,
|
|
2708
|
+
`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).`,
|
|
2709
|
+
`Do not skip any ID. Use the bad/good patterns below as reference for each field.`,
|
|
2710
|
+
``,
|
|
2711
|
+
`Bad example (empty expectedOutcome, empty targets -- REJECTED at contract materialization):`,
|
|
2712
|
+
`- AC-001: expectedOutcome="" implementationTargets=[] verificationTargets=[]`,
|
|
2713
|
+
``,
|
|
2714
|
+
`Good example (concrete expectedOutcome, real files, real verification targets):`,
|
|
2715
|
+
`- 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"]`,
|
|
2716
|
+
``,
|
|
2717
|
+
...requirementIds.map((id) => `- ${id}: [expectedOutcome] [implementation files] [verification targets]`),
|
|
2718
|
+
``,
|
|
2719
|
+
`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.`,
|
|
2720
|
+
].join("\n")
|
|
2721
|
+
: "";
|
|
2722
|
+
const verificationTargetFileInstruction = [
|
|
2723
|
+
`## Verification target file semantics`,
|
|
2724
|
+
`verificationTargets[].file is the code file that the target verifies (the file the writer changes), NOT where the command is defined.`,
|
|
2725
|
+
`Non-static targets (type unit/component/integration/mock) MUST set file to a concrete code file inside the implementation writeSet (task allowedPaths); the prewrite gate rejects any non-static target whose file falls outside the writeSet.`,
|
|
2726
|
+
`Command-level checks that run project-wide (all tests, typecheck, build, governance) MUST use type "static" and must NOT be bound as non-static targets with file=package.json/tsconfig.json/vite.config.ts/scripts/*. Static targets are exempt from the writeSet containment check.`,
|
|
2727
|
+
].join("\n");
|
|
2890
2728
|
const strategy = resolveDagVerifyStrategy(taskConfig);
|
|
2891
2729
|
const readOnlyPaths = taskConfig.allowedPaths.length > 0 ? taskConfig.allowedPaths : ["**"];
|
|
2892
2730
|
const behaviorPaths = deriveFrontendBehaviorPaths(taskConfig);
|
|
@@ -2896,15 +2734,15 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2896
2734
|
? [`See 执行约束.md in task source (${sources.taskId})`]
|
|
2897
2735
|
: []),
|
|
2898
2736
|
...STANDARD_GLOBAL_CONSTRAINTS,
|
|
2899
|
-
"Frontend
|
|
2900
|
-
"
|
|
2901
|
-
"
|
|
2902
|
-
"
|
|
2737
|
+
"Frontend implementation DAGs must pass the effective final design verdict gate before any write node executes; an initial pass uses the original plan, while request-revision selects the read-only revision and final-review branch.",
|
|
2738
|
+
"Final design gate pass is the only authorization for frontend implementation writes.",
|
|
2739
|
+
"Plan revision remains read-only and never edits business code.",
|
|
2740
|
+
"Design revision failures route to replan-and-rerun, never dev-fix.",
|
|
2903
2741
|
"Frontend planning must consume the read-only Mock assessment strategy produced after scouting; MOCK_STRATEGY: blocked must not pass the deterministic Mock contract gate.",
|
|
2904
2742
|
"Mock implementations must preserve the real request path as the default, require explicit test/dev activation, and never rely on commenting out the real request.",
|
|
2905
2743
|
"Mock-backed behavior evidence proves only the documented frontend contract, never real API integration.",
|
|
2906
2744
|
"frontend-implementation DAGs must complete deterministic static verification before final review. Behavior verification is also required when the task declares a behavior entrypoint or the implementation contract contains a non-static verification target; static-only contracts must map every target to the declared static entrypoint.",
|
|
2907
|
-
"
|
|
2745
|
+
"frontend review must block closeout unless review verdict is exactly VERDICT: pass.",
|
|
2908
2746
|
`Frontend risk classification: ${frontendRisk.selectedRisk} — ${frontendRisk.reason}`,
|
|
2909
2747
|
frontendRisk.forceFullGates
|
|
2910
2748
|
? "High-risk or supervised: keep full design gates; do not weaken write boundaries."
|
|
@@ -3035,56 +2873,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3035
2873
|
frontendMockStrategyMustBeNotNeeded(frontendSources)) {
|
|
3036
2874
|
advisories.push("auto 模式已将 Mock 策略收窄为 not-needed:任务源提到接口/API/Mock 需求,但仓库无确认 Mock 能力或无确定性 Mock 验证命令。若项目规范要求 Mock,请声明 frontendMock.verifyCommands 或 policy:required 后重新生成 DAG。");
|
|
3037
2875
|
}
|
|
3038
|
-
const openspecGate =
|
|
3039
|
-
const requiresOpenspecClassification = openspecGate.openspecPolicy === "cited" &&
|
|
3040
|
-
openspecGate.openspecCandidatePaths.length > 0;
|
|
3041
|
-
// Prompt 内联的候选只保留与任务相关的子集:mandatory(任务显式声明/
|
|
3042
|
-
// 引用)始终保留;scan-strict 候选按路径段是否命中任务源关键词过滤。
|
|
3043
|
-
// runtime 的选型/read 门禁仍消费完整 openspecCandidatePaths——这里只
|
|
3044
|
-
// 减小 prompt 体积,不改变门禁语义;未提及的候选 runtime 默认 irrelevant。
|
|
3045
|
-
const promptCandidatePaths = filterRelevantOpenspecCandidates({
|
|
3046
|
-
candidates: openspecGate.openspecCandidateSummaries.map((candidate) => candidate.path),
|
|
3047
|
-
mandatoryPaths: openspecGate.openspecMandatoryPaths,
|
|
3048
|
-
sourceMarkdown: [
|
|
3049
|
-
sources.requirementMarkdown,
|
|
3050
|
-
sources.constraintMarkdown ?? "",
|
|
3051
|
-
].join("\n"),
|
|
3052
|
-
maxCandidates: 24,
|
|
3053
|
-
});
|
|
3054
|
-
const candidateByPath = new Map(openspecGate.openspecCandidateSummaries.map((candidate) => [
|
|
3055
|
-
candidate.path,
|
|
3056
|
-
candidate,
|
|
3057
|
-
]));
|
|
3058
|
-
// Preserve the filter's mandatory/relevance order. Sorting then slicing here
|
|
3059
|
-
// used to be able to drop a mandatory path after it passed the Top-K filter.
|
|
3060
|
-
const promptCandidates = promptCandidatePaths.flatMap((candidatePath) => {
|
|
3061
|
-
const candidate = candidateByPath.get(candidatePath);
|
|
3062
|
-
return candidate
|
|
3063
|
-
? [
|
|
3064
|
-
{
|
|
3065
|
-
path: candidate.path,
|
|
3066
|
-
kind: candidate.kind,
|
|
3067
|
-
score: candidate.score,
|
|
3068
|
-
reasons: candidate.reasons,
|
|
3069
|
-
source: candidate.source,
|
|
3070
|
-
},
|
|
3071
|
-
]
|
|
3072
|
-
: [];
|
|
3073
|
-
});
|
|
3074
|
-
const openspecSelectionContext = JSON.stringify({
|
|
3075
|
-
schemaVersion: 1,
|
|
3076
|
-
schemaId: "frontend-openspec-selection-v1",
|
|
3077
|
-
candidates: promptCandidates,
|
|
3078
|
-
mandatoryPaths: openspecGate.openspecMandatoryPaths,
|
|
3079
|
-
});
|
|
3080
|
-
const scopedOpenspecContext = promptCandidates.length > 0
|
|
3081
|
-
? [
|
|
3082
|
-
"## Task-relevant OpenSpec Top-K (generation frozen)",
|
|
3083
|
-
`Policy: ${openspecGate.openspecPolicy}. This is the bounded prompt index; the deterministic gate retains ${openspecGate.openspecCandidatePaths.length} frozen candidates.`,
|
|
3084
|
-
...promptCandidates.map((candidate) => `- ${openspecGate.openspecMandatoryPaths.includes(candidate.path) ? "mandatory" : "candidate"}: ${candidate.path} (${candidate.kind}; ${candidate.source})`),
|
|
3085
|
-
"Apply or cite only paths relevant to the concrete contract. Report applied rules with path/section/line and surface conflicts or missing specifications; unlisted candidates default to irrelevant unless the deterministic gate requires them.",
|
|
3086
|
-
].join("\n")
|
|
3087
|
-
: "";
|
|
2876
|
+
const openspecGate = resolveFrontendOpenspecGateConfig(sources);
|
|
3088
2877
|
// Generation-frozen component/theme specification bucket (ADR 0016). Derived
|
|
3089
2878
|
// from the classified component/theme/rule.components buckets; the prewrite
|
|
3090
2879
|
// gate consumes it to enforce uiComponentChoices presence and specReference
|
|
@@ -3101,7 +2890,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3101
2890
|
advisories.push("openspec 策略 cited:契约声明的 requiredReadPaths 与任务源引用均为空,prewrite gate 不强制读取 openspec;如需增强规范门禁,请在 task.json.frontendOpenspec.requiredReadPaths 声明必读路径或在任务源中显式引用 openspec 文件。");
|
|
3102
2891
|
}
|
|
3103
2892
|
else {
|
|
3104
|
-
advisories.push(`openspec 策略 cited:候选 ${openspecGate.openspecCandidatePaths.length} 个(declared ${openspecGate.openspecCandidateSources.declared.length} / task-source-cited ${openspecGate.openspecCandidateSources.taskSourceCited.length}),plan 必须在
|
|
2893
|
+
advisories.push(`openspec 策略 cited:候选 ${openspecGate.openspecCandidatePaths.length} 个(declared ${openspecGate.openspecCandidateSources.declared.length} / task-source-cited ${openspecGate.openspecCandidateSources.taskSourceCited.length}),plan/review 必须在 openspec-citations 引用块中逐条引用并真实读取。`);
|
|
3105
2894
|
}
|
|
3106
2895
|
}
|
|
3107
2896
|
else {
|
|
@@ -3138,25 +2927,13 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3138
2927
|
writePolicy: "read-only",
|
|
3139
2928
|
allowedPaths: readOnlyPaths,
|
|
3140
2929
|
forbiddenPaths,
|
|
3141
|
-
skills:
|
|
3142
|
-
outputContract: "
|
|
2930
|
+
skills: FRONTEND_IMPLEMENTATION_SKILLS,
|
|
2931
|
+
outputContract: "Markdown contract with Scope, Non-goals, Acceptance Criteria, UI States, Target Runtime Environment, Risks, and Verification Expectations. No file writes.",
|
|
3143
2932
|
subtask_prompt: [
|
|
3144
|
-
"
|
|
3145
|
-
"
|
|
3146
|
-
"Assign each requirement the SAME id as the ledger canonical requirement it covers (sourceBinding.requirementIds, e.g. AC-001) — do NOT invent new REQ/BR prefixed ids for canonical requirements: the compiled contract must match the ledger canonical requirement ids exactly or schema validation rejects it (unknown requirement id). Requirements use the canonical id with a source span (task-source section or repository file:line). Label each requirement's disposition as explicit | repository-resolvable | assumption | blocking; a blocking requirement must name its owner (human-decision or external-state) and evidence refs.",
|
|
3147
|
-
"Source fidelity ledger: when the DAG sourceBinding carries a requirement→fragment mapping (requirementToFragments, e.g. REQ-SRC-* ids from the managed ledger), each record_requirement MUST declare the fragments that requirement is bound to: set sourceFragmentIds to the mapped fragment ids (the authoritative provenance evidence the design policy verifies). sourceRefs (fragment→path display refs) are optional — declare them only when you have the exact path from the materialized source; otherwise omit them rather than inventing paths. Declare exactly what the ledger binds — do not invent ids, do not omit them, and do not re-derive them from prose. A requirement that the ledger binds but the contract omits (or fabricates) fails writer admission.",
|
|
3148
|
-
"Register evidence expectations for each requirement across static, behavior, Mock, and real integration as required | optional | not-applicable; required must follow from user requirements, task risk, or project governance, never from model convenience. For UI-visible or interactive requirements, register a non-blocking frontend-test handoff intent.",
|
|
3149
|
-
"Cover scope, non-goals, acceptance criteria, UI states, target runtime environment, risks, and verification expectations. Do not fix target files, components, styling, or implementation methods as requirements; leave those to Scout and Plan.",
|
|
3150
|
-
"If the task is too large for one bounded writer, record a task split proposal instead of silently widening scope.",
|
|
3151
|
-
"End the contract with a single disposition: ready, ready-with-assumptions (bounded assumptions that do not change product behavior), or blocked.",
|
|
3152
|
-
scopedOpenspecContext,
|
|
3153
|
-
...(requiresOpenspecClassification ? [
|
|
3154
|
-
"Classify OpenSpec candidates incrementally while contracting — only the ones you actually use. Call record_openspec_selection once per path with disposition required (must be read and cited by the plan) or relevant (may inform planning). Never call it for irrelevant candidates and never list them: candidates you do not mention are treated as irrelevant by the runtime. Explicit task declarations / source citations are already required and must-read regardless; you never need to re-declare them.",
|
|
3155
|
-
"Mandatory paths are enforced by the runtime from the frozen task configuration — do not enumerate them, do not downgrade them.",
|
|
3156
|
-
openspecSelectionContext,
|
|
3157
|
-
] : []),
|
|
2933
|
+
"Read task source and produce a concise frontend implementation contract.",
|
|
2934
|
+
"Cover scope, non-goals, acceptance criteria, UI states, target runtime environment, risks, and verification expectations.",
|
|
3158
2935
|
"Read-only: do not modify code, docs, artifacts, or repository files.",
|
|
3159
|
-
|
|
2936
|
+
sourceContext,
|
|
3160
2937
|
].join("\n\n"),
|
|
3161
2938
|
},
|
|
3162
2939
|
{
|
|
@@ -3166,19 +2943,17 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3166
2943
|
executor: "pi",
|
|
3167
2944
|
complexity: mapTaskComplexity(taskConfig.complexity),
|
|
3168
2945
|
writePolicy: "read-only",
|
|
3169
|
-
retryPolicy: FRONTEND_SCOUT_COMPLETENESS_RETRY_POLICY,
|
|
3170
2946
|
allowedPaths: readOnlyPaths,
|
|
3171
2947
|
forbiddenPaths,
|
|
3172
|
-
skills:
|
|
3173
|
-
outputContract: "Markdown scout report
|
|
2948
|
+
skills: FRONTEND_IMPLEMENTATION_SKILLS,
|
|
2949
|
+
outputContract: "Markdown scout report with a required TARGET_SURFACE section covering frontend stack, routes, components, styling system, existing design conventions, state/data flow, test entry points, reuse opportunities, and risks. No file writes.",
|
|
3174
2950
|
subtask_prompt: [
|
|
3175
2951
|
"Inspect frontend code, routing, components, styles, package scripts, and tests.",
|
|
3176
2952
|
"Return code and design observations, existing reuse opportunities, and verification entry points.",
|
|
3177
|
-
"
|
|
2953
|
+
"Begin with a TARGET_SURFACE section containing exactly these labels: entrypoint, routeOrMount, implementationPaths, testPaths, dataSource, allowedPathConflicts. Use repository-relative POSIX paths. implementationPaths and testPaths must name the existing files/directories that actually own the requested behavior; allowedPathConflicts must list every discovered path not covered by task allowedPaths, or [] when none exists.",
|
|
3178
2954
|
"Derive all file paths from this target workspace. Do not assume the project uses src/, test/, React, or the loop-agent repository layout.",
|
|
3179
2955
|
"Read-only: do not modify repository files.",
|
|
3180
|
-
|
|
3181
|
-
scopedOpenspecContext,
|
|
2956
|
+
sourceContext,
|
|
3182
2957
|
].join("\n\n"),
|
|
3183
2958
|
},
|
|
3184
2959
|
{
|
|
@@ -3188,197 +2963,239 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3188
2963
|
executor: "pi",
|
|
3189
2964
|
complexity: "MED",
|
|
3190
2965
|
writePolicy: "read-only",
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
forbiddenPaths,
|
|
3194
|
-
skills: FRONTEND_PLAN_SKILLS,
|
|
2966
|
+
outputMode: "structured-required",
|
|
2967
|
+
retryPolicy: STRUCTURED_REQUIRED_PI_RETRY_POLICY,
|
|
3195
2968
|
structuredContractOutput: {
|
|
3196
|
-
schemaId:
|
|
2969
|
+
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_PLAN_PATCH_SCHEMA_ID,
|
|
3197
2970
|
retryOnInvalid: true,
|
|
3198
2971
|
skeleton: frontendContractSkeleton,
|
|
3199
2972
|
},
|
|
3200
|
-
|
|
2973
|
+
allowedPaths: readOnlyPaths,
|
|
2974
|
+
forbiddenPaths,
|
|
2975
|
+
skills: FRONTEND_IMPLEMENTATION_SKILLS,
|
|
2976
|
+
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 writes a hash-bound canonical JSON artifact for downstream review. Do NOT emit a full contract, Markdown plan explanation, raw JSON, or any other fenced block. No file writes.",
|
|
3201
2977
|
subtask_prompt: [
|
|
3202
|
-
"
|
|
3203
|
-
"
|
|
3204
|
-
"
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
"
|
|
3208
|
-
"
|
|
3209
|
-
"
|
|
2978
|
+
"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.",
|
|
2979
|
+
"The patch fields become the complete implementation plan after deterministic merge. Do not produce a separate plan document, prose mirror, or full contract.",
|
|
2980
|
+
"Select the Mock / API strategy only in the patch. Encode endpoint/fixture mapping, explicit activation, verification commands, and Real Integration Gap in schema-defined editable fields; productionDefaultOff comes from the protected skeleton and there is no second plan output.",
|
|
2981
|
+
"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.",
|
|
2982
|
+
"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.",
|
|
2983
|
+
"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.",
|
|
2984
|
+
"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.",
|
|
2985
|
+
"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.",
|
|
2986
|
+
requirementCoverageInstruction,
|
|
2987
|
+
"verificationTargets[].commandLabel MUST be one of the frozen command labels listed above. Any other value will be rejected at contract materialization.",
|
|
2988
|
+
verificationTargetFileInstruction,
|
|
2989
|
+
"Read-only: do not modify code, docs, artifacts, or repository files.",
|
|
3210
2990
|
fixedVerificationContext,
|
|
3211
|
-
|
|
2991
|
+
sourceContext,
|
|
3212
2992
|
mockContextBlock,
|
|
3213
|
-
|
|
2993
|
+
frontendContractSchemaBlock,
|
|
3214
2994
|
frontendComponentConformanceInstruction,
|
|
2995
|
+
openspecCitationInstruction,
|
|
3215
2996
|
].join("\n\n"),
|
|
3216
2997
|
},
|
|
3217
2998
|
{
|
|
3218
|
-
id: "frontend-design-
|
|
2999
|
+
id: "frontend-design-review-pi",
|
|
3219
3000
|
depends_on: ["frontend-plan-pi"],
|
|
3220
|
-
role: "
|
|
3221
|
-
executor: "
|
|
3222
|
-
complexity: "
|
|
3001
|
+
role: "reviewer",
|
|
3002
|
+
executor: "pi",
|
|
3003
|
+
complexity: "MED",
|
|
3223
3004
|
writePolicy: "read-only",
|
|
3224
3005
|
allowedPaths: readOnlyPaths,
|
|
3225
3006
|
forbiddenPaths,
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
openspecSpecRoots: taskConfig.frontendOpenspec?.specRoots ?? [
|
|
3256
|
-
...DEFAULT_FRONTEND_SPEC_ROOTS,
|
|
3257
|
-
],
|
|
3258
|
-
...(requiresOpenspecClassification
|
|
3259
|
-
? { openspecSelectionNodeId: "frontend-contract-pi" }
|
|
3260
|
-
: {}),
|
|
3261
|
-
openspecMandatoryPaths: openspecGate.openspecMandatoryPaths,
|
|
3262
|
-
openspecCandidateSources: {
|
|
3263
|
-
declared: openspecGate.openspecCandidateSources.declared,
|
|
3264
|
-
taskSourceCited: openspecGate.openspecCandidateSources.taskSourceCited,
|
|
3265
|
-
scanStrict: openspecGate.openspecCandidateSources.scanStrict,
|
|
3266
|
-
},
|
|
3267
|
-
openspecCandidatePaths: openspecGate.openspecCandidatePaths,
|
|
3268
|
-
...(openspecGate.openspecCandidateSnapshots
|
|
3269
|
-
? {
|
|
3270
|
-
openspecCandidateSnapshots: openspecGate.openspecCandidateSnapshots,
|
|
3271
|
-
}
|
|
3272
|
-
: {}),
|
|
3273
|
-
componentSpecCandidatePaths,
|
|
3274
|
-
allowedDependencies: declaredDependencies,
|
|
3275
|
-
},
|
|
3276
|
-
cwd: ".",
|
|
3277
|
-
timeoutMs: 60000,
|
|
3007
|
+
skills: FRONTEND_DESIGN_REVIEW_SKILLS,
|
|
3008
|
+
outputProtocol: REVIEW_VERDICT_OUTPUT_PROTOCOL,
|
|
3009
|
+
outputContract: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by Findings, Required Plan Corrections, and Checked Items. No file writes.",
|
|
3010
|
+
subtask_prompt: [
|
|
3011
|
+
"Audit the frontend plan before implementation. frontend-plan-pi is emitted as a hash-bound canonical JSON artifact after the runtime applied and validated the planner's editable patch against its protected skeleton; read that artifact with the read tool and do not infer the contract from stdout. There is no separate plan prose.",
|
|
3012
|
+
"First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
|
|
3013
|
+
"Request revision when the Mock strategy is MOCK_STRATEGY: blocked, missing, unsupported by repository evidence, inconsistent with the API contract, outside authorized paths/dependencies, unable to prove production-default-off behavior with the fixed production/default-real-path static check, or missing deterministic behavior verification for a declared behavior target or selected Mock strategy. Mock strategies require Mock-backed evidence. A static-only contract is allowed only when every verification target is static and maps to a declared static entrypoint. not-needed otherwise requires applicable real/no-remote behavior evidence unless auto mode explicitly skipped Mock because no project Mock capability exists; in that case the plan must preserve the real request path and record the Real Integration Gap.",
|
|
3014
|
+
"Also request revision for missing applicable UI states, unsupported dependency additions, design-system drift without reason, weak interaction coverage, broad scope, inline fake data, schema drift, or missing deterministic verification commands.",
|
|
3015
|
+
"Component selection conformance is a hard blocking condition: VERDICT: request-revision when the frontend spec (component/theme/rule.components bucket) already defines a component for a purpose but the plan selects another or self-invents one without a declared deviation; when uiComponentChoices is missing/empty for UI-visible work while the frozen component/theme bucket is non-empty; or when any uiComponentChoices specReference.path is not cited in the openspec-citations block or has no successful read event.",
|
|
3016
|
+
"Read-only: do not modify repository files.",
|
|
3017
|
+
fixedVerificationContext,
|
|
3018
|
+
sourceContext,
|
|
3019
|
+
frontendContractFieldSummary,
|
|
3020
|
+
mockContextBlock,
|
|
3021
|
+
].join("\n\n"),
|
|
3022
|
+
},
|
|
3023
|
+
{
|
|
3024
|
+
id: "frontend-plan-revision-pi",
|
|
3025
|
+
depends_on: ["frontend-plan-pi", "frontend-design-review-pi"],
|
|
3026
|
+
runIf: "$.nodes['frontend-design-review-pi'].firstVerdictLine == 'VERDICT: request-revision'",
|
|
3027
|
+
role: "planner",
|
|
3028
|
+
executor: "pi",
|
|
3029
|
+
complexity: "MED",
|
|
3030
|
+
writePolicy: "read-only",
|
|
3031
|
+
outputMode: "structured-required",
|
|
3032
|
+
retryPolicy: STRUCTURED_REQUIRED_PI_RETRY_POLICY,
|
|
3033
|
+
structuredContractOutput: {
|
|
3034
|
+
schemaId: "frontend-implementation-contract-revision-patch-v1",
|
|
3035
|
+
retryOnInvalid: true,
|
|
3278
3036
|
},
|
|
3037
|
+
allowedPaths: readOnlyPaths,
|
|
3038
|
+
forbiddenPaths,
|
|
3039
|
+
skills: FRONTEND_IMPLEMENTATION_SKILLS,
|
|
3040
|
+
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; this node compiles the patch onto the original canonical artifact and writes a hash-bound revised contract. 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.",
|
|
3041
|
+
subtask_prompt: [
|
|
3042
|
+
"Consume frontend-plan-pi (original contract JSON) and frontend-design-review-pi (first design review findings).",
|
|
3043
|
+
"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.",
|
|
3044
|
+
"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 (this node applies the patch on the original canonical artifact). null deletes a key; arrays and scalars replace; plain objects merge recursively.",
|
|
3045
|
+
requirementCoverageInstruction,
|
|
3046
|
+
"Do not turn MOCK_STRATEGY: blocked into an implementable strategy without new repository or contract evidence that resolves every blocker.",
|
|
3047
|
+
"Read-only: do not modify code, docs, artifacts, or repository files. This node revises the plan only.",
|
|
3048
|
+
"Output in this exact order: (1) exactly one fenced json object containing the merge-patch delta — this node compiles it onto the original canonical artifact; (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.",
|
|
3049
|
+
"Preserve each requirement expectedOutcome and each interaction trigger/expectedBehavior in the effective (merged) contract; do not reduce behavior semantics to IDs and paths.",
|
|
3050
|
+
"verificationTargets[].commandLabel MUST be one of the frozen command labels listed above. Any other value will be rejected at contract materialization.",
|
|
3051
|
+
verificationTargetFileInstruction,
|
|
3052
|
+
fixedVerificationContext,
|
|
3053
|
+
sourceContext,
|
|
3054
|
+
frontendContractSchemaBlock,
|
|
3055
|
+
mockContextBlock,
|
|
3056
|
+
frontendComponentConformanceInstruction,
|
|
3057
|
+
openspecCitationInstruction,
|
|
3058
|
+
].join("\n\n"),
|
|
3279
3059
|
},
|
|
3280
3060
|
{
|
|
3281
|
-
id: "frontend-design-review-pi",
|
|
3282
|
-
depends_on: [
|
|
3061
|
+
id: "frontend-final-design-review-pi",
|
|
3062
|
+
depends_on: [
|
|
3063
|
+
"frontend-plan-revision-pi",
|
|
3064
|
+
"frontend-plan-pi",
|
|
3065
|
+
"frontend-design-review-pi",
|
|
3066
|
+
],
|
|
3067
|
+
dependsPolicy: "all-or-condition-skip",
|
|
3068
|
+
runIf: "$.nodes['frontend-design-review-pi'].firstVerdictLine == 'VERDICT: request-revision'",
|
|
3283
3069
|
role: "reviewer",
|
|
3284
3070
|
executor: "pi",
|
|
3285
3071
|
complexity: "MED",
|
|
3286
3072
|
writePolicy: "read-only",
|
|
3287
|
-
readBudget: FRONTEND_READ_BUDGETS.designReview,
|
|
3288
|
-
retryPolicy: DEFAULT_READ_ONLY_PI_RETRY_POLICY,
|
|
3289
3073
|
allowedPaths: readOnlyPaths,
|
|
3290
3074
|
forbiddenPaths,
|
|
3291
3075
|
skills: FRONTEND_DESIGN_REVIEW_SKILLS,
|
|
3292
|
-
|
|
3076
|
+
outputProtocol: REVIEW_VERDICT_OUTPUT_PROTOCOL,
|
|
3077
|
+
outputContract: "For the effective frontend plan, return plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by Findings and Checked Items. No file writes.",
|
|
3293
3078
|
subtask_prompt: [
|
|
3294
|
-
"Audit the frontend plan before implementation.
|
|
3295
|
-
"
|
|
3296
|
-
"
|
|
3297
|
-
"
|
|
3298
|
-
"
|
|
3299
|
-
"
|
|
3300
|
-
"
|
|
3301
|
-
"For uiComponentChoices, purpose is the stable coverage key and must match interaction.name or uiState.name. Responsibility is expressed by the matched expectedBehavior plus rationale; you must not reject it merely for matching an interaction or component identifier.",
|
|
3302
|
-
"You must NOT make authoritative assertions about the execution result of frozen verification commands (typecheck/test/build/lint/etc.). Predicting that a command will necessarily pass or fail, or declaring an acceptance criterion unreachable on that basis, is out of your authority: command results are deterministically established by frontend-verify-shell. Any concern about verification feasibility must be recorded only as a non-blocking verification concern in findings (severity must not be Critical, and it must never be the sole fatal basis for request_design_changes). Only semantic design defects (component selection, state flow, interaction contract, or conflicts with the specification) may be Critical. A pure command-will-fail prediction must not be classified as contract-requirement-gap.",
|
|
3079
|
+
"Audit the revised frontend plan before implementation. This node runs only after request-revision and consumes the merge-patch delta from frontend-plan-revision-pi applied on the original frontend-plan-pi contract JSON — there is no separate plan prose.",
|
|
3080
|
+
"First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
|
|
3081
|
+
"Verify that every Required Plan Correction from the initial design review has been fully addressed.",
|
|
3082
|
+
"Recheck the selected Mock / API strategy, contract-to-fixture mapping, authorized paths/dependencies, explicit activation, production-default-off behavior, behavior verification, and Real Integration Gap. MOCK_STRATEGY: blocked cannot receive VERDICT: pass.",
|
|
3083
|
+
"Review every explicit REQ-/BR-/AC- mapping; the downstream prewrite gate also checks identifier retention deterministically.",
|
|
3084
|
+
"Request revision if any design gap remains, if corrections are incomplete, or if the revised plan introduces new unaddressed issues.",
|
|
3085
|
+
"Also request revision for component selection non-conformance: spec-defined components silently replaced or self-invented without a declared deviation, uiComponentChoices missing for UI-visible work, or a uiComponentChoices specReference.path not cited in the openspec-citations block / not actually read.",
|
|
3303
3086
|
"Read-only: do not modify repository files.",
|
|
3304
|
-
"LARGE-FILE AUDIT (avoid full reads): style/theme audit files can be large (e.g. styles.css is often hundreds of KB). Prefer grep to locate the exact rules/variables you must verify (e.g. grep the oc- class, is-* modifier, or --oc- theme variables with their line numbers), then read only the narrow line range when surrounding context is needed. Do not read a large style/test file in full — a single full read can exhaust the read budget and fail the attempt.",
|
|
3305
|
-
"Canonical contract reading: frontend-design-policy-shell prints absolute paths for Contract, Contract index, and the non-blocking Capacity diagnostic. Read the capacity diagnostic first. When it recommends full-contract, read the exact Contract path. When it recommends indexed-sections, read the Contract index and its hash-bound section files instead of opening the full contract. Never resolve a bare contracts/... path against the repository root or hunt for substitutes. Implementation target files inside the writeSet are created later by the implement node: do not read them and do not treat their absence as a design defect.",
|
|
3306
3087
|
fixedVerificationContext,
|
|
3307
|
-
|
|
3308
|
-
scopedOpenspecContext,
|
|
3088
|
+
sourceContext,
|
|
3309
3089
|
frontendContractFieldSummary,
|
|
3310
3090
|
mockContextBlock,
|
|
3311
3091
|
].join("\n\n"),
|
|
3312
3092
|
},
|
|
3313
3093
|
{
|
|
3314
|
-
id: "frontend-
|
|
3315
|
-
depends_on: [
|
|
3094
|
+
id: "frontend-prewrite-gate-shell",
|
|
3095
|
+
depends_on: [
|
|
3096
|
+
"frontend-final-design-review-pi",
|
|
3097
|
+
"frontend-design-review-pi",
|
|
3098
|
+
"frontend-plan-revision-pi",
|
|
3099
|
+
"frontend-plan-pi",
|
|
3100
|
+
],
|
|
3101
|
+
dependsPolicy: "all-or-condition-skip",
|
|
3316
3102
|
role: "verifier",
|
|
3317
3103
|
executor: "shell",
|
|
3318
3104
|
complexity: "LOW",
|
|
3319
3105
|
writePolicy: "read-only",
|
|
3320
3106
|
allowedPaths: readOnlyPaths,
|
|
3321
3107
|
forbiddenPaths,
|
|
3322
|
-
outputContract: "Deterministic
|
|
3323
|
-
subtask_prompt: "Fail closed unless the
|
|
3108
|
+
outputContract: "Deterministic prewrite authorization: resolve effective plan/review, require VERDICT: pass, retain every requirement id, validate Mock policy, and materialize the canonical implementation contract.",
|
|
3109
|
+
subtask_prompt: "Fail closed unless the effective reviewed plan is source-bound, requirement-complete, Mock-policy compliant, schema-valid, and approved.",
|
|
3324
3110
|
shell: {
|
|
3325
3111
|
commands: [],
|
|
3326
|
-
|
|
3112
|
+
frontendPrewriteGate: {
|
|
3327
3113
|
schemaVersion: 1,
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
],
|
|
3114
|
+
planFromNodeId: "frontend-plan-revision-pi",
|
|
3115
|
+
planFallbackFromNodeIds: ["frontend-plan-pi"],
|
|
3116
|
+
reviewFromNodeId: "frontend-final-design-review-pi",
|
|
3117
|
+
reviewFallbackFromNodeIds: ["frontend-design-review-pi"],
|
|
3118
|
+
requiredRequirementIds: requirementIds,
|
|
3119
|
+
mockCommandLabels: mockVerifyEvidence?.commandLabels ?? [],
|
|
3334
3120
|
allowedMockStrategies: taskConfig.frontendMock?.policy === "disabled" ||
|
|
3335
3121
|
frontendMockStrategyMustBeNotNeeded(frontendSources)
|
|
3336
3122
|
? ["not-needed"]
|
|
3337
3123
|
: taskConfig.frontendMock?.policy === "required"
|
|
3338
|
-
? [
|
|
3339
|
-
"native",
|
|
3340
|
-
"browser-intercept",
|
|
3341
|
-
"request-adapter",
|
|
3342
|
-
]
|
|
3124
|
+
? ["native", "browser-intercept", "request-adapter"]
|
|
3343
3125
|
: [
|
|
3344
3126
|
"native",
|
|
3345
3127
|
"browser-intercept",
|
|
3346
3128
|
"request-adapter",
|
|
3347
3129
|
"not-needed",
|
|
3348
3130
|
],
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3131
|
+
artifactName: "frontend-implementation-contract.json",
|
|
3132
|
+
outputDir: "contracts",
|
|
3133
|
+
revisionPatch: true,
|
|
3134
|
+
planMdArtifactName: "frontend-plan.md",
|
|
3135
|
+
requireSourceFreshness: true,
|
|
3136
|
+
implementationWriteSet: implementPaths.writeSet,
|
|
3137
|
+
openspecPolicy: openspecGate.openspecPolicy,
|
|
3138
|
+
openspecCandidateSources: {
|
|
3139
|
+
declared: openspecGate.openspecCandidateSources.declared,
|
|
3140
|
+
taskSourceCited: openspecGate.openspecCandidateSources.taskSourceCited,
|
|
3141
|
+
scanStrict: openspecGate.openspecCandidateSources.scanStrict,
|
|
3142
|
+
},
|
|
3143
|
+
openspecCandidatePaths: openspecGate.openspecCandidatePaths,
|
|
3144
|
+
componentSpecCandidatePaths,
|
|
3355
3145
|
},
|
|
3356
3146
|
cwd: ".",
|
|
3357
3147
|
timeoutMs: 60000,
|
|
3358
3148
|
},
|
|
3359
3149
|
},
|
|
3150
|
+
...(lintShellCommands.length > 0 && lintVerifyEvidence
|
|
3151
|
+
? [
|
|
3152
|
+
{
|
|
3153
|
+
id: "frontend-lint-baseline-shell",
|
|
3154
|
+
depends_on: ["frontend-prewrite-gate-shell"],
|
|
3155
|
+
role: "verifier",
|
|
3156
|
+
executor: "shell",
|
|
3157
|
+
complexity: "LOW",
|
|
3158
|
+
writePolicy: "read-only",
|
|
3159
|
+
allowedPaths: readOnlyPaths,
|
|
3160
|
+
forbiddenPaths,
|
|
3161
|
+
outputContract: "Capture writer-preceding lint output as frontend-lint-baseline-v1 without treating existing lint diagnostics as writer failure.",
|
|
3162
|
+
subtask_prompt: "Run the frozen lint commands read-only. Preserve raw output and mark the baseline unavailable on timeout, execution failure, unparseable output, or worktree mutation.",
|
|
3163
|
+
shell: {
|
|
3164
|
+
commands: lintShellCommands,
|
|
3165
|
+
frontendLintBaseline: {
|
|
3166
|
+
schemaVersion: 1,
|
|
3167
|
+
lintCommands: lintShellCommands,
|
|
3168
|
+
lintEvidence: lintVerifyEvidence,
|
|
3169
|
+
},
|
|
3170
|
+
cwd: ".",
|
|
3171
|
+
timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
3172
|
+
},
|
|
3173
|
+
},
|
|
3174
|
+
]
|
|
3175
|
+
: []),
|
|
3360
3176
|
{
|
|
3361
3177
|
id: implementId,
|
|
3362
|
-
depends_on: [
|
|
3178
|
+
depends_on: [
|
|
3179
|
+
"frontend-prewrite-gate-shell",
|
|
3180
|
+
...(lintShellCommands.length > 0
|
|
3181
|
+
? ["frontend-lint-baseline-shell"]
|
|
3182
|
+
: []),
|
|
3183
|
+
],
|
|
3363
3184
|
...buildFrontendWriterNodeDefaults({
|
|
3364
3185
|
complexity: resolveWriterComplexity(taskConfig),
|
|
3365
3186
|
writeSet: implementPaths.writeSet,
|
|
3366
3187
|
allowedPaths: implementPaths.allowedPaths,
|
|
3367
3188
|
forbiddenPaths,
|
|
3368
|
-
writerOutcomePolicyType: "frontend-facts-v1",
|
|
3369
3189
|
}),
|
|
3370
|
-
outputContract: "
|
|
3190
|
+
outputContract: "First non-empty line must be exactly one of: IMPLEMENTATION_OUTCOME: changed; IMPLEMENTATION_OUTCOME: already-satisfied; IMPLEMENTATION_OUTCOME: blocked. Then a Markdown delivery summary with Contract Ref (path/schema/hash), Changed Files, Requirements Implemented, UI States, Tests Changed, Verification Attempts, Deviations, and Residual Risks. Follow fixed stages: contract confirm → tests → component/state → API/Mock → focused checks → diff cleanup.",
|
|
3371
3191
|
subtask_prompt: [
|
|
3372
|
-
"Implement against the validated run-owned Frontend Implementation Contract
|
|
3192
|
+
"Implement against the validated run-owned Frontend Implementation Contract from frontend-prewrite-gate-shell (path/schema/hash). Do not rebuild the contract from Markdown alone.",
|
|
3373
3193
|
"The canonical contract already contains the approved requirement, target-file, UI-state, verification, design, and Mock/API decisions. Do not re-open task sources, OpenSpec, AI workspace, plan/revision, or design-review prose, and do not repeat broad repository research. Inspect only contract target files and directly related local code needed to implement them.",
|
|
3374
3194
|
"Execute in fixed stages and report each in the delivery summary: (1) Contract confirm, (2) Tests sync, (3) Component/UI state implementation, (4) API/Mock wiring per contract.mockApi, (5) Focused checks behind frozen entrypoints only, (6) Diff cleanup.",
|
|
3375
3195
|
"Map every requirement id, expectedOutcome, interaction trigger/expectedBehavior, and applicable UI state from the contract to concrete files. Do not invent shell verification commands; only frozen static/behavior entrypoints will run.",
|
|
3376
|
-
"
|
|
3377
|
-
"Begin implementation after the contract and its target files are confirmed. Do not spend the turn collecting optional context. If the canonical contract lacks behavior needed to edit safely, stop and state the blocking reason in the summary instead of reopening broad discovery.",
|
|
3378
|
-
"Your implementation status is derived by the executor from mechanical facts (persisted write-tool events, run delta, write guard, requirement coverage, focused-check failures), never from any IMPLEMENTATION_OUTCOME first line. Do not emit an IMPLEMENTATION_OUTCOME first line.",
|
|
3379
|
-
"The node runs a bounded micro-loop: after each write attempt the executor re-runs frozen focused checks and records a per-round diff checkpoint; the write guard stays active every round. Only repair local issues attributable to the current diff (syntax/type/import/format/unit-assert/obvious omission). Never change requirements, design, writeSet, or verification strictness inside the loop.",
|
|
3196
|
+
"Begin implementation after the contract and its target files are confirmed. Do not spend the turn collecting optional context. If the canonical contract lacks behavior needed to edit safely, return IMPLEMENTATION_OUTCOME: blocked instead of reopening broad discovery.",
|
|
3380
3197
|
"Implement only the approved Mock strategy carried by the validated contract. Preserve the real request path as the default, require explicit test/dev activation, and never comment out or replace the real request with inline data.",
|
|
3381
|
-
"frontend-
|
|
3198
|
+
"frontend-prewrite-gate-shell confirmed the effective plan/review, requirement coverage, Mock policy, and contract. Stay within writeSet and preserve unrelated files.",
|
|
3382
3199
|
"For native, browser-intercept, or request-adapter, implement contract-aligned fixtures/states and a dev/test-only activation boundary in this same writer. For not-needed, do not add Mock files or a framework and state the positive reason.",
|
|
3383
3200
|
"Do not write root artifacts/** unless explicitly included in writeSet. Do not claim Browser/visual verification.",
|
|
3384
3201
|
"Edit existing files with the structured edit/write tools. NEVER rewrite Markdown (or any file with quoting/backticks/indentation-sensitive content) via bash sed/awk/echo redirection: escaping mistakes silently corrupt the file and self-repair loops burn the run.",
|
|
@@ -3393,7 +3210,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3393
3210
|
.join("\n\n"),
|
|
3394
3211
|
},
|
|
3395
3212
|
{
|
|
3396
|
-
id: "frontend-verify-shell",
|
|
3213
|
+
id: "frontend-verify-assess-shell",
|
|
3397
3214
|
depends_on: [implementId],
|
|
3398
3215
|
role: "verifier",
|
|
3399
3216
|
executor: "shell",
|
|
@@ -3401,8 +3218,8 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3401
3218
|
writePolicy: "read-only",
|
|
3402
3219
|
allowedPaths: readOnlyPaths,
|
|
3403
3220
|
forbiddenPaths,
|
|
3404
|
-
outputContract: "Run frozen Mock/static/behavior commands
|
|
3405
|
-
subtask_prompt: "Execute the frontend verification bundle. Preserve per-command evidence;
|
|
3221
|
+
outputContract: "Run frozen Mock/static/behavior commands, materialize verification trace and repair assessment, and fail closed for non-repairable failures.",
|
|
3222
|
+
subtask_prompt: "Execute the frontend verification bundle. Preserve per-command evidence; eligible repairable failures select the bounded repair branch.",
|
|
3406
3223
|
shell: {
|
|
3407
3224
|
commands: [],
|
|
3408
3225
|
frontendVerificationBundle: {
|
|
@@ -3416,7 +3233,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3416
3233
|
staticEvidence: staticVerifyEvidence,
|
|
3417
3234
|
behaviorEvidence: behaviorVerifyEvidence,
|
|
3418
3235
|
lintBaselineNodeId: lintShellCommands.length > 0
|
|
3419
|
-
? "frontend-
|
|
3236
|
+
? "frontend-lint-baseline-shell"
|
|
3420
3237
|
: undefined,
|
|
3421
3238
|
writerNodeIds: lintShellCommands.length > 0 ? [implementId] : [],
|
|
3422
3239
|
mode: "initial",
|
|
@@ -3425,17 +3242,85 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3425
3242
|
timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
3426
3243
|
},
|
|
3427
3244
|
},
|
|
3245
|
+
{
|
|
3246
|
+
id: "frontend-repair-pi",
|
|
3247
|
+
depends_on: ["frontend-verify-assess-shell", implementId],
|
|
3248
|
+
runIf: "$.nodes['frontend-verify-assess-shell'].json.eligible == true",
|
|
3249
|
+
...buildFrontendWriterNodeDefaults({
|
|
3250
|
+
complexity: resolveWriterComplexity(taskConfig),
|
|
3251
|
+
writeSet: implementPaths.writeSet,
|
|
3252
|
+
allowedPaths: implementPaths.allowedPaths,
|
|
3253
|
+
forbiddenPaths,
|
|
3254
|
+
}),
|
|
3255
|
+
outputContract: "First non-empty line must be exactly one of: IMPLEMENTATION_OUTCOME: changed; IMPLEMENTATION_OUTCOME: already-satisfied; IMPLEMENTATION_OUTCOME: blocked. Then a repair summary for an eligible repairable assessment. Must not expand writeSet, re-interpret requirements, skip tests, or enable Mock by default.",
|
|
3256
|
+
subtask_prompt: [
|
|
3257
|
+
"Read contracts/frontend-repair-assessment.json and the validated frontend implementation contract.",
|
|
3258
|
+
"This node runs only for eligible=true. Apply the smallest fix for the classified repairable failure inside the original implement writeSet only.",
|
|
3259
|
+
"The repair assessment and canonical implementation contract are complete inputs for this phase. Do not re-open task sources, OpenSpec, AI workspace, plan/revision, or design-review prose, and do not repeat repository-wide discovery.",
|
|
3260
|
+
"Do not change lint/type/test config, do not add .skip/.only, do not comment out real requests, do not default-enable Mock, do not add dependencies.",
|
|
3261
|
+
"Do not re-plan requirements or expand allowed paths. Browser/visual remain not-run.",
|
|
3262
|
+
...(implementPaths.docIndexCompanions.length > 0
|
|
3263
|
+
? [
|
|
3264
|
+
`Doc index sync is MANDATORY: ${implementPaths.docIndexCompanions.join(", ")} are catalog index files for this writeSet. When your repair adds, renames, or removes any indexed file, update ${implementPaths.docIndexCompanions.join(" and ")} in the same run; verification runs check-doc-index and fails the run on a missing index entry.`,
|
|
3265
|
+
]
|
|
3266
|
+
: []),
|
|
3267
|
+
writerDeliveryContract(taskConfig),
|
|
3268
|
+
]
|
|
3269
|
+
.filter((value) => Boolean(value))
|
|
3270
|
+
.join("\n\n"),
|
|
3271
|
+
},
|
|
3272
|
+
{
|
|
3273
|
+
id: "frontend-reverify-shell",
|
|
3274
|
+
depends_on: ["frontend-repair-pi"],
|
|
3275
|
+
role: "verifier",
|
|
3276
|
+
executor: "shell",
|
|
3277
|
+
complexity: "LOW",
|
|
3278
|
+
writePolicy: "read-only",
|
|
3279
|
+
allowedPaths: readOnlyPaths,
|
|
3280
|
+
forbiddenPaths,
|
|
3281
|
+
outputContract: "Post-repair Mock/static/behavior re-verification plus refreshed canonical trace; any failure blocks review.",
|
|
3282
|
+
subtask_prompt: "Re-run the frozen frontend verification bundle after bounded repair and fail on any command or trace failure.",
|
|
3283
|
+
shell: {
|
|
3284
|
+
commands: [],
|
|
3285
|
+
frontendVerificationBundle: {
|
|
3286
|
+
schemaVersion: 1,
|
|
3287
|
+
mockCommands: mockShellCommands,
|
|
3288
|
+
lintCommands: lintShellCommands,
|
|
3289
|
+
staticCommands: staticShellCommands,
|
|
3290
|
+
behaviorCommands: behaviorShellCommands,
|
|
3291
|
+
mockEvidence: mockVerifyEvidence,
|
|
3292
|
+
lintEvidence: lintVerifyEvidence,
|
|
3293
|
+
staticEvidence: staticVerifyEvidence,
|
|
3294
|
+
behaviorEvidence: behaviorVerifyEvidence,
|
|
3295
|
+
lintBaselineNodeId: lintShellCommands.length > 0
|
|
3296
|
+
? "frontend-lint-baseline-shell"
|
|
3297
|
+
: undefined,
|
|
3298
|
+
writerNodeIds: lintShellCommands.length > 0
|
|
3299
|
+
? [implementId, "frontend-repair-pi"]
|
|
3300
|
+
: [],
|
|
3301
|
+
mode: "repair",
|
|
3302
|
+
},
|
|
3303
|
+
cwd: ".",
|
|
3304
|
+
timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
3305
|
+
},
|
|
3306
|
+
},
|
|
3428
3307
|
{
|
|
3429
3308
|
id: "frontend-review-context-shell",
|
|
3430
|
-
depends_on: [
|
|
3309
|
+
depends_on: [
|
|
3310
|
+
"frontend-reverify-shell",
|
|
3311
|
+
"frontend-repair-pi",
|
|
3312
|
+
"frontend-verify-assess-shell",
|
|
3313
|
+
implementId,
|
|
3314
|
+
],
|
|
3315
|
+
dependsPolicy: "all-or-condition-skip",
|
|
3431
3316
|
role: "verifier",
|
|
3432
3317
|
executor: "shell",
|
|
3433
3318
|
complexity: "LOW",
|
|
3434
3319
|
writePolicy: "read-only",
|
|
3435
3320
|
allowedPaths: readOnlyPaths,
|
|
3436
3321
|
forbiddenPaths,
|
|
3437
|
-
outputContract: "Canonical frontend review context containing
|
|
3438
|
-
subtask_prompt: "Capture the actual diff and bind it to the effective verification evidence for final review.",
|
|
3322
|
+
outputContract: "Canonical frontend review context containing validated contract, lint assessment when configured, effective verification trace, repair assessment, and actual worktree diff.",
|
|
3323
|
+
subtask_prompt: "Capture the actual diff and bind it to the effective initial-or-post-repair verification evidence for final review.",
|
|
3439
3324
|
shell: {
|
|
3440
3325
|
commands: [],
|
|
3441
3326
|
frontendReviewContext: { schemaVersion: 1, requireBaseline: true },
|
|
@@ -3450,100 +3335,81 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3450
3335
|
executor: "pi",
|
|
3451
3336
|
complexity: "HIGH",
|
|
3452
3337
|
writePolicy: "read-only",
|
|
3453
|
-
readBudget: FRONTEND_READ_BUDGETS.finalReview,
|
|
3454
|
-
retryPolicy: FRONTEND_REVIEW_TERMINAL_RETRY_POLICY,
|
|
3455
3338
|
allowedPaths: readOnlyPaths,
|
|
3456
3339
|
forbiddenPaths,
|
|
3457
3340
|
skills: FRONTEND_REVIEW_SKILLS,
|
|
3458
|
-
outputContract: '
|
|
3341
|
+
outputContract: 'Structured JSON review verdict only: {"schemaVersion":1,"verdict":"pass|request-revision","findings":[...],"verificationAssessment":"...","uxAssessment":"...","residualRisks":[...]}. No file writes.',
|
|
3342
|
+
outputProtocol: REVIEW_JSON_VERDICT_OUTPUT_PROTOCOL,
|
|
3459
3343
|
subtask_prompt: [
|
|
3460
3344
|
"Review the frontend implementation and verification evidence.",
|
|
3461
|
-
"
|
|
3462
|
-
|
|
3463
|
-
"
|
|
3464
|
-
|
|
3345
|
+
"Return exactly one final JSON object in this response. Do not repeat it, do not emit a second revision, do not wrap it in Markdown, and do not include prose outside the JSON.",
|
|
3346
|
+
'Required fields: schemaVersion: 1; verdict: "pass" or "request-revision"; findings: array of objects with severity ("Critical" | "Important" | "Minor" | "Info"), optional file, optional positive integer line, issue, and optional requiredChange.',
|
|
3347
|
+
'verdict "request-revision" requires at least one finding. verdict "pass" is invalid if any finding severity is Critical or Important.',
|
|
3348
|
+
'Any Critical or Important finding must force verdict "request-revision".',
|
|
3349
|
+
"Read contracts/frontend-review-context.json from frontend-review-context-shell. It binds the validated implementation contract, frontend lint assessment when lint is configured, effective initial-or-post-repair verification trace, repair assessment, and the run-owned actual diff (contracts/frontend-worktree-diff.json + artifacts/diff_patch.patch). Do not claim actual diff is missing when those artifacts exist; do not invent a diff from the implementation summary alone. Trace proves command/file/symbol binding only—not semantic correctness.",
|
|
3465
3350
|
"Treat lint status exactly as passed | baseline-debt | failed | unavailable. baseline-debt may continue only with intact evidence and zero diagnostics on writer-changed files; report the tolerated debt count and never rewrite it as lint passed. Typecheck, build, and test still require successful final exits.",
|
|
3466
3351
|
"Flag .skip/.only, deleted or weakened tests, unauthorized config changes, Mock-only evidence claimed as real integration, and Browser/visual claims (always not-run in this workflow).",
|
|
3467
|
-
"The contract
|
|
3468
|
-
"For uiComponentChoices, purpose is the stable coverage key and must match interaction.name or uiState.name. Responsibility is expressed by the matched expectedBehavior plus rationale; you must not reject it merely for matching an interaction or component identifier.",
|
|
3352
|
+
"The contract embedded in frontend-review-context.json is the effective reviewed plan materialized by the prewrite gate. Do not re-open task sources, OpenSpec, AI workspace, plan/revision, design-review, writer summary, or verification node prose. Inspect only the canonical review context, its bound diff, and diff-referenced files when semantic review requires source code.",
|
|
3469
3353
|
"Treat a commented-out real request, default-enabled Mock, production entrypoint importing test mocks, API/fixture contract drift, unauthorized Mock dependency/path, or missing behavior evidence for the selected strategy as at least Important. Mock strategies require Mock-backed evidence. not-needed requires applicable real/no-remote behavior evidence unless auto mode explicitly skipped Mock because no project Mock capability exists; in that case verify that the real request remains the default and the Real Integration Gap is preserved.",
|
|
3470
|
-
"Inspect the frontend-verify-shell evidence in the review context directly, including the production/default-real-path static check, and require Mock activation to be off for that check.",
|
|
3354
|
+
"Inspect the frontend-verify-assess-shell or selected frontend-reverify-shell evidence in the review context directly, including the production/default-real-path static check, and require Mock activation to be off for that check.",
|
|
3471
3355
|
"Distinguish Mock-backed evidence from real API integration evidence and preserve the Real Integration Gap when the backend was not exercised.",
|
|
3472
3356
|
"Review implementation quality, behavior/state coverage, verification evidence, and maintainability. Read-only: do not modify files.",
|
|
3473
3357
|
].join("\n\n"),
|
|
3474
3358
|
},
|
|
3475
3359
|
{
|
|
3476
|
-
id: "frontend-
|
|
3477
|
-
depends_on: ["frontend-review-
|
|
3478
|
-
role: "
|
|
3360
|
+
id: "frontend-review-gate-shell",
|
|
3361
|
+
depends_on: ["frontend-review-pi"],
|
|
3362
|
+
role: "verifier",
|
|
3479
3363
|
executor: "shell",
|
|
3480
3364
|
complexity: "LOW",
|
|
3481
3365
|
writePolicy: "read-only",
|
|
3482
3366
|
allowedPaths: readOnlyPaths,
|
|
3483
3367
|
forbiddenPaths,
|
|
3484
|
-
outputContract:
|
|
3485
|
-
subtask_prompt:
|
|
3368
|
+
outputContract: 'Deterministic frontend review verdict gate: exit 0 only when frontend-review-pi emits JSON verdict "pass".',
|
|
3369
|
+
subtask_prompt: 'Deterministic gate: block downstream closeout unless frontend-review-pi emitted JSON verdict "pass".',
|
|
3486
3370
|
shell: {
|
|
3487
3371
|
commands: [],
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3372
|
+
verdictGate: {
|
|
3373
|
+
fromNodeId: "frontend-review-pi",
|
|
3374
|
+
accept: ["pass"],
|
|
3375
|
+
routingAccept: ["request-revision"],
|
|
3376
|
+
label: "frontend review",
|
|
3377
|
+
source: "json-review-verdict",
|
|
3491
3378
|
},
|
|
3492
3379
|
cwd: ".",
|
|
3493
3380
|
timeoutMs: 60000,
|
|
3494
3381
|
},
|
|
3495
3382
|
},
|
|
3383
|
+
{
|
|
3384
|
+
id: "frontend-closeout-pi",
|
|
3385
|
+
depends_on: [
|
|
3386
|
+
"frontend-review-context-shell",
|
|
3387
|
+
"frontend-review-gate-shell",
|
|
3388
|
+
],
|
|
3389
|
+
role: "closeout",
|
|
3390
|
+
executor: "pi",
|
|
3391
|
+
complexity: "MED",
|
|
3392
|
+
writePolicy: "read-only",
|
|
3393
|
+
allowedPaths: taskConfig.allowedPaths.length > 0
|
|
3394
|
+
? [...taskConfig.allowedPaths, "docs/**"]
|
|
3395
|
+
: ["**", "docs/**"],
|
|
3396
|
+
forbiddenPaths,
|
|
3397
|
+
skills: FRONTEND_VERIFICATION_SKILLS,
|
|
3398
|
+
outputContract: "Markdown closeout summary with Changes, Mock Decision / Strategy / Files / Verification / Production Boundary, Verification Evidence, Review Result, Frontend Status, Real Integration Status, Known Risks, and Follow-up. No file writes.",
|
|
3399
|
+
subtask_prompt: [
|
|
3400
|
+
"Return a frontend closeout summary covering Mock decision/strategy/files/verification/production boundary, changes, verification evidence, review result, known risks, and follow-up.",
|
|
3401
|
+
"Use only the canonical frontend-review-context.json plus the final frontend review gate verdict. Do not re-open task sources, OpenSpec, AI workspace, plan/revision, design-review, writer, repair, or verification prose, and do not perform new repository research during closeout.",
|
|
3402
|
+
"Include a coverage matrix for each requirement id, applicable UI state, and verification target/check with status passed|failed|not-run|blocked|unavailable. Report lint separately as passed|baseline-debt|failed|unavailable; baseline-debt is explicit debt, not passed. Always state Browser accessibility verification: not-run and Visual regression: not-run. Use contracts/frontend-review-context.json and the effective frontend-verify-assess-shell or frontend-reverify-shell facts; do not invent Browser evidence from component tests.",
|
|
3403
|
+
`When only Mock-backed evidence passed, state exactly Frontend status: mock-validated and Real integration: pending, summarize the Real Integration Gap, and name ${taskConfig.taskId}-real-api-integration-verify as the explicit follow-up task to create/run after backend readiness. This follow-up is not auto-created or auto-executed. Never describe Mock evidence as real API integration.`,
|
|
3404
|
+
`When Mock was skipped in auto mode and no real API evidence passed, state exactly Frontend status: locally-validated and Real integration: pending, summarize the Real Integration Gap, and name ${taskConfig.taskId}-real-api-integration-verify as the explicit follow-up task when backend readiness matters.`,
|
|
3405
|
+
"Read-only: do not modify code, docs, artifacts, or .harness/dag-runs/.",
|
|
3406
|
+
].join("\n\n"),
|
|
3407
|
+
},
|
|
3496
3408
|
],
|
|
3497
3409
|
};
|
|
3498
|
-
|
|
3499
|
-
// chain must match the template's node set and order, and the template's
|
|
3500
|
-
// depends_on must be a subset of the generated one (the runtime may add
|
|
3501
|
-
// dependencies, e.g. Mock-required planning depending on the contract
|
|
3502
|
-
// node's Mock facts). The template's own tasks carry simplified placeholder
|
|
3503
|
-
// configs; the generated node definitions (budgets, retry, skeleton,
|
|
3504
|
-
// skills, prompts) and any runtime-added dependencies win.
|
|
3505
|
-
const frontendTemplate = await loadFrontendDagTemplate(sources.repoRoot);
|
|
3506
|
-
if (frontendTemplate) {
|
|
3507
|
-
const byId = new Map(spec.tasks.map((task) => [task.id, task]));
|
|
3508
|
-
const templateIds = frontendTemplate.tasks.map((task) => task.id);
|
|
3509
|
-
const missing = templateIds.filter((id) => !byId.has(id));
|
|
3510
|
-
if (missing.length > 0) {
|
|
3511
|
-
throw new Error(`frontend DAG template topology drift: generated chain is missing template node(s): ${missing.join(", ")}`);
|
|
3512
|
-
}
|
|
3513
|
-
for (const templateTask of frontendTemplate.tasks) {
|
|
3514
|
-
const generated = byId.get(templateTask.id);
|
|
3515
|
-
const generatedDeps = new Set(generated.depends_on);
|
|
3516
|
-
const templateOnly = templateTask.depends_on.filter((dep) => !generatedDeps.has(dep));
|
|
3517
|
-
if (templateOnly.length > 0) {
|
|
3518
|
-
throw new Error(`frontend DAG template topology drift: generated node ${templateTask.id} is missing template dependency ${templateOnly.join(", ")}`);
|
|
3519
|
-
}
|
|
3520
|
-
}
|
|
3521
|
-
const templateSet = new Set(templateIds);
|
|
3522
|
-
const ordered = templateIds.map((id) => byId.get(id));
|
|
3523
|
-
const extra = spec.tasks.filter((task) => !templateSet.has(task.id));
|
|
3524
|
-
spec.tasks = [...ordered, ...extra];
|
|
3525
|
-
}
|
|
3526
|
-
if (frontendTaskShape.shape === "split-required") {
|
|
3527
|
-
spec.tasks = pruneFrontendTasksForSplitRequired(spec.tasks);
|
|
3528
|
-
}
|
|
3529
|
-
else {
|
|
3530
|
-
const reshapeRaisedTopology = frontendTaskShape.signals.includes("shape-transition-reshape");
|
|
3531
|
-
if (!reshapeRaisedTopology) {
|
|
3532
|
-
spec.tasks = pruneFrontendTasksForRisk(spec.tasks, frontendRisk);
|
|
3533
|
-
}
|
|
3534
|
-
if (frontendTaskShape.shape === "micro") {
|
|
3535
|
-
spec.tasks = pruneFrontendTasksForMicro(spec.tasks);
|
|
3536
|
-
}
|
|
3537
|
-
}
|
|
3538
|
-
spec.advisories = [
|
|
3539
|
-
...(spec.advisories ?? []),
|
|
3540
|
-
`frontend-task-shape: ${frontendTaskShape.shape} (${frontendTaskShape.reason})`,
|
|
3541
|
-
];
|
|
3542
|
-
// Freeze the frontend recovery continuation quota from task.json so the
|
|
3543
|
-
// runner can bound M6 auto-recovery without re-reading the task config.
|
|
3544
|
-
const frontendMaxContinuations = sources.taskConfig.frontendRecovery?.maxContinuations ?? 1;
|
|
3545
|
-
spec.frontendRecovery = { maxContinuations: frontendMaxContinuations };
|
|
3410
|
+
spec.tasks = pruneFrontendTasksForRisk(spec.tasks, frontendRisk);
|
|
3546
3411
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
3412
|
+
stampGeneratedArtifactBindings(spec);
|
|
3547
3413
|
parseDagSpec(spec);
|
|
3548
3414
|
assertValidDagSpec(spec);
|
|
3549
3415
|
return spec;
|
|
@@ -4211,6 +4077,84 @@ const BACKEND_TEST_SKILLS_BY_ROLE = {
|
|
|
4211
4077
|
verifier: ["verification-before-completion", "systematic-debugging"],
|
|
4212
4078
|
closeout: ["loop-agent", "verification-before-completion"],
|
|
4213
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
|
+
}
|
|
4214
4158
|
/**
|
|
4215
4159
|
* Plan D: gap-fill incremental DAG (mode=gap-fill).
|
|
4216
4160
|
*
|
|
@@ -4548,7 +4492,7 @@ async function buildBackendTestGapFillDag(sources) {
|
|
|
4548
4492
|
commands: [],
|
|
4549
4493
|
backendTestPipeline: "markdown-execute-html",
|
|
4550
4494
|
cwd: ".",
|
|
4551
|
-
timeoutMs: 300000,
|
|
4495
|
+
timeoutMs: taskConfig.backendTest?.executeTimeoutMs ?? 300000,
|
|
4552
4496
|
envAllowlist,
|
|
4553
4497
|
},
|
|
4554
4498
|
};
|
|
@@ -4612,6 +4556,7 @@ async function buildBackendTestGapFillDag(sources) {
|
|
|
4612
4556
|
spec.backendTestSharedSetup = { ...sharedSetup };
|
|
4613
4557
|
}
|
|
4614
4558
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
4559
|
+
stampGeneratedArtifactBindings(spec);
|
|
4615
4560
|
parseDagSpec(spec);
|
|
4616
4561
|
assertValidDagSpec(spec);
|
|
4617
4562
|
return spec;
|
|
@@ -5024,7 +4969,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
5024
4969
|
'mkdir -p "${HARNESS_DAG_RUN_DIR}/reports"',
|
|
5025
4970
|
'echo "pytest targets are resolved at runtime from execution-readiness v2 eligibleItemIds"',
|
|
5026
4971
|
].join("; ");
|
|
5027
|
-
const execute = shellNode("execute-backend-pytest-and-html-report-shell", [manifest.id], "markdown-execute-html", "Read canonical contracts/backend-test-execution-readiness.json v2, verify final asset and eligibility-input hashes, then execute exactly its `eligibleItemIds` pytest node IDs once. Never execute `excludedItems`; retain each exclusion reason as residual TestBug/automation evidence rather than ProductBug. Prefer the deterministic module one-to-one path when a mapped script is missing but the module stem file exists. Generate a native pytest-html self-contained report, then render the primary self-contained Chinese HTML report from the same pytest-html plus final Markdown case metadata without rerun. Keep 测试结论 and quality status; make node 6 Markdown validation + case coverage and node 13 traceability + Markdown-to-pytest correspondence expandable to their full escaped details; show each failure overview item with its original pytest message plus deterministic evidence-based reason analysis; list failure/error case cards before the remaining cases while preserving stable order. Each polished per-case result card includes concise scenario, automation test name, result, duration, and redacted bounded HTTP request parameters/response results for both passed and failed cases. Do not render a technical/execution evidence section in HTML; retain auditable paths and hashes in facts.", "One scoped pytest execution over readiness-authorized eligible pytest items producing a valid pytest-html report with per-case captured output, self-contained reports/backend-test.html, reports/backend-test.md, reports/backend-test-facts.md, a deterministic self-contained reports/backend-test-l5-dashboard.html (machine-computed L-5 metrics, no JSON), and an optional contracts/code-coverage-v1.json when jacocoCoverage is configured (JaCoCo TCP dump → jacoco.xml → parsed; failure-safe); exit 0/1 with valid evidence continues.", [pytestCommand], 300000);
|
|
4972
|
+
const execute = shellNode("execute-backend-pytest-and-html-report-shell", [manifest.id], "markdown-execute-html", "Read canonical contracts/backend-test-execution-readiness.json v2, verify final asset and eligibility-input hashes, then execute exactly its `eligibleItemIds` pytest node IDs once. Never execute `excludedItems`; retain each exclusion reason as residual TestBug/automation evidence rather than ProductBug. Prefer the deterministic module one-to-one path when a mapped script is missing but the module stem file exists. Generate a native pytest-html self-contained report, then render the primary self-contained Chinese HTML report from the same pytest-html plus final Markdown case metadata without rerun. Keep 测试结论 and quality status; make node 6 Markdown validation + case coverage and node 13 traceability + Markdown-to-pytest correspondence expandable to their full escaped details; show each failure overview item with its original pytest message plus deterministic evidence-based reason analysis; list failure/error case cards before the remaining cases while preserving stable order. Each polished per-case result card includes concise scenario, automation test name, result, duration, and redacted bounded HTTP request parameters/response results for both passed and failed cases. Do not render a technical/execution evidence section in HTML; retain auditable paths and hashes in facts.", "One scoped pytest execution over readiness-authorized eligible pytest items producing a valid pytest-html report with per-case captured output, self-contained reports/backend-test.html, reports/backend-test.md, reports/backend-test-facts.md, a deterministic self-contained reports/backend-test-l5-dashboard.html (machine-computed L-5 metrics, no JSON), and an optional contracts/code-coverage-v1.json when jacocoCoverage is configured (JaCoCo TCP dump → jacoco.xml → parsed; failure-safe); exit 0/1 with valid evidence continues.", [pytestCommand], taskConfig.backendTest?.executeTimeoutMs ?? 300000);
|
|
5028
4973
|
if (execute.shell) {
|
|
5029
4974
|
execute.shell.envAllowlist = collectBackendTestShellEnvAllowlist(sources);
|
|
5030
4975
|
}
|
|
@@ -5114,6 +5059,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
5114
5059
|
spec.backendTestSharedSetup = { ...intake.sharedSetup };
|
|
5115
5060
|
}
|
|
5116
5061
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
5062
|
+
stampGeneratedArtifactBindings(spec);
|
|
5117
5063
|
parseDagSpec(spec);
|
|
5118
5064
|
assertValidDagSpec(spec);
|
|
5119
5065
|
return spec;
|
|
@@ -5868,6 +5814,7 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
5868
5814
|
};
|
|
5869
5815
|
applyFrontendTestLayoutToDagSpec(spec, layout);
|
|
5870
5816
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
5817
|
+
stampGeneratedArtifactBindings(spec);
|
|
5871
5818
|
parseDagSpec(spec);
|
|
5872
5819
|
assertValidDagSpec(spec);
|
|
5873
5820
|
return spec;
|
|
@@ -6370,6 +6317,7 @@ function buildKnowledgeSyncHybridDag(sources) {
|
|
|
6370
6317
|
buildKnowledgeSyncPointerNode(sources, featureId),
|
|
6371
6318
|
],
|
|
6372
6319
|
};
|
|
6320
|
+
stampGeneratedArtifactBindings(spec);
|
|
6373
6321
|
parseDagSpec(spec);
|
|
6374
6322
|
assertValidDagSpec(spec);
|
|
6375
6323
|
return spec;
|
|
@@ -6816,6 +6764,7 @@ function buildKnowledgeGraphBootstrapHybridDag(sources) {
|
|
|
6816
6764
|
buildKgBootstrapMaterializeNode(sources),
|
|
6817
6765
|
],
|
|
6818
6766
|
};
|
|
6767
|
+
stampGeneratedArtifactBindings(spec);
|
|
6819
6768
|
parseDagSpec(spec);
|
|
6820
6769
|
assertValidDagSpec(spec);
|
|
6821
6770
|
return spec;
|
|
@@ -6913,6 +6862,8 @@ async function buildHybridDagForTemplate(sources, template, options = {}) {
|
|
|
6913
6862
|
}
|
|
6914
6863
|
else if (template === "frontend-test-dag")
|
|
6915
6864
|
spec = buildFrontendTestHybridDag(sources);
|
|
6865
|
+
else if (template === "backend-test-analysis-dag")
|
|
6866
|
+
spec = buildBackendTestAnalysisHybridDag(sources);
|
|
6916
6867
|
else if (template === "backend-test-dag")
|
|
6917
6868
|
spec = await buildBackendTestHybridDag(sources);
|
|
6918
6869
|
else if (template === "knowledge-sync-dag")
|
|
@@ -6949,6 +6900,7 @@ async function buildHybridDagForTemplate(sources, template, options = {}) {
|
|
|
6949
6900
|
template === "supervised-implementation") {
|
|
6950
6901
|
stampTargetTemplateTransientRetryProfile(spec);
|
|
6951
6902
|
}
|
|
6903
|
+
stampGeneratedArtifactBindings(spec);
|
|
6952
6904
|
spec = parseDagSpec(spec);
|
|
6953
6905
|
assertValidDagSpec(spec);
|
|
6954
6906
|
return spec;
|
|
@@ -6956,6 +6908,7 @@ async function buildHybridDagForTemplate(sources, template, options = {}) {
|
|
|
6956
6908
|
const GOVERNANCE_DISALLOWED_TEMPLATES = new Set([
|
|
6957
6909
|
"frontend-implementation",
|
|
6958
6910
|
"frontend-test-dag",
|
|
6911
|
+
"backend-test-analysis-dag",
|
|
6959
6912
|
"backend-test-dag",
|
|
6960
6913
|
"knowledge-sync-dag",
|
|
6961
6914
|
"knowledge-graph-bootstrap-dag",
|
|
@@ -7316,6 +7269,7 @@ function buildReviewGatedHybridDag(standard, sources) {
|
|
|
7316
7269
|
applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
|
|
7317
7270
|
stampTargetTemplateTransientRetryProfile(spec);
|
|
7318
7271
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
7272
|
+
stampGeneratedArtifactBindings(spec);
|
|
7319
7273
|
parseDagSpec(spec);
|
|
7320
7274
|
assertValidDagSpec(spec);
|
|
7321
7275
|
return spec;
|
|
@@ -7821,6 +7775,7 @@ async function buildSupervisedHybridDag(standard, sources) {
|
|
|
7821
7775
|
applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
|
|
7822
7776
|
stampTargetTemplateTransientRetryProfile(spec);
|
|
7823
7777
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
7778
|
+
stampGeneratedArtifactBindings(spec);
|
|
7824
7779
|
parseDagSpec(spec);
|
|
7825
7780
|
assertValidDagSpec(spec);
|
|
7826
7781
|
return spec;
|