@tea-agent/loop-agent 0.38.0 → 0.38.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 +95 -0
- package/dist/build-stamp.json +3 -3
- package/dist/commands/init-upgrade.js +97 -19
- package/dist/commands/init.js +14 -2
- package/dist/executors/dag-pi-executor.js +1 -0
- package/dist/governance/manifest-types.js +2 -0
- package/dist/infrastructure/harness/atomic-write.js +2 -1
- package/dist/shared/openspec-spec.js +76 -0
- package/dist/shared/operator/capabilities.js +65 -8
- package/dist/task/config-types.js +21 -0
- package/dist/task/frontend-project-capability.js +292 -38
- package/dist/worker/console/chat/mcp-inventory.js +221 -0
- package/dist/worker/console/chat/pi-runtime.js +155 -78
- package/dist/worker/console/chat/repo-walk.js +10 -1
- package/dist/worker/console/chat/routes.js +19 -8
- package/dist/worker/console/chat/semantic-activity.js +17 -5
- package/dist/worker/console/chat/turn-process.js +17 -30
- package/dist/worker/console/harness-pi-model-matrix.js +146 -0
- package/dist/worker/console/operator-actions.js +123 -0
- package/dist/worker/console/operator-user-error.js +19 -0
- package/dist/worker/console/pi-readiness.js +7 -1
- package/dist/worker/console/security.js +20 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DSvF6RXQ.js → abnfDiagram-N423BO3Z-Hfsot6rP.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DuI4ogtJ.js → arc-YGwEASin.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DdBbgD4y.js → architectureDiagram-T3A2C74G-CmOM0F92.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-xQC2HELm.js → blockDiagram-VBNYF7ZC-DDLuaHC5.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DPMSgwIZ.js → c4Diagram-5PPSVZJV-B0Um43W4.js} +1 -1
- package/dist/worker/console/static/assets/channel-Dp_2Aqf1.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-WTEKTxin.js → chunk-2GRJ4B5K-DvBN4JZc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B6vZ0Pv_.js → chunk-2Q5K7J3B-B1UVgW4E.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BrerNAix.js → chunk-5RXB4S5H-Cf0GgXzX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D_ch2aaI.js → chunk-5VM5RSS4-DbNA24sj.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-VvfacNrq.js → chunk-6Q2QTUOP-Bi_0Be06.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B-uGFIg2.js → chunk-GF5L2VYU-C8pPGvRd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BlysCtod.js → chunk-JWPE2WC7-BitQY8Z-.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BjAUAfcT.js → chunk-KBJHAD2P-U67GCiEo.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BeZnBmb-.js → chunk-RYQCIY6F-IoJwXPBA.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Bbfe3UJG.js → chunk-XXDRQBXY-YDZ_UHgE.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL--CODy37j.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE--CODy37j.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BKJQnYVM.js → cose-bilkent-JH36ORCC-CUZgsiRC.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-B1wtI3hU.js → cynefin-VYW2F7L2-COmCh4pT.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DCF6DBSu.js → cynefinDiagram-MW4NZA55-Ta6FSm68.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJyNGRlP.js → dagre-VZM6K2ZE-UdcR-VW9.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B7j1-VsZ.js → diagram-7IWD3JNH-DybcO3Et.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BREJ140a.js → diagram-B4RE2ZJO-wErDFWLw.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CDPHmHGH.js → diagram-LBJQPF4R-DkVHZWqz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BtZLrez1.js → diagram-Q27KOJAE-DdqWs7QN.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-mzybu8oU.js → diagram-UB23O5K3-C9dMVmDo.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Dk3I2uO6.js → ebnfDiagram-BXEA7PRR-B2T2WcBK.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BT97u_tQ.js → erDiagram-JOGREHBK-Bt9rjDFq.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CMOtYzui.js → flowDiagram-UKHOOZJN-U8ic_tvi.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DiNydkuY.js → ganttDiagram-PKOTCBZU-BfQJdQoO.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js → gitGraphDiagram-DS77QQ5N-CpMftH4O.js} +1 -1
- package/dist/worker/console/static/assets/index-BA5-N5ER.css +1 -0
- package/dist/worker/console/static/assets/index-DKPXBrdc.js +325 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-AsGIGKKB.js → infoDiagram-6WML65LV-DzqWXNO_.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BL8ITZh3.js → ishikawaDiagram-WSZJBQD7-CJmGQ5Yp.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-VPKAUX8M.js → journeyDiagram-NVQOT4AX-ChPtKFLz.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BL56hfGp.js → kanban-definition-27J2QSJJ-PRe7wB5u.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BmSG3nIZ.js → linear-DpKX6QNp.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DWGK9kwP.js → mermaid.core-Dnl76b7Y.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS--vvKLjo0.js → mindmap-definition-FAOFIHXS-S9PCXc3J.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-B41mxb2-.js → pegDiagram-VL7TDLO6-Du8e-FSX.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DTlu77sw.js → pieDiagram-7S7Q4E2Y-CY6OmHTc.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bk5A7alv.js → quadrantDiagram-CIZ2JOQS-C65GPhpj.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Csf9jzQy.js → railroadDiagram-AXF67PYL-U6IUiTCL.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CeptoSNx.js → requirementDiagram-LRYGKXZP-D-I_9Qhi.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-A-DLCLer.js → sankeyDiagram-W5VNT64P-B57Fhed0.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-22VbPdOI.js → sequenceDiagram-SI44F4Z6-CJOtiw21.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DWvAyBC9.js → sizeCapture-X5ZJPWSS-FyX8zxSh.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-qICZ75yT.js → stateDiagram-OKZ733FA-eF-pbs9t.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B-mvnLEc.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-xi7lcMyp.js → swimlanes-SLNWSIFB-CcfkA-Dq.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-JtpBmIv4.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Bu6Nl6O2.js → timeline-definition-Z64GVDOM-DH8FjnOK.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BzMz4Bn9.js → vennDiagram-T6HMQDX7-beoTagJg.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CxaJnzc5.js → wardleyDiagram-T6FBY63Y-CQSfw4cK.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-5JK-omQK.js → xychartDiagram-ELKLHX3M-CQgzQ6jn.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +51 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +44 -21
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +45 -0
- package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +15 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +17 -4
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +28 -22
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
- package/dist/workflows/dag/contract-validator-registrations.js +2 -1
- package/dist/workflows/dag/failure-routing.js +9 -4
- package/dist/workflows/dag/frontend-implementation-contract.js +158 -0
- package/dist/workflows/dag/frontend-plan-render.js +90 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +411 -20
- package/dist/workflows/dag/init-hybrid.js +272 -27
- package/dist/workflows/dag/lifecycle.js +4 -0
- package/dist/workflows/dag/node-execution.js +51 -9
- package/dist/workflows/dag/report.js +6 -0
- package/dist/workflows/dag/retry-policy.js +15 -0
- package/dist/workflows/dag/types.js +50 -4
- package/dist/workflows/dag/validate.js +7 -2
- package/docs/templates/frontend-design-contract.md +40 -14
- package/docs/templates/frontend-implementation-contract.schema.json +9 -2
- package/docs/templates/frontend-task-constraints.md +40 -13
- package/docs/templates/frontend-task-requirement.md +54 -20
- package/docs/templates/harness.schema.json +8 -0
- package/harness.json +11 -2
- package/package.json +1 -1
- package/skills/frontend-design-review/references/review-checklist.md +2 -0
- package/skills/frontend-implementation/references/design-spec.md +16 -19
- package/skills/frontend-implementation/references/node-contracts.md +15 -11
- package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/index-DQxiso1w.js +0 -325
- package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +0 -8
|
@@ -144,14 +144,40 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
144
144
|
if (previousFailureCategory === "invalid-output" &&
|
|
145
145
|
task.structuredContractOutput &&
|
|
146
146
|
previousProtocolReason) {
|
|
147
|
+
// Revision nodes emit an RFC 7386 merge-patch delta (no schemaVersion /
|
|
148
|
+
// targets), so the retry instruction must not push them toward a full
|
|
149
|
+
// contract rewrite; plan nodes keep the strict v1 guidance.
|
|
150
|
+
const guidance = task.structuredContractOutput.schemaId ===
|
|
151
|
+
"frontend-implementation-contract-revision-patch-v1"
|
|
152
|
+
? [
|
|
153
|
+
"Return exactly one fenced json object containing the RFC 7386 merge-patch delta against the original contract (null deletes a key; arrays and scalars replace; plain objects merge recursively) + exactly one openspec-citations block.",
|
|
154
|
+
"The delta contains only the fields you change; do not emit a full contract or a schemaVersion/targets section.",
|
|
155
|
+
]
|
|
156
|
+
: [
|
|
157
|
+
"Return exactly one fenced json block conforming to the frontend-implementation-contract-v1 schema.",
|
|
158
|
+
"Fix every reported field violation: do not emit null for optional fields, do not misspell field names, and match the required types exactly.",
|
|
159
|
+
"The Markdown explanation may be omitted; prioritize a complete contract.",
|
|
160
|
+
];
|
|
147
161
|
return [
|
|
148
162
|
basePrompt,
|
|
149
163
|
"",
|
|
150
164
|
"<retry_instruction>",
|
|
151
165
|
"Previous attempt produced an invalid frontend implementation contract:",
|
|
152
166
|
previousProtocolReason,
|
|
153
|
-
|
|
154
|
-
"
|
|
167
|
+
...guidance,
|
|
168
|
+
"</retry_instruction>",
|
|
169
|
+
].join("\n");
|
|
170
|
+
}
|
|
171
|
+
if (previousFailureCategory === "structured-output-truncated" &&
|
|
172
|
+
task.structuredContractOutput) {
|
|
173
|
+
return [
|
|
174
|
+
basePrompt,
|
|
175
|
+
"",
|
|
176
|
+
"<retry_instruction>",
|
|
177
|
+
"Previous attempt was truncated by the provider (stopReason=length) before the JSON contract was completed.",
|
|
178
|
+
"This attempt: output ONLY the single fenced json contract block, immediately followed by exactly one openspec-citations block.",
|
|
179
|
+
"Do not emit any Markdown explanation, evidence excerpts, or duplicated upstream context.",
|
|
180
|
+
"The contract JSON must be complete; the trailing Markdown explanation may be omitted entirely.",
|
|
155
181
|
"</retry_instruction>",
|
|
156
182
|
].join("\n");
|
|
157
183
|
}
|
|
@@ -216,18 +242,28 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
216
242
|
"</retry_instruction>",
|
|
217
243
|
].join("\n");
|
|
218
244
|
}
|
|
219
|
-
if (
|
|
220
|
-
previousFailureCategory !== "output-too-large") {
|
|
245
|
+
if (previousFailureCategory !== "output-too-large") {
|
|
221
246
|
return basePrompt;
|
|
222
247
|
}
|
|
248
|
+
if (task.outputMode === "structured-required") {
|
|
249
|
+
return [
|
|
250
|
+
basePrompt,
|
|
251
|
+
"",
|
|
252
|
+
"<retry_instruction>",
|
|
253
|
+
"Previous attempt exceeded the structured output size limit.",
|
|
254
|
+
"Return only the compact structured artifact required by this node's output contract.",
|
|
255
|
+
"Do not include explanatory prose, duplicated upstream context, long evidence excerpts, or additional markdown sections.",
|
|
256
|
+
"If a fenced JSON object is required, output exactly one fenced json block and nothing else.",
|
|
257
|
+
"</retry_instruction>",
|
|
258
|
+
].join("\n");
|
|
259
|
+
}
|
|
223
260
|
return [
|
|
224
261
|
basePrompt,
|
|
225
262
|
"",
|
|
226
263
|
"<retry_instruction>",
|
|
227
|
-
"Previous attempt exceeded the
|
|
228
|
-
"Return
|
|
229
|
-
"
|
|
230
|
-
"If a fenced JSON object is required, output exactly one fenced json block and nothing else.",
|
|
264
|
+
"Previous attempt exceeded the output size limit.",
|
|
265
|
+
"Return a minimal plan: ordered steps, narrow writeSet boundaries, and verification commands.",
|
|
266
|
+
"Drop verbatim upstream quotes, long evidence excerpts, and repeated context.",
|
|
231
267
|
"</retry_instruction>",
|
|
232
268
|
].join("\n");
|
|
233
269
|
}
|
|
@@ -758,10 +794,16 @@ export async function executeDagNode(input) {
|
|
|
758
794
|
sourceBinding: spec.sourceBinding,
|
|
759
795
|
});
|
|
760
796
|
if (!contractCheck.ok) {
|
|
797
|
+
// A provider stopReason=length means the response was cut before the
|
|
798
|
+
// JSON contract could complete; separate it from an ordinary bad
|
|
799
|
+
// contract so the retry switches to a JSON-only output strategy.
|
|
800
|
+
const attemptStopReason = result.stopReason;
|
|
761
801
|
result = {
|
|
762
802
|
...result,
|
|
763
803
|
ok: false,
|
|
764
|
-
failureCategory: "
|
|
804
|
+
failureCategory: attemptStopReason === "length"
|
|
805
|
+
? "structured-output-truncated"
|
|
806
|
+
: "invalid-output",
|
|
765
807
|
stderr: [result.stderr, contractCheck.reason]
|
|
766
808
|
.filter(Boolean)
|
|
767
809
|
.join("\n"),
|
|
@@ -441,6 +441,7 @@ export async function buildDagRunReportEntry(input) {
|
|
|
441
441
|
normalizedFailureCategory,
|
|
442
442
|
nodeId,
|
|
443
443
|
executor: node.executor,
|
|
444
|
+
skippedReason: node.skippedReason,
|
|
444
445
|
});
|
|
445
446
|
const followUp = node.status === "ERROR" || node.status === "SKIPPED"
|
|
446
447
|
? recommendFollowUpForFailureCategory(node.failureCategory)
|
|
@@ -505,6 +506,7 @@ export async function buildDagRunReportEntry(input) {
|
|
|
505
506
|
normalizedFailureCategory: normalizeDagFailureCategory(pausedNode.failureCategory, pausedNode.status),
|
|
506
507
|
failureCategory: pausedNode.failureCategory,
|
|
507
508
|
nodeId: input.state.pausedByNodeId,
|
|
509
|
+
skippedReason: pausedNode.skippedReason,
|
|
508
510
|
}
|
|
509
511
|
: firstActionableNode
|
|
510
512
|
? {
|
|
@@ -513,6 +515,7 @@ export async function buildDagRunReportEntry(input) {
|
|
|
513
515
|
normalizeDagFailureCategory(firstActionableNode.failureCategory, firstActionableNode.status),
|
|
514
516
|
failureCategory: firstActionableNode.failureCategory,
|
|
515
517
|
nodeId: firstActionableNode.nodeId,
|
|
518
|
+
skippedReason: input.state.nodes[firstActionableNode.nodeId]?.skippedReason,
|
|
516
519
|
}
|
|
517
520
|
: {
|
|
518
521
|
status: input.state.status,
|
|
@@ -529,6 +532,9 @@ export async function buildDagRunReportEntry(input) {
|
|
|
529
532
|
rawFailureCategory: runRecoverySource.failureCategory,
|
|
530
533
|
normalizedFailureCategory: runRecoverySource.normalizedFailureCategory,
|
|
531
534
|
nodeId: "nodeId" in runRecoverySource ? runRecoverySource.nodeId : undefined,
|
|
535
|
+
skippedReason: "skippedReason" in runRecoverySource
|
|
536
|
+
? runRecoverySource.skippedReason
|
|
537
|
+
: undefined,
|
|
532
538
|
});
|
|
533
539
|
const runFollowUp = input.state.status === "failed" ||
|
|
534
540
|
input.state.status === "partial_failed"
|
|
@@ -21,6 +21,8 @@ export const STRUCTURED_OUTPUT_RETRY_CATEGORY = "output-too-large";
|
|
|
21
21
|
export const PROTOCOL_INVALID_RETRY_CATEGORY = "protocol-invalid";
|
|
22
22
|
/** Recoverable model artifact/schema formatting failure on read-only structured nodes. */
|
|
23
23
|
export const STRUCTURED_ARTIFACT_INVALID_RETRY_CATEGORY = "invalid-output";
|
|
24
|
+
/** Provider stopReason=length truncated the response before the JSON contract completed. */
|
|
25
|
+
export const STRUCTURED_OUTPUT_TRUNCATED_RETRY_CATEGORY = "structured-output-truncated";
|
|
24
26
|
/** Retry only a proven no-op from an explicitly opt-in bounded Pi writer. */
|
|
25
27
|
export const WRITER_EMPTY_DIFF_RETRY_CATEGORY = "writer-empty-diff";
|
|
26
28
|
/** Retry when a backend-test writer finished but Completeness Gate found missing/broken targets. */
|
|
@@ -36,6 +38,7 @@ export const STRUCTURED_REQUIRED_DAG_RETRY_CATEGORIES = [
|
|
|
36
38
|
...DEFAULT_DAG_RETRY_CATEGORIES,
|
|
37
39
|
STRUCTURED_OUTPUT_RETRY_CATEGORY,
|
|
38
40
|
STRUCTURED_ARTIFACT_INVALID_RETRY_CATEGORY,
|
|
41
|
+
STRUCTURED_OUTPUT_TRUNCATED_RETRY_CATEGORY,
|
|
39
42
|
];
|
|
40
43
|
/** Categories allowed on nodes that declare a machine-readable outputProtocol. */
|
|
41
44
|
export const PROTOCOL_AWARE_DAG_RETRY_CATEGORIES = [
|
|
@@ -48,6 +51,7 @@ export const ALL_DAG_RETRY_CATEGORIES = [
|
|
|
48
51
|
STRUCTURED_OUTPUT_RETRY_CATEGORY,
|
|
49
52
|
PROTOCOL_INVALID_RETRY_CATEGORY,
|
|
50
53
|
STRUCTURED_ARTIFACT_INVALID_RETRY_CATEGORY,
|
|
54
|
+
STRUCTURED_OUTPUT_TRUNCATED_RETRY_CATEGORY,
|
|
51
55
|
WRITER_EMPTY_DIFF_RETRY_CATEGORY,
|
|
52
56
|
INCOMPLETE_WRITE_SET_RETRY_CATEGORY,
|
|
53
57
|
WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY,
|
|
@@ -118,6 +122,17 @@ export const STRUCTURED_REQUIRED_PI_RETRY_POLICY = {
|
|
|
118
122
|
...DEFAULT_READ_ONLY_PI_RETRY_POLICY,
|
|
119
123
|
retryCategories: [...STRUCTURED_REQUIRED_DAG_RETRY_CATEGORIES],
|
|
120
124
|
};
|
|
125
|
+
/**
|
|
126
|
+
* Planner read-only nodes (standard DAG contract-pi / plan-pi) may retry with
|
|
127
|
+
* a compact-output instruction when the model produced an oversized assistant
|
|
128
|
+
* response. Only `output-too-large` is added on top of the default set;
|
|
129
|
+
* report/review and other read-only roles keep the default set so they do not
|
|
130
|
+
* silently learn new output semantics.
|
|
131
|
+
*/
|
|
132
|
+
export const PLANNER_OUTPUT_LIMIT_RETRY_POLICY = {
|
|
133
|
+
...DEFAULT_READ_ONLY_PI_RETRY_POLICY,
|
|
134
|
+
retryCategories: [...DEFAULT_DAG_RETRY_CATEGORIES, STRUCTURED_OUTPUT_RETRY_CATEGORY],
|
|
135
|
+
};
|
|
121
136
|
/**
|
|
122
137
|
* Default retry for reviewer / recovery nodes that declare outputProtocol.
|
|
123
138
|
* Includes protocol-invalid so missing VERDICT lines are corrected in-node.
|
|
@@ -171,6 +171,9 @@ export const dagJsonArtifactGateSchema = z.object({
|
|
|
171
171
|
const dagFrontendNodeIdSchema = z
|
|
172
172
|
.string()
|
|
173
173
|
.regex(/^[a-z][a-z0-9-]*$/, "frontend node id must be kebab-case");
|
|
174
|
+
const openspecCandidatePathSchema = z
|
|
175
|
+
.string()
|
|
176
|
+
.refine((candidate) => !candidate.includes("\\") && isOpenspecSpecFilePath(candidate), "openspec candidate must be a repo-relative supported file under openspec/schemas/, openspec/project-specs/, or ai_workspace/");
|
|
174
177
|
export const dagFrontendPrewriteGateSchema = z.object({
|
|
175
178
|
schemaVersion: z.literal(1),
|
|
176
179
|
planFromNodeId: dagFrontendNodeIdSchema,
|
|
@@ -189,11 +192,51 @@ export const dagFrontendPrewriteGateSchema = z.object({
|
|
|
189
192
|
outputDir: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/),
|
|
190
193
|
requireSourceFreshness: z.literal(true),
|
|
191
194
|
implementationWriteSet: z.array(z.string().min(1)).min(1).optional(),
|
|
192
|
-
openspecCandidatePaths: z
|
|
193
|
-
|
|
195
|
+
openspecCandidatePaths: z.array(openspecCandidatePathSchema).default([]),
|
|
196
|
+
/**
|
|
197
|
+
* Generation-frozen component/theme specification bucket. Derived from
|
|
198
|
+
* `designEvidence.classified.component ∪ theme ∪ rule.components` at DAG
|
|
199
|
+
* generation. When non-empty and the canonical contract has UI-visible
|
|
200
|
+
* work, the prewrite gate requires a non-empty `uiComponentChoices` and
|
|
201
|
+
* cross-checks each `specReference.path` against this set + the effective
|
|
202
|
+
* plan openspec-citations block. Legacy DAGs without this field keep the
|
|
203
|
+
* field-absent semantics (component-choices-missing is not enforced).
|
|
204
|
+
*/
|
|
205
|
+
componentSpecCandidatePaths: z.array(openspecCandidatePathSchema).optional(),
|
|
206
|
+
/**
|
|
207
|
+
* OpenSpec gate policy. Omitted on legacy DAGs: the runtime treats
|
|
208
|
+
* undefined as scan-strict so pre-existing full-read semantics are
|
|
209
|
+
* preserved. Generation always writes it explicitly for new DAGs.
|
|
210
|
+
*/
|
|
211
|
+
openspecPolicy: z.enum(["cited", "scan-strict"]).optional(),
|
|
212
|
+
/**
|
|
213
|
+
* When true, the effective plan node (planFromNodeId when it is selected
|
|
214
|
+
* over the fallback) emits an RFC 7386 merge-patch delta instead of a full
|
|
215
|
+
* contract. The gate applies it on the original contract node
|
|
216
|
+
* (planFallbackFromNodeIds[0]) before analysis/materialization. Legacy DAGs
|
|
217
|
+
* without this field keep full-contract semantics.
|
|
218
|
+
*/
|
|
219
|
+
revisionPatch: z.boolean().optional(),
|
|
220
|
+
/**
|
|
221
|
+
* Deterministically rendered plan.md artifact name (same outputDir as the
|
|
222
|
+
* contract). Defaults to frontend-plan.md at runtime.
|
|
223
|
+
*/
|
|
224
|
+
planMdArtifactName: z
|
|
194
225
|
.string()
|
|
195
|
-
.
|
|
196
|
-
.
|
|
226
|
+
.regex(/^[a-z0-9][a-z0-9._-]*\.md$/)
|
|
227
|
+
.optional(),
|
|
228
|
+
/**
|
|
229
|
+
* Generation-frozen candidate source metadata (diagnostic). cited mode
|
|
230
|
+
* candidates are declared ∪ taskSourceCited; scan-strict candidates are
|
|
231
|
+
* scanStrict (auto-discovered designEvidence.normativePaths).
|
|
232
|
+
*/
|
|
233
|
+
openspecCandidateSources: z
|
|
234
|
+
.object({
|
|
235
|
+
declared: z.array(openspecCandidatePathSchema).default([]),
|
|
236
|
+
taskSourceCited: z.array(openspecCandidatePathSchema).default([]),
|
|
237
|
+
scanStrict: z.array(openspecCandidatePathSchema).default([]),
|
|
238
|
+
})
|
|
239
|
+
.optional(),
|
|
197
240
|
});
|
|
198
241
|
export const dagFrontendLintBaselineSchema = z
|
|
199
242
|
.object({
|
|
@@ -661,6 +704,7 @@ export const dagConvergenceSpecSchema = z
|
|
|
661
704
|
*/
|
|
662
705
|
export const structuredContractOutputSchemaIds = [
|
|
663
706
|
"frontend-implementation-contract-v1",
|
|
707
|
+
"frontend-implementation-contract-revision-patch-v1",
|
|
664
708
|
];
|
|
665
709
|
export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/, "task id must be kebab-case"),
|
|
666
710
|
depends_on: z.array(z.string()).default([]),
|
|
@@ -822,6 +866,8 @@ export const dagSpecSchema = z
|
|
|
822
866
|
defaults: dagDefaultsSchema,
|
|
823
867
|
skillsByRole: z.record(z.string(), z.array(z.string())).optional(),
|
|
824
868
|
executorModels: dagExecutorModelsSchema.optional(),
|
|
869
|
+
/** Generation-time advisories surfaced as machine-visible warnings. */
|
|
870
|
+
advisories: z.array(z.string()).optional(),
|
|
825
871
|
tasks: z.array(dagTaskSchema).min(1),
|
|
826
872
|
})
|
|
827
873
|
.superRefine((spec, ctx) => {
|
|
@@ -14,6 +14,7 @@ const GOVERNANCE_WARNING_TYPES = new Set([
|
|
|
14
14
|
"shell-verdict-gate-multi-command-state",
|
|
15
15
|
"shell-verdict-gate-handwritten-inline",
|
|
16
16
|
"orphan-writer-source-binding",
|
|
17
|
+
"advisory",
|
|
17
18
|
]);
|
|
18
19
|
const ROOT_ARTIFACT_PATH_PROBES = [
|
|
19
20
|
"artifacts/修改记录.md",
|
|
@@ -32,10 +33,11 @@ export function collectGovernanceWarnings(issues) {
|
|
|
32
33
|
return issues.filter(isGovernanceWarning);
|
|
33
34
|
}
|
|
34
35
|
function collectBlockingIssues(issues, options = {}) {
|
|
36
|
+
const nonAdvisory = issues.filter((issue) => issue.type !== "advisory");
|
|
35
37
|
if (options.strictGovernance) {
|
|
36
|
-
return
|
|
38
|
+
return nonAdvisory;
|
|
37
39
|
}
|
|
38
|
-
return
|
|
40
|
+
return nonAdvisory.filter((issue) => !isGovernanceWarning(issue));
|
|
39
41
|
}
|
|
40
42
|
function normalizePath(value) {
|
|
41
43
|
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
@@ -956,6 +958,9 @@ export function validateDagSpec(spec) {
|
|
|
956
958
|
validateSameRankWriteSetConflicts(spec, ranks, issues);
|
|
957
959
|
validateSameRankAgentAttributionRisks(spec, ranks, issues);
|
|
958
960
|
validateWriterSourceBinding(spec, issues);
|
|
961
|
+
for (const advisory of spec.advisories ?? []) {
|
|
962
|
+
issues.push({ type: "advisory", message: advisory });
|
|
963
|
+
}
|
|
959
964
|
return issues;
|
|
960
965
|
}
|
|
961
966
|
function validateProjectGovernanceTaskConfig(task, spec, issues) {
|
|
@@ -1,42 +1,68 @@
|
|
|
1
1
|
# 前端设计契约模板
|
|
2
2
|
|
|
3
|
+
本模板只承载**通用编写协议 + 三要素 + fail-closed 规则**,不包含任何具体业务组件名、项目专属路径或项目专属命令。设计契约在进入实现前由 `frontend-plan-pi` 落入结构化 implementation contract,`frontend-design-review-pi` 按本模板逐节审查;每一节都要给出「**要写什么** / **DAG 如何消费** / **缺失/冲突时 fail-closed**」三要素。
|
|
4
|
+
|
|
3
5
|
## 页面目标
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
- **要写什么**:写清每个目标页面的职责、用户到达该页面的目的与成功结果,逐页列出,与需求.md 的用户目标/目标页面一一对应。
|
|
8
|
+
- **DAG 如何消费**:`frontend-plan-pi` 据它确定 `targets.routes` 与每页的实现步骤,`frontend-design-review-pi` 据它核对范围与需求一致性。
|
|
9
|
+
- **缺失/冲突时 fail-closed**:页面目标与需求不一致、缺页或目标含糊(无成功结果)时阻塞,不得自行补页或改目标。
|
|
6
10
|
|
|
7
11
|
## 信息结构
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
- **要写什么**:写清每页展示的信息层级与数据来源(字段、分组、优先级、空/错误占位),接口数据与静态文案分开写。
|
|
14
|
+
- **DAG 如何消费**:`frontend-plan-pi` 据它安排组件数据流与状态展示,`frontend-design-review-pi` 据它核对接口字段映射。
|
|
15
|
+
- **缺失/冲突时 fail-closed**:信息层级缺失、数据来源不可追溯、与接口字段冲突时阻塞,不得臆造字段或层级。
|
|
10
16
|
|
|
11
17
|
## 组件拆分
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
- **要写什么**:把页面拆成组件树,逐组件写职责、复用边界、props/状态归属与可替换点;复用现有设计系统组件时引用其来源路径。
|
|
20
|
+
- **DAG 如何消费**:`frontend-plan-pi` 据它确定 `targets.files` 与实现顺序,`frontend-design-review-pi` 据它审查拆分是否越界/重复。
|
|
21
|
+
- **缺失/冲突时 fail-closed**:组件边界不清、同一职责被拆到多处、或与设计规范冲突时阻塞,不得按邻近代码自行决定复用。
|
|
14
22
|
|
|
15
23
|
## 交互规则
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
- **要写什么**:逐条写清每个交互的触发、预期行为、状态变化与可恢复性(对应需求.md 的交互要求)。
|
|
26
|
+
- **DAG 如何消费**:`frontend-plan-pi` 据它填充 `interactions[]`(`trigger` + `expectedBehavior`),verify 用行为命令断言。
|
|
27
|
+
- **缺失/冲突时 fail-closed**:交互无预期行为、与 UI 状态冲突、或不可自动化断言时阻塞。
|
|
18
28
|
|
|
19
29
|
## UI 状态
|
|
20
30
|
|
|
21
|
-
|
|
31
|
+
- **要写什么**:逐状态(loading/empty/error/success/disabled)写清触发条件、展示内容与转移;不适用状态写 `N/A` + 非空理由。
|
|
32
|
+
- **DAG 如何消费**:进入 implementation contract 的 `uiStates[]`,verify 逐状态断言,`frontend-review-pi` 据它核对实现是否遗漏状态。
|
|
33
|
+
- **缺失/冲突时 fail-closed**:适用状态缺失预期、N/A 无理由、或状态展示与交互/接口状态冲突时阻塞。
|
|
22
34
|
|
|
23
35
|
## Mock / API 策略
|
|
24
36
|
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
- DAG
|
|
30
|
-
-
|
|
37
|
+
策略枚举固定为 `native | browser-intercept | request-adapter | not-needed | blocked`,与 runtime Mock 语义一致:`blocked` 永远不通过(缺少/冲突接口契约、路径或依赖未授权、无法证明生产默认关闭、固定验证入口无法覆盖、或唯一方案是注释真实请求时必须选它);`not-needed` 必须有后端可用或任务不涉及远程接口的真实/无远程证据,且固定 behavior 入口能覆盖相应行为;显式 `policy=required` 不接受 `not-needed`。契约只接受当前 policy 允许的非 `blocked` 首行。
|
|
38
|
+
|
|
39
|
+
- **接口文档或 schema — 要写什么**:引用接口文档/schema 路径与版本,作为 fixture 与字段映射的唯一来源。**DAG 如何消费**:`frontend-plan-pi` 据它冻结 endpoint/fixture 映射,prewrite gate 校验 fixture 可追溯。**缺失/冲突时 fail-closed**:无文档/schema、字段冲突时选 `blocked`,不得自行发明接口。
|
|
40
|
+
- **策略**:只写枚举中的一个值:`native | browser-intercept | request-adapter | not-needed | blocked`。
|
|
41
|
+
- **endpoint / fixture / UI 状态映射 — 要写什么**:逐 endpoint 列 method、path、fixture 路径与消费组件,并映射到 UI 状态。**DAG 如何消费**:写入 `mockApi.endpoints[]`(method/path/fixture/consumer),非 `not-needed` 策略要求每个 endpoint 都有 fixture 与 consumer。**缺失/冲突时 fail-closed**:非 `not-needed` 却缺 fixture/consumer、endpoint 与 UI 状态映射不一致时阻塞。
|
|
42
|
+
- **显式启用方式与 production 默认关闭边界 — 要写什么**:写清真实请求为默认路径、Mock 仅通过显式开关(环境变量/构建开关)启用的具体边界。**DAG 如何消费**:`productionDefaultOff` 恒为 `true`,`frontend-review-pi` 检查是否注释真实请求或默认开启 Mock。**缺失/冲突时 fail-closed**:无法证明生产默认关闭、或 Mock 会进入生产入口时选 `blocked`。
|
|
43
|
+
- **DAG 已固化的验证入口 — 要写什么**:引用 DAG 生成时已冻结的 static/behavior/Mock 命令 label,不发明新命令。**DAG 如何消费**:`verificationTarget.commandLabel` 必须逐字落在冻结命令集合内,否则 contract 物化 `invalid-output`。**缺失/冲突时 fail-closed**:策略需要 Mock 验证命令却没有冻结命令时生成期 fail-closed(`no authorized Mock verification commands`)。
|
|
44
|
+
- **Real Integration Gap 与后端就绪后的复验 — 要写什么**:写清当前未联通的真实集成缺口,以及后端就绪后的复验路径。**DAG 如何消费**:进入 `evidenceGaps[]`,closeout 报告 `Real integration: pending`,复验任务 `<task-id>-real-api-integration-verify` 由操作者显式触发。**缺失/冲突时 fail-closed**:把 Mock 证据当真实联调证据时 review 拒绝。
|
|
31
45
|
|
|
32
46
|
## 样式与设计系统映射
|
|
33
47
|
|
|
34
|
-
|
|
48
|
+
- **要写什么**:写清页面用到的主题/Token、设计系统组件与样式来源(引用具体来源路径),自定义样式与复用样式的边界。
|
|
49
|
+
- **DAG 如何消费**:`frontend-plan-pi` 据它安排样式实现,`frontend-design-review-pi` 据它核对是否与设计系统冲突。
|
|
50
|
+
- **缺失/冲突时 fail-closed**:样式来源缺失、Token/组件冲突、或自定义样式会破坏设计系统时阻塞。
|
|
35
51
|
|
|
36
52
|
## 响应式范围
|
|
37
53
|
|
|
38
|
-
|
|
54
|
+
- **要写什么**:写清支持的视口范围(desktop/mobile/tablet)与断点、每个断点下的布局差异。
|
|
55
|
+
- **DAG 如何消费**:进入 contract 的 target runtime environment,`frontend-plan-pi` 据它决定响应式策略。
|
|
56
|
+
- **缺失/冲突时 fail-closed**:视口范围与需求运行环境不一致、或断点未覆盖声明环境时阻塞。
|
|
39
57
|
|
|
40
58
|
## 风险与非目标
|
|
41
59
|
|
|
42
|
-
|
|
60
|
+
- **要写什么**:写清设计上的已知风险、依赖缺口与明确不做/排除的设计范围。
|
|
61
|
+
- **DAG 如何消费**:进入 `evidenceGaps[]` 与 Non-goals,`frontend-review-pi`/closeout 据它保留风险与后续项。
|
|
62
|
+
- **缺失/冲突时 fail-closed**:范围与需求非目标冲突、或风险被隐藏时阻塞。
|
|
63
|
+
|
|
64
|
+
## OpenSpec 引用块(openspec-citations)
|
|
65
|
+
|
|
66
|
+
- **要写什么**:在 fenced `json` 契约块之后追加**恰好一个** fenced `openspec-citations` 块,每行一个 JSON `{"path","section","line"}`(`section` 可空串、`line` 为 int 或 null),逐条列出本计划实际读取并应用的每个 openspec 规范文件。
|
|
67
|
+
- **DAG 如何消费**:`frontend-prewrite-gate-shell` 按 fence 语言标签解析该块,并与生效 plan/review 节点的成功 read 事件核验;`cited` 模式下候选未引用 → `openspec-not-cited`,引用无 read 背书 → `openspec-citation-not-read`,块缺失/不可解析 → `openspec-citation-block-unparseable`。
|
|
68
|
+
- **缺失/冲突时 fail-closed**:候选非空而引用块缺失/不可解析、候选未引用、或引用未真实读取时,prewrite gate 以 `retryable-invalid` fail-closed;不要引用未读取的路径,也不要遗漏已读取的 openspec 文件。
|
|
@@ -16,13 +16,20 @@
|
|
|
16
16
|
"mockApi": { "type": "object", "additionalProperties": false, "required": ["strategy", "productionDefaultOff", "activation", "endpoints"], "properties": { "strategy": { "enum": ["native", "browser-intercept", "request-adapter", "not-needed"] }, "productionDefaultOff": { "const": true }, "activation": { "type": ["string", "null"], "minLength": 1 }, "endpoints": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["method", "path"], "properties": { "method": { "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"] }, "path": { "type": "string", "pattern": "^/" }, "fixture": { "anyOf": [{ "$ref": "#/$defs/path" }, { "type": "null" }] }, "consumer": { "anyOf": [{ "$ref": "#/$defs/path" }, { "type": "null" }] } } } } } },
|
|
17
17
|
"designEvidence": { "type": "object", "additionalProperties": false, "required": ["source", "paths", "conflicts"], "properties": { "source": { "type": "string", "minLength": 1 }, "paths": { "type": "array", "items": { "$ref": "#/$defs/path" } }, "conflicts": { "type": "array", "items": { "type": "string", "minLength": 1 } } } },
|
|
18
18
|
"verificationTargets": { "type": "array", "minItems": 1, "items": { "type": "object", "additionalProperties": false, "required": ["id", "type", "commandLabel", "file", "requirementIds", "uiStates"], "properties": { "id": { "type": "string", "minLength": 1 }, "type": { "enum": ["static", "unit", "component", "integration", "mock"] }, "commandLabel": { "type": "string", "minLength": 1 }, "file": { "$ref": "#/$defs/path" }, "symbol": { "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }] }, "requirementIds": { "type": "array", "items": { "$ref": "#/$defs/requirementId" } }, "uiStates": { "type": "array", "items": { "type": "string", "minLength": 1 } } } } },
|
|
19
|
-
"evidenceGaps": { "type": "array", "items": { "$ref": "#/$defs/gap" } }
|
|
19
|
+
"evidenceGaps": { "type": "array", "items": { "$ref": "#/$defs/gap" } },
|
|
20
|
+
"implementationSteps": { "type": "array", "items": { "type": "string", "minLength": 1 } },
|
|
21
|
+
"stylingStrategy": { "type": "string", "minLength": 1 },
|
|
22
|
+
"uiComponentChoices": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": ["purpose", "component", "decision", "rationale"], "properties": { "purpose": { "type": "string", "minLength": 1 }, "component": { "type": "string", "minLength": 1 }, "decision": { "enum": ["specified", "reuse-existing", "new"] }, "specReference": { "anyOf": [{ "$ref": "#/$defs/uiComponentSpecReference" }, { "type": "null" }] }, "rationale": { "type": "string", "minLength": 1 } }, "allOf": [ { "if": { "properties": { "decision": { "const": "specified" } }, "required": ["decision"] }, "then": { "required": ["specReference"], "properties": { "specReference": { "$ref": "#/$defs/uiComponentSpecReference" } } } }, { "if": { "properties": { "decision": { "const": "new" } }, "required": ["decision"] }, "then": { "properties": { "specReference": { "type": "null" } } } } ] } },
|
|
23
|
+
"dependencyPolicy": { "type": "string", "minLength": 1 },
|
|
24
|
+
"residualRisks": { "type": "array", "items": { "type": "string", "minLength": 1 } },
|
|
25
|
+
"realIntegrationGap": { "type": "string", "minLength": 1 }
|
|
20
26
|
},
|
|
21
27
|
"allOf": [{ "if": { "properties": { "mockApi": { "properties": { "strategy": { "enum": ["native", "browser-intercept", "request-adapter"] } } } } }, "then": { "properties": { "mockApi": { "properties": { "endpoints": { "minItems": 1, "items": { "required": ["method", "path", "fixture", "consumer"] } } } } } } }],
|
|
22
28
|
"$defs": {
|
|
23
29
|
"path": { "type": "string", "minLength": 1, "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))(?!.*\\\\).+$" },
|
|
24
30
|
"requirementId": { "type": "string", "pattern": "^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$" },
|
|
25
31
|
"gap": { "type": "object", "additionalProperties": false, "required": ["description", "blocking"], "properties": { "requirementId": { "$ref": "#/$defs/requirementId" }, "description": { "type": "string", "minLength": 1 }, "blocking": { "type": "boolean" } } },
|
|
26
|
-
"sourceBinding": { "type": "object", "additionalProperties": false, "required": ["taskId", "requirementPath", "requirementSha256", "referencePaths", "requirementIds"], "properties": { "taskId": { "type": "string", "minLength": 1 }, "requirementPath": { "$ref": "#/$defs/path" }, "requirementSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" }, "referencePaths": { "type": "array", "items": { "$ref": "#/$defs/path" } }, "requirementIds": { "type": "array", "items": { "$ref": "#/$defs/requirementId" } } } }
|
|
32
|
+
"sourceBinding": { "type": "object", "additionalProperties": false, "required": ["taskId", "requirementPath", "requirementSha256", "referencePaths", "requirementIds"], "properties": { "taskId": { "type": "string", "minLength": 1 }, "requirementPath": { "$ref": "#/$defs/path" }, "requirementSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" }, "referencePaths": { "type": "array", "items": { "$ref": "#/$defs/path" } }, "requirementIds": { "type": "array", "items": { "$ref": "#/$defs/requirementId" } } } },
|
|
33
|
+
"uiComponentSpecReference": { "type": "object", "additionalProperties": false, "required": ["path", "section"], "properties": { "path": { "$ref": "#/$defs/path" }, "section": { "type": "string" }, "line": { "anyOf": [{ "type": "integer" }, { "type": "null" }] } } }
|
|
27
34
|
}
|
|
28
35
|
}
|
|
@@ -1,35 +1,62 @@
|
|
|
1
1
|
# 前端任务执行约束模板
|
|
2
2
|
|
|
3
|
+
本模板只承载**通用探索协议 + 结果 schema + 缺失/冲突时 fail-closed 规则**,不包含任何具体业务组件名、项目专属路径或项目专属命令。项目事实由生成期能力探测(`openspec/schemas/**`、`openspec/project-specs/**`、`ai_workspace/**`)产出并经语义归类注入任务契约。
|
|
4
|
+
|
|
3
5
|
## 技术约束
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
- **要探索什么**:项目 `package.json` 的直接依赖与 scripts、框架/路由/状态/数据获取/组件库/样式/测试/构建相关配置与引导文件,以及 `openspec/schemas/**` 与 `openspec/project-specs/rules/**` 中声明技术边界的规范。
|
|
8
|
+
- **结果必须包含**:每一条适用技术规则的源路径、命中章节与行号;被排除依赖的明确理由;与既有技术栈一致的运行时要求(编译、构建、入口、环境变量)。
|
|
9
|
+
- **缺失/冲突时如何 fail-closed**:无法找到技术规范来源时,显式标注 `source: unavailable` 并只采用任务源与现有代码的可验证事实;发现冲突规则时不臆造取舍,先记录冲突并返回 `request-revision`/blocked,而不是静默采用邻近代码惯例。
|
|
6
10
|
|
|
7
11
|
## 代码风格约束
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
- **要探索什么**:`openspec/project-specs/templates/**`(代码模板)与 `openspec/project-specs/rules/**` 中组件/钩子/工具等代码风格规则,以及现有组件、主题/Token、stories 与测试作为较弱 repository fallback。
|
|
14
|
+
- **结果必须包含**:命中模板/规则的源路径、章节与行号;命名、目录、导出、类型、样式组织等可执行约定;冲突字段逐条列出。
|
|
15
|
+
- **缺失/冲突时如何 fail-closed**:没有模板/规则命中时明确说明并记录 `repository fallback` 证据;冲突未解决时不得进入实现,必须返回阻塞信息。
|
|
10
16
|
|
|
11
17
|
## 设计约束
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
- **要探索什么**:`openspec/project-specs/ui/**`(主题/组件目录)与 `openspec/project-specs/rules/**` 中页面/组件拆分、交互、UI 状态与响应式相关规则。
|
|
20
|
+
- **结果必须包含**:命中设计规范/组件目录的源路径、章节与行号;组件拆分与复用边界、样式与设计系统映射、UI 状态与响应式范围。
|
|
21
|
+
- **缺失/冲突时如何 fail-closed**:设计规范缺失时明确记录来源为 `unavailable` 并保留为已知风险;组件/主题冲突时必须阻断,不能按邻近代码自行决定。
|
|
14
22
|
|
|
15
23
|
## 验证约束
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
- **要探索什么**:项目 `package.json` scripts 中真实存在的 lint/typecheck/build/test 命令,以及任务源 `需求.md`/`执行约束.md` 中声明的验证命令。
|
|
26
|
+
- **结果必须包含**:命令的 label、原始命令文本与来源(scripts/任务源);每条命令可自终止(启动→断言→退出)的说明。
|
|
27
|
+
- **缺失/冲突时如何 fail-closed**:只在确定命令真实存在时写入冻结命令集;不发明 shell 命令。命令不可执行或漂移时 fail closed,不宣称验证通过。
|
|
18
28
|
|
|
19
29
|
## Mock 约束(数据型任务)
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
- `
|
|
31
|
+
本段决定生成期 `allowedMockStrategies` 与冻结的 Mock 验证命令。只有当生成期**能证明任务确实包含 Mock 且有确定性验证命令**时,DAG 才会放行 `native` / `browser-intercept` / `request-adapter`;否则自动收敛为 `not-needed`,design review 若按项目规范改选 `native`,会在 prewrite 被 `mock-strategy-outside-allowed` 拦截、`implement` 被跳过。
|
|
32
|
+
|
|
33
|
+
- **何时必须填**:项目规范(`openspec/project-specs/**`、`ai_workspace/**`、mock 规则或类似 DEC-* 决策)要求/建议 Mock;或需求涉及远端接口且后端未就绪。规范要求 Mock 时,优先 `task.json.frontendMock.policy: "required"`。
|
|
34
|
+
- **命令要探索、不要硬编码**:到项目 `package.json` scripts 里找实际存在的 Mock 相关脚本(如 `mock`、`mock:*`、`dev:mock`,或名字含 mock 的脚本),结合既有 service root 的 handler/fixture/bootstrap 启动方式,确定一条**真实存在、确定、可自终止**(启动→断言→退出 0)的验证命令。常驻 dev server 必须包装成自终止脚本(start→assert→stop),否则 verify shell 会超时 fail-closed。
|
|
35
|
+
- **写入 task.json**:把探索到的命令原样写进 `frontendMock.verifyCommands`(`label` 唯一、`command` 与项目脚本一致)。`label` 会进入冻结命令集,implementation contract 的 `verificationTarget.commandLabel` 必须逐字引用它。
|
|
36
|
+
- **命令来源白名单**:只允许项目 `package.json` 已有脚本、Mock capability seed、或本段声明的 `verifyCommands`;plan/design 阶段不能发明 shell 命令。
|
|
37
|
+
- **Mock/API/schema 规范路径**:到 `openspec/project-specs/**`(rules 中的 mock/api 规则、templates 中的接口模板)与 `ai_workspace/**` 检索;结果必须列出源路径、章节与行号。缺失或冲突时 fail closed,不自行发明接口契约。
|
|
38
|
+
- **既有 Mock service root、handler/fixture/bootstrap**:检索项目现有 Mock 服务根目录、handler、fixture 与 bootstrap 启动方式;结果必须列出发现路径,未发现时明确记录 `unavailable`。
|
|
39
|
+
- **既有 browser/e2e interception 或 request adapter/DI seam**:检索项目现有浏览器/e2e 拦截或请求适配层/依赖注入 seam;结果必须列出发现路径或明确记录缺失。
|
|
40
|
+
- **production 禁用边界**:结果必须说明真实请求为默认路径、Mock 仅通过显式测试/开发开关启用的具体边界;无法证明生产默认关闭时返回 `blocked`。
|
|
41
|
+
- **真实请求默认路径与 Mock 显式启用方式**:结果必须写出真实请求默认路径与 Mock 显式启用方式(如环境变量/构建开关),不得通过注释真实请求或在生产组件内硬编码假数据实现。
|
|
42
|
+
- **`task.json.frontendMock.policy`**:`auto | required | disabled`(规范强制 Mock 用 `required`)
|
|
43
|
+
- **被拦截时怎么修**:`mock-strategy-outside-allowed` / `no authorized Mock verification commands` 是**生成期契约问题,不是 plan 问题**——补 `frontendMock.verifyCommands`(或 `policy: "required"` + 命令)后**重新生成 DAG** 再跑,plan-revision 无法修复它。
|
|
28
44
|
|
|
29
45
|
## allowedPaths
|
|
30
46
|
|
|
31
|
-
|
|
47
|
+
- **要探索什么**:任务源 `需求.md`/`执行约束.md`/`task.json` 中声明的写入边界,以及本次交付实际涉及的文件/目录。
|
|
48
|
+
- **结果必须包含**:与 `task.json.allowedPaths` 一致且窄化的路径列表;任何越界路径都必须在 plan 中记为阻断性 scope conflict。
|
|
49
|
+
- **缺失/冲突时如何 fail-closed**:缺失允许路径或发现计划目标在允许路径之外时,不得擅自扩大边界,返回 blocked。
|
|
32
50
|
|
|
33
51
|
## forbiddenPaths
|
|
34
52
|
|
|
35
|
-
|
|
53
|
+
- **要探索什么**:任务源与 `task.json` 中声明的禁止写入路径(运行时产物、依赖目录、构建输出、私有路径等)。
|
|
54
|
+
- **结果必须包含**:禁止写入的路径清单,以及实现/验证步骤不触碰这些路径的确认。
|
|
55
|
+
- **缺失/冲突时如何 fail-closed**:任何写入落入禁止路径立即阻断;禁止路径语义不变,不得新增读取权限字段或扩大写入边界。
|
|
56
|
+
|
|
57
|
+
## OpenSpec 必读与引用核验(frontendOpenspec)
|
|
58
|
+
|
|
59
|
+
- **要探索什么**:`task.json.frontendOpenspec.requiredReadPaths`(契约显式声明的必读 openspec 路径)与任务源 `需求.md`/`执行约束.md` 中显式引用的 openspec 路径。
|
|
60
|
+
- **结果必须包含**:cited 模式候选集 = `requiredReadPaths` ∪ 任务源显式引用(去重排序);每个候选在 plan 的 fenced `openspec-citations` 引用块中逐条出现(每行一个 JSON `{"path","section","line"}`),且每条引用都能在真实 read 事件中背书。
|
|
61
|
+
- **缺失/冲突时如何 fail-closed**:候选非空而引用块缺失/不可解析 → `openspec-citation-block-unparseable`;候选未引用 → `openspec-not-cited`;引用存在但无成功 read 事件 → `openspec-citation-not-read`。三个 code 均为 `retryable-invalid`。候选为空时不强制并写 advisory(可声明 `requiredReadPaths` 增强)。`scan-strict` 模式保留全量必读语义与 `openspec-not-read`。
|
|
62
|
+
- **`task.json.frontendOpenspec`**:`policy` 为 `cited | scan-strict`(缺省 `cited`);`requiredReadPaths` 为 repo 相对路径数组,经 `isOpenspecSpecFilePath` 校验,非法路径生成期确定性失败。
|
|
@@ -1,70 +1,104 @@
|
|
|
1
1
|
# 前端任务需求模板
|
|
2
2
|
|
|
3
|
+
本模板只承载**通用编写协议 + 三要素 + fail-closed 规则**,不包含任何具体业务组件名、项目专属路径或项目专属命令。填写时每一节都要给出「**要写什么** / **DAG 如何消费** / **缺失/冲突时 fail-closed**」三要素;缺节、空占位或与验收标准冲突时必须阻塞,不得由模型猜测补写。
|
|
4
|
+
|
|
3
5
|
## 用户目标
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
- **要写什么**:以用户视角写清本次要交付的前端行为与用户价值——用户能完成什么、为什么需要它、成功后的可观察结果。必须可验证(可被界面状态或交互断言覆盖),不得只写技术动作或孤立关键词。
|
|
8
|
+
- **DAG 如何消费**:`frontend-contract-pi` 把它映射为 Scope 与 `requirements[].expectedOutcome`,`frontend-plan-pi` 据它安排实现步骤并映射验收标准。
|
|
9
|
+
- **缺失/冲突时 fail-closed**:缺失用户目标,或用户目标与验收标准互相矛盾时阻塞(`request-revision`/blocked),不得猜测补写或改写为技术实现描述。
|
|
6
10
|
|
|
7
11
|
## 目标页面/组件/路由
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
- **要写什么**:明确本次交付涉及的目标路由、页面入口与组件范围,逐条列出路径/名称与职责边界;相关但超出范围的页面写清「不做」。
|
|
14
|
+
- **DAG 如何消费**:对齐 `docs/runtime/frontend-implementation-workflow.md` §「需求.md 应包含」的「目标路由/页面/组件」,`frontend-contract-pi` 据它确定 `targets.files` / `targets.routes`,`frontend-plan-pi` 据它划定 writeSet 与实现步骤。
|
|
15
|
+
- **缺失/冲突时 fail-closed**:无法确定目标路由/页面/组件,或范围与验收标准不一致时阻塞;只写泛化名词(如「页面」「组件」)而不给具体范围视为缺失。
|
|
10
16
|
|
|
11
17
|
## 用户流程
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
- **要写什么**:写清端到端用户路径,从入口到结果,逐步列出触发动作、顺序与关键分支;涉及多页面/多状态时把每条路径分开写。
|
|
20
|
+
- **DAG 如何消费**:`frontend-contract-pi` 据它抽取 `interactions[]`(每条带 `trigger` 与 `expectedBehavior`),`frontend-plan-pi` 据它安排组件/状态/交互实现步骤。
|
|
21
|
+
- **缺失/冲突时 fail-closed**:缺少可执行的端到端路径、流程与验收标准冲突或关键分支未写时阻塞,不得只写「用户进入页面即可」。
|
|
14
22
|
|
|
15
23
|
## 必须状态
|
|
16
24
|
|
|
25
|
+
每个适用状态必须写清该状态的触发条件与预期界面表现;不适用状态必须写 `N/A` 并给出非空理由(对齐 runtime 的 `uiStates` / N/A state 规则)。
|
|
26
|
+
|
|
17
27
|
### loading
|
|
18
28
|
|
|
19
|
-
|
|
29
|
+
- **要写什么**:loading 状态出现与结束的触发条件、展示内容(骨架/文案/禁用交互)与超时/失败转移。
|
|
30
|
+
- **DAG 如何消费**:进入 implementation contract 的 `uiStates[]`(`name: "loading"`),`frontend-implement-pi` 按它实现,verify 用它的 `verificationTargetIds` 断言。
|
|
31
|
+
- **缺失/冲突时 fail-closed**:适用但缺失预期行为时阻塞;不适用必须写 `N/A` 并给非空理由,只写 `N/A` 不带理由同样阻塞。
|
|
20
32
|
|
|
21
33
|
### empty
|
|
22
34
|
|
|
23
|
-
|
|
35
|
+
- **要写什么**:无数据/无结果状态的触发条件与展示(空态文案/引导动作/是否可刷新)。
|
|
36
|
+
- **DAG 如何消费**:进入 `uiStates[]`(`name: "empty"`),verify 断言空态展示与引导。
|
|
37
|
+
- **缺失/冲突时 fail-closed**:适用但缺失预期行为、或与 success/error 展示冲突时阻塞;不适用写 `N/A` + 非空理由。
|
|
24
38
|
|
|
25
39
|
### error
|
|
26
40
|
|
|
27
|
-
|
|
41
|
+
- **要写什么**:失败状态(请求失败/校验失败/权限失败)的触发条件、错误展示与可恢复动作。
|
|
42
|
+
- **DAG 如何消费**:进入 `uiStates[]`(`name: "error"`),verify 断言错误展示与重试/回退行为。
|
|
43
|
+
- **缺失/冲突时 fail-closed**:适用但缺失错误预期、或错误态会覆盖用户数据时阻塞;不适用写 `N/A` + 非空理由。
|
|
28
44
|
|
|
29
45
|
### success
|
|
30
46
|
|
|
31
|
-
|
|
47
|
+
- **要写什么**:成功状态的触发条件与展示(数据呈现/确认反馈/后续入口)。
|
|
48
|
+
- **DAG 如何消费**:进入 `uiStates[]`(`name: "success"`),verify 断言成功展示与验收标准对应。
|
|
49
|
+
- **缺失/冲突时 fail-closed**:成功态与验收标准不一致、或缺失成功结果时阻塞;不适用写 `N/A` + 非空理由。
|
|
32
50
|
|
|
33
51
|
### disabled
|
|
34
52
|
|
|
35
|
-
|
|
53
|
+
- **要写什么**:禁用/不可交互状态的触发条件(权限/前置未满足/进行中)与视觉/交互表现。
|
|
54
|
+
- **DAG 如何消费**:进入 `uiStates[]`(`name: "disabled"`),verify 断言禁用态不会被误触。
|
|
55
|
+
- **缺失/冲突时 fail-closed**:适用但缺失禁用条件、或禁用态与交互要求冲突时阻塞;不适用写 `N/A` + 非空理由。
|
|
36
56
|
|
|
37
57
|
## 目标运行环境
|
|
38
58
|
|
|
59
|
+
每个环境必须声明适用性;不适用环境写 `N/A` 并给非空理由。
|
|
60
|
+
|
|
39
61
|
### desktop
|
|
40
62
|
|
|
41
|
-
|
|
63
|
+
- **要写什么**:desktop 视口下的适用性声明与关键布局/交互差异(断点、栅格、悬停等)。
|
|
64
|
+
- **DAG 如何消费**:进入 contract 的 target runtime environment,`frontend-plan-pi` 据它决定响应式范围与样式策略。
|
|
65
|
+
- **缺失/冲突时 fail-closed**:适用但缺失断点/布局说明,或与响应式要求冲突时阻塞;不适用写 `N/A` + 非空理由。
|
|
42
66
|
|
|
43
67
|
### mobile
|
|
44
68
|
|
|
45
|
-
|
|
69
|
+
- **要写什么**:mobile 视口下的适用性声明与触控/布局/安全区差异。
|
|
70
|
+
- **DAG 如何消费**:进入 target runtime environment,`frontend-plan-pi` 据它决定响应式范围与组件策略。
|
|
71
|
+
- **缺失/冲突时 fail-closed**:适用但缺失移动端表现,或与交互要求冲突时阻塞;不适用写 `N/A` + 非空理由。
|
|
46
72
|
|
|
47
73
|
### tablet
|
|
48
74
|
|
|
49
|
-
|
|
75
|
+
- **要写什么**:tablet 视口下的适用性声明与中间断点表现。
|
|
76
|
+
- **DAG 如何消费**:进入 target runtime environment,`frontend-plan-pi` 据它决定响应式策略。
|
|
77
|
+
- **缺失/冲突时 fail-closed**:适用但缺失 tablet 表现时阻塞;不适用写 `N/A` + 非空理由。
|
|
50
78
|
|
|
51
79
|
## 交互要求
|
|
52
80
|
|
|
53
|
-
|
|
81
|
+
- **要写什么**:逐条写清每个可交互元素的触发动作与预期行为(点击/输入/提交/滚动/键盘等),可被自动化断言覆盖。
|
|
82
|
+
- **DAG 如何消费**:对齐 §「需求.md 应包含」的「交互要求」,`frontend-contract-pi` 抽取 `interactions[]`(`trigger` + `expectedBehavior`),verify 用行为命令断言。
|
|
83
|
+
- **缺失/冲突时 fail-closed**:交互触发或预期行为缺失、与 UI 状态冲突、或不可自动化断言时阻塞,不得用「用户可正常操作」这类空话。
|
|
54
84
|
|
|
55
85
|
## 接口与 Mock 输入
|
|
56
86
|
|
|
57
|
-
-
|
|
58
|
-
- endpoint、method
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
- success/empty/error/permission
|
|
62
|
-
-
|
|
87
|
+
- **接口文档/schema — 要写什么**:引用接口文档或任务附件路径与版本,声明请求/响应 schema 来源。**DAG 如何消费**:`frontend-plan-pi` 据此选择 Mock 策略并冻结 endpoint/fixture 映射。**缺失/冲突时 fail-closed**:涉及接口但未引用文档或 schema 时阻塞,不得自行发明字段。
|
|
88
|
+
- **endpoint、method、关键请求/响应字段 — 要写什么**:逐条列出 method、path 与关键字段及含义。**DAG 如何消费**:写入 `mockApi.endpoints[]`(method/path/fixture/consumer)。**缺失/冲突时 fail-closed**:字段缺失、与接口文档冲突或路径不合法时阻塞。
|
|
89
|
+
- **是否允许依赖真实后端 — 要写什么**:明确声明真实后端当前是否可调用、是否允许生产/预览依赖真实请求。**DAG 如何消费**:决定 `mockApi.strategy` 是否可选 `not-needed`(需要真实/无远程证据)。**缺失/冲突时 fail-closed**:未声明后端就绪度却要求真实数据时阻塞,选 `not-needed` 却没有真实/无远程证据会被拦截。
|
|
90
|
+
- **是否要求离线或独立行为验证 — 要写什么**:声明是否需要离线、本地预览或自动化行为验证(或两者都要)。**DAG 如何消费**:决定生成期 `frontendMock.verifyCommands` 与冻结命令集。**缺失/冲突时 fail-closed**:要求离线/自动化却未声明确定性验证命令时,生成期收敛为 `not-needed` 或直接 blocked,plan-revision 无法修复。
|
|
91
|
+
- **success/empty/error/permission 状态 — 要写什么**:逐条声明接口各响应状态对应的 UI 表现。**DAG 如何消费**:与「必须状态」交叉校验,写入 `uiStates[]` 与 fixture 映射。**缺失/冲突时 fail-closed**:接口状态与 UI 状态不匹配或缺失 permission 分支时阻塞。
|
|
92
|
+
- **后端当前就绪状态与 Real Integration Gap — 要写什么**:写清后端是否就绪、未就绪时保留的 Real Integration Gap 与后端就绪后的复验路径。**DAG 如何消费**:进入 `evidenceGaps[]` 与 closeout 的 `Real integration: pending`。**缺失/冲突时 fail-closed**:声称已联通真实接口却无真实证据时阻塞;未就绪却把 Mock 结果写成真实联调会被 review 拒绝。
|
|
63
93
|
|
|
64
94
|
## 验收标准
|
|
65
95
|
|
|
66
|
-
|
|
96
|
+
- **要写什么**:逐条编号(如 `AC-001`)写出可独立断言的前端验收标准,每条都能映射到需求与一个 verification target(可逐条追踪)。
|
|
97
|
+
- **DAG 如何消费**:`frontend-contract-pi` 生成 `requirements[].verificationTargetIds` 映射,`frontend-plan-pi` 把每条 AC 落到有序步骤与验证命令,verify 逐条断言。
|
|
98
|
+
- **缺失/冲突时 fail-closed**:无验收标准、AC 无法映射到需求/验证目标、或 AC 与其它节冲突时阻塞,不得把「看起来能用」当验收。
|
|
67
99
|
|
|
68
100
|
## 非目标
|
|
69
101
|
|
|
70
|
-
|
|
102
|
+
- **要写什么**:明确列出本次不做的前端范围/排除项(页面、组件、交互、浏览器/视觉/无障碍检查等)。
|
|
103
|
+
- **DAG 如何消费**:`frontend-contract-pi` 记录为 Non-goals,`frontend-review-pi` 据它拒绝范围扩散。
|
|
104
|
+
- **缺失/冲突时 fail-closed**:非目标与目标/验收标准冲突、或范围被悄悄扩大时阻塞;明确不做前端时也必须写入非目标。
|