@tea-agent/loop-agent 0.41.1-beta.0 → 0.42.0-next.2
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 +78 -206
- package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
- package/dist/adapters/context-transfer/pi-session.js +61 -0
- package/dist/application/dag/generate-task-dag.js +19 -75
- package/dist/application/task-lifecycle/advance.js +5 -24
- package/dist/application/task-lifecycle/observe.js +17 -171
- package/dist/application/task-lifecycle/plan-transitions.js +7 -42
- package/dist/application/task-lifecycle/recommendations.js +2 -13
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +12 -7
- package/dist/cli/program.js +23 -6
- package/dist/commands/client-recovery.js +0 -3
- package/dist/commands/dag-artifact.js +284 -0
- package/dist/commands/dag-context.js +184 -0
- package/dist/commands/dag-rerun.js +206 -1
- package/dist/commands/init.js +1 -27
- package/dist/commands/task-advance.js +0 -19
- package/dist/executors/dag-pi-executor.js +266 -3792
- package/dist/executors/pi-executor.js +4 -15
- package/dist/executors/pi-sdk-executor.js +3 -0
- package/dist/executors/shell-executor.js +217 -945
- package/dist/executors/shell-write-guard.js +0 -7
- package/dist/{worker → infrastructure}/console/app-data.js +4 -0
- package/dist/infrastructure/console/artifact-revision-store.js +430 -0
- package/dist/infrastructure/console/context-export-store.js +160 -0
- package/dist/infrastructure/console/dir-lock.js +132 -0
- package/dist/infrastructure/console/operation-store.js +197 -0
- package/dist/infrastructure/harness/artifact-store.js +10 -1
- package/dist/infrastructure/harness/atomic-write.js +12 -2
- package/dist/shared/context-transfer/artifact-revision.js +172 -0
- package/dist/shared/context-transfer.js +418 -0
- package/dist/shared/dag-failure-category.js +0 -12
- package/dist/shared/openspec-spec.js +4 -70
- package/dist/shared/operator/capabilities.js +14 -7
- package/dist/shared/operator/safe-run-summary.js +1 -0
- package/dist/shared/path-safety.js +93 -0
- package/dist/shared/preview.js +28 -4
- package/dist/task/config-types.js +80 -35
- package/dist/task/contract/adopt.js +0 -4
- package/dist/task/contract/import-revision.js +0 -4
- package/dist/task/contract/project.js +0 -3
- package/dist/task/contract/schema.js +1 -2
- package/dist/task/frontend-project-capability.js +20 -203
- package/dist/task/runtime.js +2 -5
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +9 -15
- package/dist/task/source-prepare/prepare.js +1 -89
- package/dist/task/source-prepare/semantic-intake.js +2 -6
- package/dist/task/source-references.js +1 -22
- package/dist/task/task-demand-routing.js +3 -0
- package/dist/worker/console/chat/chat-event-store.js +2 -2
- package/dist/worker/console/chat/pi-runtime.js +3 -2
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +13 -4
- package/dist/worker/console/chat/semantic-activity.js +12 -11
- package/dist/worker/console/chat/session-stats.js +96 -0
- package/dist/worker/console/chat/session-store.js +1 -1
- package/dist/worker/console/chat/turn-process.js +28 -4
- package/dist/worker/console/chat/user-questions.js +1 -1
- package/dist/worker/console/console-update-runtime.js +1 -1
- package/dist/worker/console/context-transfer-diagnostics.js +198 -0
- package/dist/worker/console/dag-confirmation.js +1 -1
- package/dist/worker/console/dag-execution-receipt.js +1 -1
- package/dist/worker/console/doctor.js +1 -1
- package/dist/worker/console/draft-store.js +1 -1
- package/dist/worker/console/human-gate-token.js +1 -1
- package/dist/worker/console/index.js +3 -6
- package/dist/worker/console/interview/assessment.js +1 -1
- package/dist/worker/console/interview/session.js +1 -1
- package/dist/worker/console/operation-runner.js +45 -4
- package/dist/worker/console/operation-sse.js +1 -1
- package/dist/worker/console/operation-wait.js +1 -1
- package/dist/worker/console/operator-actions.js +107 -168
- package/dist/worker/console/operator-surface-health.js +1 -1
- package/dist/worker/console/operator-user-error.js +169 -0
- package/dist/worker/console/pi-plugins.js +60 -1
- package/dist/worker/console/routes.js +637 -8
- package/dist/worker/console/security.js +35 -0
- package/dist/worker/console/server.js +6 -6
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bho4we4c.js → abnfDiagram-N423BO3Z-DZZ8m3PO.js} +1 -1
- package/dist/worker/console/static/assets/{arc-TBDkTeK0.js → arc-D6PvaVd-.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BF_fZrn2.js → architectureDiagram-T3A2C74G-B_OTOiI8.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CCnPgUe2.js → blockDiagram-VBNYF7ZC-Bv6rqHBg.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCYZJLAT.js → c4Diagram-5PPSVZJV-B8eHr0oz.js} +1 -1
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D8K6adhU.js → chunk-2GRJ4B5K-DYwR0im2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DkT1Q78b.js → chunk-2Q5K7J3B-D2WPGqXt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DuhvpIr7.js → chunk-5RXB4S5H-CQISJ_I7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BnK6dC49.js → chunk-5VM5RSS4-C0o2Du1e.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DPz__z7r.js → chunk-6Q2QTUOP-4f8kr-U7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-0kkppM2j.js → chunk-GF5L2VYU-D_OWvXzX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BB41iK5t.js → chunk-JWPE2WC7-CasdPz5X.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CiVrxKHU.js → chunk-KBJHAD2P-Cj8lRrla.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C2h9Xje1.js → chunk-RYQCIY6F-CUvD1FSd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-3iOSHdbM.js → chunk-XXDRQBXY-CqLw_eqB.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BN3A7tkk.js → cose-bilkent-JH36ORCC-ku-WqJPx.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CTuBy0Or.js → cynefin-VYW2F7L2-Bq_PYDhl.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-wb6tUlMN.js → cynefinDiagram-MW4NZA55-BiGZV641.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-GP1Ivx8B.js → dagre-VZM6K2ZE-C4JRlglF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ejrcK6L0.js → diagram-7IWD3JNH-B0dNeWYG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-YFkCCvUU.js → diagram-B4RE2ZJO-Cj35YvYM.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-B4rrvIaL.js → diagram-LBJQPF4R-uzQoJ2-8.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C-YIZcUf.js → diagram-Q27KOJAE-D1a-Buoz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPF5wcL1.js → diagram-UB23O5K3-XjRrLRSs.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CU5SzXjO.js → ebnfDiagram-BXEA7PRR-Da_O24cW.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CpL414DE.js → erDiagram-JOGREHBK-BLJ8jrYU.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-MrhLzNau.js → flowDiagram-UKHOOZJN-B9GrLjM0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Bg4qFuUo.js → ganttDiagram-PKOTCBZU-CeJ0TqiK.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-ColxSXLR.js → gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js} +1 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +1 -0
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +451 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czvtm6gc.js → infoDiagram-6WML65LV-DB26i3d8.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js → ishikawaDiagram-WSZJBQD7-BOGRyeZT.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DWYZcvph.js → journeyDiagram-NVQOT4AX-CZRoBO_6.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-KTLZRUjx.js → kanban-definition-27J2QSJJ-qlWhJyoB.js} +1 -1
- package/dist/worker/console/static/assets/{linear-_Exn7bfl.js → linear-CfUiDDB3.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Cxg8kX8V.js → mermaid.core-BQe6fpqj.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-oKrlo2Bt.js → mindmap-definition-FAOFIHXS-zFzWHw64.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-XAeteiNd.js → pegDiagram-VL7TDLO6-DFGUqjZO.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BTf1K-lp.js → pieDiagram-7S7Q4E2Y-BB5i0l8Z.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js → quadrantDiagram-CIZ2JOQS-obbVZ_X5.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BVbKtZhW.js → railroadDiagram-AXF67PYL-BwdaNzc1.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C5e4JFcr.js → requirementDiagram-LRYGKXZP-CkTJaugh.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cz30hc29.js → sankeyDiagram-W5VNT64P-BwJ-hIgq.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DS33n6d0.js → sequenceDiagram-SI44F4Z6-BzSgmm7w.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-46w7v-9T.js → sizeCapture-X5ZJPWSS-Bn3oj5Q1.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-pbcD5SsZ.js → stateDiagram-OKZ733FA-dxhGtL8K.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DuivYBSj.js → swimlanes-SLNWSIFB-BLpEInya.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cb9VZVIl.js → timeline-definition-Z64GVDOM-ChJGYcXy.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-D2-mfOgR.js → vennDiagram-T6HMQDX7-DK-qjRer.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DeS2aC76.js → wardleyDiagram-T6FBY63Y-3qeaWAg-.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-WGsrFjxH.js → xychartDiagram-ELKLHX3M-CootlyP9.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
- package/dist/worker/console/static-src/app/useRunProgress.js +9 -2
- package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +22 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +106 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
- package/dist/worker/console/workspace-context.js +28 -112
- package/dist/worker/console/workspace-registry.js +2 -2
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/observability/read-model.js +4 -0
- package/dist/worker/observe/health.js +1 -1
- package/dist/worker/observe/node-transparency.js +572 -0
- package/dist/worker/observe/routes.js +51 -1
- package/dist/worker/observe/static/api.js +69 -5
- package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
- package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
- package/dist/worker/observe/static/dag-node-purpose.js +0 -5
- package/dist/worker/observe/static/state.js +3 -1
- package/dist/worker/observe/static/styles.css +1037 -126
- package/dist/worker/observe/static/views/dag-inspector.js +1217 -65
- package/dist/worker/observe/static/views/session-timeline.js +121 -25
- package/dist/worker/outcomes/adapters.js +19 -0
- package/dist/worker/outcomes/types.js +1 -0
- package/dist/worker/pool/attempt-lease.js +97 -66
- package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
- package/dist/worker/pool/reconcile.js +46 -1
- package/dist/worker/pool/run-store.js +2 -0
- package/dist/worker/pool/state-projection.js +2 -0
- package/dist/worker/task-spec/workflow-routing.js +8 -3
- package/dist/workflows/dag/artifact-bindings.js +149 -0
- package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +99 -4
- package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-writer-completeness.js +45 -3
- package/dist/workflows/dag/budget-enforcement.js +10 -0
- package/dist/workflows/dag/context-receipt.js +305 -0
- package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
- package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
- package/dist/workflows/dag/context-transfer/renderers.js +93 -0
- package/dist/workflows/dag/contract-validator-registrations.js +2 -1
- package/dist/workflows/dag/frontend-implementation-contract.js +162 -932
- package/dist/workflows/dag/frontend-plan-render.js +1 -2
- package/dist/workflows/dag/frontend-prewrite-gate.js +349 -255
- package/dist/workflows/dag/frontend-recovery-plan.js +10 -17
- package/dist/workflows/dag/frontend-recovery-run.js +20 -33
- package/dist/workflows/dag/frontend-repair.js +432 -1
- package/dist/workflows/dag/frontend-review-context.js +15 -261
- package/dist/workflows/dag/frontend-verification-trace.js +24 -249
- package/dist/workflows/dag/frontend-worktree-diff.js +17 -250
- package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
- package/dist/workflows/dag/init-hybrid.js +740 -718
- package/dist/workflows/dag/interrupt-request.js +0 -7
- package/dist/workflows/dag/node-execution.js +136 -759
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +30 -38
- package/dist/workflows/dag/report.js +1 -37
- package/dist/workflows/dag/rerun-feedback.js +1 -256
- package/dist/workflows/dag/rerun-plan.js +557 -11
- package/dist/workflows/dag/rerun-run.js +601 -38
- package/dist/workflows/dag/rerun-task.js +0 -29
- package/dist/workflows/dag/retry-policy.js +109 -214
- package/dist/workflows/dag/runner.js +142 -430
- package/dist/workflows/dag/scheduler.js +16 -114
- package/dist/workflows/dag/skill-snapshot.js +17 -0
- package/dist/workflows/dag/types.js +195 -246
- package/dist/workflows/dag/validate.js +12 -28
- package/docs/architecture/runtime-boundaries.md +6 -6
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/init-surface.manifest.json +12 -30
- package/docs/skills/vetted-skill-registry.md +2 -4
- package/docs/templates/README.md +0 -2
- package/docs/templates/agent-dag-report.schema.json +2 -8
- package/docs/templates/agent-dag.schema.json +33 -1
- package/docs/templates/backend-test-dag.json +18 -18
- package/docs/templates/frontend-implementation-contract.schema.json +1 -4
- package/docs/templates/product-line/task.yaml +1 -1
- package/harness.json +3 -3
- package/package.json +3 -1
- package/skills/frontend-bounded-implement/SKILL.md +14 -15
- package/skills/frontend-design-review/SKILL.md +41 -22
- package/skills/frontend-implementation/SKILL.md +52 -0
- package/skills/frontend-implementation/references/code-standards.md +33 -0
- package/skills/frontend-implementation/references/design-spec.md +56 -0
- package/skills/frontend-implementation/references/node-contracts.md +31 -0
- package/skills/frontend-review/SKILL.md +15 -20
- package/skills/frontend-review/references/review-findings.md +7 -6
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +6 -1
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/commands/dag-follow-up.js +0 -138
- package/dist/executors/pi-read-budget-policy.js +0 -239
- package/dist/worker/console/frontend-human-decision-adapter.js +0 -19
- package/dist/worker/console/frontend-split-operation-adapter.js +0 -20
- package/dist/worker/console/operation-store.js +0 -389
- package/dist/worker/console/static/assets/channel-bnXW0U3C.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +0 -1
- package/dist/worker/console/static/assets/index-D3CC4eUz.css +0 -1
- package/dist/worker/console/static/assets/index-DLt4ZFvD.js +0 -437
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +0 -8
- package/dist/worker/console/static-src/active-run-badge.js +0 -17
- package/dist/worker/materialize/frontend-split-task-materializer.js +0 -72
- package/dist/workflows/dag/dag-retry-schema.js +0 -138
- package/dist/workflows/dag/frontend-closeout.js +0 -221
- package/dist/workflows/dag/frontend-design-policy.js +0 -400
- package/dist/workflows/dag/frontend-human-decision.js +0 -182
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +0 -159
- package/dist/workflows/dag/frontend-read-budgets.js +0 -12
- package/dist/workflows/dag/frontend-recovery-capsule.js +0 -455
- package/dist/workflows/dag/frontend-recovery-controller.js +0 -202
- package/dist/workflows/dag/frontend-recovery-lineage.js +0 -178
- package/dist/workflows/dag/frontend-review-findings.js +0 -270
- package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -914
- package/dist/workflows/dag/frontend-shape-capsule-store.js +0 -191
- package/dist/workflows/dag/frontend-shape-facts.js +0 -409
- package/dist/workflows/dag/frontend-shape.js +0 -427
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +0 -108
- package/dist/workflows/dag/frontend-split-application-service.js +0 -203
- package/dist/workflows/dag/frontend-split-orchestrator.js +0 -899
- package/dist/workflows/dag/frontend-typed-event-store.js +0 -452
- package/dist/workflows/dag/frontend-typed-event-transaction.js +0 -180
- package/dist/workflows/dag/frontend-writer-admission.js +0 -285
- package/dist/workflows/dag/frontend-writer-status.js +0 -256
- package/dist/workflows/dag/recovery-lease.js +0 -80
- package/docs/templates/frontend-implementation-dag.json +0 -89
- package/docs/templates/spec-registry.schema.json +0 -45
- package/skills/frontend-bounded-implement/references/code-standards.md +0 -19
- package/skills/frontend-contract/SKILL.md +0 -23
- package/skills/frontend-contract/references/contract-protocol.md +0 -34
- package/skills/frontend-plan/SKILL.md +0 -26
- package/skills/frontend-plan/references/decision-contract.md +0 -37
- package/skills/frontend-plan/references/design-decisions.md +0 -17
- package/skills/frontend-scout/SKILL.md +0 -25
- package/skills/frontend-scout/references/design-evidence.md +0 -16
- package/skills/frontend-scout/references/scout-evidence.md +0 -23
- /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { isTerminalOperationState, } from "../../worker/console/operation-store.js";
|
|
2
|
-
import { runOperation, } from "../../worker/console/operation-runner.js";
|
|
3
|
-
import { buildFailureCapsule, classifyFailureOwner, projectProtocolFailureReason, } from "./frontend-recovery-capsule.js";
|
|
4
|
-
import { computeNormalizedFailureFingerprint, evaluateNonConvergence, foldOperationBudgets, } from "./frontend-recovery-lineage.js";
|
|
5
|
-
import { projectedInterruptStatus, } from "./interrupt-request.js";
|
|
6
|
-
import { evaluateNodeLiveness, } from "./liveness-policy.js";
|
|
7
|
-
/**
|
|
8
|
-
* Deterministic legacy Console operation-state → canonical frontend
|
|
9
|
-
* operation-state/terminal projection. This is the single adapter-layer
|
|
10
|
-
* projection at the controller boundary; the generic Console operation schema
|
|
11
|
-
* is not changed (AC-001).
|
|
12
|
-
*/
|
|
13
|
-
export function projectLegacyOperationState(state) {
|
|
14
|
-
switch (state) {
|
|
15
|
-
case "queued":
|
|
16
|
-
case "starting":
|
|
17
|
-
case "running":
|
|
18
|
-
case "restarting":
|
|
19
|
-
return { operationState: "active" };
|
|
20
|
-
case "succeeded":
|
|
21
|
-
return { operationState: "success", operationTerminal: "success" };
|
|
22
|
-
case "failed":
|
|
23
|
-
case "timed-out":
|
|
24
|
-
case "needs-reconcile":
|
|
25
|
-
return {
|
|
26
|
-
operationState: "failed-internal",
|
|
27
|
-
operationTerminal: "failed-internal",
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Conservative default recovery mode by owner. Non-writer owners restart as a
|
|
33
|
-
* new DAG; environment issues wait for the external dependency; unknown never
|
|
34
|
-
* auto-selects writer recovery (AC-003).
|
|
35
|
-
*/
|
|
36
|
-
export function projectDefaultRecoveryMode(owner) {
|
|
37
|
-
switch (owner) {
|
|
38
|
-
case "contract":
|
|
39
|
-
case "scout":
|
|
40
|
-
case "plan":
|
|
41
|
-
case "implementation":
|
|
42
|
-
case "verification-config":
|
|
43
|
-
case "scope":
|
|
44
|
-
return "new-dag";
|
|
45
|
-
case "environment":
|
|
46
|
-
return "wait-external";
|
|
47
|
-
case "unknown":
|
|
48
|
-
return "none";
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function buildCapsuleOnTerminal(input) {
|
|
52
|
-
const protocolFailureReason = projectProtocolFailureReason(input.protocolFailureReason);
|
|
53
|
-
if (input.terminalReason === "non-converging") {
|
|
54
|
-
return buildFailureCapsule({
|
|
55
|
-
parentRunId: input.parentRunId,
|
|
56
|
-
operationState: "non-converging",
|
|
57
|
-
recoveryMode: "none",
|
|
58
|
-
protocolFailureReason,
|
|
59
|
-
failureOwner: input.owner,
|
|
60
|
-
evidenceRefs: [],
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
return buildFailureCapsule({
|
|
64
|
-
parentRunId: input.parentRunId,
|
|
65
|
-
operationState: "recoverable",
|
|
66
|
-
recoveryMode: input.deps.recoveryMode ?? projectDefaultRecoveryMode(input.owner),
|
|
67
|
-
protocolFailureReason,
|
|
68
|
-
failureOwner: input.owner,
|
|
69
|
-
evidenceRefs: [],
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
export async function runRecoveryOperationController(input) {
|
|
73
|
-
const { operationId, parentRunId, deps } = input;
|
|
74
|
-
const maxAttempts = Math.max(1, deps.maxAttempts ?? 5);
|
|
75
|
-
const now = deps.now ?? (() => new Date());
|
|
76
|
-
const readInterrupt = deps.readInterrupt ?? (async () => undefined);
|
|
77
|
-
const readLedgers = deps.readLedgers ?? (async () => []);
|
|
78
|
-
const readChangedFiles = deps.readChangedFiles ?? (async () => []);
|
|
79
|
-
const fingerprints = [...(deps.previousFingerprints ?? [])];
|
|
80
|
-
let budgetAggregate;
|
|
81
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
82
|
-
const operation = await deps.store.get(operationId);
|
|
83
|
-
if (!operation) {
|
|
84
|
-
return {
|
|
85
|
-
terminalReason: "operation-not-found",
|
|
86
|
-
attempts: attempt,
|
|
87
|
-
fingerprints,
|
|
88
|
-
detail: `operation not found: ${operationId}`,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
// Total budget fold + hard stop (resource protection, fail-closed).
|
|
92
|
-
const ledgers = await readLedgers();
|
|
93
|
-
if (deps.budget) {
|
|
94
|
-
budgetAggregate = foldOperationBudgets({
|
|
95
|
-
ledgers,
|
|
96
|
-
budget: deps.budget,
|
|
97
|
-
now,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
if (budgetAggregate?.status === "breached") {
|
|
101
|
-
return {
|
|
102
|
-
terminalReason: "budget-exhausted",
|
|
103
|
-
attempts: attempt,
|
|
104
|
-
budgetAggregate,
|
|
105
|
-
fingerprints,
|
|
106
|
-
detail: "operation total budget breached",
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
const owner = deps.failureOwner ??
|
|
110
|
-
classifyFailureOwner({
|
|
111
|
-
rawFailureCategory: operation.errorCode,
|
|
112
|
-
nodeId: operationId,
|
|
113
|
-
});
|
|
114
|
-
const protocolFailureReason = deps.protocolFailureReason ??
|
|
115
|
-
operation.errorMessage ??
|
|
116
|
-
operation.errorCode ??
|
|
117
|
-
"unknown failure";
|
|
118
|
-
if (isTerminalOperationState(operation.state)) {
|
|
119
|
-
if (operation.state === "succeeded") {
|
|
120
|
-
return {
|
|
121
|
-
terminalReason: "succeeded",
|
|
122
|
-
attempts: attempt,
|
|
123
|
-
budgetAggregate,
|
|
124
|
-
fingerprints,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
// Failed/timed-out: fingerprint the failure and stop if it repeats.
|
|
128
|
-
fingerprints.push(computeNormalizedFailureFingerprint({
|
|
129
|
-
failureOwner: owner,
|
|
130
|
-
protocolFailureReason,
|
|
131
|
-
invalidatedFactIds: [],
|
|
132
|
-
}));
|
|
133
|
-
const nonConvergence = evaluateNonConvergence({
|
|
134
|
-
fingerprints,
|
|
135
|
-
changedFiles: await readChangedFiles(),
|
|
136
|
-
// Budget breach already short-circuited above; fail-closed here.
|
|
137
|
-
budgetBreached: false,
|
|
138
|
-
});
|
|
139
|
-
if (nonConvergence.terminal) {
|
|
140
|
-
return {
|
|
141
|
-
terminalReason: nonConvergence.terminalReason,
|
|
142
|
-
attempts: attempt,
|
|
143
|
-
budgetAggregate,
|
|
144
|
-
fingerprints,
|
|
145
|
-
capsule: buildCapsuleOnTerminal({
|
|
146
|
-
parentRunId,
|
|
147
|
-
deps,
|
|
148
|
-
owner,
|
|
149
|
-
protocolFailureReason,
|
|
150
|
-
terminalReason: "non-converging",
|
|
151
|
-
}),
|
|
152
|
-
detail: nonConvergence.reason,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
return {
|
|
156
|
-
terminalReason: "failed",
|
|
157
|
-
attempts: attempt,
|
|
158
|
-
budgetAggregate,
|
|
159
|
-
fingerprints,
|
|
160
|
-
capsule: buildCapsuleOnTerminal({
|
|
161
|
-
parentRunId,
|
|
162
|
-
deps,
|
|
163
|
-
owner,
|
|
164
|
-
protocolFailureReason,
|
|
165
|
-
terminalReason: "failed",
|
|
166
|
-
}),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
// Non-terminal: gate on liveness + interrupt before advancing.
|
|
170
|
-
const liveness = evaluateNodeLiveness({ node: deps.node ?? {}, now: now() });
|
|
171
|
-
if (liveness.status === "needs-attention") {
|
|
172
|
-
return {
|
|
173
|
-
terminalReason: "needs-attention",
|
|
174
|
-
attempts: attempt,
|
|
175
|
-
budgetAggregate,
|
|
176
|
-
fingerprints,
|
|
177
|
-
detail: "node liveness requires attention; abort recovery loop",
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
const interrupt = await readInterrupt();
|
|
181
|
-
const projected = projectedInterruptStatus(interrupt);
|
|
182
|
-
if (projected === "requested" ||
|
|
183
|
-
projected === "acknowledged" ||
|
|
184
|
-
projected === "needs-reconcile") {
|
|
185
|
-
return {
|
|
186
|
-
terminalReason: "interrupted",
|
|
187
|
-
attempts: attempt,
|
|
188
|
-
budgetAggregate,
|
|
189
|
-
fingerprints,
|
|
190
|
-
detail: `interrupt projection: ${projected}`,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
await runOperation(operationId, deps.operationRunnerDeps);
|
|
194
|
-
}
|
|
195
|
-
return {
|
|
196
|
-
terminalReason: "max-attempts",
|
|
197
|
-
attempts: maxAttempts,
|
|
198
|
-
budgetAggregate,
|
|
199
|
-
fingerprints,
|
|
200
|
-
detail: `recovery loop exceeded maxAttempts=${maxAttempts}`,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { assembleCumulativeDiffContext, } from "./frontend-review-context.js";
|
|
3
|
-
import { deterministicSha256 } from "./frontend-implementation-contract.js";
|
|
4
|
-
/**
|
|
5
|
-
* M6 candidate lineage + operation budget primitives (pure functions).
|
|
6
|
-
*
|
|
7
|
-
* Self-contained types only: this module never extends `DagRunState`/`DagSpec`
|
|
8
|
-
* and never mutates the legacy operation/budget modules — it only imports them.
|
|
9
|
-
* Candidate lineage distinguishes a committed parent candidate (retained into
|
|
10
|
-
* lineage) from an uncommitted partial write (CAS-rolled back, never retained).
|
|
11
|
-
*/
|
|
12
|
-
const hash64 = z.string().regex(/^[a-f0-9]{64}$/);
|
|
13
|
-
/* ------------------------------------------------------------------ *
|
|
14
|
-
* Commit state (AC-002). *
|
|
15
|
-
* ------------------------------------------------------------------ */
|
|
16
|
-
export const commitStateSchema = z.enum([
|
|
17
|
-
"uncommitted-partial",
|
|
18
|
-
"committed-candidate",
|
|
19
|
-
]);
|
|
20
|
-
/**
|
|
21
|
-
* Deterministic commit-state classifier. A journal that reached `completed`
|
|
22
|
-
* (or a restore result that did) means the parent candidate was committed and
|
|
23
|
-
* must be retained into lineage. Anything else — `blocked`, an uncommitted
|
|
24
|
-
* half-write, or a rollback that could not be proven — is fail-closed to
|
|
25
|
-
* `uncommitted-partial` and is never retained.
|
|
26
|
-
*/
|
|
27
|
-
export function classifyCommitState(input) {
|
|
28
|
-
if (input.journalState === "completed") {
|
|
29
|
-
return "committed-candidate";
|
|
30
|
-
}
|
|
31
|
-
if (input.restoreResult?.state === "completed") {
|
|
32
|
-
return "committed-candidate";
|
|
33
|
-
}
|
|
34
|
-
return "uncommitted-partial";
|
|
35
|
-
}
|
|
36
|
-
/* ------------------------------------------------------------------ *
|
|
37
|
-
* Candidate lineage (AC-002). *
|
|
38
|
-
* ------------------------------------------------------------------ */
|
|
39
|
-
export const candidateLineageV1Schema = z
|
|
40
|
-
.object({
|
|
41
|
-
schemaVersion: z.literal(1),
|
|
42
|
-
taskBaselineHash: hash64,
|
|
43
|
-
parentRunId: z.string().min(1),
|
|
44
|
-
parentCandidateHash: hash64,
|
|
45
|
-
runBaselineHash: hash64,
|
|
46
|
-
runDeltaHash: hash64,
|
|
47
|
-
cumulativeDiffHash: hash64,
|
|
48
|
-
commitState: commitStateSchema,
|
|
49
|
-
})
|
|
50
|
-
.strict();
|
|
51
|
-
/**
|
|
52
|
-
* taskBaseline → candidate cumulative diff digest (AC-004). Reuses the
|
|
53
|
-
* deterministic `assembleCumulativeDiffContext` and returns only its
|
|
54
|
-
* `cumulativeDigest`, so the lineage hash is stable regardless of input order.
|
|
55
|
-
*/
|
|
56
|
-
export function computeCumulativeDiffHash(input) {
|
|
57
|
-
return assembleCumulativeDiffContext(input).cumulativeDigest;
|
|
58
|
-
}
|
|
59
|
-
function pushAggregateBreach(aggregate, dimension, consumed, limit, now) {
|
|
60
|
-
if (limit === undefined || consumed <= limit)
|
|
61
|
-
return;
|
|
62
|
-
const duplicate = aggregate.breaches.some((existing) => existing.dimension === dimension && existing.limit === limit);
|
|
63
|
-
if (!duplicate) {
|
|
64
|
-
aggregate.breaches.push({
|
|
65
|
-
dimension,
|
|
66
|
-
limit,
|
|
67
|
-
consumed,
|
|
68
|
-
at: now().toISOString(),
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
if (aggregate.mode === "hard") {
|
|
72
|
-
aggregate.status = "breached";
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Fold parent + all continuation/recovery run ledgers into a single operation
|
|
77
|
-
* budget aggregate and evaluate it against the operation total `CampaignBudget`
|
|
78
|
-
* limits. Missing token samples never count as zero (aligned with the ledger
|
|
79
|
-
* contract): if any ledger lacks a token total, the aggregate token total is
|
|
80
|
-
* `null` and no token breach is derived.
|
|
81
|
-
*/
|
|
82
|
-
export function foldOperationBudgets(input) {
|
|
83
|
-
const now = input.now ?? (() => new Date());
|
|
84
|
-
const aggregate = {
|
|
85
|
-
schemaVersion: 1,
|
|
86
|
-
mode: input.budget.mode,
|
|
87
|
-
limits: { ...input.budget.limits },
|
|
88
|
-
consumed: {
|
|
89
|
-
executorCalls: 0,
|
|
90
|
-
wallTimeMs: 0,
|
|
91
|
-
tokens: null,
|
|
92
|
-
repairPasses: 0,
|
|
93
|
-
peakContextChars: 0,
|
|
94
|
-
missingTokenNodeIds: [],
|
|
95
|
-
},
|
|
96
|
-
breaches: [],
|
|
97
|
-
status: input.budget.mode === "record-only" ? "record-only" : "ok",
|
|
98
|
-
};
|
|
99
|
-
let anyUnknownTokens = false;
|
|
100
|
-
let knownTokenSum = 0;
|
|
101
|
-
const missing = new Set();
|
|
102
|
-
for (const ledger of input.ledgers) {
|
|
103
|
-
aggregate.consumed.executorCalls += ledger.consumed.executorCalls;
|
|
104
|
-
aggregate.consumed.wallTimeMs += ledger.consumed.wallTimeMs;
|
|
105
|
-
aggregate.consumed.repairPasses += ledger.consumed.repairPasses;
|
|
106
|
-
aggregate.consumed.peakContextChars = Math.max(aggregate.consumed.peakContextChars, ledger.consumed.peakContextChars);
|
|
107
|
-
if (ledger.consumed.tokens === null) {
|
|
108
|
-
anyUnknownTokens = true;
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
knownTokenSum += ledger.consumed.tokens;
|
|
112
|
-
}
|
|
113
|
-
for (const nodeId of ledger.consumed.missingTokenNodeIds) {
|
|
114
|
-
missing.add(nodeId);
|
|
115
|
-
}
|
|
116
|
-
aggregate.breaches.push(...ledger.breaches);
|
|
117
|
-
}
|
|
118
|
-
aggregate.consumed.tokens = anyUnknownTokens ? null : knownTokenSum;
|
|
119
|
-
aggregate.consumed.missingTokenNodeIds = [...missing].sort((a, b) => a.localeCompare(b));
|
|
120
|
-
if (aggregate.mode === "hard") {
|
|
121
|
-
const limits = aggregate.limits;
|
|
122
|
-
pushAggregateBreach(aggregate, "executorCalls", aggregate.consumed.executorCalls, limits.maxExecutorCalls, now);
|
|
123
|
-
pushAggregateBreach(aggregate, "wallTimeMs", aggregate.consumed.wallTimeMs, limits.maxWallTimeMs, now);
|
|
124
|
-
pushAggregateBreach(aggregate, "repairPasses", aggregate.consumed.repairPasses, limits.maxRepairPasses, now);
|
|
125
|
-
pushAggregateBreach(aggregate, "contextChars", aggregate.consumed.peakContextChars, limits.maxContextChars, now);
|
|
126
|
-
if (aggregate.consumed.tokens !== null) {
|
|
127
|
-
pushAggregateBreach(aggregate, "tokens", aggregate.consumed.tokens, limits.maxTokens, now);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return aggregate;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Stable, pure failure fingerprint. No wall clock, no random bytes: identical
|
|
134
|
-
* inputs always produce the same digest, so repeated failures across recovery
|
|
135
|
-
* runs can be compared for non-convergence detection.
|
|
136
|
-
*/
|
|
137
|
-
export function computeNormalizedFailureFingerprint(input) {
|
|
138
|
-
const payload = {
|
|
139
|
-
failureOwner: input.failureOwner ?? "unknown",
|
|
140
|
-
protocolFailureReason: input.protocolFailureReason ?? "",
|
|
141
|
-
restartPhase: input.restartPhase ?? "unknown",
|
|
142
|
-
invalidatedFactIds: [...(input.invalidatedFactIds ?? [])].sort(),
|
|
143
|
-
commitState: input.commitState ?? "uncommitted-partial",
|
|
144
|
-
};
|
|
145
|
-
return deterministicSha256(payload);
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Bounded-loop stop decision. A repeated failure fingerprint with an empty
|
|
149
|
-
* change manifest proves no workspace progress → `non-converging`. An exceeded
|
|
150
|
-
* operation total budget → `budget-exhausted`. Otherwise continue (but never
|
|
151
|
-
* unbounded: the controller still caps attempts).
|
|
152
|
-
*/
|
|
153
|
-
export function evaluateNonConvergence(input) {
|
|
154
|
-
if (input.budgetBreached) {
|
|
155
|
-
return {
|
|
156
|
-
terminal: true,
|
|
157
|
-
terminalReason: "budget-exhausted",
|
|
158
|
-
reason: "operation total budget breached; stop before exhausting resources",
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
const counts = new Map();
|
|
162
|
-
for (const fingerprint of input.fingerprints) {
|
|
163
|
-
counts.set(fingerprint, (counts.get(fingerprint) ?? 0) + 1);
|
|
164
|
-
}
|
|
165
|
-
const repeated = [...counts.values()].some((count) => count >= 2);
|
|
166
|
-
if (repeated && input.changedFiles.length === 0) {
|
|
167
|
-
return {
|
|
168
|
-
terminal: true,
|
|
169
|
-
terminalReason: "non-converging",
|
|
170
|
-
reason: "repeated failure fingerprint with an empty change manifest; recovery would loop forever",
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
return {
|
|
174
|
-
terminal: false,
|
|
175
|
-
terminalReason: "continue",
|
|
176
|
-
reason: "progress observable or fingerprint changed; continue bounded recovery",
|
|
177
|
-
};
|
|
178
|
-
}
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { readTypedEventStoreFromJsonl, } from "./frontend-typed-event-store.js";
|
|
4
|
-
/**
|
|
5
|
-
* Frontend review findings → downstream work (direction A).
|
|
6
|
-
*
|
|
7
|
-
* When `frontend-review-pi` commits `request_review_changes`, the implementation
|
|
8
|
-
* must not silently finish: the run becomes "not passed" (partial_failed +
|
|
9
|
-
* terminalReason=review-request-changes), and the committed findings are
|
|
10
|
-
* rendered into a downstream-task PRD draft that a later DAG round can consume
|
|
11
|
-
* instead of repeating the previous flow. Rendering is deterministic (pure);
|
|
12
|
-
* only the JSONL read and PRD write are IO.
|
|
13
|
-
*
|
|
14
|
-
* The design-review analog (`request_design_changes`) reuses the same PRD
|
|
15
|
-
* rendering and typed-fact channel: a design rejection is a definite finding
|
|
16
|
-
* (not a protocol/environment error), so it auto-re-enters the next plan round
|
|
17
|
-
* with the committed design findings instead of terminating the run.
|
|
18
|
-
*/
|
|
19
|
-
export const FRONTEND_REVIEW_FINDINGS_PRD_REL = "contracts/frontend-review-findings-prd.md";
|
|
20
|
-
/** Review node id whose committed typed facts drive the findings PRD. */
|
|
21
|
-
export const FRONTEND_REVIEW_NODE_ID = "frontend-review-pi";
|
|
22
|
-
/** Design review node id whose committed typed facts drive the findings PRD. */
|
|
23
|
-
export const FRONTEND_DESIGN_REVIEW_NODE_ID = "frontend-design-review-pi";
|
|
24
|
-
/** Deterministic issueCategory → failure owner mapping (A+B AC-009 §4.10). */
|
|
25
|
-
export function classifyReviewIssueCategoryFailureOwner(issueCategory) {
|
|
26
|
-
switch (issueCategory) {
|
|
27
|
-
case "implementation-mismatch":
|
|
28
|
-
return "implementation";
|
|
29
|
-
case "approved-design-defect":
|
|
30
|
-
return "plan";
|
|
31
|
-
case "target-surface-defect":
|
|
32
|
-
return "scout";
|
|
33
|
-
case "contract-requirement-gap":
|
|
34
|
-
return "contract";
|
|
35
|
-
case "unknown":
|
|
36
|
-
default:
|
|
37
|
-
return "unknown";
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
const API_SIGNAL_RE = /\b(api|fetch|axios|graphql|endpoint|远程|接口)\b/i;
|
|
41
|
-
/** Negations for the API signal. CJK "无 API/无接口" has no \b boundary, so
|
|
42
|
-
* it is matched separately from the ASCII no api/not-needed forms. */
|
|
43
|
-
const API_NEGATION_RE = /(?:^|[^\w])(?:no\s+api|not-needed)(?:$|[^\w])|无\s*api|无\s*接口/i;
|
|
44
|
-
const SMALL_SIGNAL_WORDS = [
|
|
45
|
-
"单文件局部修复",
|
|
46
|
-
"局部样式",
|
|
47
|
-
"文案调整",
|
|
48
|
-
"无远程接口",
|
|
49
|
-
];
|
|
50
|
-
function topLevelDir(p) {
|
|
51
|
-
const normalized = p.replace(/\\/g, "/");
|
|
52
|
-
const first = normalized.split("/").filter(Boolean)[0] ?? "";
|
|
53
|
-
return first;
|
|
54
|
-
}
|
|
55
|
-
/** Collect unique non-empty finding `file` values (sorted, de-duplicated). */
|
|
56
|
-
function findingTargetFiles(fact) {
|
|
57
|
-
const files = new Set();
|
|
58
|
-
for (const finding of fact.findings) {
|
|
59
|
-
if (typeof finding.file === "string" && finding.file.trim()) {
|
|
60
|
-
files.add(finding.file.trim());
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return [...files].sort();
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Deterministically derive the fix scope for the downstream task. Same fact
|
|
67
|
-
* always yields the same scope. The shape mirrors `resolveFrontendTaskShape`
|
|
68
|
-
* signals (concentrated paths, no remote API → micro/small) so that when the
|
|
69
|
-
* PRD draft is used as the next task's requirement, the frontend DAG generator
|
|
70
|
-
* routes a small fix to micro/small instead of the full 11-node standard
|
|
71
|
-
* topology.
|
|
72
|
-
*/
|
|
73
|
-
export function deriveFrontendReviewFixScope(fact) {
|
|
74
|
-
const targetFiles = findingTargetFiles(fact);
|
|
75
|
-
const blob = fact.findings
|
|
76
|
-
.map((f) => `${f.issue} ${f.requiredChange ?? ""} ${f.file ?? ""}`)
|
|
77
|
-
.join("\n");
|
|
78
|
-
const hasApiSignal = API_SIGNAL_RE.test(blob) && !API_NEGATION_RE.test(blob);
|
|
79
|
-
const topDirs = new Set(targetFiles.map(topLevelDir));
|
|
80
|
-
const concentrated = targetFiles.length > 0 && topDirs.size <= 1 && targetFiles.length <= 4;
|
|
81
|
-
let shape;
|
|
82
|
-
let complexity;
|
|
83
|
-
let reason;
|
|
84
|
-
if (targetFiles.length === 0) {
|
|
85
|
-
shape = "standard";
|
|
86
|
-
complexity = "medium";
|
|
87
|
-
reason =
|
|
88
|
-
"findings carry no file references; scope unknown, keep standard topology";
|
|
89
|
-
}
|
|
90
|
-
else if (hasApiSignal) {
|
|
91
|
-
shape = "standard";
|
|
92
|
-
complexity = "medium";
|
|
93
|
-
reason = `findings reference a remote API signal; standard topology required (files=${targetFiles.length})`;
|
|
94
|
-
}
|
|
95
|
-
else if (concentrated && targetFiles.length <= 1) {
|
|
96
|
-
shape = "micro";
|
|
97
|
-
complexity = "trivial";
|
|
98
|
-
reason = "single-file concentrated fix without remote API";
|
|
99
|
-
}
|
|
100
|
-
else if (concentrated) {
|
|
101
|
-
shape = "small";
|
|
102
|
-
complexity = "low";
|
|
103
|
-
reason = `concentrated fix across ${targetFiles.length} file(s) in one top-level directory`;
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
shape = "standard";
|
|
107
|
-
complexity = "medium";
|
|
108
|
-
reason = `fix spans ${targetFiles.length} file(s) across ${topDirs.size} top-level directories; standard topology`;
|
|
109
|
-
}
|
|
110
|
-
const allowedPaths = [
|
|
111
|
-
...new Set([
|
|
112
|
-
...targetFiles,
|
|
113
|
-
...targetFiles.map((f) => `${topLevelDir(f)}/**`),
|
|
114
|
-
]),
|
|
115
|
-
].sort();
|
|
116
|
-
return {
|
|
117
|
-
shape,
|
|
118
|
-
complexity,
|
|
119
|
-
targetFiles,
|
|
120
|
-
allowedPaths,
|
|
121
|
-
smallSignals: [...SMALL_SIGNAL_WORDS],
|
|
122
|
-
hasApiSignal,
|
|
123
|
-
reason,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Read the committed `request_review_changes` fact from the review node's typed
|
|
128
|
-
* event store. Returns undefined when no committed request fact exists (e.g.
|
|
129
|
-
* approve_review, missing store, or an unreadable store).
|
|
130
|
-
*/
|
|
131
|
-
export async function readCommittedReviewRequestFact(input) {
|
|
132
|
-
const { runDir: reviewRunDir } = input;
|
|
133
|
-
const reviewNodeId = input.reviewNodeId ?? FRONTEND_REVIEW_NODE_ID;
|
|
134
|
-
return readCommittedRequestFactByKind({
|
|
135
|
-
runDir: reviewRunDir,
|
|
136
|
-
nodeId: reviewNodeId,
|
|
137
|
-
factsFile: "review-typed-facts.jsonl",
|
|
138
|
-
kind: "request_review_changes",
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Read the committed `request_design_changes` fact from the design review
|
|
143
|
-
* node's typed event store. Returns undefined when no committed request fact
|
|
144
|
-
* exists (e.g. approve_design, missing store, or an unreadable store).
|
|
145
|
-
*/
|
|
146
|
-
export async function readCommittedDesignRequestFact(input) {
|
|
147
|
-
const { runDir: designRunDir } = input;
|
|
148
|
-
const designNodeId = input.designNodeId ?? FRONTEND_DESIGN_REVIEW_NODE_ID;
|
|
149
|
-
return readCommittedRequestFactByKind({
|
|
150
|
-
runDir: designRunDir,
|
|
151
|
-
nodeId: designNodeId,
|
|
152
|
-
factsFile: "design-typed-facts.jsonl",
|
|
153
|
-
kind: "request_design_changes",
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
async function readCommittedRequestFactByKind(input) {
|
|
157
|
-
let records;
|
|
158
|
-
try {
|
|
159
|
-
records = await readTypedEventStoreFromJsonl(path.join(input.runDir, input.nodeId, input.factsFile));
|
|
160
|
-
}
|
|
161
|
-
catch {
|
|
162
|
-
return undefined;
|
|
163
|
-
}
|
|
164
|
-
const committed = records.filter((record) => record.phase === "committed");
|
|
165
|
-
if (committed.length === 0)
|
|
166
|
-
return undefined;
|
|
167
|
-
const request = committed.find((record) => record.fact.kind === input.kind);
|
|
168
|
-
if (!request)
|
|
169
|
-
return undefined;
|
|
170
|
-
const fact = request.fact;
|
|
171
|
-
if (typeof fact !== "object" || fact === null)
|
|
172
|
-
return undefined;
|
|
173
|
-
return fact;
|
|
174
|
-
}
|
|
175
|
-
function bulletList(items) {
|
|
176
|
-
if (!items || items.length === 0)
|
|
177
|
-
return "_(none)_";
|
|
178
|
-
return items.map((item) => `- ${item}`).join("\n");
|
|
179
|
-
}
|
|
180
|
-
function findingLines(findings) {
|
|
181
|
-
if (findings.length === 0)
|
|
182
|
-
return "_(none)_";
|
|
183
|
-
return findings
|
|
184
|
-
.map((finding) => {
|
|
185
|
-
const location = [finding.file, finding.line]
|
|
186
|
-
.filter((value) => value !== undefined)
|
|
187
|
-
.join(":");
|
|
188
|
-
const prefix = location ? `[${location}] ` : "";
|
|
189
|
-
const requiredChange = finding.requiredChange
|
|
190
|
-
? `\n - requiredChange: ${finding.requiredChange}`
|
|
191
|
-
: "";
|
|
192
|
-
return `- ${prefix}${finding.severity}: ${finding.issue}${requiredChange}`;
|
|
193
|
-
})
|
|
194
|
-
.join("\n");
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Deterministically render a downstream-task PRD draft from a committed
|
|
198
|
-
* `request_review_changes` / `request_design_changes` fact. Same fact always
|
|
199
|
-
* renders byte-identical output; the parent run id and review node id are the
|
|
200
|
-
* only context.
|
|
201
|
-
*/
|
|
202
|
-
export function renderFrontendReviewFindingsPrd(input) {
|
|
203
|
-
const { parentRunId, fact } = input;
|
|
204
|
-
const isDesign = fact.kind === "request_design_changes";
|
|
205
|
-
const reviewNodeId = input.reviewNodeId ??
|
|
206
|
-
(isDesign ? FRONTEND_DESIGN_REVIEW_NODE_ID : FRONTEND_REVIEW_NODE_ID);
|
|
207
|
-
const restartPhase = classifyReviewIssueCategoryFailureOwner(fact.issueCategory);
|
|
208
|
-
const scope = deriveFrontendReviewFixScope(fact);
|
|
209
|
-
const targetSummary = scope.targetFiles.length > 0
|
|
210
|
-
? scope.targetFiles.join(", ")
|
|
211
|
-
: fact.issueCategory;
|
|
212
|
-
return [
|
|
213
|
-
`# 修复 ${isDesign ? "设计" : "review"} 发现(${scope.shape}):${targetSummary}`,
|
|
214
|
-
"",
|
|
215
|
-
"## 背景",
|
|
216
|
-
"",
|
|
217
|
-
`- parentRunId: \`${parentRunId}\``,
|
|
218
|
-
`- reviewNodeId: \`${reviewNodeId}\``,
|
|
219
|
-
`- typedVerdict: \`${fact.kind}\``,
|
|
220
|
-
`- issueCategory: \`${fact.issueCategory}\``,
|
|
221
|
-
`- restartPhase/failureOwner: \`${restartPhase}\``,
|
|
222
|
-
"",
|
|
223
|
-
"## 修复范围(供下一轮任务生成路由)",
|
|
224
|
-
"",
|
|
225
|
-
`- 建议 shape:\`${scope.shape}\`(micro=7 / small=10 / standard=11 节点)`,
|
|
226
|
-
`- 建议 complexity:\`${scope.complexity}\``,
|
|
227
|
-
`- 推导依据:${scope.reason}`,
|
|
228
|
-
...(scope.hasApiSignal ? ["- ⚠️ findings 含远程 API/接口信号,走 standard 拓扑"] : []),
|
|
229
|
-
scope.targetFiles.length > 0
|
|
230
|
-
? [`- 目标文件:${scope.targetFiles.map((f) => `\`${f}\``).join(", ")}`]
|
|
231
|
-
: ["- 目标文件:_(findings 未提供 file 引用,需人工补全)_"],
|
|
232
|
-
scope.allowedPaths.length > 0
|
|
233
|
-
? [`- 建议 allowedPaths:${scope.allowedPaths.map((p) => `\`${p}\``).join(", ")}`]
|
|
234
|
-
: [],
|
|
235
|
-
scope.shape === "micro" || scope.shape === "small"
|
|
236
|
-
? [`- 范围信号:${scope.smallSignals.join("、")}(供 shape 分类器识别,避免退化为全节点流程)`]
|
|
237
|
-
: [],
|
|
238
|
-
"",
|
|
239
|
-
"## 问题清单",
|
|
240
|
-
"",
|
|
241
|
-
findingLines(fact.findings),
|
|
242
|
-
"",
|
|
243
|
-
"## 证据引用",
|
|
244
|
-
"",
|
|
245
|
-
bulletList(fact.evidenceRefs),
|
|
246
|
-
"",
|
|
247
|
-
"## 修复目标",
|
|
248
|
-
"",
|
|
249
|
-
`- 按 issueCategory 映射的重启阶段(${restartPhase})定点修复,不复用上一轮未通过的实现。`,
|
|
250
|
-
"- 逐条关闭上面「问题清单」中的 findings,并保留上一轮已通过的验证目标继续有效。",
|
|
251
|
-
`- 完成修复后重新走 frontend DAG 的 ${isDesign ? "plan → design" : "implement → verify → review"} 链,直到${isDesign ? " design-review 提交 approve_design" : " review 提交 approve_review"}。`,
|
|
252
|
-
"",
|
|
253
|
-
].join("\n");
|
|
254
|
-
}
|
|
255
|
-
/** Write the findings PRD draft under the run dir (run-owned artifact). */
|
|
256
|
-
export async function writeFrontendReviewFindingsPrd(input) {
|
|
257
|
-
const markdown = renderFrontendReviewFindingsPrd(input);
|
|
258
|
-
const abs = path.join(input.runDir, FRONTEND_REVIEW_FINDINGS_PRD_REL);
|
|
259
|
-
await mkdir(path.dirname(abs), { recursive: true });
|
|
260
|
-
await writeFile(abs, markdown, "utf8");
|
|
261
|
-
return FRONTEND_REVIEW_FINDINGS_PRD_REL;
|
|
262
|
-
}
|
|
263
|
-
export async function readFrontendReviewFindingsPrd(runDir) {
|
|
264
|
-
try {
|
|
265
|
-
return await readFile(path.join(runDir, FRONTEND_REVIEW_FINDINGS_PRD_REL), "utf8");
|
|
266
|
-
}
|
|
267
|
-
catch {
|
|
268
|
-
return undefined;
|
|
269
|
-
}
|
|
270
|
-
}
|