@tea-agent/loop-agent 0.39.0-beta.0 → 0.39.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -1
- package/CHANGELOG.md +248 -95
- package/README.md +9 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +23 -5
- package/dist/cli/program.js +21 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/cli.js +2 -1
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +455 -28
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +2306 -61
- package/dist/executors/pi-executor.js +7 -0
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +74 -28
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/pi-sdk-executor.js +180 -33
- package/dist/executors/shell-executor.js +1249 -224
- package/dist/executors/shell-write-guard.js +14 -0
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/infrastructure/harness/atomic-write.js +23 -0
- package/dist/shared/dag-failure-category.js +150 -0
- package/dist/shared/dag-prompt-override.js +27 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +39 -0
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +70 -15
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/config-types.js +50 -5
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +18 -1
- package/dist/task/contract/schema.js +4 -1
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/frontend-project-capability.js +199 -18
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +477 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/task/source-prepare/prepare.js +298 -1
- package/dist/task/source-prepare/semantic-intake.js +25 -5
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/task/source-references.js +22 -1
- package/dist/worker/cli.js +18 -2
- package/dist/worker/console/chat/chat-event-store.js +62 -4
- package/dist/worker/console/chat/context-panel.js +7 -0
- package/dist/worker/console/chat/deferred-turn.js +12 -0
- package/dist/worker/console/chat/operation-card.js +8 -1
- package/dist/worker/console/chat/pi-console-config.js +81 -10
- package/dist/worker/console/chat/pi-runtime.js +264 -16
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/routes.js +298 -32
- package/dist/worker/console/chat/session-stats.js +179 -0
- package/dist/worker/console/chat/session-store.js +93 -63
- package/dist/worker/console/chat/todos.js +115 -0
- package/dist/worker/console/chat/tool-preview.js +42 -0
- package/dist/worker/console/chat/turn-process.js +20 -55
- package/dist/worker/console/chat/user-questions.js +266 -0
- package/dist/worker/console/chat/workspace-landing.js +14 -0
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/draft-store.js +49 -0
- 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/inspect-split.js +18 -0
- package/dist/worker/console/native-directory-picker.js +13 -0
- package/dist/worker/console/operation-run-facts.js +19 -3
- package/dist/worker/console/operation-store.js +227 -7
- package/dist/worker/console/operator-actions.js +388 -0
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +355 -3
- package/dist/worker/console/security.js +67 -13
- package/dist/worker/console/server.js +609 -164
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-BAsjg7ew.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-CyIlrwZw.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-Bo4tobhH.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-Dfpow9gu.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-DPmzG_B4.js} +1 -1
- package/dist/worker/console/static/assets/channel-DSrJAn0-.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-pKQpB1UW.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-5n9YYquZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-DP3CtA2a.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-DYxMJPUy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-CS3D50Io.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-gSdDfo5_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-A48q0ZNt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-ow8Mm3DY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-CMKfLWYE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-BiTBdQPX.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DeMmPl0_.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DeMmPl0_.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-Cm7exRz_.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-D9O5qGOu.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-CkvQnako.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-B1rzp7CB.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-BP07Ipcc.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BlPzweQb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-CxPa7n0S.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-CMTZs53A.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-CxG_X52r.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-D8axtJM0.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-DIsGzn7b.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-CU9zkDG0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-DCjTmz5P.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-BksdmFzg.js} +1 -1
- package/dist/worker/console/static/assets/index-B-O5ChYq.css +1 -0
- package/dist/worker/console/static/assets/index-CfnY8AQx.js +407 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-CGzzzJtL.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-BdseB80N.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-DM9gsgha.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-3q-_9R2b.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-B8N-Mxsd.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-EPIHUUsu.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-CxwO12aa.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-bPqbVX6Y.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-KzqIvzeX.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-CyofpWiV.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-cTWJvLTP.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-BK--q27B.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-C0SDOHXK.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-Cong6YIo.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-DUVkgZx8.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-C7XgxuZg.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dzbnd5oX.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-D9Ayvwcy.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BY0hHk6G.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-Byb000cH.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DMLegPt4.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-ChMMeOsP.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-bEMEPisW.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/app/console-types.js +68 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +45 -10
- package/dist/worker/console/static-src/app/useRunProgress.js +47 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +140 -8
- package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
- package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +16 -3
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +168 -72
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +102 -21
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +184 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
- package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
- package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
- package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +337 -0
- package/dist/worker/console/workspace-context.js +344 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/materialize/harness-task-lineage.js +5 -2
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observability/read-model.js +22 -0
- package/dist/worker/observe/node-input.js +160 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +224 -3
- package/dist/worker/observe/server.js +4 -0
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +90 -4
- package/dist/worker/observe/static/app.js +18 -70
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
- package/dist/worker/observe/static/dag-node-purpose.js +208 -0
- package/dist/worker/observe/static/format.js +60 -0
- package/dist/worker/observe/static/index.html +6 -1
- package/dist/worker/observe/static/inspect-workspace.js +307 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.css +145 -20
- package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
- package/dist/worker/observe/static/operator-chrome.js +330 -96
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/router.d.ts +46 -0
- package/dist/worker/observe/static/router.js +25 -1
- package/dist/worker/observe/static/state.d.ts +50 -0
- package/dist/worker/observe/static/state.js +184 -1
- package/dist/worker/observe/static/styles.css +395 -0
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +1067 -9
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +14 -5
- package/dist/worker/observe/static/views/dashboard.js +6 -4
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/night.js +2 -2
- package/dist/worker/observe/static/views/pool.js +1 -0
- package/dist/worker/observe/static/views/run.js +3 -1
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/worker/observe/static/views/task.js +56 -1
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +49 -3
- package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
- package/dist/workflows/dag/backend-test-writer-completeness.js +10 -5
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -1
- package/dist/workflows/dag/failure-category.js +7 -116
- 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 +534 -178
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +371 -393
- 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 +7 -424
- package/dist/workflows/dag/frontend-review-context.js +288 -14
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-shadow-dual-write.js +814 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +409 -0
- package/dist/workflows/dag/frontend-shape.js +427 -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-test-case-checklist.js +30 -4
- package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
- package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
- package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
- package/dist/workflows/dag/frontend-test-html-report.js +3 -1
- package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
- package/dist/workflows/dag/frontend-test-layout.js +159 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -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 +52 -17
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +272 -0
- package/dist/workflows/dag/frontend-writer-status.js +244 -0
- package/dist/workflows/dag/init-hybrid.js +984 -618
- package/dist/workflows/dag/interrupt-request.js +10 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/node-execution.js +468 -34
- package/dist/workflows/dag/prompt.js +44 -19
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +124 -1
- package/dist/workflows/dag/rerun-plan.js +144 -5
- package/dist/workflows/dag/rerun-run.js +81 -6
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +318 -8
- package/dist/workflows/dag/runner.js +429 -122
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +321 -31
- package/dist/workflows/dag/upstream-artifacts.js +0 -4
- package/dist/workflows/dag/validate.js +92 -32
- package/docs/README.md +2 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +9 -0
- package/docs/operations/README.md +1 -1
- package/docs/operations/local-development-environment.md +21 -0
- package/docs/templates/README.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +45 -1
- package/docs/templates/frontend-implementation-contract.schema.json +684 -24
- package/docs/templates/frontend-test-case-checklist.md +2 -2
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +3 -3
- package/package.json +7 -1
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/frontend-bounded-implement/SKILL.md +8 -14
- package/skills/frontend-design-review/SKILL.md +20 -41
- package/skills/frontend-implementation/SKILL.md +3 -4
- package/skills/frontend-implementation/references/design-spec.md +15 -10
- package/skills/frontend-implementation/references/node-contracts.md +23 -13
- package/skills/frontend-review/SKILL.md +22 -14
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/loop-agent/references/command-reference.md +12 -4
- package/skills/loop-agent/references/hybrid-dag.md +8 -1
- package/skills/playwright-cli/SKILL.md +23 -1
- package/skills/playwright-cli-case-generator/SKILL.md +29 -8
- package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/index-DTZOKgAn.js +0 -404
- package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
|
@@ -1,17 +1,194 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { readFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { writeDagRunJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
|
|
4
5
|
import { formatFrontendWorktreeDiffStdout, runFrontendWorktreeDiffGate, } from "./frontend-worktree-diff.js";
|
|
5
6
|
import { frontendLintAssessmentArtifactSchema } from "./frontend-lint-baseline.js";
|
|
6
|
-
import { frontendImplementationContractSchema } from "./frontend-implementation-contract.js";
|
|
7
|
-
import {
|
|
7
|
+
import { deterministicSha256, frontendImplementationContractSchema, } from "./frontend-implementation-contract.js";
|
|
8
|
+
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
9
|
+
import { ledgerStatsLine, parseLedgerJson } from "../../task/source-prepare/ledger.js";
|
|
8
10
|
export const FRONTEND_REVIEW_CONTEXT_SCHEMA_ID = "frontend-review-context-v1";
|
|
11
|
+
/**
|
|
12
|
+
* Deterministically assemble the cumulative diff from a task baseline and the
|
|
13
|
+
* current candidate snapshot. Same input always yields the same output:
|
|
14
|
+
* paths are sorted, statuses are derived from a fixed decision table, and both
|
|
15
|
+
* digests use canonical JSON hashing.
|
|
16
|
+
*/
|
|
17
|
+
export function assembleCumulativeDiffContext(input) {
|
|
18
|
+
const baselineByPath = new Map(input.baselineEntries.map((entry) => [entry.path, entry.sha256]));
|
|
19
|
+
const currentByPath = new Map(input.currentEntries.map((entry) => [entry.path, entry.sha256]));
|
|
20
|
+
const allPaths = new Set([
|
|
21
|
+
...baselineByPath.keys(),
|
|
22
|
+
...currentByPath.keys(),
|
|
23
|
+
]);
|
|
24
|
+
const sortedPaths = [...allPaths].sort();
|
|
25
|
+
const perPath = sortedPaths
|
|
26
|
+
.map((p) => {
|
|
27
|
+
const baselineSha256 = baselineByPath.get(p) ?? null;
|
|
28
|
+
const hasCurrent = currentByPath.has(p);
|
|
29
|
+
const currentSha256 = hasCurrent
|
|
30
|
+
? (currentByPath.get(p) ?? null)
|
|
31
|
+
: null;
|
|
32
|
+
let status;
|
|
33
|
+
if (baselineSha256 === null) {
|
|
34
|
+
status = currentSha256 === null ? "deleted" : "added";
|
|
35
|
+
}
|
|
36
|
+
else if (currentSha256 === null) {
|
|
37
|
+
status = hasCurrent ? "deleted" : "unchanged";
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
status =
|
|
41
|
+
currentSha256 === baselineSha256 ? "unchanged" : "modified";
|
|
42
|
+
}
|
|
43
|
+
return { path: p, status, baselineSha256, currentSha256 };
|
|
44
|
+
})
|
|
45
|
+
// Cumulative diff carries only actual changes. The worktree baseline is
|
|
46
|
+
// the full repository inventory (git ls-files -co), so an unfiltered
|
|
47
|
+
// perPath enumerates thousands of unchanged hashes (e.g. 3000+ entries
|
|
48
|
+
// ≈ 700KB), ballooning the review context and blowing the review node
|
|
49
|
+
// read budget on a single read. Unchanged entries add no diff evidence.
|
|
50
|
+
.filter((entry) => entry.status !== "unchanged");
|
|
51
|
+
const baselineDigest = deterministicSha256([...input.baselineEntries].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
52
|
+
const cumulativeDigest = deterministicSha256(perPath);
|
|
53
|
+
return {
|
|
54
|
+
schemaId: "frontend-cumulative-diff-context-v1",
|
|
55
|
+
baselineDigest,
|
|
56
|
+
perPath,
|
|
57
|
+
cumulativeDigest,
|
|
58
|
+
empty: perPath.length === 0,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function sha256File(filePath) {
|
|
62
|
+
try {
|
|
63
|
+
const bytes = await readFile(filePath);
|
|
64
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Capture the cumulative diff from the frozen worktree baseline and the
|
|
72
|
+
* run-owned diff gate output. Pure status/digest logic lives in
|
|
73
|
+
* `assembleCumulativeDiffContext`; this wrapper only does IO.
|
|
74
|
+
*/
|
|
75
|
+
export async function captureCumulativeDiffContext(input) {
|
|
76
|
+
const root = path.resolve(input.workspaceRoot);
|
|
77
|
+
let baselineEntries = [];
|
|
78
|
+
try {
|
|
79
|
+
const raw = JSON.parse(await readFile(path.join(input.runDir, "contracts/frontend-worktree-baseline.json"), "utf8"));
|
|
80
|
+
baselineEntries = (raw.files ?? [])
|
|
81
|
+
.map((file) => ({ path: file, sha256: raw.hashes?.[file] ?? null }))
|
|
82
|
+
.filter((entry) => entry.sha256 !== null);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Missing baseline → empty baseline entries (still deterministic).
|
|
86
|
+
}
|
|
87
|
+
const candidatePaths = [
|
|
88
|
+
...new Set([...input.diff.changedFiles, ...input.diff.untrackedFiles]),
|
|
89
|
+
];
|
|
90
|
+
const currentEntries = [];
|
|
91
|
+
for (const file of candidatePaths) {
|
|
92
|
+
currentEntries.push({
|
|
93
|
+
path: file,
|
|
94
|
+
sha256: await sha256File(path.join(root, file)),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return assembleCumulativeDiffContext({ baselineEntries, currentEntries });
|
|
98
|
+
}
|
|
99
|
+
const TYPED_TO_LEGACY = {
|
|
100
|
+
approve_review: "pass",
|
|
101
|
+
request_review_changes: "request-revision",
|
|
102
|
+
};
|
|
103
|
+
function isTypedReviewTerminalVerdict(value) {
|
|
104
|
+
return value === "approve_review" || value === "request_review_changes";
|
|
105
|
+
}
|
|
106
|
+
function isLegacyReviewVerdict(value) {
|
|
107
|
+
return value === "pass" || value === "request-revision";
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Deterministic, fail-closed equivalence between the typed review terminal
|
|
111
|
+
* verdict(s) observed in the attempt and the legacy JSON verdict.
|
|
112
|
+
*
|
|
113
|
+
* Fail-closed cases (all yield `match: false` with an explicit reason):
|
|
114
|
+
* - no typed terminal fact;
|
|
115
|
+
* - more than one typed terminal fact (conflicting);
|
|
116
|
+
* - an unknown typed terminal kind;
|
|
117
|
+
* - a missing/illegal legacy JSON verdict.
|
|
118
|
+
*/
|
|
119
|
+
export function compareTypedReviewToLegacyJsonVerdict(input) {
|
|
120
|
+
const kinds = [...input.typedKinds];
|
|
121
|
+
const legacyVerdict = isLegacyReviewVerdict(input.legacyVerdict ?? "")
|
|
122
|
+
? input.legacyVerdict
|
|
123
|
+
: undefined;
|
|
124
|
+
if (kinds.length === 0) {
|
|
125
|
+
return {
|
|
126
|
+
typedVerdict: undefined,
|
|
127
|
+
legacyVerdict,
|
|
128
|
+
match: false,
|
|
129
|
+
reason: "typed review terminal fact missing",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (kinds.length > 1) {
|
|
133
|
+
return {
|
|
134
|
+
typedVerdict: undefined,
|
|
135
|
+
legacyVerdict,
|
|
136
|
+
match: false,
|
|
137
|
+
reason: `conflicting typed review terminal facts: ${[...new Set(kinds)].join(",")}`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const typedVerdict = kinds[0];
|
|
141
|
+
if (!isTypedReviewTerminalVerdict(typedVerdict)) {
|
|
142
|
+
return {
|
|
143
|
+
typedVerdict: undefined,
|
|
144
|
+
legacyVerdict,
|
|
145
|
+
match: false,
|
|
146
|
+
reason: `unknown typed review terminal fact kind: ${typedVerdict}`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (!legacyVerdict) {
|
|
150
|
+
return {
|
|
151
|
+
typedVerdict,
|
|
152
|
+
legacyVerdict: undefined,
|
|
153
|
+
match: false,
|
|
154
|
+
reason: "legacy JSON verdict missing or not pass|request-revision",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const expected = TYPED_TO_LEGACY[typedVerdict];
|
|
158
|
+
return {
|
|
159
|
+
typedVerdict,
|
|
160
|
+
legacyVerdict,
|
|
161
|
+
match: expected === legacyVerdict,
|
|
162
|
+
reason: expected === legacyVerdict
|
|
163
|
+
? "typed review verdict matches legacy JSON verdict"
|
|
164
|
+
: `typed review verdict ${typedVerdict} maps to ${expected} but legacy JSON verdict is ${legacyVerdict}`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* review-context 稳定失败枚举(AC-011 / AC-HARD-005):v2 drift / manifest 失败返回
|
|
169
|
+
* 结构化、可消费的 failure code(稳定枚举 + reason 的 envelope),既有调用方无需字符串
|
|
170
|
+
* 匹配裸 Error 文本。message 保持人类可读(shell stderr 兼容),code 供机械化路由。
|
|
171
|
+
*/
|
|
172
|
+
export const FRONTEND_REVIEW_CONTEXT_FAILURE_CODES = [
|
|
173
|
+
"review-context-invalid-contract",
|
|
174
|
+
"review-context-verification-unbound",
|
|
175
|
+
"review-context-ledger-drift",
|
|
176
|
+
"review-context-manifest-missing",
|
|
177
|
+
];
|
|
178
|
+
export class FrontendReviewContextFailure extends Error {
|
|
179
|
+
code;
|
|
180
|
+
constructor(code, message, options) {
|
|
181
|
+
super(message, options);
|
|
182
|
+
this.name = "FrontendReviewContextFailure";
|
|
183
|
+
this.code = code;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
9
186
|
async function readRequiredJson(runDir, relativePath) {
|
|
10
187
|
try {
|
|
11
188
|
return JSON.parse(await readFile(path.join(runDir, relativePath), "utf8"));
|
|
12
189
|
}
|
|
13
|
-
catch {
|
|
14
|
-
throw new
|
|
190
|
+
catch (error) {
|
|
191
|
+
throw new FrontendReviewContextFailure("review-context-manifest-missing", `frontend review context missing or invalid ${relativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
15
192
|
}
|
|
16
193
|
}
|
|
17
194
|
async function readOptionalLintAssessment(runDir) {
|
|
@@ -30,10 +207,42 @@ async function readOptionalLintAssessment(runDir) {
|
|
|
30
207
|
throw new Error(`frontend review context invalid ${relativePath}`);
|
|
31
208
|
}
|
|
32
209
|
}
|
|
210
|
+
async function readOptionalRepairAssessment(runDir) {
|
|
211
|
+
const relativePath = "contracts/frontend-repair-assessment.json";
|
|
212
|
+
try {
|
|
213
|
+
return JSON.parse(await readFile(path.join(runDir, relativePath), "utf8"));
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
if (error &&
|
|
217
|
+
typeof error === "object" &&
|
|
218
|
+
"code" in error &&
|
|
219
|
+
error.code === "ENOENT") {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
throw new Error(`frontend review context invalid ${relativePath}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** M8: verify failure ownership facts written by frontend-verify-shell. */
|
|
226
|
+
async function readOptionalVerifyFailure(runDir) {
|
|
227
|
+
const relativePath = "contracts/frontend-verify-failure.json";
|
|
228
|
+
try {
|
|
229
|
+
return JSON.parse(await readFile(path.join(runDir, relativePath), "utf8"));
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
if (error &&
|
|
233
|
+
typeof error === "object" &&
|
|
234
|
+
"code" in error &&
|
|
235
|
+
error.code === "ENOENT") {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
throw new Error(`frontend review context invalid ${relativePath}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
33
241
|
function assertReviewEvidence(input) {
|
|
34
242
|
const contract = frontendImplementationContractSchema.safeParse(input.contract);
|
|
35
|
-
if (!contract.success)
|
|
36
|
-
throw new
|
|
243
|
+
if (!contract.success) {
|
|
244
|
+
throw new FrontendReviewContextFailure("review-context-invalid-contract", "frontend review context invalid implementation contract");
|
|
245
|
+
}
|
|
37
246
|
const trace = input.verificationTrace;
|
|
38
247
|
const expectedTargets = new Map(contract.data.verificationTargets.map((target) => [target.id, target]));
|
|
39
248
|
const actualTargets = Array.isArray(trace?.targets) ? trace.targets : [];
|
|
@@ -69,44 +278,109 @@ function assertReviewEvidence(input) {
|
|
|
69
278
|
actualIds.size !== expectedTargets.size ||
|
|
70
279
|
!mockEvidenceBound ||
|
|
71
280
|
trace.mockStrategy !== contract.data.mockApi.strategy) {
|
|
72
|
-
throw new
|
|
281
|
+
throw new FrontendReviewContextFailure("review-context-verification-unbound", "frontend review context verification trace is not bound to a passing contract");
|
|
73
282
|
}
|
|
74
|
-
const assessment = frontendRepairAssessmentSchema.safeParse(input.repairAssessment);
|
|
75
|
-
if (!assessment.success)
|
|
76
|
-
throw new Error("frontend review context invalid repair assessment");
|
|
77
283
|
}
|
|
78
284
|
export async function runFrontendReviewContextGate(input) {
|
|
79
285
|
const contract = await readRequiredJson(input.runDir, "contracts/frontend-implementation-contract.json");
|
|
80
286
|
const parsedContract = frontendImplementationContractSchema.safeParse(contract);
|
|
81
|
-
if (!parsedContract.success)
|
|
82
|
-
throw new
|
|
287
|
+
if (!parsedContract.success) {
|
|
288
|
+
throw new FrontendReviewContextFailure("review-context-invalid-contract", "frontend review context invalid implementation contract");
|
|
289
|
+
}
|
|
83
290
|
const diff = await runFrontendWorktreeDiffGate({
|
|
84
291
|
...input,
|
|
85
292
|
authorizedChangedPaths: parsedContract.data.targets.files,
|
|
86
293
|
});
|
|
87
294
|
const verificationTrace = await readRequiredJson(input.runDir, "contracts/frontend-verification-trace.json");
|
|
88
|
-
const repairAssessment = await
|
|
295
|
+
const repairAssessment = await readOptionalRepairAssessment(input.runDir);
|
|
296
|
+
const verifyFailure = await readOptionalVerifyFailure(input.runDir);
|
|
89
297
|
const lintAssessment = await readOptionalLintAssessment(input.runDir);
|
|
298
|
+
const cumulativeDiff = await captureCumulativeDiffContext({
|
|
299
|
+
runDir: input.runDir,
|
|
300
|
+
workspaceRoot: input.workspaceRoot,
|
|
301
|
+
diff,
|
|
302
|
+
});
|
|
90
303
|
assertReviewEvidence({ contract, verificationTrace, repairAssessment });
|
|
304
|
+
// Source fidelity ledger 核对(AC-008 / AC-FIDELITY-008):ledger 缺失/漂移即阻断;
|
|
305
|
+
// 同时把核对清单注入 review-context 供最终 reviewer 机械化核对。
|
|
306
|
+
const sourceFidelity = await buildReviewSourceFidelityChecklist(input, parsedContract.data);
|
|
91
307
|
const payload = {
|
|
92
308
|
schemaVersion: 1,
|
|
93
309
|
schemaId: FRONTEND_REVIEW_CONTEXT_SCHEMA_ID,
|
|
94
310
|
contract,
|
|
95
311
|
verificationTrace,
|
|
96
|
-
repairAssessment,
|
|
312
|
+
...(repairAssessment ? { repairAssessment } : {}),
|
|
313
|
+
...(verifyFailure ? { verifyFailure } : {}),
|
|
97
314
|
...(lintAssessment ? { lintAssessment } : {}),
|
|
315
|
+
sourceFidelity,
|
|
98
316
|
diff: {
|
|
99
317
|
schemaId: diff.schemaId,
|
|
100
318
|
patchPath: diff.patchPath,
|
|
101
319
|
patchSha256: diff.patchSha256,
|
|
320
|
+
reviewSummaryPath: diff.reviewSummaryPath,
|
|
321
|
+
reviewIndexPath: diff.reviewIndexPath,
|
|
102
322
|
changedFiles: diff.changedFiles,
|
|
103
323
|
untrackedFiles: diff.untrackedFiles,
|
|
104
324
|
empty: diff.empty,
|
|
105
325
|
},
|
|
326
|
+
cumulativeDiff,
|
|
106
327
|
};
|
|
107
328
|
const artifactPath = await writeDagRunJsonArtifact(input.runDir, "contracts/frontend-review-context.json", payload);
|
|
108
329
|
return { ok: true, artifactPath, diff };
|
|
109
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* 最终 review 核对清单(机械化):ledger 缺失/漂移、canonical requirement 漏项、
|
|
333
|
+
* diff 越界、验证证据缺失均阻断 pass。清单以有界安全摘要注入提示词,不泄漏原文。
|
|
334
|
+
*/
|
|
335
|
+
async function buildReviewSourceFidelityChecklist(input, contract) {
|
|
336
|
+
const binding = contract.sourceBinding;
|
|
337
|
+
const checklist = [];
|
|
338
|
+
// 契约级 sourceBinding 的 v2 语义:四个 ledger 字段必须齐全(缺失即 v1 遗留绑定)。
|
|
339
|
+
const boundV2 = Boolean(binding.ledgerPath) &&
|
|
340
|
+
Boolean(binding.ledgerSha256) &&
|
|
341
|
+
Boolean(binding.inputDigest) &&
|
|
342
|
+
binding.requirementToFragments !== undefined;
|
|
343
|
+
if (!boundV2) {
|
|
344
|
+
return {
|
|
345
|
+
bound: false,
|
|
346
|
+
ledger: null,
|
|
347
|
+
checklist: [
|
|
348
|
+
"source fidelity ledger: not bound (v1 binding); 按现有语义评审",
|
|
349
|
+
"canonical contract 与实际 diff 逐项核对,验证证据齐全后 verdict 可为 pass",
|
|
350
|
+
],
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const absolute = resolveDagTaskSourcePath({
|
|
354
|
+
workspaceRoot: input.workspaceRoot,
|
|
355
|
+
taskId: contract.sourceBinding.taskId,
|
|
356
|
+
sourcePath: binding.ledgerPath,
|
|
357
|
+
});
|
|
358
|
+
let ledger = null;
|
|
359
|
+
try {
|
|
360
|
+
const raw = await readFile(absolute, "utf8");
|
|
361
|
+
const actualSha = createHash("sha256").update(raw).digest("hex");
|
|
362
|
+
if (actualSha !== binding.ledgerSha256) {
|
|
363
|
+
throw new Error(`ledger sha256 mismatch (bound ${binding.ledgerSha256}, observed ${actualSha})`);
|
|
364
|
+
}
|
|
365
|
+
ledger = parseLedgerJson(raw);
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
// AC-011 / AC-HARD-005:v2 drift 返回结构化 failure code(稳定枚举 + reason),
|
|
369
|
+
// 不再裸 throw new Error;message 保持人类可读供 shell stderr。
|
|
370
|
+
throw new FrontendReviewContextFailure("review-context-ledger-drift", `frontend review context source fidelity ledger unavailable or drifted: ${error instanceof Error ? error.message : String(error)}`);
|
|
371
|
+
}
|
|
372
|
+
checklist.push("ledger: 存在且绑定 sha256 一致(无缺失/漂移)", `ledger 覆盖统计: ${ledgerStatsLine(ledger)}`, "canonical contract requirements 必须覆盖 ledger 全部 canonical requirement IDs", "实际 diff 必须限制在契约 targets 内(越界即阻断 pass)", "验证证据必须绑定契约 verificationTargets(缺失即阻断 pass)", "仅满足派生摘要而遗漏 ledger requirement 时 verdict 不得为 pass");
|
|
373
|
+
return {
|
|
374
|
+
bound: true,
|
|
375
|
+
ledger: {
|
|
376
|
+
path: binding.ledgerPath,
|
|
377
|
+
sha256: binding.ledgerSha256,
|
|
378
|
+
inputDigest: binding.inputDigest,
|
|
379
|
+
stats: ledgerStatsLine(ledger),
|
|
380
|
+
},
|
|
381
|
+
checklist,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
110
384
|
export function formatFrontendReviewContextStdout(result) {
|
|
111
385
|
return [
|
|
112
386
|
"Frontend review context: pass",
|
|
@@ -0,0 +1,270 @@
|
|
|
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
|
+
}
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
* Deterministic frontend-implementation risk classifier (M4).
|
|
3
3
|
* Pure function: no I/O, no model, no side effects.
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Dependency-installation language in task text. Mentioning package.json or
|
|
7
|
+
* allowing its path is NOT dependency introduction by itself; the manifest
|
|
8
|
+
* path check in classifyFrontendRisk requires this language to escalate.
|
|
9
|
+
*/
|
|
10
|
+
const DEPENDENCY_INSTALL_LANGUAGE = /new\s+dependency|add\s+dependencies?|install\s+dependencies?|npm\s+(?:install|i)\b|yarn\s+(?:add|install)\b|pnpm\s+(?:add|install)\b|bun\s+(?:add|install)\b|新增依赖|添加依赖|安装依赖|引入依赖/i;
|
|
5
11
|
const HIGH_RISK_PATTERNS = [
|
|
6
12
|
{
|
|
7
13
|
id: "new-route",
|
|
@@ -25,7 +31,7 @@ const HIGH_RISK_PATTERNS = [
|
|
|
25
31
|
},
|
|
26
32
|
{
|
|
27
33
|
id: "new-dependency",
|
|
28
|
-
re:
|
|
34
|
+
re: DEPENDENCY_INSTALL_LANGUAGE,
|
|
29
35
|
},
|
|
30
36
|
{
|
|
31
37
|
id: "schema-api-change",
|
|
@@ -88,7 +94,14 @@ export function classifyFrontendRisk(input) {
|
|
|
88
94
|
const paths = input.allowedPaths ?? [];
|
|
89
95
|
const spread = pathSpreadScore(paths);
|
|
90
96
|
if (paths.some((p) => /package\.json|pnpm-lock|yarn\.lock|package-lock/.test(p))) {
|
|
91
|
-
|
|
97
|
+
if (DEPENDENCY_INSTALL_LANGUAGE.test(blob)) {
|
|
98
|
+
signals.push("new-dependency");
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// Editing manifest scripts/config is not dependency introduction;
|
|
102
|
+
// record the rejected escalation instead of forcing full gates.
|
|
103
|
+
rejectedSignals.push("new-dependency:manifest-path-without-install-language");
|
|
104
|
+
}
|
|
92
105
|
}
|
|
93
106
|
// Explicit multi-domain language only — path count alone is not high-risk
|
|
94
107
|
if (spread >= 3 &&
|