@tea-agent/loop-agent 0.42.0-next.1 → 0.42.0-next.3
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 +260 -1
- package/dist/application/dag/generate-task-dag.js +75 -19
- package/dist/application/task-lifecycle/advance.js +24 -5
- package/dist/application/task-lifecycle/observe.js +171 -17
- package/dist/application/task-lifecycle/plan-transitions.js +42 -7
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +7 -0
- package/dist/cli/program.js +5 -0
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/init.js +27 -1
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +3784 -231
- package/dist/executors/pi-executor.js +15 -4
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/shell-executor.js +942 -187
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/infrastructure/console/operation-store.js +226 -6
- package/dist/shared/dag-failure-category.js +12 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +7 -0
- package/dist/task/config-types.js +105 -5
- package/dist/task/contract/adopt.js +4 -0
- package/dist/task/contract/import-revision.js +4 -0
- package/dist/task/contract/project.js +3 -0
- package/dist/task/contract/schema.js +2 -1
- package/dist/task/frontend-project-capability.js +203 -20
- package/dist/task/runtime.js +5 -2
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +15 -9
- package/dist/task/source-prepare/prepare.js +89 -1
- package/dist/task/source-prepare/semantic-intake.js +6 -2
- package/dist/task/source-references.js +22 -1
- package/dist/worker/console/chat/chat-ui-policy.js +4 -2
- package/dist/worker/console/chat/pi-runtime.js +173 -7
- package/dist/worker/console/chat/repo-browser.js +10 -3
- package/dist/worker/console/chat/routes.js +30 -19
- package/dist/worker/console/chat/session-store.js +117 -70
- package/dist/worker/console/chat/shortcuts.js +1 -1
- package/dist/worker/console/chat/turn-process.js +111 -56
- package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/operator-actions.js +169 -7
- package/dist/worker/console/server.js +3 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DZZ8m3PO.js → abnfDiagram-N423BO3Z-CeznJ4Iw.js} +1 -1
- package/dist/worker/console/static/assets/{arc-D6PvaVd-.js → arc-B4u9u7SX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-B_OTOiI8.js → architectureDiagram-T3A2C74G-oghpT81p.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Bv6rqHBg.js → blockDiagram-VBNYF7ZC-BPJ5Kvcj.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8eHr0oz.js → c4Diagram-5PPSVZJV-CoIu69pd.js} +1 -1
- package/dist/worker/console/static/assets/channel-DmIXqJ6B.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DYwR0im2.js → chunk-2GRJ4B5K-DtgiAsSc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D2WPGqXt.js → chunk-2Q5K7J3B-YSu_EVSD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CQISJ_I7.js → chunk-5RXB4S5H-DaRO-NB6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C0o2Du1e.js → chunk-5VM5RSS4-Dh0ZKiGA.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-4f8kr-U7.js → chunk-6Q2QTUOP-DOrvbR-H.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D_OWvXzX.js → chunk-GF5L2VYU-C3b22LN2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CasdPz5X.js → chunk-JWPE2WC7-CXlqO8l6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Cj8lRrla.js → chunk-KBJHAD2P-BblTBrDu.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUvD1FSd.js → chunk-RYQCIY6F-DswqPALe.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CqLw_eqB.js → chunk-XXDRQBXY-CfTwVOlc.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-MFV5Qxb5.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-MFV5Qxb5.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-ku-WqJPx.js → cose-bilkent-JH36ORCC-bLD8jVFo.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bq_PYDhl.js → cynefin-VYW2F7L2-BvoG1fh5.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BiGZV641.js → cynefinDiagram-MW4NZA55-DEPAdJu3.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C4JRlglF.js → dagre-VZM6K2ZE-DmrNvBLy.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0dNeWYG.js → diagram-7IWD3JNH-CdwzIwvs.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cj35YvYM.js → diagram-B4RE2ZJO-CJ3hj-md.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-uzQoJ2-8.js → diagram-LBJQPF4R-C6YJT2a4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D1a-Buoz.js → diagram-Q27KOJAE-C_LPfjhX.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-XjRrLRSs.js → diagram-UB23O5K3-xvsSb5Ie.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Da_O24cW.js → ebnfDiagram-BXEA7PRR-Rxp2tKcD.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BLJ8jrYU.js → erDiagram-JOGREHBK-DRyLYmZh.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B9GrLjM0.js → flowDiagram-UKHOOZJN-TLSU_D2B.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CeJ0TqiK.js → ganttDiagram-PKOTCBZU-DGqeTvHC.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js → gitGraphDiagram-DS77QQ5N-BZxyH2wh.js} +1 -1
- package/dist/worker/console/static/assets/index-C8V8K9b4.js +451 -0
- package/dist/worker/console/static/assets/index-Cnf_KxTQ.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DB26i3d8.js → infoDiagram-6WML65LV-BsYtzx01.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BOGRyeZT.js → ishikawaDiagram-WSZJBQD7-Qr-ezGv-.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CZRoBO_6.js → journeyDiagram-NVQOT4AX-DYbB3ibo.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-qlWhJyoB.js → kanban-definition-27J2QSJJ-BHJHewZO.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CfUiDDB3.js → linear-CmPIPmCG.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BQe6fpqj.js → mermaid.core-F--C1uV0.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-zFzWHw64.js → mindmap-definition-FAOFIHXS-YTXfL1st.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DFGUqjZO.js → pegDiagram-VL7TDLO6-DdDMYXP9.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BB5i0l8Z.js → pieDiagram-7S7Q4E2Y-C3nH4KVd.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-obbVZ_X5.js → quadrantDiagram-CIZ2JOQS-kXyxjG7Y.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwdaNzc1.js → railroadDiagram-AXF67PYL-KNzbl-7w.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CkTJaugh.js → requirementDiagram-LRYGKXZP-CSwI6dG0.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BwJ-hIgq.js → sankeyDiagram-W5VNT64P-Bl1AyuXy.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BzSgmm7w.js → sequenceDiagram-SI44F4Z6-2y4EmXQa.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Bn3oj5Q1.js → sizeCapture-X5ZJPWSS-E8v8YrMg.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-dxhGtL8K.js → stateDiagram-OKZ733FA-eGGlR856.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8e4J3at.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BLpEInya.js → swimlanes-SLNWSIFB-B7g7xc07.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Do88I1Nb.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ChJGYcXy.js → timeline-definition-Z64GVDOM-DR3VCHBG.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DK-qjRer.js → vennDiagram-T6HMQDX7-LUvapZIy.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-3qeaWAg-.js → wardleyDiagram-T6FBY63Y-C7GDcaXq.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CootlyP9.js → xychartDiagram-ELKLHX3M-CbNAqoGK.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
- package/dist/worker/console/workspace-context.js +111 -1
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observe/static/dag-node-purpose.js +5 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +99 -4
- package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
- package/dist/workflows/dag/backend-test-writer-completeness.js +45 -3
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dag-retry-schema.js +138 -0
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +907 -162
- package/dist/workflows/dag/frontend-plan-render.js +2 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +255 -349
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
- package/dist/workflows/dag/frontend-recovery-run.js +33 -20
- package/dist/workflows/dag/frontend-repair.js +1 -432
- package/dist/workflows/dag/frontend-review-context.js +261 -15
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +914 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +419 -0
- package/dist/workflows/dag/frontend-shape.js +435 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +249 -24
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +285 -0
- package/dist/workflows/dag/frontend-writer-status.js +256 -0
- package/dist/workflows/dag/init-hybrid.js +777 -525
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +752 -4
- package/dist/workflows/dag/prompt.js +34 -1
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +256 -1
- package/dist/workflows/dag/rerun-plan.js +17 -6
- package/dist/workflows/dag/rerun-run.js +23 -0
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +219 -104
- package/dist/workflows/dag/runner.js +433 -126
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/types.js +239 -12
- package/dist/workflows/dag/validate.js +28 -12
- package/docs/examples/README.md +5 -0
- package/docs/init-surface.manifest.json +30 -12
- package/docs/skills/vetted-skill-registry.md +4 -2
- package/docs/templates/README.md +2 -0
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +1 -1
- package/docs/templates/backend-test-dag.json +18 -18
- package/docs/templates/frontend-implementation-contract.schema.json +4 -1
- package/docs/templates/frontend-implementation-dag.json +89 -0
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/frontend-bounded-implement/SKILL.md +15 -14
- package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
- package/skills/frontend-contract/SKILL.md +23 -0
- package/skills/frontend-contract/references/contract-protocol.md +34 -0
- package/skills/frontend-design-review/SKILL.md +22 -41
- package/skills/frontend-plan/SKILL.md +26 -0
- package/skills/frontend-plan/references/decision-contract.md +37 -0
- package/skills/frontend-plan/references/design-decisions.md +17 -0
- package/skills/frontend-review/SKILL.md +20 -15
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/frontend-scout/SKILL.md +25 -0
- package/skills/frontend-scout/references/design-evidence.md +16 -0
- package/skills/frontend-scout/references/scout-evidence.md +23 -0
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +0 -1
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +0 -451
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +0 -8
- package/skills/frontend-implementation/SKILL.md +0 -52
- package/skills/frontend-implementation/references/code-standards.md +0 -33
- package/skills/frontend-implementation/references/design-spec.md +0 -56
- package/skills/frontend-implementation/references/node-contracts.md +0 -31
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { computeFrontendSplitDigest } from "../../workflows/dag/frontend-split-orchestrator.js";
|
|
3
|
+
const idSchema = z.string().trim().min(1);
|
|
4
|
+
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
5
|
+
const positiveIntegerSchema = z.number().int().positive();
|
|
6
|
+
const pathSchema = z.string().trim().min(1);
|
|
7
|
+
export const frontendSplitTaskMaterializationRequestSchema = z.object({
|
|
8
|
+
kind: z.enum(["child", "integration"]),
|
|
9
|
+
parentOperationId: idSchema,
|
|
10
|
+
manifestDigest: digestSchema,
|
|
11
|
+
generation: positiveIntegerSchema,
|
|
12
|
+
taskId: idSchema,
|
|
13
|
+
operationId: idSchema,
|
|
14
|
+
payloadDigest: digestSchema,
|
|
15
|
+
allowedPaths: z.array(pathSchema),
|
|
16
|
+
writeSet: z.array(pathSchema),
|
|
17
|
+
verifyRefs: z.array(idSchema),
|
|
18
|
+
}).strict();
|
|
19
|
+
export const frontendSplitTaskMaterializationResultSchema = z.object({
|
|
20
|
+
idempotencyKey: digestSchema,
|
|
21
|
+
requestDigest: digestSchema,
|
|
22
|
+
taskId: idSchema,
|
|
23
|
+
operationId: idSchema,
|
|
24
|
+
}).strict();
|
|
25
|
+
function identityPayload(request) {
|
|
26
|
+
return { kind: request.kind, parentOperationId: request.parentOperationId, manifestDigest: request.manifestDigest, generation: request.generation, taskId: request.taskId, operationId: request.operationId };
|
|
27
|
+
}
|
|
28
|
+
export class FrontendSplitTaskMaterializer {
|
|
29
|
+
backend;
|
|
30
|
+
completed = new Map();
|
|
31
|
+
inFlight = new Map();
|
|
32
|
+
constructor(backend) {
|
|
33
|
+
this.backend = backend;
|
|
34
|
+
}
|
|
35
|
+
async materialize(raw) {
|
|
36
|
+
const request = frontendSplitTaskMaterializationRequestSchema.parse(raw);
|
|
37
|
+
const idempotencyKey = computeFrontendSplitDigest(identityPayload(request));
|
|
38
|
+
const requestDigest = computeFrontendSplitDigest(request);
|
|
39
|
+
const existing = this.completed.get(idempotencyKey);
|
|
40
|
+
if (existing) {
|
|
41
|
+
if (existing.requestDigest !== requestDigest)
|
|
42
|
+
throw new Error("frontend split materialization payload drift");
|
|
43
|
+
return existing;
|
|
44
|
+
}
|
|
45
|
+
const pending = this.inFlight.get(idempotencyKey);
|
|
46
|
+
if (pending) {
|
|
47
|
+
const result = await pending;
|
|
48
|
+
if (result.requestDigest !== requestDigest)
|
|
49
|
+
throw new Error("frontend split materialization payload drift");
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
const promise = this.perform(request, idempotencyKey, requestDigest);
|
|
53
|
+
this.inFlight.set(idempotencyKey, promise);
|
|
54
|
+
try {
|
|
55
|
+
const result = await promise;
|
|
56
|
+
this.completed.set(idempotencyKey, result);
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
this.inFlight.delete(idempotencyKey);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async perform(request, idempotencyKey, requestDigest) {
|
|
64
|
+
const task = await this.backend.ensureTask({ idempotencyKey, taskId: request.taskId, payloadDigest: requestDigest, request });
|
|
65
|
+
if (!task.complete || task.taskId !== request.taskId)
|
|
66
|
+
throw new Error("frontend split task materialization incomplete or drifted");
|
|
67
|
+
const operation = await this.backend.ensureOperation({ idempotencyKey, taskId: request.taskId, operationId: request.operationId, payloadDigest: requestDigest, request });
|
|
68
|
+
if (!operation.complete || operation.taskId !== request.taskId || operation.operationId !== request.operationId)
|
|
69
|
+
throw new Error("frontend split operation materialization incomplete or drifted");
|
|
70
|
+
return frontendSplitTaskMaterializationResultSchema.parse({ idempotencyKey, requestDigest, taskId: request.taskId, operationId: request.operationId });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -39,20 +39,25 @@ const NODE_PURPOSE_BY_ID = {
|
|
|
39
39
|
"closeout-summary-pi": "把验证与交付事实写成简短交接摘要。",
|
|
40
40
|
|
|
41
41
|
"frontend-contract-pi": "把前端需求整理成可追踪契约:页面、状态、验收与边界。",
|
|
42
|
+
"frontend-contract-import-shell": "导入并固化前端实现契约,供后续设计、写入与验证节点使用。",
|
|
42
43
|
"frontend-scout-pi": "只读侦察前端结构、组件与接口用法,不改文件。",
|
|
43
44
|
"frontend-plan-pi": "选定实现与 Mock/API 策略,输出可执行的前端实现契约补丁。",
|
|
45
|
+
"frontend-design-policy-shell": "确定前端设计与 Mock/API 策略,冻结后续实现必须遵守的规则。",
|
|
44
46
|
"frontend-design-review-pi": "审查前端计划是否可写:通过则沿用原计划,否则要求修订。",
|
|
45
47
|
"frontend-plan-revision-pi": "按设计审查意见修订前端契约,不扩大范围。",
|
|
46
48
|
"frontend-final-design-review-pi": "复核修订后的前端计划,只有通过才能授权写入。",
|
|
47
49
|
"frontend-prewrite-gate-shell": "写入前门禁:核对契约、覆盖与 Mock 策略,通过才允许改前端。",
|
|
50
|
+
"frontend-writer-admission-shell": "核对契约、设计策略与 writeSet,满足准入条件后才允许 writer 写入。",
|
|
48
51
|
"frontend-lint-baseline-shell": "记录写入前的 lint 基线,供实现后对比,不阻断写入。",
|
|
49
52
|
"frontend-implement-pi": "按已批准契约改前端代码、状态与交互,留在 writeSet 内。",
|
|
50
53
|
"frontend-verify-assess-shell": "跑静态/行为/Mock 验证并评估能否有界修复。",
|
|
54
|
+
"frontend-verify-shell": "执行冻结的前端验证命令,产出可复查的通过或失败证据。",
|
|
51
55
|
"frontend-repair-pi": "只修评估为可修复的前端失败,不重开需求。",
|
|
52
56
|
"frontend-reverify-shell": "修复后用同一套冻结命令复验。",
|
|
53
57
|
"frontend-review-context-shell": "打包真实 diff 与有效验证证据,供审查节点使用。",
|
|
54
58
|
"frontend-review-pi": "对照契约审查前端实现与证据,给出通过或需修订。",
|
|
55
59
|
"frontend-review-gate-shell": "前端审查门禁:结论为通过才进入收口。",
|
|
60
|
+
"frontend-closeout-shell": "汇总实现、验证与遗留事项,形成确定性的前端收口事实。",
|
|
56
61
|
"frontend-closeout-pi": "汇总前端交付状态(含真实联调是否仍待完成)。",
|
|
57
62
|
"frontend-mock-assess-pi": "评估项目是否具备授权的 Mock 验证能力。",
|
|
58
63
|
"frontend-mock-contract-gate-shell": "Mock 契约门禁:策略与验证命令不一致则阻断。",
|
|
@@ -668,6 +668,92 @@ function automationLabelBody(body, labels) {
|
|
|
668
668
|
const match = new RegExp(`^\\s*[-*+]\\s*(?:${escaped})\\s*[::]\\s*(.*)$`, "mi").exec(automation);
|
|
669
669
|
return match?.[1]?.replaceAll("`", "").trim() ?? "";
|
|
670
670
|
}
|
|
671
|
+
const BINDING_NEGATION_OR_AMBIGUITY = /(?:不是|并非|非变体|非断言|非横切|可能|待定|或者|\b(?:TBD|unknown)\b)/i;
|
|
672
|
+
function explicitBindingModeFromLine(line) {
|
|
673
|
+
if (BINDING_NEGATION_OR_AMBIGUITY.test(line))
|
|
674
|
+
return undefined;
|
|
675
|
+
const modes = TEST_POINT_BINDING_MODES.filter((mode) => AUTOMATION_BINDING_LABELS[mode].some((label) => line.includes(label)));
|
|
676
|
+
return modes.length === 1 ? modes[0] : undefined;
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Canonicalize only explicit, unambiguous Test Point classification marks.
|
|
680
|
+
* The model occasionally writes `TP-X(变体测试点)`, `[场景断言测试点] TP-X`
|
|
681
|
+
* or a pipe-delimited equivalent. Those forms carry deterministic semantics,
|
|
682
|
+
* but are not the machine contract consumed by automationLabelBody(). Keep the
|
|
683
|
+
* original prose and materialize the same binding under `### 自动化映射` as the
|
|
684
|
+
* canonical single-line labels. Never infer a mode from ordinary prose.
|
|
685
|
+
*/
|
|
686
|
+
export function canonicalizeBackendTestMarkdownTestPointBindings(markdown) {
|
|
687
|
+
const headings = [...markdown.matchAll(CASE_HEADING)];
|
|
688
|
+
let output = "";
|
|
689
|
+
let cursor = 0;
|
|
690
|
+
const unsafeTestPoints = [];
|
|
691
|
+
const canonicalizedTestPoints = [];
|
|
692
|
+
for (let index = 0; index < headings.length; index += 1) {
|
|
693
|
+
const heading = headings[index];
|
|
694
|
+
const start = heading.index;
|
|
695
|
+
const end = headings[index + 1]?.index ?? markdown.length;
|
|
696
|
+
const body = markdown.slice(start, end);
|
|
697
|
+
output += markdown.slice(cursor, start);
|
|
698
|
+
cursor = end;
|
|
699
|
+
const declared = listTokens(body, CASE_SECTION_ALIASES.testPoints, TEST_POINT);
|
|
700
|
+
const current = testPointBindingFacts(body, declared);
|
|
701
|
+
const currentByMode = new Map(TEST_POINT_BINDING_MODES.map((mode) => [
|
|
702
|
+
mode,
|
|
703
|
+
current.testPointBindings.filter((item) => item.mode === mode).map((item) => item.testPoint),
|
|
704
|
+
]));
|
|
705
|
+
const unresolved = new Set(current.unclassifiedTestPoints);
|
|
706
|
+
for (const line of body.split(/\r?\n/)) {
|
|
707
|
+
const mode = explicitBindingModeFromLine(line);
|
|
708
|
+
if (!mode)
|
|
709
|
+
continue;
|
|
710
|
+
const tokens = orderedUnique(line.match(/\bTP-[A-Z0-9]+(?:-[A-Z0-9]+)*\b/g) ?? []);
|
|
711
|
+
for (const token of tokens) {
|
|
712
|
+
if (!unresolved.has(token))
|
|
713
|
+
continue;
|
|
714
|
+
const existingModes = TEST_POINT_BINDING_MODES.filter((candidate) => (currentByMode.get(candidate) ?? []).includes(token));
|
|
715
|
+
if (existingModes.length > 0)
|
|
716
|
+
continue;
|
|
717
|
+
currentByMode.set(mode, orderedUnique([...(currentByMode.get(mode) ?? []), token]));
|
|
718
|
+
unresolved.delete(token);
|
|
719
|
+
canonicalizedTestPoints.push(token);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
unsafeTestPoints.push(...unresolved);
|
|
723
|
+
if (unresolved.size === current.unclassifiedTestPoints.length) {
|
|
724
|
+
output += body;
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
const automationMarker = /^###\s+(?:自动化映射|Automation Mapping)\s*$/mi.exec(body);
|
|
728
|
+
if (!automationMarker) {
|
|
729
|
+
output += body;
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
const automationStart = automationMarker.index + automationMarker[0].length;
|
|
733
|
+
const rest = body.slice(automationStart);
|
|
734
|
+
const nextSection = /^###\s+/m.exec(rest);
|
|
735
|
+
const automationEnd = automationStart + (nextSection?.index ?? rest.length);
|
|
736
|
+
let automation = body.slice(automationStart, automationEnd);
|
|
737
|
+
for (const mode of TEST_POINT_BINDING_MODES) {
|
|
738
|
+
const labels = AUTOMATION_BINDING_LABELS[mode];
|
|
739
|
+
const escaped = labels.map((label) => label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
740
|
+
const canonicalLine = `- ${AUTOMATION_BINDING_LABELS[mode][0]}:${(currentByMode.get(mode) ?? []).join("; ") || "无"}`;
|
|
741
|
+
const linePattern = new RegExp(`^\\s*[-*+]\\s*(?:${escaped})\\s*[::].*$`, "mi");
|
|
742
|
+
if (linePattern.test(automation))
|
|
743
|
+
automation = automation.replace(linePattern, canonicalLine);
|
|
744
|
+
else
|
|
745
|
+
automation = `${automation.replace(/\s*$/, "")}\n${canonicalLine}\n`;
|
|
746
|
+
}
|
|
747
|
+
output += body.slice(0, automationStart) + automation + body.slice(automationEnd);
|
|
748
|
+
}
|
|
749
|
+
output += markdown.slice(cursor);
|
|
750
|
+
return {
|
|
751
|
+
markdown: output,
|
|
752
|
+
changed: output !== markdown,
|
|
753
|
+
unsafeTestPoints: orderedUnique(unsafeTestPoints),
|
|
754
|
+
canonicalizedTestPoints: orderedUnique(canonicalizedTestPoints),
|
|
755
|
+
};
|
|
756
|
+
}
|
|
671
757
|
export function inspectBackendTestMarkdownTestPointBindings(markdown) {
|
|
672
758
|
const headings = [...markdown.matchAll(CASE_HEADING)];
|
|
673
759
|
return headings.map((heading, index) => {
|
|
@@ -1312,8 +1398,16 @@ export async function analyzeBackendTestCaseCoverage(input) {
|
|
|
1312
1398
|
function normalizeModuleStem(markdownPath) {
|
|
1313
1399
|
return path.posix.basename(markdownPath).replace(/\.md$/i, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/_+/g, "_");
|
|
1314
1400
|
}
|
|
1315
|
-
function expectedScript(markdownPath, layout) {
|
|
1401
|
+
function expectedScript(markdownPath, layout, declaredScripts = []) {
|
|
1316
1402
|
const resolved = layout ?? resolveBackendTestLayout(undefined);
|
|
1403
|
+
if (declaredScripts.length === 1) {
|
|
1404
|
+
const candidate = declaredScripts[0].replaceAll("\\", "/").replace(/^\.\//, "");
|
|
1405
|
+
const prefix = resolved.scriptDir === "." ? "" : `${resolved.scriptDir}/`;
|
|
1406
|
+
const relative = prefix && candidate.startsWith(prefix) ? candidate.slice(prefix.length) : candidate;
|
|
1407
|
+
if ((!prefix || candidate.startsWith(prefix)) && !relative.includes("..") && /^test_[A-Za-z0-9_.-]+\.py$/.test(path.posix.basename(candidate))) {
|
|
1408
|
+
return candidate;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1317
1411
|
return `${resolved.scriptDir}/test_${normalizeModuleStem(markdownPath)}.py`;
|
|
1318
1412
|
}
|
|
1319
1413
|
function symbolCaseId(symbol) {
|
|
@@ -1863,7 +1957,7 @@ export async function analyzeBackendTestMarkdownPytestCorrespondence(input) {
|
|
|
1863
1957
|
const layout = input.layout ?? resolveBackendTestLayout(undefined);
|
|
1864
1958
|
const cases = await parseMarkdownCases(input.workspaceRoot, layout);
|
|
1865
1959
|
const modulePaths = orderedUnique(cases.map((item) => item.markdownPath));
|
|
1866
|
-
const scripts = orderedUnique(cases.flatMap((item) => [
|
|
1960
|
+
const scripts = orderedUnique(cases.flatMap((item) => [expectedScript(item.markdownPath, layout, item.declaredScripts)]));
|
|
1867
1961
|
const allSymbols = [];
|
|
1868
1962
|
const scriptFiles = [];
|
|
1869
1963
|
for (const script of scripts) {
|
|
@@ -1876,7 +1970,7 @@ export async function analyzeBackendTestMarkdownPytestCorrespondence(input) {
|
|
|
1876
1970
|
const findings = [];
|
|
1877
1971
|
const entries = [];
|
|
1878
1972
|
for (const testCase of cases) {
|
|
1879
|
-
const expected = expectedScript(testCase.markdownPath, layout);
|
|
1973
|
+
const expected = expectedScript(testCase.markdownPath, layout, testCase.declaredScripts);
|
|
1880
1974
|
const declared = testCase.declaredScripts[0];
|
|
1881
1975
|
const refs = allSymbols.filter((item) => item.caseIds.includes(testCase.caseId));
|
|
1882
1976
|
const actualScripts = orderedUnique(refs.map((item) => item.script));
|
|
@@ -2034,8 +2128,9 @@ export async function analyzeBackendTestMarkdownPytestCorrespondence(input) {
|
|
|
2034
2128
|
for (const [signature, owners] of duplicateSignatureGroups)
|
|
2035
2129
|
findings.push(`duplicate execution signature ${signature}: ${orderedUnique(owners).join(", ")}`);
|
|
2036
2130
|
const exactModuleCount = modulePaths.filter((modulePath) => {
|
|
2037
|
-
const expected = expectedScript(modulePath, layout);
|
|
2038
2131
|
const moduleCases = cases.filter((item) => item.markdownPath === modulePath);
|
|
2132
|
+
const moduleDeclaredScripts = orderedUnique(moduleCases.flatMap((item) => item.declaredScripts));
|
|
2133
|
+
const expected = expectedScript(modulePath, layout, moduleDeclaredScripts);
|
|
2039
2134
|
return moduleCases.every((item) => item.declaredScripts.length === 1 && item.declaredScripts[0] === expected) && scriptFiles.some((file) => path.relative(input.workspaceRoot, file).replaceAll(path.sep, "/") === expected);
|
|
2040
2135
|
}).length;
|
|
2041
2136
|
const caseEntries = entries.filter((entry) => entry.cardinality !== "0:1");
|
|
@@ -285,6 +285,7 @@ export async function resolveBackendTestManifestModules(workspaceRoot, layout) {
|
|
|
285
285
|
moduleIndexHeadingCount,
|
|
286
286
|
declaredStems: [],
|
|
287
287
|
declaredMdFiles: [],
|
|
288
|
+
declaredScriptByMarkdown: {},
|
|
288
289
|
declaredMissing: [],
|
|
289
290
|
unmanifested: [],
|
|
290
291
|
});
|
|
@@ -306,6 +307,15 @@ export async function resolveBackendTestManifestModules(workspaceRoot, layout) {
|
|
|
306
307
|
return invalid("duplicate", moduleIndexHeadingCount);
|
|
307
308
|
}
|
|
308
309
|
const declaredStems = extractDeclaredModuleIndexFilenames(readme);
|
|
310
|
+
const declaredScriptByMarkdown = {};
|
|
311
|
+
const mdPrefix = escapeRegExp(`${resolved.markdownDir}/`);
|
|
312
|
+
const scriptPrefix = escapeRegExp(`${resolved.scriptDir}/`);
|
|
313
|
+
for (const line of readme.split(/\r?\n/)) {
|
|
314
|
+
const markdown = new RegExp(`(${mdPrefix}[A-Za-z0-9_.-]+\\.md)`).exec(line)?.[1];
|
|
315
|
+
const pytest = new RegExp(`(${scriptPrefix}[A-Za-z0-9_./-]+\\.py)`).exec(line)?.[1];
|
|
316
|
+
if (markdown && pytest)
|
|
317
|
+
declaredScriptByMarkdown[markdown] = pytest;
|
|
318
|
+
}
|
|
309
319
|
if (declaredStems.length === 0) {
|
|
310
320
|
return invalid("empty-unparsable", moduleIndexHeadingCount);
|
|
311
321
|
}
|
|
@@ -345,6 +355,7 @@ export async function resolveBackendTestManifestModules(workspaceRoot, layout) {
|
|
|
345
355
|
moduleIndexHeadingCount,
|
|
346
356
|
declaredStems,
|
|
347
357
|
declaredMdFiles: declaredMdFiles.sort(),
|
|
358
|
+
declaredScriptByMarkdown,
|
|
348
359
|
declaredMissing: declaredMissing.sort(),
|
|
349
360
|
unmanifested: unmanifested.sort(),
|
|
350
361
|
};
|
|
@@ -694,7 +705,8 @@ export async function validateBackendMarkdownCases(input) {
|
|
|
694
705
|
if (!hasAssertableExpectedResult(expected)) {
|
|
695
706
|
findings.push(`${testCase.id} has no structured expected result`);
|
|
696
707
|
}
|
|
697
|
-
const expectedScript =
|
|
708
|
+
const expectedScript = manifest.declaredScriptByMarkdown[relativeFile] ??
|
|
709
|
+
expectedBackendTestPytestScriptForMarkdownModule(relativeFile, layout);
|
|
698
710
|
const mappedScripts = extractMappedPytestScripts(testCase.body);
|
|
699
711
|
if (isEvidenceOnlyMarkdownCaseBody(testCase.body)) {
|
|
700
712
|
// Evidence-only meta cases intentionally declare 脚本/primary symbol as 无 and keep only assertion/cross-cutting TPs.
|
|
@@ -4,7 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { BACKEND_TEST_MAX_MODULE_COUNT, isOpaqueHashBackendTestModuleStem, isPriorityOnlyBackendTestModuleStem, } from "./backend-test-module-stem.js";
|
|
6
6
|
import { resolveBackendTestLayout, } from "./backend-test-layout.js";
|
|
7
|
-
import { inspectBackendTestMarkdownTestPointBindings } from "./backend-test-case-coverage-analysis.js";
|
|
7
|
+
import { canonicalizeBackendTestMarkdownTestPointBindings, inspectBackendTestMarkdownTestPointBindings, } from "./backend-test-case-coverage-analysis.js";
|
|
8
8
|
/**
|
|
9
9
|
* Maps a backend-test generation writer task id to its writer-progress role.
|
|
10
10
|
* Returns undefined for nodes that are not completeness-gated generators.
|
|
@@ -47,8 +47,16 @@ const progressSchema = z
|
|
|
47
47
|
path: z.string().optional(),
|
|
48
48
|
detail: z.string(),
|
|
49
49
|
recoverable: z.boolean(),
|
|
50
|
+
failureSubreason: z.literal("markdown-test-point-binding-format").optional(),
|
|
51
|
+
affectedCases: z.array(z.string()).optional(),
|
|
52
|
+
testPointIds: z.array(z.string()).optional(),
|
|
53
|
+
canonicalizationEligible: z.boolean().optional(),
|
|
54
|
+
canonicalizationOutcome: z.enum(["not-attempted", "changed", "already-canonical", "unsafe", "revalidation-failed"]).optional(),
|
|
50
55
|
})
|
|
51
56
|
.strict()),
|
|
57
|
+
canonicalizationOutcome: z.enum(["not-attempted", "changed", "already-canonical", "unsafe", "revalidation-failed"]).optional(),
|
|
58
|
+
canonicalizedTestPointCount: z.number().int().min(0).optional(),
|
|
59
|
+
lightweightRepairEligible: z.boolean().optional(),
|
|
52
60
|
})
|
|
53
61
|
.strict();
|
|
54
62
|
async function exists(filePath) {
|
|
@@ -520,6 +528,9 @@ export async function assessBackendTestShardChildCompleteness(input) {
|
|
|
520
528
|
const actualPaths = [];
|
|
521
529
|
const missingPaths = [];
|
|
522
530
|
const brokenPaths = [];
|
|
531
|
+
let canonicalizationOutcome = role === "md-generate" ? "already-canonical" : undefined;
|
|
532
|
+
let canonicalizedTestPointCount = 0;
|
|
533
|
+
let lightweightRepairEligible = false;
|
|
523
534
|
for (const target of targetPaths) {
|
|
524
535
|
// Rendered map-child writeSet entries are concrete (e.g.
|
|
525
536
|
// testcase/md/health.md / testcase/test_orders.py); glob-only entries are
|
|
@@ -538,16 +549,44 @@ export async function assessBackendTestShardChildCompleteness(input) {
|
|
|
538
549
|
continue;
|
|
539
550
|
}
|
|
540
551
|
actualPaths.push(target);
|
|
541
|
-
|
|
552
|
+
let body = await readFile(absolute, "utf8");
|
|
542
553
|
if (role === "md-generate") {
|
|
554
|
+
const beforeBindings = inspectBackendTestMarkdownTestPointBindings(body);
|
|
555
|
+
const canonicalization = canonicalizeBackendTestMarkdownTestPointBindings(body);
|
|
556
|
+
if (canonicalization.changed) {
|
|
557
|
+
await writeFile(absolute, canonicalization.markdown, "utf8");
|
|
558
|
+
body = canonicalization.markdown;
|
|
559
|
+
canonicalizationOutcome = "changed";
|
|
560
|
+
canonicalizedTestPointCount += canonicalization.canonicalizedTestPoints.length;
|
|
561
|
+
}
|
|
562
|
+
const afterBindings = inspectBackendTestMarkdownTestPointBindings(body);
|
|
563
|
+
const unresolved = afterBindings.flatMap((entry) => entry.unclassifiedTestPoints);
|
|
564
|
+
const bindingCases = afterBindings
|
|
565
|
+
.filter((entry) => entry.unclassifiedTestPoints.length > 0 || entry.duplicateBindingTestPoints.length > 0)
|
|
566
|
+
.map((entry) => entry.caseId);
|
|
543
567
|
const structural = moduleStructurallyComplete(body);
|
|
544
568
|
if (!structural.ok) {
|
|
569
|
+
lightweightRepairEligible = bindingCases.length > 0;
|
|
570
|
+
if (bindingCases.length > 0)
|
|
571
|
+
canonicalizationOutcome = canonicalization.changed ? "revalidation-failed" : "unsafe";
|
|
545
572
|
brokenPaths.push(target);
|
|
573
|
+
const bindingFormatIssue = bindingCases.length > 0;
|
|
546
574
|
issues.push({
|
|
547
575
|
code: "T5",
|
|
548
576
|
path: target,
|
|
549
577
|
detail: moduleStructuralDetail(structural),
|
|
550
578
|
recoverable: true,
|
|
579
|
+
...(bindingFormatIssue
|
|
580
|
+
? {
|
|
581
|
+
failureSubreason: "markdown-test-point-binding-format",
|
|
582
|
+
affectedCases: orderedUnique(bindingCases),
|
|
583
|
+
testPointIds: orderedUnique(unresolved),
|
|
584
|
+
canonicalizationEligible: canonicalization.canonicalizedTestPoints.length > 0,
|
|
585
|
+
canonicalizationOutcome: canonicalization.changed
|
|
586
|
+
? (unresolved.length === 0 ? "changed" : "revalidation-failed")
|
|
587
|
+
: (beforeBindings.some((entry) => entry.unclassifiedTestPoints.length > 0) ? "unsafe" : "already-canonical"),
|
|
588
|
+
}
|
|
589
|
+
: {}),
|
|
551
590
|
});
|
|
552
591
|
}
|
|
553
592
|
}
|
|
@@ -586,6 +625,9 @@ export async function assessBackendTestShardChildCompleteness(input) {
|
|
|
586
625
|
brokenPaths: orderedUnique(brokenPaths),
|
|
587
626
|
targetPaths: recoverableTargets,
|
|
588
627
|
issues,
|
|
628
|
+
...(role === "md-generate"
|
|
629
|
+
? { canonicalizationOutcome, canonicalizedTestPointCount, lightweightRepairEligible }
|
|
630
|
+
: {}),
|
|
589
631
|
};
|
|
590
632
|
}
|
|
591
633
|
export async function assessBackendTestMdWriterCompleteness(workspaceRoot, layout) {
|
|
@@ -853,7 +895,7 @@ export async function writeBackendTestWriterProgressArtifacts(input) {
|
|
|
853
895
|
"## Issues",
|
|
854
896
|
"",
|
|
855
897
|
...(input.progress.issues.length
|
|
856
|
-
? input.progress.issues.map((issue) => `- [${issue.code}] ${issue.path ?? "(workspace)"}: ${issue.detail}`)
|
|
898
|
+
? input.progress.issues.map((issue) => `- [${issue.code}] ${issue.path ?? "(workspace)"}: ${issue.detail}${issue.failureSubreason ? `; failureSubreason=${issue.failureSubreason}; canonicalizationOutcome=${issue.canonicalizationOutcome ?? "not-attempted"}; canonicalizationEligible=${issue.canonicalizationEligible ?? false}; affectedCases=${(issue.affectedCases ?? []).join(",") || "none"}; testPointIds=${(issue.testPointIds ?? []).join(",") || "none"}` : ""}`)
|
|
857
899
|
: ["- None"]),
|
|
858
900
|
"",
|
|
859
901
|
`<!-- sha256:${createHash("sha256").update(JSON.stringify(parsed)).digest("hex")} -->`,
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* its schemaId in the contract output registry. Imported once by the node
|
|
4
4
|
* executor; keeps per-contract imports out of the executor.
|
|
5
5
|
*/
|
|
6
|
-
import { validateFrontendContractNodeOutput, validateFrontendPlanPatchNodeOutput,
|
|
6
|
+
import { validateFrontendContractNodeOutput, validateFrontendPlanPatchNodeOutput, } from "./frontend-implementation-contract.js";
|
|
7
7
|
import { registerStructuredContractValidator } from "./contract-output-registry.js";
|
|
8
8
|
registerStructuredContractValidator("frontend-implementation-contract-v1", validateFrontendContractNodeOutput);
|
|
9
9
|
registerStructuredContractValidator("frontend-implementation-contract-plan-patch-v1", validateFrontendPlanPatchNodeOutput);
|
|
10
|
-
registerStructuredContractValidator("frontend-implementation-contract-revision-patch-v1", validateFrontendRevisionPatchNodeOutput);
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Leaf module for the retry-policy schema surface: failure-category constants,
|
|
4
|
+
* the zod retry-policy schema, and the target-template transient retry profile
|
|
5
|
+
* literal.
|
|
6
|
+
*
|
|
7
|
+
* `types.ts` reads `dagRetryPolicySchema` / `TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE`
|
|
8
|
+
* during its own module initialization, and the retry-policy module transitively
|
|
9
|
+
* reaches `types.ts` (retry-policy → frontend-recovery-capsule →
|
|
10
|
+
* frontend-recovery-lineage → frontend-implementation-contract → types). With
|
|
11
|
+
* these definitions living in retry-policy itself, a run whose entry module is
|
|
12
|
+
* retry-policy (e.g. test/dag-node-retry.test.ts) evaluates `types.ts` while
|
|
13
|
+
* retry-policy is still initializing and reads an undefined
|
|
14
|
+
* `dagRetryPolicySchema`. Keeping this module a leaf (zod is its only import)
|
|
15
|
+
* makes the cycle resolve order-independently; retry-policy re-exports
|
|
16
|
+
* everything for source compatibility.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Failure categories that are safe to auto-retry for read-only Pi nodes.
|
|
20
|
+
*
|
|
21
|
+
* These are failures with no repository write side-effects: model connection
|
|
22
|
+
* interruption, provider rate-limiting, temporary provider unavailability,
|
|
23
|
+
* request timeout, or a settled read-only turn with no assistant text.
|
|
24
|
+
*
|
|
25
|
+
* `quota` is intentionally NOT included: a quota exhaustion is an account
|
|
26
|
+
* billing/plan state, not a transient rate limit, and retrying immediately
|
|
27
|
+
* will not help and may waste budget.
|
|
28
|
+
*/
|
|
29
|
+
export const DEFAULT_DAG_RETRY_CATEGORIES = [
|
|
30
|
+
"timeout",
|
|
31
|
+
"network",
|
|
32
|
+
"rate-limit",
|
|
33
|
+
"unavailable",
|
|
34
|
+
"empty-output",
|
|
35
|
+
];
|
|
36
|
+
export const STRUCTURED_OUTPUT_RETRY_CATEGORY = "output-too-large";
|
|
37
|
+
export const PROTOCOL_INVALID_RETRY_CATEGORY = "protocol-invalid";
|
|
38
|
+
/** Recoverable model artifact/schema formatting failure on read-only structured nodes. */
|
|
39
|
+
export const STRUCTURED_ARTIFACT_INVALID_RETRY_CATEGORY = "invalid-output";
|
|
40
|
+
/** Provider stopReason=length truncated the response before the JSON contract completed. */
|
|
41
|
+
export const STRUCTURED_OUTPUT_TRUNCATED_RETRY_CATEGORY = "structured-output-truncated";
|
|
42
|
+
/**
|
|
43
|
+
* The review node emitted a verdict in response text (e.g. "VERDICT: pass")
|
|
44
|
+
* but never committed the authoritative typed terminal tool call
|
|
45
|
+
* (approve_review / request_review_changes). Read-only and safe to retry with
|
|
46
|
+
* a corrected instruction; the typed terminal is the only authority.
|
|
47
|
+
*/
|
|
48
|
+
export const REVIEW_TERMINAL_MISSING_RETRY_CATEGORY = "review-terminal-missing";
|
|
49
|
+
/**
|
|
50
|
+
* The plan node issued too many read-only tool calls (read/grep/ls/find),
|
|
51
|
+
* blowing up the context window and eventually a 400 request-too-large. The
|
|
52
|
+
* plan should trust upstream typed facts instead of re-reading contract/scout
|
|
53
|
+
* outputs and source files repeatedly. Read-only and safe to retry with a
|
|
54
|
+
* reduced-reading instruction.
|
|
55
|
+
*/
|
|
56
|
+
export const READ_BURST_RETRY_CATEGORY = "read-burst";
|
|
57
|
+
/**
|
|
58
|
+
* Provider returned a context-overflow error (400 request-too-large / context
|
|
59
|
+
* window exceeded). The SDK compacts and auto-retries once internally; when it
|
|
60
|
+
* still surfaces, the executor classifies this so retry policies and reports
|
|
61
|
+
* treat it as overflow (not empty-output) and can retry with a compacted
|
|
62
|
+
* session / fresh context.
|
|
63
|
+
*/
|
|
64
|
+
export const CONTEXT_OVERFLOW_RETRY_CATEGORY = "context-overflow";
|
|
65
|
+
/** Retry only a proven no-op from an explicitly opt-in bounded Pi writer. */
|
|
66
|
+
export const WRITER_EMPTY_DIFF_RETRY_CATEGORY = "writer-empty-diff";
|
|
67
|
+
/** Retry when a backend-test writer finished but Completeness Gate found missing/broken targets. */
|
|
68
|
+
export const INCOMPLETE_WRITE_SET_RETRY_CATEGORY = "incomplete-write-set";
|
|
69
|
+
/**
|
|
70
|
+
* Provider/transport stall or absolute timeout on a Pi exclusive writer that
|
|
71
|
+
* left zero attributed workspace writes and zero write-tool calls. Safe to
|
|
72
|
+
* retry once because the attempt was a pure provider flake (no side effects).
|
|
73
|
+
* Partial writes keep the original `timeout` category and do NOT retry.
|
|
74
|
+
*/
|
|
75
|
+
export const WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY = "writer-clean-timeout";
|
|
76
|
+
export const STRUCTURED_REQUIRED_DAG_RETRY_CATEGORIES = [
|
|
77
|
+
...DEFAULT_DAG_RETRY_CATEGORIES,
|
|
78
|
+
STRUCTURED_OUTPUT_RETRY_CATEGORY,
|
|
79
|
+
STRUCTURED_ARTIFACT_INVALID_RETRY_CATEGORY,
|
|
80
|
+
STRUCTURED_OUTPUT_TRUNCATED_RETRY_CATEGORY,
|
|
81
|
+
];
|
|
82
|
+
/** Categories allowed on nodes that declare a machine-readable outputProtocol. */
|
|
83
|
+
export const PROTOCOL_AWARE_DAG_RETRY_CATEGORIES = [
|
|
84
|
+
...DEFAULT_DAG_RETRY_CATEGORIES,
|
|
85
|
+
PROTOCOL_INVALID_RETRY_CATEGORY,
|
|
86
|
+
STRUCTURED_ARTIFACT_INVALID_RETRY_CATEGORY,
|
|
87
|
+
];
|
|
88
|
+
export const ALL_DAG_RETRY_CATEGORIES = [
|
|
89
|
+
...DEFAULT_DAG_RETRY_CATEGORIES,
|
|
90
|
+
STRUCTURED_OUTPUT_RETRY_CATEGORY,
|
|
91
|
+
PROTOCOL_INVALID_RETRY_CATEGORY,
|
|
92
|
+
STRUCTURED_ARTIFACT_INVALID_RETRY_CATEGORY,
|
|
93
|
+
STRUCTURED_OUTPUT_TRUNCATED_RETRY_CATEGORY,
|
|
94
|
+
WRITER_EMPTY_DIFF_RETRY_CATEGORY,
|
|
95
|
+
INCOMPLETE_WRITE_SET_RETRY_CATEGORY,
|
|
96
|
+
WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY,
|
|
97
|
+
REVIEW_TERMINAL_MISSING_RETRY_CATEGORY,
|
|
98
|
+
READ_BURST_RETRY_CATEGORY,
|
|
99
|
+
CONTEXT_OVERFLOW_RETRY_CATEGORY,
|
|
100
|
+
];
|
|
101
|
+
export const dagRetryCategorySchema = z.enum(ALL_DAG_RETRY_CATEGORIES);
|
|
102
|
+
export const dagRetryBackoffSchema = z.enum(["exponential"]);
|
|
103
|
+
/**
|
|
104
|
+
* Opt-in retry policy for a DagTask. Generated for safe read-only Pi nodes,
|
|
105
|
+
* except the single validation-gated writer-empty-diff policy. Validation
|
|
106
|
+
* rejects all other writers, dynamic, shell, static, and decision-gate nodes.
|
|
107
|
+
*/
|
|
108
|
+
export const dagRetryPolicySchema = z
|
|
109
|
+
.object({
|
|
110
|
+
/**
|
|
111
|
+
* Total number of attempts (including the first try). Must be 1..5.
|
|
112
|
+
* A value of 1 disables retry (one attempt, no retry).
|
|
113
|
+
*/
|
|
114
|
+
maxAttempts: z.number().int().positive().max(5),
|
|
115
|
+
backoff: dagRetryBackoffSchema.default("exponential"),
|
|
116
|
+
/** Initial delay before the second attempt, in milliseconds. */
|
|
117
|
+
initialDelayMs: z.number().int().nonnegative().default(2000),
|
|
118
|
+
/** Upper bound on a single backoff delay, in milliseconds. */
|
|
119
|
+
maxDelayMs: z.number().int().nonnegative().default(30000),
|
|
120
|
+
/**
|
|
121
|
+
* Explicit set of failure categories eligible for retry. Defaults to
|
|
122
|
+
* {@link DEFAULT_DAG_RETRY_CATEGORIES}. `quota` and other categories
|
|
123
|
+
* are never retried.
|
|
124
|
+
*/
|
|
125
|
+
retryCategories: z
|
|
126
|
+
.array(dagRetryCategorySchema)
|
|
127
|
+
.default([...DEFAULT_DAG_RETRY_CATEGORIES]),
|
|
128
|
+
})
|
|
129
|
+
.superRefine((policy, ctx) => {
|
|
130
|
+
if (policy.maxDelayMs < policy.initialDelayMs) {
|
|
131
|
+
ctx.addIssue({
|
|
132
|
+
code: z.ZodIssueCode.custom,
|
|
133
|
+
message: "retryPolicy.maxDelayMs must be >= retryPolicy.initialDelayMs",
|
|
134
|
+
path: ["maxDelayMs"],
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
export const TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE = "standard-reviewed-supervised-v1";
|