@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
|
@@ -8,11 +8,14 @@ import { writeTextArtifactFile } from "../../infrastructure/harness/artifact-sto
|
|
|
8
8
|
import { findPackageRoot } from "../../shared/package-metadata.js";
|
|
9
9
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
10
10
|
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
11
|
+
import { isDagSourceBindingV2 } from "./types.js";
|
|
12
|
+
import { canonicalContractRel, classifyStructuredContractFailure, formatStructuredArtifactPointer, openspecCitationsRel, parseOpenspecCitationsBlock, persistRuntimeSkeleton, } from "./structured-output-repair.js";
|
|
11
13
|
export const frontendNormalizationActionSchema = z.enum([
|
|
12
14
|
"remove-trailing-commas",
|
|
13
15
|
"strip-comments",
|
|
14
16
|
"canonicalize-requirement-ids",
|
|
15
17
|
"canonicalize-verification-alias",
|
|
18
|
+
"drop-unresolved-verification-symbols",
|
|
16
19
|
"inject-source-binding",
|
|
17
20
|
]);
|
|
18
21
|
export function sha256Hex(input) {
|
|
@@ -102,6 +105,21 @@ async function deriveFrozenCommandLabelsFromRun(runDir) {
|
|
|
102
105
|
return [...labels];
|
|
103
106
|
}
|
|
104
107
|
export const FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID = "frontend-implementation-contract-v1";
|
|
108
|
+
export const FRONTEND_IMPLEMENTATION_CONTRACT_PLAN_PATCH_SCHEMA_ID = "frontend-implementation-contract-plan-patch-v1";
|
|
109
|
+
/**
|
|
110
|
+
* Build the deterministic, run-owned portion of the frontend contract. The
|
|
111
|
+
* planner is allowed to fill semantic fields with an RFC 7386 merge patch but
|
|
112
|
+
* cannot redefine task identity, risk, or the implementation write boundary.
|
|
113
|
+
*/
|
|
114
|
+
export function buildFrontendImplementationContractSkeleton(input) {
|
|
115
|
+
return {
|
|
116
|
+
schemaVersion: 1,
|
|
117
|
+
sourceBinding: canonicalFrontendContractSourceBinding(input.sourceBinding),
|
|
118
|
+
riskLevel: input.riskLevel,
|
|
119
|
+
targets: { files: [...input.targetFiles] },
|
|
120
|
+
mockApi: { productionDefaultOff: true },
|
|
121
|
+
};
|
|
122
|
+
}
|
|
105
123
|
/**
|
|
106
124
|
* Load the canonical frontend-implementation-contract-v1 JSON Schema from the
|
|
107
125
|
* installed loop-agent package docs/templates/ path. Package-root discovery
|
|
@@ -262,8 +280,33 @@ export const frontendImplementationContractSchema = z
|
|
|
262
280
|
requirementSha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
263
281
|
referencePaths: z.array(safePath),
|
|
264
282
|
requirementIds: z.array(id),
|
|
283
|
+
// Source binding v2 (frontend): bound ledger pointer + fragment map.
|
|
284
|
+
ledgerPath: safePath.optional(),
|
|
285
|
+
ledgerSha256: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
286
|
+
inputDigest: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
287
|
+
requirementToFragments: z
|
|
288
|
+
.record(z.string(), z.array(z.string()))
|
|
289
|
+
.optional(),
|
|
265
290
|
})
|
|
266
|
-
.strict()
|
|
291
|
+
.strict()
|
|
292
|
+
.superRefine((value, ctx) => {
|
|
293
|
+
// v2 all-or-none:任一 ledger 字段出现则四项必须齐全,防止 v1+optional 冒充 v2。
|
|
294
|
+
const anyLedgerField = value.ledgerPath !== undefined ||
|
|
295
|
+
value.ledgerSha256 !== undefined ||
|
|
296
|
+
value.inputDigest !== undefined ||
|
|
297
|
+
value.requirementToFragments !== undefined;
|
|
298
|
+
const allLedgerFields = value.ledgerPath !== undefined &&
|
|
299
|
+
value.ledgerSha256 !== undefined &&
|
|
300
|
+
value.inputDigest !== undefined &&
|
|
301
|
+
value.requirementToFragments !== undefined;
|
|
302
|
+
if (anyLedgerField && !allLedgerFields) {
|
|
303
|
+
ctx.addIssue({
|
|
304
|
+
code: z.ZodIssueCode.custom,
|
|
305
|
+
path: ["sourceBinding"],
|
|
306
|
+
message: "source binding v2 ledger fields (ledgerPath/ledgerSha256/inputDigest/requirementToFragments) must be provided together; partial or optional v1+ledger bindings are rejected",
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}),
|
|
267
310
|
riskLevel: z.enum(["small", "standard", "high-risk"]),
|
|
268
311
|
targets: z
|
|
269
312
|
.object({
|
|
@@ -279,6 +322,10 @@ export const frontendImplementationContractSchema = z
|
|
|
279
322
|
implementationTargets: z.array(safePath),
|
|
280
323
|
verificationTargetIds: z.array(z.string().min(1)),
|
|
281
324
|
evidenceGap: gap.optional(),
|
|
325
|
+
// Source fidelity ledger binding (AC-005/AC-006): 每个 requirement
|
|
326
|
+
// 携带来源 fragment 证据,prewrite 门禁校验其真实性与覆盖。
|
|
327
|
+
sourceFragmentIds: z.array(z.string().min(1)).optional(),
|
|
328
|
+
sourceRefs: z.array(safePath).optional(),
|
|
282
329
|
})
|
|
283
330
|
.strict()).min(1),
|
|
284
331
|
uiStates: z.array(z
|
|
@@ -415,6 +462,30 @@ export const frontendImplementationContractSchema = z
|
|
|
415
462
|
message: `unknown verification target ${targetId}`,
|
|
416
463
|
path: ["requirements"],
|
|
417
464
|
});
|
|
465
|
+
// Source fidelity ledger binding (AC-005/AC-006): 绑定携带
|
|
466
|
+
// requirementToFragments(ledger v2)时,每个 requirement 必须携带
|
|
467
|
+
// 有效 sourceFragmentIds/sourceRefs,否则 fail closed(防引用伪造/缺失)。
|
|
468
|
+
const boundFragments = value.sourceBinding.requirementToFragments?.[requirement.id];
|
|
469
|
+
if (value.sourceBinding.ledgerPath &&
|
|
470
|
+
(!requirement.sourceFragmentIds ||
|
|
471
|
+
requirement.sourceFragmentIds.length === 0 ||
|
|
472
|
+
!requirement.sourceRefs ||
|
|
473
|
+
requirement.sourceRefs.length === 0)) {
|
|
474
|
+
ctx.addIssue({
|
|
475
|
+
code: "custom",
|
|
476
|
+
message: `requirement ${requirement.id} must carry sourceFragmentIds/sourceRefs when a source fidelity ledger is bound`,
|
|
477
|
+
path: ["requirements", requirement.id, "sourceFragmentIds"],
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
else if (boundFragments !== undefined &&
|
|
481
|
+
(!requirement.sourceFragmentIds ||
|
|
482
|
+
requirement.sourceFragmentIds.length === 0)) {
|
|
483
|
+
ctx.addIssue({
|
|
484
|
+
code: "custom",
|
|
485
|
+
message: `requirement ${requirement.id} is bound to ledger fragments ${boundFragments.join(", ")} but declares none`,
|
|
486
|
+
path: ["requirements", requirement.id, "sourceFragmentIds"],
|
|
487
|
+
});
|
|
488
|
+
}
|
|
418
489
|
}
|
|
419
490
|
for (const state of value.uiStates) {
|
|
420
491
|
if (state.applicable &&
|
|
@@ -499,9 +570,9 @@ function isPlainObject(value) {
|
|
|
499
570
|
* ever materialized, so a patch that removes a required field still fails
|
|
500
571
|
* closed at the gate.
|
|
501
572
|
*/
|
|
502
|
-
export function
|
|
573
|
+
export function applyFrontendContractMergePatch(target, patch) {
|
|
503
574
|
if (!isPlainObject(patch)) {
|
|
504
|
-
throw new Error("frontend
|
|
575
|
+
throw new Error("frontend contract patch must be a JSON object");
|
|
505
576
|
}
|
|
506
577
|
const merge = (base, delta) => {
|
|
507
578
|
if (delta === null)
|
|
@@ -526,6 +597,9 @@ export function applyFrontendImplementationContractPatch(target, patch) {
|
|
|
526
597
|
};
|
|
527
598
|
return merge(target, patch);
|
|
528
599
|
}
|
|
600
|
+
export function applyFrontendImplementationContractPatch(target, patch) {
|
|
601
|
+
return applyFrontendContractMergePatch(target, patch);
|
|
602
|
+
}
|
|
529
603
|
export async function assertFrontendSourceBindingFresh(input) {
|
|
530
604
|
for (const source of input.binding.sources) {
|
|
531
605
|
const absolute = resolveDagTaskSourcePath({
|
|
@@ -548,6 +622,29 @@ export async function assertFrontendSourceBindingFresh(input) {
|
|
|
548
622
|
throw new Error(`frontend source binding is stale: ${source.path}`);
|
|
549
623
|
}
|
|
550
624
|
}
|
|
625
|
+
// Source binding v2 ledger 新鲜度(AC-FIDELITY-REPAIR-001/002):v2 绑定携带的
|
|
626
|
+
// ledger 文件必须存在且 sha256 与绑定一致,否则 fail closed;v1 绑定无此路径。
|
|
627
|
+
if (isDagSourceBindingV2(input.binding)) {
|
|
628
|
+
const absolute = resolveDagTaskSourcePath({
|
|
629
|
+
workspaceRoot: input.workspaceRoot,
|
|
630
|
+
taskId: input.binding.taskId,
|
|
631
|
+
sourcePath: input.binding.ledgerPath,
|
|
632
|
+
});
|
|
633
|
+
let content;
|
|
634
|
+
try {
|
|
635
|
+
content = await readFile(absolute);
|
|
636
|
+
const info = await stat(absolute);
|
|
637
|
+
if (!info.isFile())
|
|
638
|
+
throw new Error("not a regular file");
|
|
639
|
+
}
|
|
640
|
+
catch (error) {
|
|
641
|
+
throw new Error(`frontend source fidelity ledger unavailable: ${input.binding.ledgerPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
642
|
+
}
|
|
643
|
+
const actual = createHash("sha256").update(content).digest("hex");
|
|
644
|
+
if (actual !== input.binding.ledgerSha256) {
|
|
645
|
+
throw new Error(`frontend source fidelity ledger is stale or tampered: ${input.binding.ledgerPath} (bound ${input.binding.ledgerSha256}, observed ${actual})`);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
551
648
|
}
|
|
552
649
|
const SECRET_KEY = /(?:password|passwd|secret|token|api[_-]?key|private[_-]?key|credential|authorization)/i;
|
|
553
650
|
const SECRET_VALUE = /(?:-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:sk|ghp|github_pat|xox[baprs]|AKIA)[-_A-Za-z0-9]{12,}\b)/;
|
|
@@ -722,7 +819,7 @@ export function canonicalFrontendContractSourceBinding(binding) {
|
|
|
722
819
|
const requirement = binding.sources.find((source) => source.kind === "requirement");
|
|
723
820
|
if (!requirement)
|
|
724
821
|
throw new Error("frontend implementation contract gate requires a requirement source in DAG sourceBinding");
|
|
725
|
-
|
|
822
|
+
const sourceBinding = {
|
|
726
823
|
taskId: binding.taskId,
|
|
727
824
|
requirementPath: requirement.path,
|
|
728
825
|
requirementSha256: requirement.sha256,
|
|
@@ -732,6 +829,15 @@ export function canonicalFrontendContractSourceBinding(binding) {
|
|
|
732
829
|
.sort(),
|
|
733
830
|
requirementIds: binding.requirementIds.map(canonicalizeRequirementId),
|
|
734
831
|
};
|
|
832
|
+
// Source binding v2(schemaVersion=2 强制字段):透传 ledger 指针与
|
|
833
|
+
// requirement→fragment 映射。v1 绑定不得以 optional 字段冒充 v2。
|
|
834
|
+
if (isDagSourceBindingV2(binding)) {
|
|
835
|
+
sourceBinding.ledgerPath = binding.ledgerPath;
|
|
836
|
+
sourceBinding.ledgerSha256 = binding.ledgerSha256;
|
|
837
|
+
sourceBinding.inputDigest = binding.inputDigest;
|
|
838
|
+
sourceBinding.requirementToFragments = binding.requirementToFragments;
|
|
839
|
+
}
|
|
840
|
+
return sourceBinding;
|
|
735
841
|
}
|
|
736
842
|
function asRecord(value) {
|
|
737
843
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
@@ -1466,7 +1572,13 @@ export async function analyzeFrontendImplementationContract(input) {
|
|
|
1466
1572
|
rawOutput = input.rawContractText;
|
|
1467
1573
|
}
|
|
1468
1574
|
else if (input.fromNodeId !== undefined) {
|
|
1469
|
-
|
|
1575
|
+
let record;
|
|
1576
|
+
try {
|
|
1577
|
+
record = JSON.parse(await readFile(path.join(input.runDir, `${input.fromNodeId}.json`), "utf8"));
|
|
1578
|
+
}
|
|
1579
|
+
catch (error) {
|
|
1580
|
+
throw new Error(`cannot parse node record ${input.fromNodeId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1581
|
+
}
|
|
1470
1582
|
rawOutput = record.assistantText ?? record.stdout ?? "";
|
|
1471
1583
|
}
|
|
1472
1584
|
else {
|
|
@@ -1608,6 +1720,243 @@ export async function writeFrontendImplementationContractArtifact(input) {
|
|
|
1608
1720
|
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID,
|
|
1609
1721
|
};
|
|
1610
1722
|
}
|
|
1723
|
+
const FRONTEND_PLAN_PATCH_PROTECTED_PATHS = [
|
|
1724
|
+
"/schemaVersion",
|
|
1725
|
+
"/sourceBinding",
|
|
1726
|
+
"/riskLevel",
|
|
1727
|
+
"/targets/files",
|
|
1728
|
+
"/mockApi/productionDefaultOff",
|
|
1729
|
+
];
|
|
1730
|
+
function frontendPlanPatchProtectedPathViolations(patch) {
|
|
1731
|
+
const record = asRecord(patch);
|
|
1732
|
+
if (!record)
|
|
1733
|
+
return ["/"];
|
|
1734
|
+
const violations = [];
|
|
1735
|
+
for (const key of ["schemaVersion", "sourceBinding", "riskLevel"]) {
|
|
1736
|
+
if (Object.hasOwn(record, key))
|
|
1737
|
+
violations.push(`/${key}`);
|
|
1738
|
+
}
|
|
1739
|
+
const targets = asRecord(record.targets);
|
|
1740
|
+
if (targets && Object.hasOwn(targets, "files"))
|
|
1741
|
+
violations.push("/targets/files");
|
|
1742
|
+
const mockApi = asRecord(record.mockApi);
|
|
1743
|
+
if (mockApi && Object.hasOwn(mockApi, "productionDefaultOff"))
|
|
1744
|
+
violations.push("/mockApi/productionDefaultOff");
|
|
1745
|
+
return violations;
|
|
1746
|
+
}
|
|
1747
|
+
function workspaceRootFromDagRunDir(runDir) {
|
|
1748
|
+
const absolute = path.resolve(runDir);
|
|
1749
|
+
const segments = absolute.split(path.sep);
|
|
1750
|
+
const harnessIndex = segments.lastIndexOf(".harness");
|
|
1751
|
+
if (harnessIndex < 0 ||
|
|
1752
|
+
segments[harnessIndex + 1] !== "dag-runs" ||
|
|
1753
|
+
harnessIndex + 3 >= segments.length) {
|
|
1754
|
+
return null;
|
|
1755
|
+
}
|
|
1756
|
+
const joined = segments.slice(0, harnessIndex).join(path.sep);
|
|
1757
|
+
return joined || path.parse(absolute).root;
|
|
1758
|
+
}
|
|
1759
|
+
function verificationSymbolMatchesFile(symbol, content) {
|
|
1760
|
+
if (symbol.trim().toLowerCase() === "all describe blocks")
|
|
1761
|
+
return content.includes("describe(");
|
|
1762
|
+
const symbols = symbol.includes(" / ")
|
|
1763
|
+
? symbol
|
|
1764
|
+
.split(/[((]/, 1)[0]
|
|
1765
|
+
.split("/")
|
|
1766
|
+
.map((item) => item.trim())
|
|
1767
|
+
.filter(Boolean)
|
|
1768
|
+
: [symbol];
|
|
1769
|
+
return symbols.every((item) => {
|
|
1770
|
+
const describeTitle = item.match(/^describe\((?:['"])(.+?)(?:['"])/i)?.[1];
|
|
1771
|
+
return [item, describeTitle, describeTitle ? "describe(" : undefined]
|
|
1772
|
+
.filter((candidate) => Boolean(candidate))
|
|
1773
|
+
.some((candidate) => content.includes(candidate));
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
async function dropUnresolvedVerificationSymbols(input) {
|
|
1777
|
+
const workspaceRoot = workspaceRootFromDagRunDir(input.runDir);
|
|
1778
|
+
const targets = asRecord(input.contract)?.verificationTargets;
|
|
1779
|
+
if (!workspaceRoot || !Array.isArray(targets))
|
|
1780
|
+
return false;
|
|
1781
|
+
let changed = false;
|
|
1782
|
+
for (const targetValue of targets) {
|
|
1783
|
+
const target = asRecord(targetValue);
|
|
1784
|
+
const file = asString(target?.file);
|
|
1785
|
+
const symbol = asString(target?.symbol);
|
|
1786
|
+
if (!target || !file || !symbol)
|
|
1787
|
+
continue;
|
|
1788
|
+
const absolute = path.resolve(workspaceRoot, file);
|
|
1789
|
+
const relative = path.relative(workspaceRoot, absolute);
|
|
1790
|
+
if (relative.startsWith("..") ||
|
|
1791
|
+
path.isAbsolute(relative) ||
|
|
1792
|
+
relative.includes("..")) {
|
|
1793
|
+
continue;
|
|
1794
|
+
}
|
|
1795
|
+
let content;
|
|
1796
|
+
try {
|
|
1797
|
+
content = await readFile(absolute, "utf8");
|
|
1798
|
+
}
|
|
1799
|
+
catch {
|
|
1800
|
+
// Missing files remain a trace-time hard failure; do not hide them by
|
|
1801
|
+
// changing the optional symbol during candidate normalization.
|
|
1802
|
+
continue;
|
|
1803
|
+
}
|
|
1804
|
+
if (verificationSymbolMatchesFile(symbol, content))
|
|
1805
|
+
continue;
|
|
1806
|
+
delete target.symbol;
|
|
1807
|
+
changed = true;
|
|
1808
|
+
}
|
|
1809
|
+
return changed;
|
|
1810
|
+
}
|
|
1811
|
+
function extractSingleOpenspecCitationsBlock(text) {
|
|
1812
|
+
// Line-anchored so a directly closed empty fence (no candidate rows) still
|
|
1813
|
+
// counts as the one required block instead of "found 0" retry loops.
|
|
1814
|
+
const blocks = [
|
|
1815
|
+
...text.matchAll(/^```openspec-citations[ \t]*\r?\n([\s\S]*?)^```[ \t]*$/gim),
|
|
1816
|
+
].map((match) => match[0]);
|
|
1817
|
+
if (blocks.length !== 1) {
|
|
1818
|
+
throw new Error(`frontend plan patch output must include exactly one fenced openspec-citations block (found ${blocks.length})`);
|
|
1819
|
+
}
|
|
1820
|
+
return blocks[0];
|
|
1821
|
+
}
|
|
1822
|
+
async function writeFrontendPlanCandidateRaw(input) {
|
|
1823
|
+
const relativePath = path.posix.join("contracts", "candidates", input.nodeId, `attempt-${input.attempt}.raw.md`);
|
|
1824
|
+
await writeTextArtifactFile(path.join(input.runDir, relativePath), input.text.endsWith("\n") ? input.text : `${input.text}\n`);
|
|
1825
|
+
return relativePath;
|
|
1826
|
+
}
|
|
1827
|
+
/**
|
|
1828
|
+
* Compile the planner's editable RFC 7386 patch against the deterministic
|
|
1829
|
+
* runtime skeleton. Every attempt is preserved under contracts/candidates so
|
|
1830
|
+
* format/schema failures can be inspected and replayed without trusting model
|
|
1831
|
+
* prose. A successful result returns a path-only pointer plus the hash-bound canonical artifact for
|
|
1832
|
+
* downstream review and prewrite nodes.
|
|
1833
|
+
*/
|
|
1834
|
+
export async function validateFrontendPlanPatchNodeOutput(input) {
|
|
1835
|
+
const nodeId = input.nodeId ?? "frontend-plan-pi";
|
|
1836
|
+
const attempt = input.attempt ?? 1;
|
|
1837
|
+
const candidateDir = path.posix.join("contracts", "candidates", nodeId);
|
|
1838
|
+
const rawPath = await writeFrontendPlanCandidateRaw({
|
|
1839
|
+
runDir: input.runDir,
|
|
1840
|
+
nodeId,
|
|
1841
|
+
attempt,
|
|
1842
|
+
text: input.text,
|
|
1843
|
+
});
|
|
1844
|
+
const reportBase = {
|
|
1845
|
+
schemaVersion: 1,
|
|
1846
|
+
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_PLAN_PATCH_SCHEMA_ID,
|
|
1847
|
+
nodeId,
|
|
1848
|
+
attempt,
|
|
1849
|
+
rawPath,
|
|
1850
|
+
protectedPaths: [...FRONTEND_PLAN_PATCH_PROTECTED_PATHS],
|
|
1851
|
+
candidateRawSha256: sha256Hex(input.text),
|
|
1852
|
+
};
|
|
1853
|
+
let extractedPatchPath;
|
|
1854
|
+
let extractedPatchSha256;
|
|
1855
|
+
const writeReport = async (value) => {
|
|
1856
|
+
await writeDeterministicJsonArtifact(input.runDir, path.posix.join(candidateDir, `attempt-${attempt}.validation.json`), {
|
|
1857
|
+
...reportBase,
|
|
1858
|
+
...(extractedPatchPath
|
|
1859
|
+
? { extractedPatchPath, extractedPatchSha256 }
|
|
1860
|
+
: {}),
|
|
1861
|
+
...value,
|
|
1862
|
+
});
|
|
1863
|
+
};
|
|
1864
|
+
try {
|
|
1865
|
+
if (!input.sourceBinding)
|
|
1866
|
+
throw new Error("frontend plan patch validator requires DAG sourceBinding");
|
|
1867
|
+
const skeleton = input.structuredContractOutput?.skeleton;
|
|
1868
|
+
if (!skeleton)
|
|
1869
|
+
throw new Error("frontend plan patch validator requires a deterministic runtime skeleton");
|
|
1870
|
+
const patch = extractFrontendImplementationJson(input.text);
|
|
1871
|
+
if (!isPlainObject(patch))
|
|
1872
|
+
throw new Error("frontend plan patch must extract to one JSON object");
|
|
1873
|
+
extractedPatchPath = path.posix.join(candidateDir, `attempt-${attempt}.extracted.json`);
|
|
1874
|
+
const extractedArtifact = await writeDeterministicJsonArtifact(input.runDir, extractedPatchPath, patch);
|
|
1875
|
+
extractedPatchSha256 = extractedArtifact.sha256;
|
|
1876
|
+
const citationsBlock = extractSingleOpenspecCitationsBlock(input.text);
|
|
1877
|
+
const protectedViolations = frontendPlanPatchProtectedPathViolations(patch);
|
|
1878
|
+
if (protectedViolations.length > 0)
|
|
1879
|
+
throw new Error(`frontend plan patch must not modify runtime-protected paths: ${protectedViolations.join(", ")}`);
|
|
1880
|
+
const merged = applyFrontendContractMergePatch(skeleton, patch);
|
|
1881
|
+
const droppedUnresolvedSymbols = await dropUnresolvedVerificationSymbols({
|
|
1882
|
+
runDir: input.runDir,
|
|
1883
|
+
contract: merged,
|
|
1884
|
+
});
|
|
1885
|
+
const analysis = await analyzeFrontendImplementationContract({
|
|
1886
|
+
runDir: input.runDir,
|
|
1887
|
+
rawContractText: serializeDeterministicJson(merged),
|
|
1888
|
+
sourceBinding: input.sourceBinding,
|
|
1889
|
+
});
|
|
1890
|
+
const normalizedArtifact = await writeDeterministicJsonArtifact(input.runDir, path.posix.join(candidateDir, `attempt-${attempt}.normalized.json`), analysis.canonical);
|
|
1891
|
+
await writeReport({
|
|
1892
|
+
classification: "accepted-normalized",
|
|
1893
|
+
normalizedContractPath: path.posix.relative(input.runDir.replaceAll(path.sep, "/"), normalizedArtifact.path.replaceAll(path.sep, "/")),
|
|
1894
|
+
normalizedContractSha256: normalizedArtifact.sha256,
|
|
1895
|
+
normalizationActions: [
|
|
1896
|
+
"apply-runtime-skeleton",
|
|
1897
|
+
...(droppedUnresolvedSymbols
|
|
1898
|
+
? ["drop-unresolved-verification-symbols"]
|
|
1899
|
+
: []),
|
|
1900
|
+
...analysis.normalizationActions,
|
|
1901
|
+
],
|
|
1902
|
+
errors: [],
|
|
1903
|
+
});
|
|
1904
|
+
const canonicalRel = canonicalContractRel(nodeId);
|
|
1905
|
+
const canonicalArtifact = await writeDeterministicJsonArtifact(input.runDir, canonicalRel, analysis.canonical);
|
|
1906
|
+
const parsedCitations = parseOpenspecCitationsBlock(citationsBlock);
|
|
1907
|
+
await writeDeterministicJsonArtifact(input.runDir, openspecCitationsRel(nodeId), {
|
|
1908
|
+
citations: parsedCitations.citations,
|
|
1909
|
+
block: citationsBlock,
|
|
1910
|
+
});
|
|
1911
|
+
await persistRuntimeSkeleton({
|
|
1912
|
+
runDir: input.runDir,
|
|
1913
|
+
nodeId,
|
|
1914
|
+
skeleton,
|
|
1915
|
+
});
|
|
1916
|
+
const artifact = {
|
|
1917
|
+
path: canonicalArtifact.path,
|
|
1918
|
+
sha256: canonicalArtifact.sha256,
|
|
1919
|
+
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID,
|
|
1920
|
+
};
|
|
1921
|
+
return {
|
|
1922
|
+
ok: true,
|
|
1923
|
+
contract: analysis.canonical,
|
|
1924
|
+
artifact,
|
|
1925
|
+
normalizedText: formatStructuredArtifactPointer(artifact),
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
catch (error) {
|
|
1929
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1930
|
+
const classified = classifyStructuredContractFailure({
|
|
1931
|
+
reason,
|
|
1932
|
+
kind: error instanceof FrontendContractFailure ? error.kind : undefined,
|
|
1933
|
+
});
|
|
1934
|
+
await writeReport({
|
|
1935
|
+
classification: classified.classification,
|
|
1936
|
+
normalizationActions: [],
|
|
1937
|
+
errors: classified.errors,
|
|
1938
|
+
});
|
|
1939
|
+
return {
|
|
1940
|
+
ok: false,
|
|
1941
|
+
reason,
|
|
1942
|
+
classification: classified.classification,
|
|
1943
|
+
errors: classified.errors,
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
export async function readFrontendCanonicalCandidate(runDir, nodeId) {
|
|
1948
|
+
const candidatePath = path.join(runDir, canonicalContractRel(nodeId));
|
|
1949
|
+
try {
|
|
1950
|
+
const raw = await readFile(candidatePath, "utf8");
|
|
1951
|
+
const parsed = frontendImplementationContractSchema.safeParse(JSON.parse(raw));
|
|
1952
|
+
return parsed.success ? parsed.data : undefined;
|
|
1953
|
+
}
|
|
1954
|
+
catch (error) {
|
|
1955
|
+
if (error.code === "ENOENT")
|
|
1956
|
+
return undefined;
|
|
1957
|
+
return undefined;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1611
1960
|
/**
|
|
1612
1961
|
* Node-output self-check for plan nodes that produce the implementation
|
|
1613
1962
|
* contract. Mirrors the prewrite gate validation so schema/typo/null violations
|
|
@@ -1626,12 +1975,122 @@ export async function validateFrontendContractNodeOutput(input) {
|
|
|
1626
1975
|
return { ok: true, contract: analysis.canonical };
|
|
1627
1976
|
}
|
|
1628
1977
|
catch (error) {
|
|
1978
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1979
|
+
const classified = classifyStructuredContractFailure({
|
|
1980
|
+
reason,
|
|
1981
|
+
kind: error instanceof FrontendContractFailure ? error.kind : undefined,
|
|
1982
|
+
});
|
|
1629
1983
|
return {
|
|
1630
1984
|
ok: false,
|
|
1631
|
-
reason
|
|
1985
|
+
reason,
|
|
1986
|
+
classification: classified.classification,
|
|
1987
|
+
errors: classified.errors,
|
|
1632
1988
|
};
|
|
1633
1989
|
}
|
|
1634
1990
|
}
|
|
1991
|
+
/**
|
|
1992
|
+
* Node-output self-check for the plan revision node. Format-level checks still
|
|
1993
|
+
* require one openspec-citations fence and one JSON object. When the original
|
|
1994
|
+
* canonical contract and sourceBinding are present, this node is the merge
|
|
1995
|
+
* authority: it applies the RFC 7386 patch, validates the full contract, and
|
|
1996
|
+
* writes the revision node's own hash-bound canonical artifact.
|
|
1997
|
+
*/
|
|
1998
|
+
export async function validateFrontendRevisionPatchNodeOutput(input) {
|
|
1999
|
+
const fail = (reason) => {
|
|
2000
|
+
const classified = classifyStructuredContractFailure({ reason });
|
|
2001
|
+
return {
|
|
2002
|
+
ok: false,
|
|
2003
|
+
reason,
|
|
2004
|
+
classification: classified.classification,
|
|
2005
|
+
errors: classified.errors,
|
|
2006
|
+
};
|
|
2007
|
+
};
|
|
2008
|
+
const nodeId = input.nodeId ?? "frontend-plan-revision-pi";
|
|
2009
|
+
const attempt = input.attempt ?? 1;
|
|
2010
|
+
await writeFrontendPlanCandidateRaw({
|
|
2011
|
+
runDir: input.runDir,
|
|
2012
|
+
nodeId,
|
|
2013
|
+
attempt,
|
|
2014
|
+
text: input.text,
|
|
2015
|
+
});
|
|
2016
|
+
let citationsBlock;
|
|
2017
|
+
try {
|
|
2018
|
+
citationsBlock = extractSingleOpenspecCitationsBlock(input.text);
|
|
2019
|
+
}
|
|
2020
|
+
catch (error) {
|
|
2021
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
2022
|
+
}
|
|
2023
|
+
const textWithoutCitations = input.text.replace(citationsBlock, "");
|
|
2024
|
+
let patch;
|
|
2025
|
+
try {
|
|
2026
|
+
patch = extractFrontendImplementationJson(textWithoutCitations);
|
|
2027
|
+
}
|
|
2028
|
+
catch (error) {
|
|
2029
|
+
return fail(`revision patch output must contain exactly one valid JSON object: ${error instanceof Error ? error.message : String(error)}`);
|
|
2030
|
+
}
|
|
2031
|
+
if (!patch || typeof patch !== "object" || Array.isArray(patch)) {
|
|
2032
|
+
return fail("revision patch output must extract to exactly one JSON object (RFC 7386 merge-patch delta)");
|
|
2033
|
+
}
|
|
2034
|
+
const protectedViolations = frontendPlanPatchProtectedPathViolations(patch);
|
|
2035
|
+
if (protectedViolations.length > 0) {
|
|
2036
|
+
return fail(`frontend revision patch must not modify runtime-protected paths: ${protectedViolations.join(", ")}`);
|
|
2037
|
+
}
|
|
2038
|
+
const original = (await readFrontendCanonicalCandidate(input.runDir, "frontend-plan-pi")) ??
|
|
2039
|
+
(await readFrontendCanonicalCandidate(input.runDir, nodeId));
|
|
2040
|
+
if (!input.sourceBinding) {
|
|
2041
|
+
return { ok: true, contract: patch };
|
|
2042
|
+
}
|
|
2043
|
+
if (!original) {
|
|
2044
|
+
return fail("frontend revision patch requires the original frontend-plan-pi canonical contract");
|
|
2045
|
+
}
|
|
2046
|
+
try {
|
|
2047
|
+
const merged = applyFrontendContractMergePatch(original, patch);
|
|
2048
|
+
const droppedUnresolvedSymbols = await dropUnresolvedVerificationSymbols({
|
|
2049
|
+
runDir: input.runDir,
|
|
2050
|
+
contract: merged,
|
|
2051
|
+
});
|
|
2052
|
+
const analysis = await analyzeFrontendImplementationContract({
|
|
2053
|
+
runDir: input.runDir,
|
|
2054
|
+
rawContractText: serializeDeterministicJson(merged),
|
|
2055
|
+
sourceBinding: input.sourceBinding,
|
|
2056
|
+
});
|
|
2057
|
+
const candidateDir = path.posix.join("contracts", "candidates", nodeId);
|
|
2058
|
+
await writeDeterministicJsonArtifact(input.runDir, path.posix.join(candidateDir, `attempt-${attempt}.extracted.json`), patch);
|
|
2059
|
+
await writeDeterministicJsonArtifact(input.runDir, path.posix.join(candidateDir, `attempt-${attempt}.normalized.json`), analysis.canonical);
|
|
2060
|
+
await writeDeterministicJsonArtifact(input.runDir, path.posix.join(candidateDir, `attempt-${attempt}.validation.json`), {
|
|
2061
|
+
classification: "accepted-normalized",
|
|
2062
|
+
normalizationActions: [
|
|
2063
|
+
"apply-original-canonical",
|
|
2064
|
+
...(droppedUnresolvedSymbols
|
|
2065
|
+
? ["drop-unresolved-verification-symbols"]
|
|
2066
|
+
: []),
|
|
2067
|
+
...analysis.normalizationActions,
|
|
2068
|
+
],
|
|
2069
|
+
errors: [],
|
|
2070
|
+
});
|
|
2071
|
+
const canonicalArtifact = await writeDeterministicJsonArtifact(input.runDir, canonicalContractRel(nodeId), analysis.canonical);
|
|
2072
|
+
const parsedCitations = parseOpenspecCitationsBlock(citationsBlock);
|
|
2073
|
+
await writeDeterministicJsonArtifact(input.runDir, openspecCitationsRel(nodeId), {
|
|
2074
|
+
citations: parsedCitations.citations,
|
|
2075
|
+
block: citationsBlock,
|
|
2076
|
+
});
|
|
2077
|
+
const artifact = {
|
|
2078
|
+
path: canonicalArtifact.path,
|
|
2079
|
+
sha256: canonicalArtifact.sha256,
|
|
2080
|
+
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID,
|
|
2081
|
+
};
|
|
2082
|
+
return {
|
|
2083
|
+
ok: true,
|
|
2084
|
+
contract: analysis.canonical,
|
|
2085
|
+
artifact,
|
|
2086
|
+
normalizedText: formatStructuredArtifactPointer(artifact),
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
catch (error) {
|
|
2090
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2091
|
+
return fail(reason);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
1635
2094
|
export async function materializeFrontendImplementationContract(input) {
|
|
1636
2095
|
const analysis = await analyzeFrontendImplementationContract(input);
|
|
1637
2096
|
return writeFrontendImplementationContractArtifact({
|
|
@@ -47,6 +47,27 @@ function componentSelection(contract) {
|
|
|
47
47
|
})
|
|
48
48
|
.join("\n");
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* 有界 source fidelity ledger 指针(AC-007 / AC-FIDELITY-007):长原文不重新
|
|
52
|
+
* 全量内联到节点提示词;只注入紧凑 canonical contract 与 bound ledger 指针
|
|
53
|
+
* (路径/哈希/inputDigest),ledger coverage 证明长尾未丢失。
|
|
54
|
+
*/
|
|
55
|
+
function sourceFidelityLedger(contract) {
|
|
56
|
+
const binding = contract.sourceBinding;
|
|
57
|
+
if (!binding.ledgerPath)
|
|
58
|
+
return "_(not bound — v1 source binding)_";
|
|
59
|
+
const lines = [
|
|
60
|
+
`- ledger: ${binding.ledgerPath}`,
|
|
61
|
+
`- ledgerSha256: ${binding.ledgerSha256 ?? "(missing)"}`,
|
|
62
|
+
`- inputDigest: ${binding.inputDigest ?? "(missing)"}`,
|
|
63
|
+
"- 语义权威:原始 requirement/acceptance 文档与 requirement-ledger.json;本计划只消费派生摘要与 bound ledger 指针,长原文不内联。",
|
|
64
|
+
];
|
|
65
|
+
const boundCount = Object.keys(binding.requirementToFragments ?? {}).length;
|
|
66
|
+
if (boundCount > 0) {
|
|
67
|
+
lines.push(`- requirement→fragment bindings: ${boundCount}`);
|
|
68
|
+
}
|
|
69
|
+
return lines.join("\n");
|
|
70
|
+
}
|
|
50
71
|
export function renderFrontendPlanMarkdown(contract) {
|
|
51
72
|
return [
|
|
52
73
|
"# Frontend Implementation Plan",
|
|
@@ -54,6 +75,9 @@ export function renderFrontendPlanMarkdown(contract) {
|
|
|
54
75
|
"## Requirement Coverage",
|
|
55
76
|
requirementsCoverage(contract),
|
|
56
77
|
"",
|
|
78
|
+
"## Source Fidelity Ledger",
|
|
79
|
+
sourceFidelityLedger(contract),
|
|
80
|
+
"",
|
|
57
81
|
"## Implementation Steps",
|
|
58
82
|
bulletList(contract.implementationSteps) || "_(not specified)_",
|
|
59
83
|
"",
|