@tea-agent/loop-agent 0.39.0-next.9 → 0.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -1
- package/CHANGELOG.md +269 -69
- package/README.md +11 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/dag/generate-task-dag.js +9 -0
- package/dist/application/task-lifecycle/advance.js +16 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +16 -5
- package/dist/cli/program.js +16 -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/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 +428 -27
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +31 -0
- package/dist/executors/dag-pi-executor.js +124 -18
- package/dist/executors/pi-event-serializer.js +42 -1
- package/dist/executors/pi-executor.js +51 -0
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +74 -28
- package/dist/executors/pi-sdk-executor.js +187 -59
- package/dist/executors/shell-executor.js +555 -34
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/shared/dag-failure-category.js +138 -0
- package/dist/shared/dag-prompt-override.js +27 -0
- package/dist/shared/operator/capabilities.js +181 -27
- 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 +28 -0
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +3 -0
- package/dist/task/contract/diff.js +26 -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 +20 -1
- package/dist/task/contract/schema.js +8 -1
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/source-prepare/build-draft.js +15 -0
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +474 -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 +262 -1
- package/dist/task/source-prepare/semantic-intake.js +19 -3
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/worker/cli.js +18 -2
- package/dist/worker/console/chat/deferred-turn.js +91 -0
- package/dist/worker/console/chat/mcp-inventory.js +221 -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 +165 -83
- package/dist/worker/console/chat/repo-walk.js +10 -1
- package/dist/worker/console/chat/routes.js +480 -55
- package/dist/worker/console/chat/semantic-activity.js +20 -8
- package/dist/worker/console/chat/session-store.js +146 -2
- package/dist/worker/console/chat/turn-process.js +10 -10
- 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/inspect-split.js +18 -0
- package/dist/worker/console/operation-run-facts.js +19 -3
- package/dist/worker/console/operation-store.js +1 -1
- package/dist/worker/console/operator-actions.js +320 -4
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/pi-readiness.js +5 -4
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/recovery-cta.js +4 -4
- package/dist/worker/console/recovery-error-copy.js +4 -4
- package/dist/worker/console/routes.js +46 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +111 -7
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BLXFe4qI.js → abnfDiagram-N423BO3Z-CTx1TrNa.js} +1 -1
- package/dist/worker/console/static/assets/{arc-7iOJeqho.js → arc-B4i9cVxX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CYoyj5K7.js → architectureDiagram-T3A2C74G-BAXE8_Rl.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-fK01JNlR.js → blockDiagram-VBNYF7ZC-C5Xh59at.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-bzbw5D_Z.js → c4Diagram-5PPSVZJV-B_asOzAu.js} +1 -1
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Cf6hJFFG.js → chunk-2GRJ4B5K-wRO_iRNc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DSFXR-et.js → chunk-2Q5K7J3B-ByOaSh_G.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BagJechl.js → chunk-5RXB4S5H-DjLzudan.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-E7QNMFaW.js → chunk-5VM5RSS4-JCNGkXYT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BG_eOBrC.js → chunk-6Q2QTUOP-BhrPXXMZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bo42dqol.js → chunk-GF5L2VYU-TJ5JuEBT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-dibM-87v.js → chunk-JWPE2WC7-VVthn_mD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Bytrp3Oe.js → chunk-KBJHAD2P-D60-Mny4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DBABILrr.js → chunk-RYQCIY6F-zXv7aVye.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DCUk5Pf9.js → chunk-XXDRQBXY-By9VQoi1.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BlGE9v6E.js → cose-bilkent-JH36ORCC-C4zQLe-M.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DGl5ewph.js → cynefin-VYW2F7L2-wh5N2ReN.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DsJ_SVH_.js → cynefinDiagram-MW4NZA55-Dlu5wIqs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BIhjFdzv.js → dagre-VZM6K2ZE-CrDgthc6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dmy1ctTb.js → diagram-7IWD3JNH-zN1clg27.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DYCtWUF3.js → diagram-B4RE2ZJO-BN-o7J7-.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BMZFHVd2.js → diagram-LBJQPF4R-C8SuPt0d.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-b_U8gfaO.js → diagram-Q27KOJAE-BoizuHbZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dzy_bn-I.js → diagram-UB23O5K3-BzUzXixG.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DJCiMNEI.js → ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-28lD016B.js → erDiagram-JOGREHBK-GALUzRbs.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DGyHJmIw.js → flowDiagram-UKHOOZJN-CZJN65Sb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CzdwUjrO.js → ganttDiagram-PKOTCBZU-JLB9OESl.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-D9bGwuo8.js → gitGraphDiagram-DS77QQ5N-CcvCr4GD.js} +1 -1
- package/dist/worker/console/static/assets/index-CW88Fjbw.js +406 -0
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-LJgJUQFC.js → infoDiagram-6WML65LV-HG5z7aVg.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-98LaB8zb.js → ishikawaDiagram-WSZJBQD7-BHEu9chR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-ISzwlQ.js → journeyDiagram-NVQOT4AX-Cp_Up8-a.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-gfYOI-EL.js → kanban-definition-27J2QSJJ-DvVeY-em.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CyntZgKV.js → linear-aGmcjgTn.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BBOR7dg0.js → mermaid.core-Biyo-tqA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CQAgwPMk.js → mindmap-definition-FAOFIHXS-DyxY2pIb.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DkMiZFuV.js → pegDiagram-VL7TDLO6-CkdGuc50.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DLNXa_0J.js → pieDiagram-7S7Q4E2Y-NNQI9xVR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Dc7bdzjm.js → quadrantDiagram-CIZ2JOQS-PAJkuz_G.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BxMB0QqW.js → railroadDiagram-AXF67PYL-ZwrQod0O.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-FjumoKn4.js → requirementDiagram-LRYGKXZP-D3ZfrzM2.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CkhKc_E3.js → sankeyDiagram-W5VNT64P-DDGFmWk5.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DoBWqTTH.js → sequenceDiagram-SI44F4Z6-DO6mmXTA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CQeh_YeE.js → sizeCapture-X5ZJPWSS-DMbCQiJt.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-LuHmFW-I.js → stateDiagram-OKZ733FA-Di_YUEfC.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CHwbDctY.js → swimlanes-SLNWSIFB-Bn2PphNJ.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CoMngk0X.js → timeline-definition-Z64GVDOM-DPh8pRsp.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CURYbtbO.js → vennDiagram-T6HMQDX7-GMfl26pW.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BdqSECjU.js → wardleyDiagram-T6FBY63Y-DTlmVrmF.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DbMUYuAg.js → xychartDiagram-ELKLHX3M-YGBjKAS5.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +40 -1
- package/dist/worker/console/static-src/app/useOperatorActions.js +6 -1
- package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +38 -2
- package/dist/worker/console/static-src/app/useRunProgress.js +46 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +73 -1
- 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 +74 -26
- 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/open-preview-in-browser.js +328 -0
- package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +54 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +237 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
- package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
- 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/loop-agent/loop-agent-client.js +7 -3
- 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/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +67 -1
- 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/format.js +60 -0
- package/dist/worker/observe/static/index.html +5 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.js +4 -0
- 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/state.js +13 -2
- package/dist/worker/observe/static/styles.css +85 -0
- package/dist/worker/observe/static/views/dag-inspector.js +912 -8
- package/dist/worker/observe/static/views/dag.js +8 -1
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/worker/observe/static/views/task.js +55 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +856 -54
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
- package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
- package/dist/workflows/dag/backend-test-intake-context.js +32 -0
- package/dist/workflows/dag/backend-test-layout.js +133 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +343 -33
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +454 -36
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
- package/dist/workflows/dag/backend-test-scenario-partitions.js +321 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +110 -61
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +3 -1
- package/dist/workflows/dag/convergence/controller.js +141 -0
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
- package/dist/workflows/dag/dynamic-runtime/map.js +25 -9
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
- package/dist/workflows/dag/failure-category.js +7 -116
- package/dist/workflows/dag/frontend-implementation-contract.js +465 -6
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +293 -84
- package/dist/workflows/dag/frontend-repair.js +14 -0
- package/dist/workflows/dag/frontend-review-context.js +92 -10
- package/dist/workflows/dag/frontend-risk.js +15 -2
- 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-verification-trace.js +29 -4
- package/dist/workflows/dag/init-hybrid.js +1019 -209
- package/dist/workflows/dag/node-execution.js +228 -15
- package/dist/workflows/dag/prompt.js +60 -1
- package/dist/workflows/dag/rerun-feedback.js +212 -0
- package/dist/workflows/dag/rerun-plan.js +224 -27
- package/dist/workflows/dag/rerun-run.js +61 -6
- package/dist/workflows/dag/rerun-task.js +61 -2
- package/dist/workflows/dag/retry-policy.js +109 -0
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +185 -12
- package/dist/workflows/dag/validate.js +64 -20
- package/docs/README.md +1 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +6 -0
- package/docs/operations/README.md +1 -1
- package/docs/operations/local-development-environment.md +1 -5
- package/docs/skills/vetted-skill-registry.md +14 -0
- package/docs/templates/README.md +2 -2
- package/docs/templates/agent-dag.schema.json +44 -0
- package/docs/templates/backend-test-dag.json +60 -35
- package/docs/templates/frontend-implementation-contract.schema.json +681 -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/harness.json +6 -4
- package/package.json +12 -4
- 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/analyze-product-requirements/SKILL.md +8 -5
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
- package/skills/analyze-product-requirements/references/example.md +50 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
- package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -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-implementation/references/node-contracts.md +2 -2
- package/skills/improve-codebase-architecture/SKILL.md +81 -0
- package/skills/improve-codebase-architecture/deepening.md +37 -0
- package/skills/improve-codebase-architecture/html-report.md +123 -0
- package/skills/improve-codebase-architecture/interface-design.md +44 -0
- package/skills/improve-codebase-architecture/language.md +53 -0
- package/skills/loop-agent/references/command-reference.md +11 -4
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- 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-BBOTJHvD.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cm8Yf8tD.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cm8Yf8tD.js +0 -1
- package/dist/worker/console/static/assets/index-B-qWGThd.js +0 -325
- package/dist/worker/console/static/assets/index-C-xRgY_0.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DZihfdKc.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CKnXCjHX.js +0 -8
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { readFile, stat } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { z } from "zod";
|
|
5
|
+
import { isDagSourceBindingV2 } from "./types.js";
|
|
4
6
|
import { isOpenspecSpecFilePath } from "../../shared/openspec-spec.js";
|
|
5
7
|
import { writeTextArtifactFile } from "../../infrastructure/harness/artifact-store.js";
|
|
6
8
|
import { countExcludedGovernanceSpecFiles, resolveOpenspecGovernanceRoot, } from "../../task/frontend-project-capability.js";
|
|
7
|
-
import {
|
|
9
|
+
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
10
|
+
import { parseLedgerJson, validateRequirementLedger, } from "../../task/source-prepare/ledger.js";
|
|
11
|
+
import { analyzeFrontendImplementationContract, writeFrontendImplementationContractArtifact, writeDeterministicJsonArtifact, assertFrontendSourceBindingFresh, deterministicSha256, sha256Hex, serializeDeterministicJson, FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, FrontendContractFailure, frontendNormalizationActionSchema, } from "./frontend-implementation-contract.js";
|
|
8
12
|
import { renderFrontendPlanMarkdown } from "./frontend-plan-render.js";
|
|
9
13
|
import { captureFrontendWorktreeBaseline } from "./frontend-worktree-diff.js";
|
|
10
14
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
15
|
+
import { normalizeVerdictCandidateLine } from "./dynamic-runtime/shared.js";
|
|
11
16
|
export const FRONTEND_PREWRITE_RESULT_SCHEMA_ID = "frontend-prewrite-result-v1";
|
|
12
17
|
export const FRONTEND_PREWRITE_RESULT_ARTIFACT_NAME = "frontend-prewrite-result.json";
|
|
13
18
|
/** Canonical contract serialization must stay at or below this byte size. */
|
|
@@ -39,6 +44,15 @@ export const frontendPrewriteFailureCodeSchema = z.enum([
|
|
|
39
44
|
"component-spec-not-cited",
|
|
40
45
|
"component-choices-missing",
|
|
41
46
|
"contract-too-large",
|
|
47
|
+
"revision-patch-unmergeable",
|
|
48
|
+
// Source fidelity ledger 类目(SOURCE_FIDELITY_*/source-* 稳定错误码)。
|
|
49
|
+
"source-fidelity-ledger-missing",
|
|
50
|
+
"source-fidelity-ledger-stale",
|
|
51
|
+
"source-fidelity-unresolved",
|
|
52
|
+
"source-fidelity-conflict",
|
|
53
|
+
"source-fidelity-high-risk-uncovered",
|
|
54
|
+
"source-fidelity-requirement-not-covered",
|
|
55
|
+
"source-fidelity-source-ref-invalid",
|
|
42
56
|
]);
|
|
43
57
|
export const frontendPrewriteResultV1Schema = z
|
|
44
58
|
.object({
|
|
@@ -155,32 +169,80 @@ async function selectNode(runDir, primary, fallbacks) {
|
|
|
155
169
|
}
|
|
156
170
|
throw new Error(`frontend prewrite gate no FINISHED candidate: ${rejections.join("; ")}`);
|
|
157
171
|
}
|
|
158
|
-
async function
|
|
159
|
-
|
|
172
|
+
async function readFinishedNodeRecord(runDir, nodeId) {
|
|
173
|
+
let record;
|
|
174
|
+
try {
|
|
175
|
+
record = JSON.parse(await readFile(path.join(runDir, `${nodeId}.json`), "utf8"));
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
throw new Error(`frontend prewrite gate cannot parse node record ${nodeId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
179
|
+
}
|
|
160
180
|
if (record.status !== "FINISHED") {
|
|
161
181
|
throw new Error(`frontend prewrite gate requires FINISHED node ${nodeId} (got ${String(record.status)})`);
|
|
162
182
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
183
|
+
return record;
|
|
184
|
+
}
|
|
185
|
+
async function readNodeText(runDir, nodeId) {
|
|
186
|
+
const record = await readFinishedNodeRecord(runDir, nodeId);
|
|
187
|
+
const text = record.assistantText?.trim() || record.stdout?.trim() || "";
|
|
166
188
|
if (!text)
|
|
167
189
|
throw new Error(`frontend prewrite gate empty output from ${nodeId}`);
|
|
168
190
|
return text;
|
|
169
191
|
}
|
|
170
|
-
async function
|
|
171
|
-
const record =
|
|
172
|
-
|
|
192
|
+
async function readCanonicalContractFromNode(runDir, nodeId) {
|
|
193
|
+
const record = await readFinishedNodeRecord(runDir, nodeId);
|
|
194
|
+
if (!record.structuredArtifactPath) {
|
|
195
|
+
throw new Error(`frontend prewrite gate missing structured artifact for ${nodeId}`);
|
|
196
|
+
}
|
|
197
|
+
const artifactPath = path.isAbsolute(record.structuredArtifactPath)
|
|
198
|
+
? record.structuredArtifactPath
|
|
199
|
+
: path.join(runDir, record.structuredArtifactPath);
|
|
200
|
+
const resolvedRun = path.resolve(runDir);
|
|
201
|
+
const resolvedArtifact = path.resolve(artifactPath);
|
|
202
|
+
const relative = path.relative(resolvedRun, resolvedArtifact);
|
|
203
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
204
|
+
throw new Error(`frontend prewrite gate structured artifact escapes runDir: ${record.structuredArtifactPath}`);
|
|
205
|
+
}
|
|
206
|
+
const bytes = await readFile(resolvedArtifact);
|
|
207
|
+
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
208
|
+
if (record.structuredArtifactSha256 &&
|
|
209
|
+
record.structuredArtifactSha256 !== digest) {
|
|
210
|
+
throw new Error(`frontend prewrite gate structured artifact hash mismatch for ${nodeId}`);
|
|
211
|
+
}
|
|
212
|
+
if (record.structuredArtifactSchemaId &&
|
|
213
|
+
record.structuredArtifactSchemaId !== FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID) {
|
|
214
|
+
throw new Error(`frontend prewrite gate structured artifact schema mismatch for ${nodeId}: ${record.structuredArtifactSchemaId}`);
|
|
215
|
+
}
|
|
216
|
+
const contractText = bytes.toString("utf8");
|
|
217
|
+
let citationsText = "";
|
|
218
|
+
try {
|
|
219
|
+
const citationsRaw = await readFile(path.join(path.dirname(resolvedArtifact), "openspec-citations.json"), "utf8");
|
|
220
|
+
try {
|
|
221
|
+
const parsed = JSON.parse(citationsRaw);
|
|
222
|
+
citationsText =
|
|
223
|
+
typeof parsed.block === "string" ? parsed.block : citationsRaw;
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
citationsText = citationsRaw;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
if (error.code !== "ENOENT")
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
return { contractText, citationsText, rawSha256: digest };
|
|
173
234
|
}
|
|
174
235
|
function firstNonEmptyVerdictLine(text) {
|
|
175
236
|
const lines = text
|
|
176
237
|
.split(/\r?\n/)
|
|
177
238
|
.map((line) => line.trim())
|
|
178
239
|
.filter(Boolean);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
240
|
+
// Emphasis stripping (backtick + asterisk) is shared with the runIf
|
|
241
|
+
// selector and node-level output-protocol validation via
|
|
242
|
+
// normalizeVerdictCandidateLine. The gate keeps its own loose
|
|
243
|
+
// pass/request-revision normalization below (prefix match + trailing
|
|
244
|
+
// prose tolerance) for deterministic fail-closed behavior.
|
|
245
|
+
const normalizedLines = lines.map(normalizeVerdictCandidateLine);
|
|
184
246
|
const first = normalizedLines[0] ?? "";
|
|
185
247
|
// The prompt requires VERDICT to be the first non-empty line, but model
|
|
186
248
|
// output can still prepend a summary. Keep the protocol strict in the
|
|
@@ -391,13 +453,192 @@ async function finalizePrewrite(input, pending) {
|
|
|
391
453
|
openspecPolicy: pending.openspecPolicy,
|
|
392
454
|
};
|
|
393
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* Source fidelity ledger 门禁检查(AC-FIDELITY-002/003/006):绑定携带 ledger 指针时,
|
|
458
|
+
* 依次核验 ledger 存在、绑定 hash 一致、inputDigest 新鲜、无 unresolved/conflict/
|
|
459
|
+
* 高风险未覆盖、canonical requirement 全覆盖、来源引用有效且非伪造。任一失败 fail closed。
|
|
460
|
+
*/
|
|
461
|
+
export async function checkFrontendSourceFidelityLedger(input) {
|
|
462
|
+
const { binding } = input;
|
|
463
|
+
// v1 绑定不携带 ledger 指针:非 frontend task / 无可解析引用 → 无 v2 校验路径。
|
|
464
|
+
if (!isDagSourceBindingV2(binding)) {
|
|
465
|
+
return { ok: true, failureCode: null, failureReason: null, ledger: null };
|
|
466
|
+
}
|
|
467
|
+
const absolute = resolveDagTaskSourcePath({
|
|
468
|
+
workspaceRoot: input.workspaceRoot,
|
|
469
|
+
taskId: binding.taskId,
|
|
470
|
+
sourcePath: binding.ledgerPath,
|
|
471
|
+
});
|
|
472
|
+
let raw;
|
|
473
|
+
try {
|
|
474
|
+
raw = await readFile(absolute, "utf8");
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
return {
|
|
478
|
+
ok: false,
|
|
479
|
+
failureCode: "source-fidelity-ledger-missing",
|
|
480
|
+
failureReason: `frontend source fidelity ledger missing: ${binding.ledgerPath} (${error instanceof Error ? error.message : String(error)})`,
|
|
481
|
+
ledger: null,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
const actualSha = createHash("sha256").update(raw).digest("hex");
|
|
485
|
+
if (actualSha !== binding.ledgerSha256) {
|
|
486
|
+
return {
|
|
487
|
+
ok: false,
|
|
488
|
+
failureCode: "source-fidelity-ledger-stale",
|
|
489
|
+
failureReason: `frontend source fidelity ledger stale or tampered: ${binding.ledgerPath} (bound ${binding.ledgerSha256}, observed ${actualSha})`,
|
|
490
|
+
ledger: null,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
let ledger;
|
|
494
|
+
try {
|
|
495
|
+
ledger = parseLedgerJson(raw);
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
return {
|
|
499
|
+
ok: false,
|
|
500
|
+
failureCode: "source-fidelity-ledger-stale",
|
|
501
|
+
failureReason: `frontend source fidelity ledger is not a valid v1 ledger: ${error instanceof Error ? error.message : String(error)}`,
|
|
502
|
+
ledger: null,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
if (ledger.inputDigest !== binding.inputDigest) {
|
|
506
|
+
return {
|
|
507
|
+
ok: false,
|
|
508
|
+
failureCode: "source-fidelity-ledger-stale",
|
|
509
|
+
failureReason: `frontend source fidelity ledger inputDigest mismatch: bound ${binding.inputDigest}, ledger ${ledger.inputDigest}`,
|
|
510
|
+
ledger,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const validation = validateRequirementLedger(ledger);
|
|
514
|
+
const firstError = (code) => validation.find((error) => error.code === code)?.message ?? null;
|
|
515
|
+
if (ledger.stats.unresolvedCount > 0 || validation.some((e) => e.code === "SOURCE_FIDELITY_UNRESOLVED")) {
|
|
516
|
+
return {
|
|
517
|
+
ok: false,
|
|
518
|
+
failureCode: "source-fidelity-unresolved",
|
|
519
|
+
failureReason: firstError("SOURCE_FIDELITY_UNRESOLVED") ??
|
|
520
|
+
`frontend source fidelity ledger has ${ledger.stats.unresolvedCount} unresolved fragment(s)`,
|
|
521
|
+
ledger,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
if (ledger.stats.conflictCount > 0 || validation.some((e) => e.code === "SOURCE_FIDELITY_CONFLICT")) {
|
|
525
|
+
return {
|
|
526
|
+
ok: false,
|
|
527
|
+
failureCode: "source-fidelity-conflict",
|
|
528
|
+
failureReason: firstError("SOURCE_FIDELITY_CONFLICT") ??
|
|
529
|
+
`frontend source fidelity ledger has ${ledger.stats.conflictCount} conflict(s)`,
|
|
530
|
+
ledger,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
if (ledger.stats.highRiskUncoveredCount > 0 ||
|
|
534
|
+
validation.some((e) => e.code === "SOURCE_FIDELITY_HIGH_RISK_UNCOVERED")) {
|
|
535
|
+
return {
|
|
536
|
+
ok: false,
|
|
537
|
+
failureCode: "source-fidelity-high-risk-uncovered",
|
|
538
|
+
failureReason: firstError("SOURCE_FIDELITY_HIGH_RISK_UNCOVERED") ??
|
|
539
|
+
`frontend source fidelity ledger has ${ledger.stats.highRiskUncoveredCount} high-risk uncovered fragment(s)`,
|
|
540
|
+
ledger,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
// AC-BLOCK-001:canonical requirement 缺少 fragment/sourceRef 证据(ledger 侧
|
|
544
|
+
// REQUIREMENT_NOT_COVERED)与来源引用/片段无效(SOURCE_REF_INVALID /
|
|
545
|
+
// FRAGMENT_INVALID)均为阻断性失败,结构化失败结果不得产生可继续执行的成功。
|
|
546
|
+
const notCoveredError = validation.find((e) => e.code === "SOURCE_FIDELITY_REQUIREMENT_NOT_COVERED");
|
|
547
|
+
if (notCoveredError) {
|
|
548
|
+
return {
|
|
549
|
+
ok: false,
|
|
550
|
+
failureCode: "source-fidelity-requirement-not-covered",
|
|
551
|
+
failureReason: notCoveredError.message,
|
|
552
|
+
ledger,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
const refInvalidError = validation.find((e) => e.code === "SOURCE_FIDELITY_SOURCE_REF_INVALID" ||
|
|
556
|
+
e.code === "SOURCE_FIDELITY_FRAGMENT_INVALID");
|
|
557
|
+
if (refInvalidError) {
|
|
558
|
+
return {
|
|
559
|
+
ok: false,
|
|
560
|
+
failureCode: "source-fidelity-source-ref-invalid",
|
|
561
|
+
failureReason: refInvalidError.message,
|
|
562
|
+
ledger,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
const ledgerIds = new Set(ledger.canonicalRequirements.map((requirement) => requirement.id));
|
|
566
|
+
const contractIds = new Set(input.contractRequirements.map((r) => r.id));
|
|
567
|
+
const uncoveredRequirements = [...ledgerIds].filter((id) => !contractIds.has(id));
|
|
568
|
+
if (uncoveredRequirements.length > 0) {
|
|
569
|
+
return {
|
|
570
|
+
ok: false,
|
|
571
|
+
failureCode: "source-fidelity-requirement-not-covered",
|
|
572
|
+
failureReason: `frontend contract requirements do not cover ledger canonical requirement ids: ${uncoveredRequirements.join(", ")}`,
|
|
573
|
+
ledger,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
const fragmentById = new Map(ledger.fragments.map((fragment) => [fragment.id, fragment]));
|
|
577
|
+
for (const requirement of input.contractRequirements) {
|
|
578
|
+
if (!ledgerIds.has(requirement.id))
|
|
579
|
+
continue;
|
|
580
|
+
const declared = requirement.sourceFragmentIds ?? [];
|
|
581
|
+
if (declared.length === 0) {
|
|
582
|
+
return {
|
|
583
|
+
ok: false,
|
|
584
|
+
failureCode: "source-fidelity-source-ref-invalid",
|
|
585
|
+
failureReason: `frontend contract requirement ${requirement.id} lacks sourceFragmentIds for the bound ledger`,
|
|
586
|
+
ledger,
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const boundFragments = new Set(ledger.canonicalRequirements.find((r) => r.id === requirement.id)
|
|
590
|
+
?.sourceFragmentIds ?? []);
|
|
591
|
+
for (const fragmentId of declared) {
|
|
592
|
+
if (!fragmentById.has(fragmentId)) {
|
|
593
|
+
return {
|
|
594
|
+
ok: false,
|
|
595
|
+
failureCode: "source-fidelity-source-ref-invalid",
|
|
596
|
+
failureReason: `frontend contract requirement ${requirement.id} references unknown fragment ${fragmentId}`,
|
|
597
|
+
ledger,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
if (!boundFragments.has(fragmentId)) {
|
|
601
|
+
return {
|
|
602
|
+
ok: false,
|
|
603
|
+
failureCode: "source-fidelity-source-ref-invalid",
|
|
604
|
+
failureReason: `frontend contract requirement ${requirement.id} declares fabricated fragment reference ${fragmentId} (not bound in ledger)`,
|
|
605
|
+
ledger,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return { ok: true, failureCode: null, failureReason: null, ledger };
|
|
611
|
+
}
|
|
394
612
|
export async function runFrontendPrewriteGate(input) {
|
|
395
613
|
const workspaceRoot = input.workspaceRoot ?? input.repoRoot;
|
|
396
614
|
// selectNode throws for a scheduling defect (no FINISHED plan/review). That
|
|
397
615
|
// remains an ERROR and is intentionally not classified into prewrite-result.
|
|
398
616
|
const planNodeId = await selectNode(input.runDir, input.config.planFromNodeId, input.config.planFallbackFromNodeIds);
|
|
399
617
|
const reviewNodeId = await selectNode(input.runDir, input.config.reviewFromNodeId, input.config.reviewFallbackFromNodeIds);
|
|
400
|
-
|
|
618
|
+
let planArtifact;
|
|
619
|
+
try {
|
|
620
|
+
planArtifact = await readCanonicalContractFromNode(input.runDir, planNodeId);
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
return finalizePrewrite(input, {
|
|
624
|
+
planNodeId,
|
|
625
|
+
reviewNodeId,
|
|
626
|
+
verdict: "",
|
|
627
|
+
candidateRawSha256: sha256Hex(""),
|
|
628
|
+
candidateJsonSha256: null,
|
|
629
|
+
canonicalSha256: null,
|
|
630
|
+
normalizationActions: [],
|
|
631
|
+
mockStrategy: undefined,
|
|
632
|
+
artifact: undefined,
|
|
633
|
+
failureReason: error instanceof Error ? error.message : String(error),
|
|
634
|
+
failureCode: "plan-text-unreadable",
|
|
635
|
+
classification: "retryable-invalid",
|
|
636
|
+
openspecReadPaths: [],
|
|
637
|
+
openspecCandidatePaths: input.config.openspecCandidatePaths ?? [],
|
|
638
|
+
openspecPolicy: input.config.openspecPolicy ?? "scan-strict",
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
const candidateRawSha256 = planArtifact.rawSha256;
|
|
401
642
|
const openspecPolicy = input.config.openspecPolicy ?? "scan-strict";
|
|
402
643
|
const basePending = {
|
|
403
644
|
planNodeId,
|
|
@@ -416,18 +657,9 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
416
657
|
openspecCandidatePaths: input.config.openspecCandidatePaths ?? [],
|
|
417
658
|
openspecPolicy,
|
|
418
659
|
};
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
423
|
-
catch (error) {
|
|
424
|
-
return finalizePrewrite(input, {
|
|
425
|
-
...basePending,
|
|
426
|
-
classification: "retryable-invalid",
|
|
427
|
-
failureReason: error instanceof Error ? error.message : String(error),
|
|
428
|
-
failureCode: "plan-text-unreadable",
|
|
429
|
-
});
|
|
430
|
-
}
|
|
660
|
+
const planText = [planArtifact.contractText, planArtifact.citationsText]
|
|
661
|
+
.filter(Boolean)
|
|
662
|
+
.join("\n");
|
|
431
663
|
let reviewText;
|
|
432
664
|
try {
|
|
433
665
|
reviewText = await readNodeText(input.runDir, reviewNodeId);
|
|
@@ -450,59 +682,7 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
450
682
|
failureCode: "verdict-not-pass",
|
|
451
683
|
});
|
|
452
684
|
}
|
|
453
|
-
|
|
454
|
-
// delta against the original contract node (planFallbackFromNodeIds[0]).
|
|
455
|
-
// Legacy DAGs without revisionPatch (undefined) keep full-contract semantics.
|
|
456
|
-
const revisionPatchMode = input.config.revisionPatch === true &&
|
|
457
|
-
planNodeId === input.config.planFromNodeId &&
|
|
458
|
-
input.config.planFallbackFromNodeIds.length > 0;
|
|
459
|
-
let effectiveContractText = null;
|
|
460
|
-
if (revisionPatchMode) {
|
|
461
|
-
const originalNodeId = input.config.planFallbackFromNodeIds[0];
|
|
462
|
-
let originalText;
|
|
463
|
-
try {
|
|
464
|
-
originalText = await readNodeText(input.runDir, originalNodeId);
|
|
465
|
-
}
|
|
466
|
-
catch (error) {
|
|
467
|
-
return finalizePrewrite(input, {
|
|
468
|
-
...basePending,
|
|
469
|
-
verdict,
|
|
470
|
-
classification: "retryable-invalid",
|
|
471
|
-
failureReason: `frontend prewrite gate revisionPatch mode requires original contract node ${originalNodeId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
472
|
-
failureCode: "contract-invalid",
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
let originalValue;
|
|
476
|
-
let patchValue;
|
|
477
|
-
try {
|
|
478
|
-
originalValue = extractFrontendImplementationJson(originalText);
|
|
479
|
-
patchValue = extractFrontendImplementationJson(planText);
|
|
480
|
-
}
|
|
481
|
-
catch (error) {
|
|
482
|
-
return finalizePrewrite(input, {
|
|
483
|
-
...basePending,
|
|
484
|
-
verdict,
|
|
485
|
-
classification: "retryable-invalid",
|
|
486
|
-
failureReason: `frontend prewrite gate revisionPatch mode failed to parse contract/patch: ${error instanceof Error ? error.message : String(error)}`,
|
|
487
|
-
failureCode: "contract-invalid",
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
let merged;
|
|
491
|
-
try {
|
|
492
|
-
merged = applyFrontendImplementationContractPatch(originalValue, patchValue);
|
|
493
|
-
}
|
|
494
|
-
catch (error) {
|
|
495
|
-
return finalizePrewrite(input, {
|
|
496
|
-
...basePending,
|
|
497
|
-
verdict,
|
|
498
|
-
classification: "retryable-invalid",
|
|
499
|
-
failureReason: `frontend prewrite gate revisionPatch apply failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
500
|
-
failureCode: "contract-invalid",
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
effectiveContractText = serializeDeterministicJson(merged);
|
|
504
|
-
}
|
|
505
|
-
const requirementIdSearchText = effectiveContractText ?? planText;
|
|
685
|
+
const requirementIdSearchText = planArtifact.contractText;
|
|
506
686
|
const missingIds = input.config.requiredRequirementIds.filter((id) => !requirementIdSearchText.includes(id));
|
|
507
687
|
if (missingIds.length > 0) {
|
|
508
688
|
return finalizePrewrite(input, {
|
|
@@ -550,14 +730,13 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
550
730
|
workspaceRoot,
|
|
551
731
|
});
|
|
552
732
|
}
|
|
553
|
-
// Candidate -> canonical conversion. The gate consumes the
|
|
554
|
-
//
|
|
733
|
+
// Candidate -> canonical conversion. The gate consumes the producing node's
|
|
734
|
+
// hash-bound canonical artifact and never re-merges plan/revision stdout.
|
|
555
735
|
let analysis;
|
|
556
736
|
try {
|
|
557
737
|
analysis = await analyzeFrontendImplementationContract({
|
|
558
738
|
runDir: input.runDir,
|
|
559
|
-
|
|
560
|
-
rawContractText: effectiveContractText ?? undefined,
|
|
739
|
+
rawContractText: planArtifact.contractText,
|
|
561
740
|
sourceBinding: input.sourceBinding,
|
|
562
741
|
});
|
|
563
742
|
}
|
|
@@ -577,6 +756,33 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
577
756
|
}
|
|
578
757
|
throw error;
|
|
579
758
|
}
|
|
759
|
+
// Source fidelity ledger gate (20260824-frontend-source-fidelity-ledger-v1):
|
|
760
|
+
// 在 verdict/missing-requirement-ids/source-binding-not-fresh 之后插入 ledger
|
|
761
|
+
// 检查。缺失即 fail closed 且 writer 不获授权;所有检查通过才继续物化契约。
|
|
762
|
+
if (input.sourceBinding) {
|
|
763
|
+
const ledgerCheck = await checkFrontendSourceFidelityLedger({
|
|
764
|
+
workspaceRoot: workspaceRoot ?? "",
|
|
765
|
+
binding: input.sourceBinding,
|
|
766
|
+
contractRequirements: analysis.canonical.requirements.map((requirement) => ({
|
|
767
|
+
id: requirement.id,
|
|
768
|
+
sourceFragmentIds: requirement.sourceFragmentIds,
|
|
769
|
+
sourceRefs: requirement.sourceRefs,
|
|
770
|
+
})),
|
|
771
|
+
});
|
|
772
|
+
if (!ledgerCheck.ok && ledgerCheck.failureCode) {
|
|
773
|
+
return finalizePrewrite(input, {
|
|
774
|
+
...basePending,
|
|
775
|
+
verdict,
|
|
776
|
+
candidateJsonSha256: analysis.candidateJsonSha256,
|
|
777
|
+
normalizationActions: [],
|
|
778
|
+
mockStrategy: analysis.canonical.mockApi.strategy,
|
|
779
|
+
classification: "blocked",
|
|
780
|
+
failureReason: ledgerCheck.failureReason ??
|
|
781
|
+
"frontend source fidelity ledger gate failed",
|
|
782
|
+
failureCode: ledgerCheck.failureCode,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
}
|
|
580
786
|
// Size guard: a canonical contract above 64KB is a retryable-invalid defect
|
|
581
787
|
// (compress the contract) rather than a writer authorization failure.
|
|
582
788
|
const contractSizeBytes = Buffer.byteLength(serializeDeterministicJson(analysis.canonical), "utf8");
|
|
@@ -935,6 +1141,9 @@ export function formatFrontendPrewriteGateStdout(result) {
|
|
|
935
1141
|
}
|
|
936
1142
|
lines.push(`Prewrite result: ${result.prewriteResult.path}`);
|
|
937
1143
|
lines.push(`Openspec policy: ${result.openspecPolicy}`);
|
|
1144
|
+
if (result.failureCode?.startsWith("source-fidelity-")) {
|
|
1145
|
+
lines.push(`Source fidelity ledger: blocked (${result.failureCode})`);
|
|
1146
|
+
}
|
|
938
1147
|
if (result.failureReason)
|
|
939
1148
|
lines.push(`Failure reason: ${result.failureReason}`);
|
|
940
1149
|
if (result.openspecReadPaths.length > 0) {
|
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
5
5
|
import { FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, frontendImplementationContractSchema, } from "./frontend-implementation-contract.js";
|
|
6
|
+
import { FRONTEND_NO_VERIFICATION_MARKER_TEXT, FRONTEND_VERIFICATION_UNAVAILABLE_MESSAGE, } from "./frontend-verification-trace.js";
|
|
6
7
|
export const FRONTEND_REPAIR_ASSESSMENT_SCHEMA_ID = "frontend-repair-assessment-v1";
|
|
7
8
|
export const frontendRepairFailureClassSchema = z.enum([
|
|
8
9
|
"typecheck",
|
|
@@ -82,6 +83,12 @@ export function classifyFrontendFailure(input) {
|
|
|
82
83
|
if (input.failureCategory === "write-guard") {
|
|
83
84
|
return "path";
|
|
84
85
|
}
|
|
86
|
+
if (`${input.stderr ?? ""}\n${input.stdout ?? ""}`.includes(FRONTEND_NO_VERIFICATION_MARKER_TEXT)) {
|
|
87
|
+
// Verification unavailability is a generation-time contract gap: no
|
|
88
|
+
// authorized verification command exists. Bounded repair cannot fix it;
|
|
89
|
+
// the operator must declare commands and regenerate the DAG.
|
|
90
|
+
return "contract";
|
|
91
|
+
}
|
|
85
92
|
if (input.traceTarget || input.commandCategory === "trace") {
|
|
86
93
|
return "trace";
|
|
87
94
|
}
|
|
@@ -424,6 +431,13 @@ export async function runFrontendFailureAssessGate(input) {
|
|
|
424
431
|
: !baseEligible
|
|
425
432
|
? `non-repairable or ineligible: failureClass=${failureClass} attempt=${attempt}`
|
|
426
433
|
: `missing structured evidence for failureClass=${failureClass} on ${primary.nodeId}`;
|
|
434
|
+
if (!eligible &&
|
|
435
|
+
`${primary.record.stderr ?? ""}`.includes(FRONTEND_NO_VERIFICATION_MARKER_TEXT)) {
|
|
436
|
+
// Surface the actionable unavailable-verification reason in the
|
|
437
|
+
// assessment (and the non-repairable gate error) instead of a
|
|
438
|
+
// generic class label.
|
|
439
|
+
reason = FRONTEND_VERIFICATION_UNAVAILABLE_MESSAGE;
|
|
440
|
+
}
|
|
427
441
|
}
|
|
428
442
|
// fixScope and changedPaths must stay inside the implement writeSet.
|
|
429
443
|
if (evidence.fixScope?.length) {
|
|
@@ -1,17 +1,39 @@
|
|
|
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 { frontendImplementationContractSchema, } from "./frontend-implementation-contract.js";
|
|
7
8
|
import { frontendRepairAssessmentSchema } from "./frontend-repair.js";
|
|
9
|
+
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
10
|
+
import { ledgerStatsLine, parseLedgerJson, } from "../../task/source-prepare/ledger.js";
|
|
8
11
|
export const FRONTEND_REVIEW_CONTEXT_SCHEMA_ID = "frontend-review-context-v1";
|
|
12
|
+
/**
|
|
13
|
+
* review-context 稳定失败枚举(AC-011 / AC-HARD-005):v2 drift / manifest 失败返回
|
|
14
|
+
* 结构化、可消费的 failure code(稳定枚举 + reason 的 envelope),既有调用方无需字符串
|
|
15
|
+
* 匹配裸 Error 文本。message 保持人类可读(shell stderr 兼容),code 供机械化路由。
|
|
16
|
+
*/
|
|
17
|
+
export const FRONTEND_REVIEW_CONTEXT_FAILURE_CODES = [
|
|
18
|
+
"review-context-invalid-contract",
|
|
19
|
+
"review-context-verification-unbound",
|
|
20
|
+
"review-context-ledger-drift",
|
|
21
|
+
"review-context-manifest-missing",
|
|
22
|
+
];
|
|
23
|
+
export class FrontendReviewContextFailure extends Error {
|
|
24
|
+
code;
|
|
25
|
+
constructor(code, message, options) {
|
|
26
|
+
super(message, options);
|
|
27
|
+
this.name = "FrontendReviewContextFailure";
|
|
28
|
+
this.code = code;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
9
31
|
async function readRequiredJson(runDir, relativePath) {
|
|
10
32
|
try {
|
|
11
33
|
return JSON.parse(await readFile(path.join(runDir, relativePath), "utf8"));
|
|
12
34
|
}
|
|
13
|
-
catch {
|
|
14
|
-
throw new
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw new FrontendReviewContextFailure("review-context-manifest-missing", `frontend review context missing or invalid ${relativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
15
37
|
}
|
|
16
38
|
}
|
|
17
39
|
async function readOptionalLintAssessment(runDir) {
|
|
@@ -32,8 +54,9 @@ async function readOptionalLintAssessment(runDir) {
|
|
|
32
54
|
}
|
|
33
55
|
function assertReviewEvidence(input) {
|
|
34
56
|
const contract = frontendImplementationContractSchema.safeParse(input.contract);
|
|
35
|
-
if (!contract.success)
|
|
36
|
-
throw new
|
|
57
|
+
if (!contract.success) {
|
|
58
|
+
throw new FrontendReviewContextFailure("review-context-invalid-contract", "frontend review context invalid implementation contract");
|
|
59
|
+
}
|
|
37
60
|
const trace = input.verificationTrace;
|
|
38
61
|
const expectedTargets = new Map(contract.data.verificationTargets.map((target) => [target.id, target]));
|
|
39
62
|
const actualTargets = Array.isArray(trace?.targets) ? trace.targets : [];
|
|
@@ -69,17 +92,19 @@ function assertReviewEvidence(input) {
|
|
|
69
92
|
actualIds.size !== expectedTargets.size ||
|
|
70
93
|
!mockEvidenceBound ||
|
|
71
94
|
trace.mockStrategy !== contract.data.mockApi.strategy) {
|
|
72
|
-
throw new
|
|
95
|
+
throw new FrontendReviewContextFailure("review-context-verification-unbound", "frontend review context verification trace is not bound to a passing contract");
|
|
73
96
|
}
|
|
74
97
|
const assessment = frontendRepairAssessmentSchema.safeParse(input.repairAssessment);
|
|
75
|
-
if (!assessment.success)
|
|
76
|
-
throw new
|
|
98
|
+
if (!assessment.success) {
|
|
99
|
+
throw new FrontendReviewContextFailure("review-context-invalid-contract", "frontend review context invalid repair assessment");
|
|
100
|
+
}
|
|
77
101
|
}
|
|
78
102
|
export async function runFrontendReviewContextGate(input) {
|
|
79
103
|
const contract = await readRequiredJson(input.runDir, "contracts/frontend-implementation-contract.json");
|
|
80
104
|
const parsedContract = frontendImplementationContractSchema.safeParse(contract);
|
|
81
|
-
if (!parsedContract.success)
|
|
82
|
-
throw new
|
|
105
|
+
if (!parsedContract.success) {
|
|
106
|
+
throw new FrontendReviewContextFailure("review-context-invalid-contract", "frontend review context invalid implementation contract");
|
|
107
|
+
}
|
|
83
108
|
const diff = await runFrontendWorktreeDiffGate({
|
|
84
109
|
...input,
|
|
85
110
|
authorizedChangedPaths: parsedContract.data.targets.files,
|
|
@@ -88,6 +113,9 @@ export async function runFrontendReviewContextGate(input) {
|
|
|
88
113
|
const repairAssessment = await readRequiredJson(input.runDir, "contracts/frontend-repair-assessment.json");
|
|
89
114
|
const lintAssessment = await readOptionalLintAssessment(input.runDir);
|
|
90
115
|
assertReviewEvidence({ contract, verificationTrace, repairAssessment });
|
|
116
|
+
// Source fidelity ledger 核对(AC-008 / AC-FIDELITY-008):ledger 缺失/漂移即阻断;
|
|
117
|
+
// 同时把核对清单注入 review-context 供最终 reviewer 机械化核对。
|
|
118
|
+
const sourceFidelity = await buildReviewSourceFidelityChecklist(input, parsedContract.data);
|
|
91
119
|
const payload = {
|
|
92
120
|
schemaVersion: 1,
|
|
93
121
|
schemaId: FRONTEND_REVIEW_CONTEXT_SCHEMA_ID,
|
|
@@ -95,6 +123,7 @@ export async function runFrontendReviewContextGate(input) {
|
|
|
95
123
|
verificationTrace,
|
|
96
124
|
repairAssessment,
|
|
97
125
|
...(lintAssessment ? { lintAssessment } : {}),
|
|
126
|
+
sourceFidelity,
|
|
98
127
|
diff: {
|
|
99
128
|
schemaId: diff.schemaId,
|
|
100
129
|
patchPath: diff.patchPath,
|
|
@@ -107,6 +136,59 @@ export async function runFrontendReviewContextGate(input) {
|
|
|
107
136
|
const artifactPath = await writeDagRunJsonArtifact(input.runDir, "contracts/frontend-review-context.json", payload);
|
|
108
137
|
return { ok: true, artifactPath, diff };
|
|
109
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* 最终 review 核对清单(机械化):ledger 缺失/漂移、canonical requirement 漏项、
|
|
141
|
+
* diff 越界、验证证据缺失均阻断 pass。清单以有界安全摘要注入提示词,不泄漏原文。
|
|
142
|
+
*/
|
|
143
|
+
async function buildReviewSourceFidelityChecklist(input, contract) {
|
|
144
|
+
const binding = contract.sourceBinding;
|
|
145
|
+
const checklist = [];
|
|
146
|
+
// 契约级 sourceBinding 的 v2 语义:四个 ledger 字段必须齐全(缺失即 v1 遗留绑定)。
|
|
147
|
+
const boundV2 = Boolean(binding.ledgerPath) &&
|
|
148
|
+
Boolean(binding.ledgerSha256) &&
|
|
149
|
+
Boolean(binding.inputDigest) &&
|
|
150
|
+
binding.requirementToFragments !== undefined;
|
|
151
|
+
if (!boundV2) {
|
|
152
|
+
return {
|
|
153
|
+
bound: false,
|
|
154
|
+
ledger: null,
|
|
155
|
+
checklist: [
|
|
156
|
+
"source fidelity ledger: not bound (v1 binding); 按现有语义评审",
|
|
157
|
+
"canonical contract 与实际 diff 逐项核对,验证证据齐全后 verdict 可为 pass",
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const absolute = resolveDagTaskSourcePath({
|
|
162
|
+
workspaceRoot: input.workspaceRoot,
|
|
163
|
+
taskId: contract.sourceBinding.taskId,
|
|
164
|
+
sourcePath: binding.ledgerPath,
|
|
165
|
+
});
|
|
166
|
+
let ledger = null;
|
|
167
|
+
try {
|
|
168
|
+
const raw = await readFile(absolute, "utf8");
|
|
169
|
+
const actualSha = createHash("sha256").update(raw).digest("hex");
|
|
170
|
+
if (actualSha !== binding.ledgerSha256) {
|
|
171
|
+
throw new Error(`ledger sha256 mismatch (bound ${binding.ledgerSha256}, observed ${actualSha})`);
|
|
172
|
+
}
|
|
173
|
+
ledger = parseLedgerJson(raw);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
// AC-011 / AC-HARD-005:v2 drift 返回结构化 failure code(稳定枚举 + reason),
|
|
177
|
+
// 不再裸 throw new Error;message 保持人类可读供 shell stderr。
|
|
178
|
+
throw new FrontendReviewContextFailure("review-context-ledger-drift", `frontend review context source fidelity ledger unavailable or drifted: ${error instanceof Error ? error.message : String(error)}`);
|
|
179
|
+
}
|
|
180
|
+
checklist.push("ledger: 存在且绑定 sha256 一致(无缺失/漂移)", `ledger 覆盖统计: ${ledgerStatsLine(ledger)}`, "canonical contract requirements 必须覆盖 ledger 全部 canonical requirement IDs", "实际 diff 必须限制在契约 targets 内(越界即阻断 pass)", "验证证据必须绑定契约 verificationTargets(缺失即阻断 pass)", "仅满足派生摘要而遗漏 ledger requirement 时 verdict 不得为 pass");
|
|
181
|
+
return {
|
|
182
|
+
bound: true,
|
|
183
|
+
ledger: {
|
|
184
|
+
path: binding.ledgerPath,
|
|
185
|
+
sha256: binding.ledgerSha256,
|
|
186
|
+
inputDigest: binding.inputDigest,
|
|
187
|
+
stats: ledgerStatsLine(ledger),
|
|
188
|
+
},
|
|
189
|
+
checklist,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
110
192
|
export function formatFrontendReviewContextStdout(result) {
|
|
111
193
|
return [
|
|
112
194
|
"Frontend review context: pass",
|