@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
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import semver from "semver";
|
|
3
|
+
import { acquireUpdateLock, readUpdateState, updateLockPath, updateStatePath, writeUpdateState, } from "./state.js";
|
|
4
|
+
export async function runConsoleDailyUpdateCheck(options) {
|
|
5
|
+
void options.writeStdout;
|
|
6
|
+
void options.packageRoot;
|
|
7
|
+
void options.cwd;
|
|
8
|
+
const writeStderr = options.writeStderr ?? ((chunk) => process.stderr.write(chunk));
|
|
9
|
+
const now = options.now ?? (() => new Date());
|
|
10
|
+
try {
|
|
11
|
+
if (!canPromptInteractively(options))
|
|
12
|
+
return;
|
|
13
|
+
const statePath = updateStatePath(options.homeDir);
|
|
14
|
+
const today = formatLocalDate(now());
|
|
15
|
+
const existing = await readUpdateState(statePath);
|
|
16
|
+
if (existing.lastPromptedLocalDate === today)
|
|
17
|
+
return;
|
|
18
|
+
const release = await acquireUpdateLock(updateLockPath(options.homeDir), {
|
|
19
|
+
now,
|
|
20
|
+
});
|
|
21
|
+
if (!release)
|
|
22
|
+
return;
|
|
23
|
+
try {
|
|
24
|
+
const state = await readUpdateState(statePath);
|
|
25
|
+
if (state.lastPromptedLocalDate === today)
|
|
26
|
+
return;
|
|
27
|
+
const check = await options.client.latestVersionCheck();
|
|
28
|
+
if (!check.ok)
|
|
29
|
+
return;
|
|
30
|
+
if (!semver.gt(check.version, options.currentVersion))
|
|
31
|
+
return;
|
|
32
|
+
const parsedLatest = semver.parse(check.version);
|
|
33
|
+
if (!parsedLatest || parsedLatest.prerelease.length > 0)
|
|
34
|
+
return;
|
|
35
|
+
await markPromptedToday(statePath, state, today);
|
|
36
|
+
const answer = await ask(options, `loop-agent 有新版本:${options.currentVersion} → ${check.version}\n现在更新到 ${check.version}?[y/N] `);
|
|
37
|
+
if (!isYes(answer))
|
|
38
|
+
return;
|
|
39
|
+
const proof = await options.client.canAutoInstall();
|
|
40
|
+
if (!proof.ok) {
|
|
41
|
+
writeStderr(`当前 loop-agent 不是由此 npm 的正式全局安装管理,无法自动升级。\n请运行:npm install -g @tea-agent/loop-agent@${check.version}\n`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const install = await options.client.installExact(check.version);
|
|
45
|
+
if (!install.ok) {
|
|
46
|
+
writeStderr(`loop-agent 更新失败:${install.reason}\n`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const verified = await options.client.verifyInstalled(check.version);
|
|
50
|
+
if (!verified.ok) {
|
|
51
|
+
writeStderr(`loop-agent 已尝试安装,但验证失败:${verified.reason}\n`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
writeStderr(`loop-agent 已更新到 ${check.version};新版本将在下一次命令调用时完全生效。\n`);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await release().catch(() => undefined);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
writeStderr(`loop-agent 更新检查已跳过:${error instanceof Error ? error.message : String(error)}\n`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function canPromptInteractively(options) {
|
|
65
|
+
if (!options.stdinIsTTY || !options.stderrIsTTY)
|
|
66
|
+
return false;
|
|
67
|
+
if (options.env.CI)
|
|
68
|
+
return false;
|
|
69
|
+
if (options.env.LOOP_AGENT_DISABLE_UPDATE_CHECK === "1")
|
|
70
|
+
return false;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
function formatLocalDate(date) {
|
|
74
|
+
const year = date.getFullYear();
|
|
75
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
76
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
77
|
+
return `${year}-${month}-${day}`;
|
|
78
|
+
}
|
|
79
|
+
async function markPromptedToday(statePath, state, today) {
|
|
80
|
+
await writeUpdateState(statePath, {
|
|
81
|
+
...state,
|
|
82
|
+
lastPromptedLocalDate: today,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async function ask(options, prompt) {
|
|
86
|
+
if (options.readLine) {
|
|
87
|
+
options.writeStderr?.(prompt);
|
|
88
|
+
return options.readLine(prompt);
|
|
89
|
+
}
|
|
90
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
91
|
+
try {
|
|
92
|
+
return await rl.question(prompt);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
rl.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function isYes(answer) {
|
|
99
|
+
return /^(y|yes)$/i.test(answer.trim());
|
|
100
|
+
}
|
|
@@ -3,43 +3,77 @@ import { lstat, readFile, realpath } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import semver from "semver";
|
|
5
5
|
const PACKAGE_NAME = "@tea-agent/loop-agent";
|
|
6
|
+
export const DEFAULT_NPM_QUERY_TIMEOUT_MS = 15_000;
|
|
6
7
|
export function createNpmUpdateClient(options) {
|
|
7
8
|
const npmCommand = options.npmCommand ?? "npm";
|
|
8
9
|
const loopAgentCommand = options.loopAgentCommand ?? "loop-agent";
|
|
9
10
|
const runner = options.runner ?? runCommand;
|
|
10
|
-
const timeoutMs = options.timeoutMs ??
|
|
11
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_NPM_QUERY_TIMEOUT_MS;
|
|
12
|
+
const cwd = options.cwd;
|
|
11
13
|
const env = options.env;
|
|
14
|
+
const distTagVersionCheck = async (channel) => {
|
|
15
|
+
let result;
|
|
16
|
+
try {
|
|
17
|
+
result = await runner(npmCommand, ["view", PACKAGE_NAME, `dist-tags.${channel}`, "--json"], { timeoutMs, cwd, env });
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { ok: false, reason: "spawn-failure" };
|
|
21
|
+
}
|
|
22
|
+
if (result.timedOut)
|
|
23
|
+
return { ok: false, reason: "timeout" };
|
|
24
|
+
if (result.exitCode === null) {
|
|
25
|
+
return { ok: false, reason: "spawn-failure" };
|
|
26
|
+
}
|
|
27
|
+
if (result.exitCode !== 0) {
|
|
28
|
+
return { ok: false, reason: "command-failed" };
|
|
29
|
+
}
|
|
30
|
+
const version = parseNpmViewDistTag(result.stdout, channel);
|
|
31
|
+
return version
|
|
32
|
+
? { ok: true, version }
|
|
33
|
+
: { ok: false, reason: "invalid-response" };
|
|
34
|
+
};
|
|
35
|
+
const latestVersionCheck = () => distTagVersionCheck("latest");
|
|
12
36
|
return {
|
|
13
37
|
async latestVersion() {
|
|
14
|
-
const result = await
|
|
15
|
-
|
|
16
|
-
return undefined;
|
|
17
|
-
return parseNpmViewLatest(result.stdout);
|
|
38
|
+
const result = await latestVersionCheck();
|
|
39
|
+
return result.ok ? result.version : undefined;
|
|
18
40
|
},
|
|
41
|
+
latestVersionCheck,
|
|
42
|
+
distTagVersionCheck,
|
|
19
43
|
async canAutoInstall() {
|
|
20
44
|
return proveGlobalNpmInstall({
|
|
21
45
|
currentVersion: options.currentVersion,
|
|
22
46
|
packageRoot: options.packageRoot,
|
|
23
47
|
npmCommand,
|
|
24
48
|
runner,
|
|
49
|
+
cwd,
|
|
25
50
|
env,
|
|
26
51
|
timeoutMs,
|
|
27
52
|
});
|
|
28
53
|
},
|
|
29
|
-
async installExact(version) {
|
|
30
|
-
if (!
|
|
54
|
+
async installExact(version, channel = "latest") {
|
|
55
|
+
if (!isAllowedChannelVersion(version, channel)) {
|
|
31
56
|
return { ok: false, reason: "invalid-version" };
|
|
32
|
-
|
|
57
|
+
}
|
|
58
|
+
const result = await runner(npmCommand, buildExactInstallArgs(version, channel), {
|
|
59
|
+
timeoutMs: Math.max(timeoutMs, 120000),
|
|
60
|
+
cwd,
|
|
61
|
+
env,
|
|
62
|
+
});
|
|
33
63
|
if (result.exitCode !== 0 || result.timedOut)
|
|
34
64
|
return { ok: false, reason: "install-failed" };
|
|
35
65
|
return { ok: true };
|
|
36
66
|
},
|
|
37
67
|
async verifyInstalled(version) {
|
|
38
|
-
const list = await runner(npmCommand, ["list", "-g", PACKAGE_NAME, "--depth=0", "--json"], { timeoutMs, env });
|
|
68
|
+
const list = await runner(npmCommand, ["list", "-g", PACKAGE_NAME, "--depth=0", "--json"], { timeoutMs, cwd, env });
|
|
39
69
|
if (list.exitCode !== 0 || !verifyNpmListVersion(list.stdout, version)) {
|
|
40
70
|
return { ok: false, reason: "npm-list-version-mismatch" };
|
|
41
71
|
}
|
|
42
|
-
const pathVersion = await runner(loopAgentCommand, ["--version"], {
|
|
72
|
+
const pathVersion = await runner(loopAgentCommand, ["--version"], {
|
|
73
|
+
timeoutMs,
|
|
74
|
+
cwd,
|
|
75
|
+
env,
|
|
76
|
+
});
|
|
43
77
|
if (pathVersion.exitCode !== 0 || !verifyPathVersion(pathVersion.stdout, version)) {
|
|
44
78
|
return { ok: false, reason: "path-version-mismatch" };
|
|
45
79
|
}
|
|
@@ -48,6 +82,13 @@ export function createNpmUpdateClient(options) {
|
|
|
48
82
|
};
|
|
49
83
|
}
|
|
50
84
|
export function parseNpmViewLatest(stdout) {
|
|
85
|
+
return parseNpmViewDistTag(stdout, "latest");
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* `latest` stays stable-only. `next` is explicitly selected by a human and
|
|
89
|
+
* may legitimately be a SemVer prerelease, but remains an exact version.
|
|
90
|
+
*/
|
|
91
|
+
export function parseNpmViewDistTag(stdout, channel) {
|
|
51
92
|
const trimmed = stdout.trim();
|
|
52
93
|
let value = trimmed;
|
|
53
94
|
try {
|
|
@@ -58,11 +99,12 @@ export function parseNpmViewLatest(stdout) {
|
|
|
58
99
|
}
|
|
59
100
|
if (typeof value !== "string")
|
|
60
101
|
return undefined;
|
|
61
|
-
return
|
|
102
|
+
return isAllowedChannelVersion(value, channel) ? value : undefined;
|
|
62
103
|
}
|
|
63
|
-
export function buildExactInstallArgs(version) {
|
|
64
|
-
if (!
|
|
65
|
-
throw new Error(`invalid
|
|
104
|
+
export function buildExactInstallArgs(version, channel = "latest") {
|
|
105
|
+
if (!isAllowedChannelVersion(version, channel)) {
|
|
106
|
+
throw new Error(`invalid ${channel} version: ${version}`);
|
|
107
|
+
}
|
|
66
108
|
return ["install", "-g", `${PACKAGE_NAME}@${version}`, "--no-fund", "--no-audit"];
|
|
67
109
|
}
|
|
68
110
|
export function verifyNpmListVersion(stdout, expectedVersion) {
|
|
@@ -75,11 +117,18 @@ export function verifyNpmListVersion(stdout, expectedVersion) {
|
|
|
75
117
|
}
|
|
76
118
|
}
|
|
77
119
|
export function verifyPathVersion(stdout, expectedVersion) {
|
|
78
|
-
|
|
120
|
+
// `loop-agent --version` may append a build provenance marker. The release
|
|
121
|
+
// version itself must still match exactly; accepting no other trailing form
|
|
122
|
+
// keeps PATH verification bound to the requested stable package.
|
|
123
|
+
return new RegExp(`^${escapeRegExp(expectedVersion)}(?: \\(build [^)]+\\))?$`).test(stdout.trim());
|
|
124
|
+
}
|
|
125
|
+
function escapeRegExp(value) {
|
|
126
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
79
127
|
}
|
|
80
128
|
async function proveGlobalNpmInstall(options) {
|
|
81
129
|
const root = await options.runner(options.npmCommand, ["root", "-g"], {
|
|
82
130
|
timeoutMs: options.timeoutMs,
|
|
131
|
+
cwd: options.cwd,
|
|
83
132
|
env: options.env,
|
|
84
133
|
});
|
|
85
134
|
if (root.exitCode !== 0 || root.timedOut)
|
|
@@ -110,9 +159,15 @@ function isStrictRelease(version) {
|
|
|
110
159
|
const parsed = semver.parse(version);
|
|
111
160
|
return Boolean(parsed && parsed.prerelease.length === 0);
|
|
112
161
|
}
|
|
162
|
+
function isAllowedChannelVersion(version, channel) {
|
|
163
|
+
if (channel === "latest")
|
|
164
|
+
return isStrictRelease(version);
|
|
165
|
+
return Boolean(semver.valid(version));
|
|
166
|
+
}
|
|
113
167
|
async function runCommand(command, args, options) {
|
|
114
168
|
return new Promise((resolve) => {
|
|
115
169
|
const child = spawn(command, [...args], {
|
|
170
|
+
cwd: options.cwd,
|
|
116
171
|
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
117
172
|
stdio: ["ignore", "pipe", "pipe"],
|
|
118
173
|
shell: false,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
export function updateStatePath(homeDir) {
|
|
4
4
|
return path.join(homeDir, ".loop-agent", "update-state.json");
|
|
@@ -18,6 +18,7 @@ export async function readUpdateState(statePath) {
|
|
|
18
18
|
latestVersion: stringOrUndefined(parsed.latestVersion),
|
|
19
19
|
dismissedVersion: stringOrUndefined(parsed.dismissedVersion),
|
|
20
20
|
retryAfter: stringOrUndefined(parsed.retryAfter),
|
|
21
|
+
lastPromptedLocalDate: stringOrUndefined(parsed.lastPromptedLocalDate),
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
24
|
catch {
|
|
@@ -36,28 +37,55 @@ export async function acquireUpdateLock(lockPath, options = {}) {
|
|
|
36
37
|
await mkdir(path.dirname(lockPath), { recursive: true });
|
|
37
38
|
try {
|
|
38
39
|
await mkdir(lockPath);
|
|
40
|
+
return await claimLock(lockPath, now);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
if (await isFreshLock(lockPath, now, staleMs))
|
|
44
|
+
return undefined;
|
|
45
|
+
try {
|
|
46
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
47
|
+
await mkdir(lockPath);
|
|
48
|
+
return await claimLock(lockPath, now);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function claimLock(lockPath, now) {
|
|
56
|
+
try {
|
|
39
57
|
await writeFile(path.join(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, at: now().toISOString() }), "utf-8");
|
|
40
58
|
return async () => {
|
|
41
59
|
await rm(lockPath, { recursive: true, force: true });
|
|
42
60
|
};
|
|
43
61
|
}
|
|
62
|
+
catch {
|
|
63
|
+
await rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function isFreshLock(lockPath, now, staleMs) {
|
|
68
|
+
try {
|
|
69
|
+
const ownerRaw = await readFile(path.join(lockPath, "owner.json"), "utf-8");
|
|
70
|
+
const owner = JSON.parse(ownerRaw);
|
|
71
|
+
if (typeof owner.at !== "string")
|
|
72
|
+
return false;
|
|
73
|
+
const at = Date.parse(owner.at);
|
|
74
|
+
if (!Number.isFinite(at))
|
|
75
|
+
return false;
|
|
76
|
+
return now().getTime() - at <= staleMs;
|
|
77
|
+
}
|
|
44
78
|
catch {
|
|
45
79
|
try {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
await writeFile(path.join(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, at: now().toISOString() }), "utf-8");
|
|
52
|
-
return async () => {
|
|
53
|
-
await rm(lockPath, { recursive: true, force: true });
|
|
54
|
-
};
|
|
55
|
-
}
|
|
80
|
+
const info = await stat(lockPath);
|
|
81
|
+
const mtime = Number.isFinite(info.mtimeMs)
|
|
82
|
+
? info.mtimeMs
|
|
83
|
+
: info.mtime.getTime();
|
|
84
|
+
return now().getTime() - mtime <= staleMs;
|
|
56
85
|
}
|
|
57
86
|
catch {
|
|
58
|
-
return
|
|
87
|
+
return false;
|
|
59
88
|
}
|
|
60
|
-
return undefined;
|
|
61
89
|
}
|
|
62
90
|
}
|
|
63
91
|
function emptyState() {
|
|
@@ -87,6 +87,12 @@ export const frontendOpenspecConfigSchema = z.object({
|
|
|
87
87
|
/** Batch limits and gate profile for the browser-driven frontend test DAG.
|
|
88
88
|
* Token fields are post-case stop thresholds, not model-provider hard caps. */
|
|
89
89
|
export const frontendTestConfigSchema = z.object({
|
|
90
|
+
/**
|
|
91
|
+
* Repository-relative frontend-test artifact root.
|
|
92
|
+
* Default "testcase/frontend". Semantic validation lives in
|
|
93
|
+
* resolveFrontendTestLayout (fail-closed), not in this schema.
|
|
94
|
+
*/
|
|
95
|
+
testRoot: z.string().min(1).optional(),
|
|
90
96
|
maxCasesPerBatch: z.number().int().min(1).max(50).optional(),
|
|
91
97
|
maxTokensPerCase: z.number().int().positive().optional(),
|
|
92
98
|
maxTotalTokens: z.number().int().positive().optional(),
|
|
@@ -124,6 +130,26 @@ export const frontendTestConfigSchema = z.object({
|
|
|
124
130
|
.strict()
|
|
125
131
|
.optional(),
|
|
126
132
|
});
|
|
133
|
+
/** Backend-test artifact layout and pipeline mode configuration (plan A/B/D). */
|
|
134
|
+
export const backendTestConfigSchema = z
|
|
135
|
+
.object({
|
|
136
|
+
/** Repository-relative test root. Default "testcase". */
|
|
137
|
+
testRoot: z.string().min(1).optional(),
|
|
138
|
+
/** Markdown subdirectory relative to testRoot. Default "md". */
|
|
139
|
+
markdownRel: z.string().min(1).optional(),
|
|
140
|
+
/** Pytest script subdirectory relative to testRoot. Default "." (testRoot itself). */
|
|
141
|
+
scriptRel: z.string().min(1).optional(),
|
|
142
|
+
/** User-provided shared setup document (plan B). Read-only bound source. */
|
|
143
|
+
sharedSetupDoc: z.string().min(1).optional(),
|
|
144
|
+
/** Pipeline mode: full 16-node chain (default) or gap-fill incremental DAG (plan D). */
|
|
145
|
+
mode: z.enum(["full", "gap-fill"]).optional(),
|
|
146
|
+
/** Required for mode=gap-fill: user missing-scenario document path. */
|
|
147
|
+
gapDoc: z.string().min(1).optional(),
|
|
148
|
+
/** gap-fill pytest execution scope: only affected items (default) or all eligible. */
|
|
149
|
+
executeScope: z.enum(["affected", "all"]).optional(),
|
|
150
|
+
})
|
|
151
|
+
.strict()
|
|
152
|
+
.optional();
|
|
127
153
|
export const convergenceConfigSchema = z.object({
|
|
128
154
|
enabled: z.boolean().optional().default(false),
|
|
129
155
|
maxPasses: z.number().int().positive().optional().default(3),
|
|
@@ -196,6 +222,8 @@ const taskConfigObjectSchema = z.object({
|
|
|
196
222
|
frontendTest: frontendTestConfigSchema.optional(),
|
|
197
223
|
/** Frontend OpenSpec gate policy and contract-declared required read paths. */
|
|
198
224
|
frontendOpenspec: frontendOpenspecConfigSchema.optional(),
|
|
225
|
+
/** Backend-test layout / shared-setup / gap-fill configuration. */
|
|
226
|
+
backendTest: backendTestConfigSchema,
|
|
199
227
|
notes: z.string().optional().default(""),
|
|
200
228
|
});
|
|
201
229
|
export const taskConfigSchema = z.preprocess((raw) => {
|
|
@@ -232,8 +232,11 @@ export async function applyTaskContract(input) {
|
|
|
232
232
|
// Re-parse through schema to ensure full TaskConfig shape for hash stability
|
|
233
233
|
const fullConfig = parseTaskConfigForRuntime(mergedConfig);
|
|
234
234
|
const taskConfigContent = serializeTaskConfigForDisk(fullConfig);
|
|
235
|
-
const requirementContent = projectRequirementMarkdown(input.draft);
|
|
235
|
+
const requirementContent = projectRequirementMarkdown(input.draft, input.requirementLedger);
|
|
236
236
|
const constraintsContent = projectConstraintsMarkdown(input.draft);
|
|
237
|
+
const requirementLedgerContent = input.requirementLedger !== undefined
|
|
238
|
+
? `${JSON.stringify(input.requirementLedger, null, 2)}\n`
|
|
239
|
+
: undefined;
|
|
237
240
|
// Validate references manifest hash if present (do not modify)
|
|
238
241
|
const paths = getTaskPaths(input.repoRoot, input.taskId);
|
|
239
242
|
const manifestText = await readFile(getSourceManifestPath(paths.sourceDir), "utf-8").catch(() => null);
|
|
@@ -241,6 +244,7 @@ export async function applyTaskContract(input) {
|
|
|
241
244
|
requirementBytes: requirementContent,
|
|
242
245
|
constraintsBytes: constraintsContent,
|
|
243
246
|
referenceManifestBytes: manifestText,
|
|
247
|
+
requirementLedgerBytes: requirementLedgerContent,
|
|
244
248
|
});
|
|
245
249
|
const taskConfigSha256 = computeTaskConfigSha256(fullConfig);
|
|
246
250
|
const canonicalHash = computeCanonicalHash({
|
|
@@ -277,6 +281,9 @@ export async function applyTaskContract(input) {
|
|
|
277
281
|
constraintsContent,
|
|
278
282
|
taskConfigContent,
|
|
279
283
|
contractRef: nextRef,
|
|
284
|
+
...(requirementLedgerContent !== undefined
|
|
285
|
+
? { requirementLedgerContent }
|
|
286
|
+
: {}),
|
|
280
287
|
},
|
|
281
288
|
});
|
|
282
289
|
await appendRequestLedgerEntry({
|
|
@@ -312,6 +319,9 @@ export async function applyTaskContract(input) {
|
|
|
312
319
|
changedFiles: [
|
|
313
320
|
"source/需求.md",
|
|
314
321
|
"source/执行约束.md",
|
|
322
|
+
...(requirementLedgerContent !== undefined
|
|
323
|
+
? ["source/requirement-ledger.json"]
|
|
324
|
+
: []),
|
|
315
325
|
"task.json",
|
|
316
326
|
"contract.json",
|
|
317
327
|
],
|
|
@@ -17,6 +17,7 @@ export const MANAGED_TASK_CONFIG_FIELDS = [
|
|
|
17
17
|
"forbiddenPaths",
|
|
18
18
|
"hardConstraints",
|
|
19
19
|
"verifyCommands",
|
|
20
|
+
"backendTest",
|
|
20
21
|
];
|
|
21
22
|
export const CONTRACT_FILE_NAME = "contract.json";
|
|
22
23
|
export const TRANSACTIONS_DIR_NAME = ".transactions";
|
|
@@ -28,3 +29,5 @@ export const TRANSACTIONS_DIRNAME = TRANSACTIONS_DIR_NAME;
|
|
|
28
29
|
export const REQUEST_LEDGER_BASENAME = REQUEST_LEDGER_FILE_NAME;
|
|
29
30
|
export const REQUIREMENT_FILE_NAME = "需求.md";
|
|
30
31
|
export const CONSTRAINTS_FILE_NAME = "执行约束.md";
|
|
32
|
+
/** Source fidelity ledger (20260824-frontend-source-fidelity-ledger-v1). */
|
|
33
|
+
export const REQUIREMENT_LEDGER_FILE_NAME = "requirement-ledger.json";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canonicalizeJsonValue } from "./canonicalize.js";
|
|
1
2
|
import { MANAGED_TASK_CONFIG_FIELDS } from "./constants.js";
|
|
2
3
|
function maxRisk(a, b) {
|
|
3
4
|
const order = ["routine", "elevated", "blocking"];
|
|
@@ -139,6 +140,31 @@ export function diffDraftAgainstCurrent(input) {
|
|
|
139
140
|
path: "task.json#featureId",
|
|
140
141
|
});
|
|
141
142
|
}
|
|
143
|
+
// backendTest is managed whole-object: an absent draft value means
|
|
144
|
+
// "keep existing" and never diffs; declared values compare canonically.
|
|
145
|
+
if (input.draft.backendTest !== undefined) {
|
|
146
|
+
const before = cfg.backendTest ?? {};
|
|
147
|
+
const after = input.draft.backendTest;
|
|
148
|
+
if (canonicalizeJsonValue(before) !== canonicalizeJsonValue(after)) {
|
|
149
|
+
changedManagedFields.push("backendTest");
|
|
150
|
+
risks.push({
|
|
151
|
+
code: "BACKEND_TEST_CONFIG_CHANGED",
|
|
152
|
+
level: "routine",
|
|
153
|
+
message: "backendTest configuration changed",
|
|
154
|
+
path: "task.json#backendTest",
|
|
155
|
+
});
|
|
156
|
+
const afterKeys = new Set(Object.keys(after));
|
|
157
|
+
const droppedKeys = Object.keys(before).filter((key) => !afterKeys.has(key));
|
|
158
|
+
if (droppedKeys.length > 0) {
|
|
159
|
+
risks.push({
|
|
160
|
+
code: "BACKEND_TEST_CONFIG_WEAKENED",
|
|
161
|
+
level: "elevated",
|
|
162
|
+
message: `drop backendTest keys: ${droppedKeys.sort().join(", ")}`,
|
|
163
|
+
path: "task.json#backendTest",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
142
168
|
}
|
|
143
169
|
else {
|
|
144
170
|
for (const field of MANAGED_TASK_CONFIG_FIELDS) {
|
|
@@ -35,6 +35,9 @@ export function computeSourceHashes(input) {
|
|
|
35
35
|
if (input.referenceManifestBytes) {
|
|
36
36
|
sourceHashes.referenceManifestSha256 = sha256Utf8(input.referenceManifestBytes);
|
|
37
37
|
}
|
|
38
|
+
if (input.requirementLedgerBytes) {
|
|
39
|
+
sourceHashes.requirementLedgerSha256 = sha256Utf8(input.requirementLedgerBytes);
|
|
40
|
+
}
|
|
38
41
|
return sourceHashes;
|
|
39
42
|
}
|
|
40
43
|
export function refMatchesObserved(ref, observedCanonicalHash) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { access, readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { CONSTRAINTS_FILE_NAME, CONTRACT_FILE_NAME, REQUIREMENT_FILE_NAME, TRANSACTIONS_DIR_NAME, } from "./constants.js";
|
|
3
|
+
import { CONSTRAINTS_FILE_NAME, CONTRACT_FILE_NAME, REQUIREMENT_FILE_NAME, REQUIREMENT_LEDGER_FILE_NAME, TRANSACTIONS_DIR_NAME, } from "./constants.js";
|
|
4
4
|
import { computeCanonicalHash, computeSourceHashes, computeTaskConfigSha256, } from "./hash.js";
|
|
5
5
|
import { parseTaskContractRef } from "./schema.js";
|
|
6
6
|
import { getTaskPaths } from "../runtime.js";
|
|
@@ -33,11 +33,13 @@ export async function loadObservedTaskBytes(repoRoot, taskId) {
|
|
|
33
33
|
const paths = getTaskPaths(repoRoot, taskId);
|
|
34
34
|
const requirementPath = path.join(paths.sourceDir, REQUIREMENT_FILE_NAME);
|
|
35
35
|
const constraintsPath = path.join(paths.sourceDir, CONSTRAINTS_FILE_NAME);
|
|
36
|
-
const
|
|
36
|
+
const requirementLedgerPath = path.join(paths.sourceDir, REQUIREMENT_LEDGER_FILE_NAME);
|
|
37
|
+
const [requirementBytes, constraintsBytes, taskConfigText, manifestText, requirementLedgerBytes] = await Promise.all([
|
|
37
38
|
readTextIfExists(requirementPath),
|
|
38
39
|
readTextIfExists(constraintsPath),
|
|
39
40
|
readTextIfExists(paths.taskConfigPath),
|
|
40
41
|
readTextIfExists(getSourceManifestPath(paths.sourceDir)),
|
|
42
|
+
readTextIfExists(requirementLedgerPath),
|
|
41
43
|
]);
|
|
42
44
|
let taskConfigRaw = null;
|
|
43
45
|
let taskConfigSha256 = null;
|
|
@@ -52,6 +54,7 @@ export async function loadObservedTaskBytes(repoRoot, taskId) {
|
|
|
52
54
|
taskConfigRaw,
|
|
53
55
|
taskConfigSha256,
|
|
54
56
|
referenceManifestBytes: manifestText,
|
|
57
|
+
requirementLedgerBytes,
|
|
55
58
|
requirementPath,
|
|
56
59
|
constraintsPath,
|
|
57
60
|
taskConfigPath: paths.taskConfigPath,
|
|
@@ -164,7 +167,9 @@ export async function observeTaskContractState(repoRoot, taskId) {
|
|
|
164
167
|
message: error instanceof Error ? error.message : String(error),
|
|
165
168
|
});
|
|
166
169
|
}
|
|
167
|
-
const observed = await computeObservedCanonicalHashSafe(repoRoot, taskId
|
|
170
|
+
const observed = await computeObservedCanonicalHashSafe(repoRoot, taskId, {
|
|
171
|
+
ref: ref ?? undefined,
|
|
172
|
+
});
|
|
168
173
|
errors.push(...(observed.errors ?? []));
|
|
169
174
|
if (!ref) {
|
|
170
175
|
return {
|
|
@@ -226,7 +231,7 @@ export async function observeTaskContractState(repoRoot, taskId) {
|
|
|
226
231
|
observationErrors: errors,
|
|
227
232
|
};
|
|
228
233
|
}
|
|
229
|
-
async function computeObservedCanonicalHashSafe(repoRoot, taskId) {
|
|
234
|
+
async function computeObservedCanonicalHashSafe(repoRoot, taskId, options) {
|
|
230
235
|
const errors = [];
|
|
231
236
|
try {
|
|
232
237
|
const bytes = await loadObservedTaskBytes(repoRoot, taskId);
|
|
@@ -256,10 +261,26 @@ async function computeObservedCanonicalHashSafe(repoRoot, taskId) {
|
|
|
256
261
|
}
|
|
257
262
|
return { hash: null, errors };
|
|
258
263
|
}
|
|
264
|
+
// Legacy compatibility (AC-FIDELITY-004): only include the ledger bytes
|
|
265
|
+
// when the stored ref actually declares requirementLedgerSha256. A ref
|
|
266
|
+
// without the ledger field keeps the existing semantics and never
|
|
267
|
+
// misreports drift for tasks that predate the ledger.
|
|
268
|
+
const refLedgerHash = options?.ref?.sourceHashes.requirementLedgerSha256 ?? null;
|
|
269
|
+
if (refLedgerHash && bytes.requirementLedgerBytes === null) {
|
|
270
|
+
errors.push({
|
|
271
|
+
code: "MISSING_REQUIREMENT_LEDGER",
|
|
272
|
+
path: "source/requirement-ledger.json",
|
|
273
|
+
message: `contract records requirementLedgerSha256 but ${REQUIREMENT_LEDGER_FILE_NAME} is missing`,
|
|
274
|
+
});
|
|
275
|
+
return { hash: null, errors };
|
|
276
|
+
}
|
|
259
277
|
const sourceHashes = computeSourceHashes({
|
|
260
278
|
requirementBytes: bytes.requirementBytes,
|
|
261
279
|
constraintsBytes: bytes.constraintsBytes,
|
|
262
280
|
referenceManifestBytes: bytes.referenceManifestBytes,
|
|
281
|
+
...(refLedgerHash
|
|
282
|
+
? { requirementLedgerBytes: bytes.requirementLedgerBytes }
|
|
283
|
+
: {}),
|
|
263
284
|
});
|
|
264
285
|
const hash = computeCanonicalHash({
|
|
265
286
|
sourceHashes,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { getTaskPaths } from "../runtime.js";
|
|
3
|
-
import { CONTRACT_JSON_BASENAME, REQUEST_LEDGER_BASENAME, TRANSACTIONS_DIRNAME, } from "./constants.js";
|
|
3
|
+
import { CONTRACT_JSON_BASENAME, REQUIREMENT_LEDGER_FILE_NAME, REQUEST_LEDGER_BASENAME, TRANSACTIONS_DIRNAME, } from "./constants.js";
|
|
4
4
|
export function getTaskContractPaths(repoRoot, taskId) {
|
|
5
5
|
const base = getTaskPaths(repoRoot, taskId);
|
|
6
6
|
return {
|
|
@@ -15,5 +15,6 @@ export function getTaskContractPaths(repoRoot, taskId) {
|
|
|
15
15
|
transactionsDir: path.join(base.taskDir, TRANSACTIONS_DIRNAME),
|
|
16
16
|
requestLedgerPath: path.join(base.taskDir, REQUEST_LEDGER_BASENAME),
|
|
17
17
|
lockPath: path.join(base.taskDir, ".contract.lock"),
|
|
18
|
+
requirementLedgerPath: path.join(base.sourceDir, REQUIREMENT_LEDGER_FILE_NAME),
|
|
18
19
|
};
|
|
19
20
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { canonicalizePathArray, canonicalizeTextProjection, } from "./canonicalize.js";
|
|
2
2
|
/**
|
|
3
3
|
* Project Draft → source/需求.md (deterministic template, projectionVersion=1).
|
|
4
|
+
* 当携带 ledger 时追加 `## Source ledger` 区块:明确 需求.md 是派生导航视图,
|
|
5
|
+
* 语义权威为原始 requirement/acceptance 文档与受管 requirement-ledger.json。
|
|
4
6
|
*/
|
|
5
|
-
export function projectRequirementMarkdown(draft) {
|
|
7
|
+
export function projectRequirementMarkdown(draft, ledger) {
|
|
6
8
|
const lines = [
|
|
7
9
|
`# 需求:${draft.title}`,
|
|
8
10
|
"",
|
|
@@ -62,6 +64,18 @@ export function projectRequirementMarkdown(draft) {
|
|
|
62
64
|
lines.push(`- [${ref.role}] ${ref.ref}`);
|
|
63
65
|
}
|
|
64
66
|
}
|
|
67
|
+
if (ledger) {
|
|
68
|
+
const gateStatus = ledger.stats.unresolvedCount > 0 ||
|
|
69
|
+
ledger.stats.conflictCount > 0 ||
|
|
70
|
+
ledger.stats.highRiskUncoveredCount > 0
|
|
71
|
+
? "blocked"
|
|
72
|
+
: "pass";
|
|
73
|
+
const ledgerLine = ledger.sourcePaths.length > 0
|
|
74
|
+
? `source/requirement-ledger.json (${ledger.sourcePaths.length} source document(s))`
|
|
75
|
+
: "source/requirement-ledger.json";
|
|
76
|
+
lines.push("", "## Source ledger", "", "> 本文件是派生导航视图,不替代原始 requirement/acceptance 文档。" +
|
|
77
|
+
" 语义权威为原始文档与 `source/requirement-ledger.json`(确定性切分账本)。", "", `- ledger: ${ledgerLine.length > 160 ? `${ledgerLine.slice(0, 160)}…` : ledgerLine}`, `- inputDigest: ${ledger.inputDigest}`, `- fragments: ${ledger.stats.fragmentCount}`, `- canonical requirements: ${ledger.stats.canonicalRequirementCount}`, `- status: ${gateStatus}`);
|
|
78
|
+
}
|
|
65
79
|
lines.push("");
|
|
66
80
|
return canonicalizeTextProjection(lines.join("\n"));
|
|
67
81
|
}
|
|
@@ -174,6 +188,11 @@ export function mergeManagedTaskConfigFields(existing, draft) {
|
|
|
174
188
|
if (draft.featureId !== undefined) {
|
|
175
189
|
next.featureId = draft.featureId;
|
|
176
190
|
}
|
|
191
|
+
// backendTest is whole-object replace when the draft declares it (`{}` is a
|
|
192
|
+
// legal explicit clear back to the default layout); absent keeps existing.
|
|
193
|
+
if (draft.backendTest !== undefined) {
|
|
194
|
+
next.backendTest = draft.backendTest;
|
|
195
|
+
}
|
|
177
196
|
return next;
|
|
178
197
|
}
|
|
179
198
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { taskKindSchema, taskVerifyCommandSchema } from "../config-types.js";
|
|
2
|
+
import { backendTestConfigSchema, taskKindSchema, taskVerifyCommandSchema, } from "../config-types.js";
|
|
3
3
|
import { TASK_CONFIG_SCHEMA_VERSION, TASK_CONTRACT_CANONICALIZER_VERSION, TASK_CONTRACT_DRAFT_SCHEMA_VERSION, TASK_CONTRACT_PROJECTION_VERSION, TASK_CONTRACT_REF_SCHEMA_VERSION, } from "./constants.js";
|
|
4
4
|
const sha256Schema = z
|
|
5
5
|
.string()
|
|
@@ -62,6 +62,11 @@ export const taskContractDraftSchema = z
|
|
|
62
62
|
requirement: taskContractDraftRequirementSchema,
|
|
63
63
|
constraints: taskContractDraftConstraintsSchema,
|
|
64
64
|
verification: taskContractDraftVerificationSchema,
|
|
65
|
+
/**
|
|
66
|
+
* Managed backend-test layout / pipeline configuration projected into
|
|
67
|
+
* task.json#backendTest. Optional: absent means "keep existing".
|
|
68
|
+
*/
|
|
69
|
+
backendTest: backendTestConfigSchema,
|
|
65
70
|
references: z.array(taskContractDraftReferenceSchema).optional(),
|
|
66
71
|
sourceResolutions: z
|
|
67
72
|
.array(taskContractDraftSourceResolutionSchema)
|
|
@@ -102,6 +107,8 @@ export const taskContractSourceHashesSchema = z
|
|
|
102
107
|
requirementSha256: sha256Schema,
|
|
103
108
|
constraintsSha256: sha256Schema,
|
|
104
109
|
referenceManifestSha256: sha256Schema.optional(),
|
|
110
|
+
/** Source fidelity ledger hash; present only when the task binds a ledger. */
|
|
111
|
+
requirementLedgerSha256: sha256Schema.optional(),
|
|
105
112
|
})
|
|
106
113
|
.strict();
|
|
107
114
|
export const taskContractRefSchema = z
|