@tea-agent/loop-agent 0.43.0-next.9 → 0.43.0
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 +1 -1
- package/CHANGELOG.md +31 -2309
- package/README.md +7 -0
- package/dist/application/dag/args.js +8 -0
- package/dist/application/task-lifecycle/advance.js +38 -7
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +2 -0
- package/dist/cli/program.js +3 -1
- package/dist/cli/update/init-surface-notifier.js +2 -0
- package/dist/cli/update/policy.js +2 -0
- package/dist/commands/client-recovery.js +20 -55
- package/dist/commands/cursor-prompt.js +2 -0
- package/dist/commands/dag-approve.js +2 -0
- package/dist/commands/dag-final-verification.js +2 -0
- package/dist/commands/dag-init-hybrid.js +2 -0
- package/dist/commands/dag-reconcile-tasks.js +2 -0
- package/dist/commands/dag-reject.js +2 -0
- package/dist/commands/dag-report.js +2 -0
- package/dist/commands/dag-rerun.js +2 -0
- package/dist/commands/dag-resume.js +2 -0
- package/dist/commands/dag-workflow-compile.js +2 -0
- package/dist/commands/dag-workflow-plan.js +2 -0
- package/dist/commands/dag-workflow-validate.js +2 -0
- package/dist/commands/delegate.js +2 -0
- package/dist/commands/examples.js +2 -0
- package/dist/commands/import-prd.js +2 -0
- package/dist/commands/init-model-catalog.js +2 -0
- package/dist/commands/init.js +19 -13
- package/dist/commands/instructions.js +2 -0
- package/dist/commands/knowledge.js +2 -0
- package/dist/commands/loop-benchmark.js +2 -0
- package/dist/commands/pi-prompt.js +2 -0
- package/dist/commands/pi-reuse-benchmark.js +2 -0
- package/dist/commands/promote-run.js +2 -0
- package/dist/commands/stats.js +1 -1
- package/dist/commands/study-init.js +2 -0
- package/dist/commands/task-advance.js +34 -0
- package/dist/commands/task-source-prepare.js +2 -0
- package/dist/commands/worktree-create.js +2 -0
- package/dist/commands/worktree-remove.js +2 -0
- package/dist/executors/dag-pi-executor.js +1650 -198
- package/dist/executors/pi-executor.js +85 -3
- package/dist/executors/pi-sdk-executor.js +18 -0
- package/dist/executors/shell-executor.js +93 -7
- package/dist/executors/shell-verification.js +3 -0
- package/dist/executors/shell-write-guard.js +27 -32
- package/dist/governance/exec-plans.js +6 -3
- package/dist/infrastructure/console/app-data.js +6 -0
- package/dist/shared/artifacts-core.js +2 -0
- package/dist/shared/backend-dogfood-preflight.js +47 -0
- package/dist/shared/dag-failure-category.js +3 -0
- package/dist/shared/git-progress.js +2 -0
- package/dist/shared/one-shot-prompt-args.js +2 -0
- package/dist/shared/operator/capabilities.js +180 -0
- package/dist/shared/package-metadata.js +6 -4
- package/dist/shared/pi-context-pressure/extension.js +26 -16
- package/dist/shared/pi-context-pressure/sift-bridge.js +1 -1
- package/dist/shared/pi-provider-recovery.js +182 -0
- package/dist/shared/pi-retry-settings.js +40 -0
- package/dist/shared/reference-context.js +2 -0
- package/dist/task/config-types.js +29 -0
- package/dist/task/contract/project.js +9 -0
- package/dist/task/contract/recover.js +6 -0
- package/dist/task/contract/schema.js +17 -0
- package/dist/task/source-prepare/artifact-meta.js +9 -2
- package/dist/task/source-prepare/build-draft.js +60 -0
- package/dist/task/source-prepare/fragment-inventory.js +4 -1
- package/dist/task/source-prepare/parse-intent.js +36 -11
- package/dist/task/task-demand-routing.js +5 -2
- package/dist/worker/console/chat/browser-automation.js +523 -0
- package/dist/worker/console/chat/browser-policy.js +6 -36
- package/dist/worker/console/chat/browser-routes.js +188 -56
- package/dist/worker/console/chat/explore-tools.js +5 -2
- package/dist/worker/console/chat/pi-mode-loop-isolation.js +155 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +44 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +184 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +202 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +79 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +94 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/scheduled-goal-tools.js +22 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +38 -0
- package/dist/worker/console/chat/pi-runtime/inspection-types.js +28 -0
- package/dist/worker/console/chat/pi-runtime/inspection.js +439 -0
- package/dist/worker/console/chat/pi-runtime/progressive-tools.js +195 -0
- package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +153 -0
- package/dist/worker/console/chat/pi-runtime/sdk-session.js +5 -0
- package/dist/worker/console/chat/pi-runtime.js +208 -1138
- package/dist/worker/console/chat/routes.js +27 -7
- package/dist/worker/console/chat/scheduled-goal-booking.js +59 -0
- package/dist/worker/console/chat/scheduled-goal-delivery.js +27 -0
- package/dist/worker/console/chat/scheduled-goal-request.js +190 -0
- package/dist/worker/console/chat/sdd-data-alignment.js +1 -1
- package/dist/worker/console/chat/session-mode-view.js +5 -4
- package/dist/worker/console/chat/session-mode.js +18 -12
- package/dist/worker/console/chat/session-store.js +42 -9
- package/dist/worker/console/chat/tool-preview.js +115 -0
- package/dist/worker/console/chat/tools.js +10 -0
- package/dist/worker/console/chat/turn-order.js +13 -0
- package/dist/worker/console/chat/turn-process.js +32 -30
- package/dist/worker/console/console-update-and-init.js +1 -19
- package/dist/worker/console/console-update-runtime.js +5 -20
- package/dist/worker/console/operator-actions.js +63 -1
- package/dist/worker/console/prd-intake-bridge.js +54 -1
- package/dist/worker/console/routes.js +8 -0
- package/dist/worker/console/scheduled-goal-host.js +98 -0
- package/dist/worker/console/scheduled-goal-operation-adapter.js +127 -0
- package/dist/worker/console/server.js +24 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-D_fNbWc_.js → abnfDiagram-N423BO3Z-8-j6y-sd.js} +1 -1
- package/dist/worker/console/static/assets/{arc-_HuqLW6m.js → arc-eoQiMvuk.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Djy2qY1m.js → architectureDiagram-T3A2C74G-C4A3uMcI.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-bB-1TSyY.js → blockDiagram-VBNYF7ZC-D4zD2F-Q.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-C5Hmghrz.js → c4Diagram-5PPSVZJV-j1RkJziL.js} +1 -1
- package/dist/worker/console/static/assets/channel-ChE7y-cx.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Duyl39oy.js → chunk-2GRJ4B5K-YBHmhik1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-x6mdnSyf.js → chunk-2Q5K7J3B-COfWyo9P.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-WlBuFj7W.js → chunk-5RXB4S5H-I99OUkHY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CTF429HX.js → chunk-5VM5RSS4-XKxoJNJ4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Dwd6r-SF.js → chunk-6Q2QTUOP-DLT_cYx1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-C-Br82D1.js → chunk-GF5L2VYU-CxcKZ9mV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CuGp0DeE.js → chunk-JWPE2WC7-V1EqXdjY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CJhFX6Lh.js → chunk-KBJHAD2P-DebTtdFp.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DpGOa_jT.js → chunk-RYQCIY6F-B-ivNRDf.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DXJaTynd.js → chunk-XXDRQBXY-DC_Ds11b.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CRBZWjE4.js → cose-bilkent-JH36ORCC-sWEqKwIb.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BdXcxtlb.js → cynefin-VYW2F7L2-BXa_dcu4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-07UKsOts.js → cynefinDiagram-MW4NZA55-C-Mle84F.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-D38p9__S.js → dagre-VZM6K2ZE-CXPDBITe.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-nSiBNNjH.js → diagram-7IWD3JNH-CC-WJQfa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BFOTot5D.js → diagram-B4RE2ZJO-CDAV5Vs4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DypKaZ6k.js → diagram-LBJQPF4R-CmFiAcNz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-B6yoOQeW.js → diagram-Q27KOJAE-DPBZHuyn.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BHYS6viX.js → diagram-UB23O5K3-jUlm_Ds3.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-D2VD5Snj.js → ebnfDiagram-BXEA7PRR-DWhQ3mfY.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-E98mWxN1.js → erDiagram-JOGREHBK-Tr2gMqet.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CT1zxhqP.js → flowDiagram-UKHOOZJN-DJjVQHPA.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-6GcngNcy.js → ganttDiagram-PKOTCBZU-D74dQ4u0.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DCL6NvKe.js → gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js} +1 -1
- package/dist/worker/console/static/assets/index-BWkIfcrK.css +1 -0
- package/dist/worker/console/static/assets/index-Cdkvw_H6.js +469 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-C0627e-9.js → infoDiagram-6WML65LV-ILCbxJyb.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C_u3P6Ik.js → ishikawaDiagram-WSZJBQD7-DeuWBQ89.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DPDPv9HL.js → journeyDiagram-NVQOT4AX-CF5ih8Fk.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-D3OHdWiO.js → kanban-definition-27J2QSJJ-C7yOSuRO.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DI88zD7n.js → linear-BDZ9riWi.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-p2l8IrWY.js → mermaid.core-7pKqYtpZ.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-B1bAX-oS.js → mindmap-definition-FAOFIHXS-LeJDybSU.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DwOnWxdD.js → pegDiagram-VL7TDLO6-BJvT3pMD.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-xRkTXnJC.js → pieDiagram-7S7Q4E2Y-_rGqpMan.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-_9UC5Zio.js → quadrantDiagram-CIZ2JOQS-DPcSv5aA.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CgEUirWJ.js → railroadDiagram-AXF67PYL-Drxx4hkJ.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-Czbpwiyz.js → requirementDiagram-LRYGKXZP-BCTUdU4z.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-_j7_KiqY.js → sankeyDiagram-W5VNT64P-B6wzbZmB.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BNJa6Hyi.js → sequenceDiagram-SI44F4Z6-BGt8d3QQ.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DedpKtOc.js → sizeCapture-X5ZJPWSS-7nlhJKo7.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BBvthr7c.js → stateDiagram-OKZ733FA-Cv2stqMA.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Ds7umgEM.js → swimlanes-SLNWSIFB-qDAo4Yc1.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKa-p1nF.js → timeline-definition-Z64GVDOM-CKbDNKTr.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-LoUNR56o.js → vennDiagram-T6HMQDX7-DI-9EHic.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Bqbl6ym7.js → wardleyDiagram-T6FBY63Y-BrzzRDpX.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-B8dUg4lf.js → xychartDiagram-ELKLHX3M-BclH5hGh.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-scroll-position.js +24 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +41 -11
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +3 -17
- package/dist/worker/console/static-src/operator-chat/composer-session-ledger.js +192 -0
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +9 -1
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +15 -5
- package/dist/worker/console/static-src/operator-chat/timeline-merge.js +29 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +92 -38
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +145 -70
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +38 -55
- package/dist/worker/console/static-src/operator-chat/useComposer.js +182 -69
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +2 -0
- package/dist/worker/console/static-src/prd-file-import.js +3 -1
- package/dist/worker/console/workspace-context.js +22 -0
- package/dist/worker/console/workspace-initialization.js +209 -0
- package/dist/worker/delivery/git-transaction.js +2 -0
- package/dist/worker/feature/fullstack-validate.js +2 -0
- package/dist/worker/loop-agent/loop-agent-client.js +8 -2
- package/dist/worker/materialize/harness-task-materializer.js +4 -1
- package/dist/worker/metrics/projector.js +1 -1
- package/dist/worker/observability/event-store.js +4 -1
- package/dist/worker/observability/read-model.js +4 -1
- package/dist/worker/observe/node-transparency.js +23 -1
- package/dist/worker/observe/routes.js +4 -0
- package/dist/worker/observe/static/operator-chrome.js +3 -1
- package/dist/worker/observe/static/styles.css +7 -4
- package/dist/worker/observe/static/views/dag-inspector.js +5 -19
- package/dist/worker/outcomes/declared-artifacts.js +2 -0
- package/dist/worker/preflight.js +3 -0
- package/dist/worker/scheduler/scheduled-goal-dispatch.js +117 -0
- package/dist/worker/scheduler/scheduled-goal-evidence.js +167 -0
- package/dist/worker/scheduler/scheduled-goal-recovery.js +62 -0
- package/dist/worker/scheduler/scheduled-goal-store.js +699 -0
- package/dist/worker/scheduler/scheduled-goal-supervisor.js +132 -0
- package/dist/worker/scheduler/scheduled-goal-time.js +102 -0
- package/dist/worker/scheduler/scheduled-goal-types.js +95 -0
- package/dist/worker/task-spec/validate.js +4 -1
- package/dist/workflows/dag/backend-test-markdown-workflow.js +6 -24
- package/dist/workflows/dag/backend-test-plan-protocol.js +82 -5
- package/dist/workflows/dag/dag-retry-schema.js +11 -0
- package/dist/workflows/dag/decision-envelope.js +2 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +2 -2
- package/dist/workflows/dag/frontend-closeout.js +3 -1
- package/dist/workflows/dag/frontend-committed-facts.js +461 -0
- package/dist/workflows/dag/frontend-durable-tools.js +15 -3
- package/dist/workflows/dag/frontend-implementation-contract.js +369 -12
- package/dist/workflows/dag/frontend-plan-canary.js +53 -0
- package/dist/workflows/dag/frontend-plan-decision-contract.js +803 -0
- package/dist/workflows/dag/frontend-plan-render.js +0 -2
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +8 -61
- package/dist/workflows/dag/frontend-recovery-run.js +57 -0
- package/dist/workflows/dag/frontend-repair.js +55 -76
- package/dist/workflows/dag/frontend-review-context.js +42 -68
- package/dist/workflows/dag/frontend-review-scopes.js +2 -2
- package/dist/workflows/dag/frontend-risk.js +92 -10
- package/dist/workflows/dag/frontend-session-budget.js +117 -3
- package/dist/workflows/dag/frontend-shape.js +11 -55
- package/dist/workflows/dag/frontend-test-execution-evidence.js +35 -10
- package/dist/workflows/dag/frontend-typed-event-store.js +32 -25
- package/dist/workflows/dag/frontend-verification-trace.js +33 -54
- package/dist/workflows/dag/frontend-writer-admission.js +3 -47
- package/dist/workflows/dag/frontend-writer-status.js +0 -23
- package/dist/workflows/dag/init-hybrid.js +47 -19
- package/dist/workflows/dag/interrupt-request.js +2 -0
- package/dist/workflows/dag/lifecycle.js +11 -2
- package/dist/workflows/dag/node-execution.js +50 -13
- package/dist/workflows/dag/reconcile-run.js +2 -0
- package/dist/workflows/dag/repair-artifact.js +3 -1
- package/dist/workflows/dag/report.js +2 -0
- package/dist/workflows/dag/rerun-plan.js +10 -0
- package/dist/workflows/dag/rerun-task.js +79 -1
- package/dist/workflows/dag/retry-policy.js +18 -0
- package/dist/workflows/dag/scheduler.js +2 -4
- package/dist/workflows/dag/types.js +44 -6
- package/dist/workflows/dag/validate.js +4 -0
- package/docs/README.md +1 -0
- package/docs/architecture/runtime-boundaries.md +3 -3
- package/docs/governance/README.md +1 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/operations/README.md +2 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +2 -2
- package/docs/templates/backend-test-dag.json +14 -10
- package/docs/templates/frontend-implementation-contract.schema.json +0 -7
- package/package.json +9 -3
- package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
- package/skills/frontend-contract/references/contract-protocol.md +3 -1
- package/skills/frontend-plan/SKILL.md +8 -6
- package/skills/frontend-plan/references/decision-contract.md +3 -3
- package/skills/frontend-review/SKILL.md +18 -28
- package/skills/frontend-review/references/review-findings.md +8 -3
- package/dist/commands/new-task.js +0 -5
- package/dist/commands/status.js +0 -57
- package/dist/commands/task-contract.js +0 -269
- package/dist/shared/prompts.js +0 -26
- package/dist/task/config.js +0 -2
- package/dist/task/contract/validate-draft.js +0 -106
- package/dist/task/goal.js +0 -3
- package/dist/task/index.js +0 -12
- package/dist/task/lifecycle.js +0 -1
- package/dist/task/operator/capabilities.js +0 -6
- package/dist/task/operator/envelope.js +0 -2
- package/dist/task/operator/index.js +0 -5
- package/dist/task/operator/registry.js +0 -2
- package/dist/task/operator/types.js +0 -1
- package/dist/task/paths.js +0 -1
- package/dist/task/source-state.js +0 -1
- package/dist/task/subagent-guidance.js +0 -1
- package/dist/worker/console/chat/sift-bridge.js +0 -1
- package/dist/worker/console/index.js +0 -20
- package/dist/worker/console/static/assets/channel-C0A6UEIa.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DzguNppy.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DzguNppy.js +0 -1
- package/dist/worker/console/static/assets/index-24jp3aBh.js +0 -468
- package/dist/worker/console/static/assets/index-CY6y6I8M.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BABVQnfM.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D0qsLaxR.js +0 -8
- package/dist/worker/scheduler/index.js +0 -25
- package/dist/workflows/dag/facts.js +0 -4
- package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -975
- package/dist/workflows/dag/index.js +0 -6
- package/dist/workflows/dynamic/index.js +0 -7
|
@@ -97,8 +97,6 @@ export function renderFrontendPlanMarkdown(contract) {
|
|
|
97
97
|
"## Source Fidelity Ledger",
|
|
98
98
|
sourceFidelityLedger(contract),
|
|
99
99
|
"",
|
|
100
|
-
"## Implementation Steps",
|
|
101
|
-
bulletList(contract.implementationSteps) || "_(not specified)_",
|
|
102
100
|
"",
|
|
103
101
|
"## Target Files",
|
|
104
102
|
bulletList(contract.targets.files) || "_(none)_",
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { TYPED_EVENT_FACT_KINDS, stageTypedEventRecord, } from "./frontend-typed-event-store.js";
|
|
3
|
-
import { createDagEventObserver } from "./event-observer.js";
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
4
|
+
* Provider/model capability fact matrix (AC-003).
|
|
6
5
|
*
|
|
7
6
|
* Records seven provider/model capability facts as read-only observations:
|
|
8
7
|
* - `terminal-commit`
|
|
@@ -13,15 +12,12 @@ import { createDagEventObserver } from "./event-observer.js";
|
|
|
13
12
|
* - `provider-ended-without-terminal`
|
|
14
13
|
* - `read-only-fact-channel`
|
|
15
14
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* control path. It is deliberately distinct from `frontend-project-capability`
|
|
20
|
-
* (framework / mock discovery).
|
|
15
|
+
* `recordProviderCapabilityFact` returns a staged record for audit, never a
|
|
16
|
+
* branch-control signal, and this module is deliberately distinct from
|
|
17
|
+
* `frontend-project-capability` (framework / mock discovery).
|
|
21
18
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* failure can never affect canonical DAG execution.
|
|
19
|
+
* These facts are staged independently from the canonical DAG event stream;
|
|
20
|
+
* recording them never participates in model selection or branch control.
|
|
25
21
|
*/
|
|
26
22
|
export const PROVIDER_CAPABILITY_FACT_KINDS = TYPED_EVENT_FACT_KINDS;
|
|
27
23
|
/** Terminal finish-reason shapes, modeled after
|
|
@@ -55,8 +51,8 @@ function readExplicitFinishReason(source) {
|
|
|
55
51
|
function isRecord(value) {
|
|
56
52
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
57
53
|
}
|
|
58
|
-
/** True when an observed provider event is terminal-shaped.
|
|
59
|
-
*
|
|
54
|
+
/** True when an observed provider event is terminal-shaped. The result is a
|
|
55
|
+
* fact, not a control decision. */
|
|
60
56
|
export function isTerminalProviderEventShape(event) {
|
|
61
57
|
const type = typeof event.type === "string" ? event.type : undefined;
|
|
62
58
|
return type !== undefined && TERMINAL_FINISH_REASON_TYPES.has(type);
|
|
@@ -108,52 +104,3 @@ export function recordProviderCapabilityFact(options) {
|
|
|
108
104
|
});
|
|
109
105
|
return { eventId, record };
|
|
110
106
|
}
|
|
111
|
-
/**
|
|
112
|
-
* Attach a shadow observer that records `read-only-fact-channel` facts while
|
|
113
|
-
* delegating the canonical event JSONL to the existing `createDagEventObserver`.
|
|
114
|
-
* The shadow channel never throws into the caller: any recording failure is
|
|
115
|
-
* swallowed so canonical execution is unaffected.
|
|
116
|
-
*/
|
|
117
|
-
export function createProviderCapabilityShadowObserver(options) {
|
|
118
|
-
const base = createDagEventObserver({
|
|
119
|
-
eventsJsonlPath: options.eventsJsonlPath,
|
|
120
|
-
dagRunId: options.dagRunId,
|
|
121
|
-
spec: options.spec,
|
|
122
|
-
});
|
|
123
|
-
const recordReadOnlyFact = (eventType) => {
|
|
124
|
-
try {
|
|
125
|
-
recordProviderCapabilityFact({
|
|
126
|
-
store: options.store,
|
|
127
|
-
attemptId: options.attemptId,
|
|
128
|
-
requestId: options.requestId,
|
|
129
|
-
fact: { kind: "read-only-fact-channel", eventType },
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
// Shadow channel must never affect canonical execution.
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
const observer = {
|
|
137
|
-
onRunStart: async (state) => {
|
|
138
|
-
recordReadOnlyFact("dag.run.started");
|
|
139
|
-
await base.observer.onRunStart?.(state);
|
|
140
|
-
},
|
|
141
|
-
onNodeStart: async (nodeId, state) => {
|
|
142
|
-
recordReadOnlyFact("dag.node.started");
|
|
143
|
-
await base.observer.onNodeStart?.(nodeId, state);
|
|
144
|
-
},
|
|
145
|
-
onNodeOutput: async (nodeId, chunk, state) => {
|
|
146
|
-
recordReadOnlyFact("dag.node.output");
|
|
147
|
-
await base.observer.onNodeOutput?.(nodeId, chunk, state);
|
|
148
|
-
},
|
|
149
|
-
onNodeFinish: async (nodeId, state) => {
|
|
150
|
-
recordReadOnlyFact("dag.node.finished");
|
|
151
|
-
await base.observer.onNodeFinish?.(nodeId, state);
|
|
152
|
-
},
|
|
153
|
-
onRunFinish: async (state) => {
|
|
154
|
-
recordReadOnlyFact("dag.run.finished");
|
|
155
|
-
await base.observer.onRunFinish?.(state);
|
|
156
|
-
},
|
|
157
|
-
};
|
|
158
|
-
return { observer, flush: base.flush };
|
|
159
|
-
}
|
|
@@ -25,6 +25,27 @@ import { topoSortToRanks } from "./topo.js";
|
|
|
25
25
|
/** Canonical contract artifact default location; overridden by the design
|
|
26
26
|
* policy shell's configured outputDir/artifactName when present. */
|
|
27
27
|
const FRONTEND_CANONICAL_CONTRACT_DEFAULT_REL_PATH = "contracts/frontend-implementation-contract.json";
|
|
28
|
+
/**
|
|
29
|
+
* Run-owned artifacts a frontend node writes *outside* its own node directory.
|
|
30
|
+
*
|
|
31
|
+
* An imported node keeps its parent FINISHED record in the child run, so the
|
|
32
|
+
* child never executes the body that produced these files. Any downstream node
|
|
33
|
+
* that reads them must still find them in the child run directory: smoke r31
|
|
34
|
+
* saw a review-triggered recovery die at `frontend-review-context-shell` with
|
|
35
|
+
* `ENOENT contracts/frontend-worktree-baseline.json`, because
|
|
36
|
+
* `frontend-writer-admission-shell` is imported (its baseline capture is a side
|
|
37
|
+
* effect of the node body) while the review context requires that file.
|
|
38
|
+
*
|
|
39
|
+
* Entries listed as required fail the staging closed when the parent lacks
|
|
40
|
+
* them; optional entries (for example the lint baseline, which only exists when
|
|
41
|
+
* lint is configured) are copied when present.
|
|
42
|
+
*/
|
|
43
|
+
const FRONTEND_IMPORTED_NODE_RUN_ARTIFACTS = {
|
|
44
|
+
"frontend-writer-admission-shell": {
|
|
45
|
+
required: ["contracts/frontend-worktree-baseline.json"],
|
|
46
|
+
optional: ["contracts/frontend-lint-baseline.json"],
|
|
47
|
+
},
|
|
48
|
+
};
|
|
28
49
|
function resolveDeps(deps) {
|
|
29
50
|
return {
|
|
30
51
|
readParentState: deps?.readParentState ?? readDagRunState,
|
|
@@ -397,6 +418,41 @@ async function materializeChildStaging(input) {
|
|
|
397
418
|
newRunDir: stagingDir,
|
|
398
419
|
relativePath: FRONTEND_WRITER_ADMISSION_RESULT_ARTIFACT,
|
|
399
420
|
});
|
|
421
|
+
// Imported nodes keep their parent FINISHED record, so the run-owned files
|
|
422
|
+
// their bodies wrote outside the node directory travel with them (smoke r31:
|
|
423
|
+
// a review-triggered recovery reached review-context with no worktree
|
|
424
|
+
// baseline and hard-failed on ENOENT).
|
|
425
|
+
const importedRunArtifacts = [];
|
|
426
|
+
for (const nodeId of plan.importedNodeIds) {
|
|
427
|
+
const artifactSpec = FRONTEND_IMPORTED_NODE_RUN_ARTIFACTS[nodeId];
|
|
428
|
+
if (!artifactSpec)
|
|
429
|
+
continue;
|
|
430
|
+
for (const relativePath of artifactSpec.required) {
|
|
431
|
+
const copied = await copyArtifactVerified({
|
|
432
|
+
parentRunDir,
|
|
433
|
+
newRunDir: stagingDir,
|
|
434
|
+
relativePath,
|
|
435
|
+
});
|
|
436
|
+
importedRunArtifacts.push({
|
|
437
|
+
sourceRelativePath: relativePath,
|
|
438
|
+
...copied,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
for (const relativePath of artifactSpec.optional) {
|
|
442
|
+
const sourcePath = path.join(parentRunDir, ...relativePath.split("/"));
|
|
443
|
+
if (!(await fileExists(sourcePath)))
|
|
444
|
+
continue;
|
|
445
|
+
const copied = await copyArtifactVerified({
|
|
446
|
+
parentRunDir,
|
|
447
|
+
newRunDir: stagingDir,
|
|
448
|
+
relativePath,
|
|
449
|
+
});
|
|
450
|
+
importedRunArtifacts.push({
|
|
451
|
+
sourceRelativePath: relativePath,
|
|
452
|
+
...copied,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
400
456
|
// The scheduler gates every shape boundary by its committed fact once the
|
|
401
457
|
// boundary's source node is FINISHED. Imported nodes stay FINISHED in the
|
|
402
458
|
// child, so their boundaries must inherit the parent's committed facts or
|
|
@@ -470,6 +526,7 @@ async function materializeChildStaging(input) {
|
|
|
470
526
|
relativePath: writerAdmissionResult.destinationRelativePath,
|
|
471
527
|
sha256: writerAdmissionResult.sha256,
|
|
472
528
|
},
|
|
529
|
+
importedRunArtifacts,
|
|
473
530
|
importedFacts,
|
|
474
531
|
};
|
|
475
532
|
await writeJsonVerified(stagingDir, FRONTEND_RECOVERY_IMPORT_MANIFEST_REL_PATH, importManifest);
|
|
@@ -24,6 +24,50 @@ export const frontendCommandCategorySchema = z.enum([
|
|
|
24
24
|
"unit-test",
|
|
25
25
|
"trace",
|
|
26
26
|
]);
|
|
27
|
+
/**
|
|
28
|
+
* Ordered string-blob rules. Order is the contract: the first matching rule wins,
|
|
29
|
+
* so rules that share keywords keep their original precedence. `nodeIdIncludes`
|
|
30
|
+
* and `blobIncludes` are alternatives within one rule (logical OR).
|
|
31
|
+
*/
|
|
32
|
+
const BLOB_RULES = [
|
|
33
|
+
{
|
|
34
|
+
result: "contract",
|
|
35
|
+
nodeIdIncludes: ["contract"],
|
|
36
|
+
blobIncludes: ["contract mismatch", "source binding"],
|
|
37
|
+
},
|
|
38
|
+
{ result: "trace", nodeIdIncludes: ["trace"], blobIncludes: ["trace:"] },
|
|
39
|
+
{ result: "review", nodeIdIncludes: ["review"] },
|
|
40
|
+
{
|
|
41
|
+
result: "unit-test",
|
|
42
|
+
nodeIdIncludes: ["behavior"],
|
|
43
|
+
blobIncludes: ["vitest", "jest", "npm test"],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
result: "typecheck",
|
|
47
|
+
blobIncludes: ["typecheck", "tsc"],
|
|
48
|
+
blobPatterns: [/error ts\d+/i],
|
|
49
|
+
},
|
|
50
|
+
{ result: "lint", blobIncludes: ["eslint", "lint"] },
|
|
51
|
+
// "vite build"/"next build" are subsumed by "build".
|
|
52
|
+
{ result: "build", blobIncludes: ["build"] },
|
|
53
|
+
{
|
|
54
|
+
result: "component-test",
|
|
55
|
+
blobIncludes: ["testing-library", "component", "render("],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
result: "dependency",
|
|
59
|
+
blobIncludes: ["package.json", "unapproved dependency", "npm install"],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
result: "credential",
|
|
63
|
+
blobIncludes: ["credential", "api key", "secret"],
|
|
64
|
+
},
|
|
65
|
+
{ result: "deploy", blobIncludes: ["deploy", "production release"] },
|
|
66
|
+
{
|
|
67
|
+
result: "spec-unclear",
|
|
68
|
+
blobIncludes: ["spec unclear", "requirement ambiguous", "needs human"],
|
|
69
|
+
},
|
|
70
|
+
];
|
|
27
71
|
export function classifyFrontendFailure(input) {
|
|
28
72
|
// AC-2: structured evidence takes priority over string-blob heuristics.
|
|
29
73
|
if (input.failureCategory === "write-guard") {
|
|
@@ -39,86 +83,21 @@ export function classifyFrontendFailure(input) {
|
|
|
39
83
|
return "trace";
|
|
40
84
|
}
|
|
41
85
|
if (input.commandCategory) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return "typecheck";
|
|
45
|
-
case "build":
|
|
46
|
-
return "build";
|
|
47
|
-
case "lint":
|
|
48
|
-
return "lint";
|
|
49
|
-
case "component-test":
|
|
50
|
-
return "component-test";
|
|
51
|
-
case "unit-test":
|
|
52
|
-
return "unit-test";
|
|
53
|
-
}
|
|
86
|
+
// "trace" already returned above, so the remaining categories map 1:1.
|
|
87
|
+
return input.commandCategory;
|
|
54
88
|
}
|
|
55
|
-
// Fallback: string-blob
|
|
56
|
-
//
|
|
89
|
+
// Fallback: ordered string-blob heuristics. Only the shell executor's
|
|
90
|
+
// structured category proves a write-boundary failure, so blob text never
|
|
91
|
+
// overrides the structured checks above.
|
|
57
92
|
const blob = `${input.nodeId}\n${input.failureCategory ?? ""}\n${input.stdout ?? ""}\n${input.stderr ?? ""}`.toLowerCase();
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// the shell executor's structured category proves a write-boundary failure.
|
|
65
|
-
if (input.failureCategory === "write-guard") {
|
|
66
|
-
return "path";
|
|
67
|
-
}
|
|
68
|
-
if (input.nodeId.includes("trace") || blob.includes("trace:")) {
|
|
69
|
-
return "trace";
|
|
70
|
-
}
|
|
71
|
-
if (input.nodeId.includes("review")) {
|
|
72
|
-
return "review";
|
|
73
|
-
}
|
|
74
|
-
// Classify the failed verification command before inspecting its output.
|
|
75
|
-
// Full test output includes unrelated passing-test titles such as
|
|
76
|
-
// "secret-shaped" and "write-guard failure".
|
|
77
|
-
if (blob.includes("vitest") ||
|
|
78
|
-
blob.includes("jest") ||
|
|
79
|
-
blob.includes("npm test") ||
|
|
80
|
-
input.nodeId.includes("behavior")) {
|
|
81
|
-
return "unit-test";
|
|
82
|
-
}
|
|
83
|
-
if (blob.includes("typecheck") ||
|
|
84
|
-
blob.includes("tsc") ||
|
|
85
|
-
/error ts\d+/i.test(blob)) {
|
|
86
|
-
return "typecheck";
|
|
87
|
-
}
|
|
88
|
-
if (blob.includes("eslint") || blob.includes("lint")) {
|
|
89
|
-
return "lint";
|
|
90
|
-
}
|
|
91
|
-
if (blob.includes("build") ||
|
|
92
|
-
blob.includes("vite build") ||
|
|
93
|
-
blob.includes("next build")) {
|
|
94
|
-
return "build";
|
|
95
|
-
}
|
|
96
|
-
if (blob.includes("testing-library") ||
|
|
97
|
-
blob.includes("component") ||
|
|
98
|
-
blob.includes("render(")) {
|
|
99
|
-
return "component-test";
|
|
100
|
-
}
|
|
101
|
-
if (blob.includes("package.json") ||
|
|
102
|
-
blob.includes("unapproved dependency") ||
|
|
103
|
-
blob.includes("npm install")) {
|
|
104
|
-
return "dependency";
|
|
105
|
-
}
|
|
106
|
-
if (blob.includes("credential") ||
|
|
107
|
-
blob.includes("api key") ||
|
|
108
|
-
blob.includes("secret")) {
|
|
109
|
-
return "credential";
|
|
110
|
-
}
|
|
111
|
-
if (blob.includes("deploy") || blob.includes("production release")) {
|
|
112
|
-
return "deploy";
|
|
113
|
-
}
|
|
114
|
-
if (blob.includes("spec unclear") ||
|
|
115
|
-
blob.includes("requirement ambiguous") ||
|
|
116
|
-
blob.includes("needs human")) {
|
|
117
|
-
return "spec-unclear";
|
|
93
|
+
for (const rule of BLOB_RULES) {
|
|
94
|
+
if (rule.nodeIdIncludes?.some((keyword) => input.nodeId.includes(keyword)) ||
|
|
95
|
+
rule.blobIncludes?.some((keyword) => blob.includes(keyword)) ||
|
|
96
|
+
rule.blobPatterns?.some((pattern) => pattern.test(blob))) {
|
|
97
|
+
return rule.result;
|
|
98
|
+
}
|
|
118
99
|
}
|
|
119
100
|
if (input.failureCategory === "invalid-output")
|
|
120
101
|
return "contract";
|
|
121
|
-
if (input.failureCategory === "write-guard")
|
|
122
|
-
return "path";
|
|
123
102
|
return "unknown";
|
|
124
103
|
}
|
|
@@ -96,74 +96,6 @@ export async function captureCumulativeDiffContext(input) {
|
|
|
96
96
|
}
|
|
97
97
|
return assembleCumulativeDiffContext({ baselineEntries, currentEntries });
|
|
98
98
|
}
|
|
99
|
-
const TYPED_TO_LEGACY = {
|
|
100
|
-
approve_review: "pass",
|
|
101
|
-
request_review_changes: "request-revision",
|
|
102
|
-
};
|
|
103
|
-
function isTypedReviewTerminalVerdict(value) {
|
|
104
|
-
return value === "approve_review" || value === "request_review_changes";
|
|
105
|
-
}
|
|
106
|
-
function isLegacyReviewVerdict(value) {
|
|
107
|
-
return value === "pass" || value === "request-revision";
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Deterministic, fail-closed equivalence between the typed review terminal
|
|
111
|
-
* verdict(s) observed in the attempt and the legacy JSON verdict.
|
|
112
|
-
*
|
|
113
|
-
* Fail-closed cases (all yield `match: false` with an explicit reason):
|
|
114
|
-
* - no typed terminal fact;
|
|
115
|
-
* - more than one typed terminal fact (conflicting);
|
|
116
|
-
* - an unknown typed terminal kind;
|
|
117
|
-
* - a missing/illegal legacy JSON verdict.
|
|
118
|
-
*/
|
|
119
|
-
export function compareTypedReviewToLegacyJsonVerdict(input) {
|
|
120
|
-
const kinds = [...input.typedKinds];
|
|
121
|
-
const legacyVerdict = isLegacyReviewVerdict(input.legacyVerdict ?? "")
|
|
122
|
-
? input.legacyVerdict
|
|
123
|
-
: undefined;
|
|
124
|
-
if (kinds.length === 0) {
|
|
125
|
-
return {
|
|
126
|
-
typedVerdict: undefined,
|
|
127
|
-
legacyVerdict,
|
|
128
|
-
match: false,
|
|
129
|
-
reason: "typed review terminal fact missing",
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
if (kinds.length > 1) {
|
|
133
|
-
return {
|
|
134
|
-
typedVerdict: undefined,
|
|
135
|
-
legacyVerdict,
|
|
136
|
-
match: false,
|
|
137
|
-
reason: `conflicting typed review terminal facts: ${[...new Set(kinds)].join(",")}`,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
const typedVerdict = kinds[0];
|
|
141
|
-
if (!isTypedReviewTerminalVerdict(typedVerdict)) {
|
|
142
|
-
return {
|
|
143
|
-
typedVerdict: undefined,
|
|
144
|
-
legacyVerdict,
|
|
145
|
-
match: false,
|
|
146
|
-
reason: `unknown typed review terminal fact kind: ${typedVerdict}`,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
if (!legacyVerdict) {
|
|
150
|
-
return {
|
|
151
|
-
typedVerdict,
|
|
152
|
-
legacyVerdict: undefined,
|
|
153
|
-
match: false,
|
|
154
|
-
reason: "legacy JSON verdict missing or not pass|request-revision",
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
const expected = TYPED_TO_LEGACY[typedVerdict];
|
|
158
|
-
return {
|
|
159
|
-
typedVerdict,
|
|
160
|
-
legacyVerdict,
|
|
161
|
-
match: expected === legacyVerdict,
|
|
162
|
-
reason: expected === legacyVerdict
|
|
163
|
-
? "typed review verdict matches legacy JSON verdict"
|
|
164
|
-
: `typed review verdict ${typedVerdict} maps to ${expected} but legacy JSON verdict is ${legacyVerdict}`,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
99
|
/**
|
|
168
100
|
* review-context 稳定失败枚举(AC-011 / AC-HARD-005):v2 drift / manifest 失败返回
|
|
169
101
|
* 结构化、可消费的 failure code(稳定枚举 + reason 的 envelope),既有调用方无需字符串
|
|
@@ -191,6 +123,37 @@ async function readRequiredJson(runDir, relativePath) {
|
|
|
191
123
|
throw new FrontendReviewContextFailure("review-context-manifest-missing", `frontend review context missing or invalid ${relativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
192
124
|
}
|
|
193
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Per-command verification evidence written by the verify shell.
|
|
128
|
+
*
|
|
129
|
+
* The review protocol treats shell exit status as authoritative ("typecheck,
|
|
130
|
+
* build, and test are required successful final exits"), but the verification
|
|
131
|
+
* trace only proves command/file/target-id *binding*. Smoke r31: with no exit
|
|
132
|
+
* evidence in the context, a compliant reviewer had to request changes for a
|
|
133
|
+
* missing check it could not perform. Absent the artifact the context says so
|
|
134
|
+
* explicitly instead of leaving the reviewer to guess.
|
|
135
|
+
*/
|
|
136
|
+
async function readVerificationEvidence(runDir) {
|
|
137
|
+
const relativePath = "contracts/frontend-verification-evidence.json";
|
|
138
|
+
try {
|
|
139
|
+
const decoded = JSON.parse(await readFile(path.join(runDir, relativePath), "utf8"));
|
|
140
|
+
if (decoded?.schemaId !== "frontend-verification-evidence-v1") {
|
|
141
|
+
return {
|
|
142
|
+
schemaId: "frontend-verification-evidence-v1",
|
|
143
|
+
status: "unavailable",
|
|
144
|
+
reason: `${relativePath} carries an unexpected schemaId`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return { ...decoded, status: "available" };
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
return {
|
|
151
|
+
schemaId: "frontend-verification-evidence-v1",
|
|
152
|
+
status: "unavailable",
|
|
153
|
+
reason: `${relativePath} unavailable: ${error instanceof Error ? error.message : String(error)}`,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
194
157
|
async function readOptionalLintAssessment(runDir) {
|
|
195
158
|
const relativePath = "contracts/frontend-lint-assessment.json";
|
|
196
159
|
try {
|
|
@@ -320,6 +283,7 @@ export async function runFrontendReviewContextGate(input) {
|
|
|
320
283
|
const repairAssessment = await readOptionalRepairAssessment(input.runDir);
|
|
321
284
|
const verifyFailure = await readOptionalVerifyFailure(input.runDir);
|
|
322
285
|
const lintAssessment = await readOptionalLintAssessment(input.runDir);
|
|
286
|
+
const verificationEvidence = await readVerificationEvidence(input.runDir);
|
|
323
287
|
const cumulativeDiff = await captureCumulativeDiffContext({
|
|
324
288
|
runDir: input.runDir,
|
|
325
289
|
workspaceRoot: input.workspaceRoot,
|
|
@@ -355,6 +319,16 @@ export async function runFrontendReviewContextGate(input) {
|
|
|
355
319
|
sections: contractReviewArtifacts.index.sections,
|
|
356
320
|
},
|
|
357
321
|
verificationTrace,
|
|
322
|
+
verificationEvidence,
|
|
323
|
+
// Protocol-legal lint vocabulary; `lintConfigured: false` tells the
|
|
324
|
+
// reviewer that this task declares no lint surface at all, so
|
|
325
|
+
// `unavailable` reads as "not applicable", not "missing evidence".
|
|
326
|
+
lintStatus: lintAssessment
|
|
327
|
+
? lintAssessment.status
|
|
328
|
+
: typeof verificationEvidence.lintStatus === "string"
|
|
329
|
+
? verificationEvidence.lintStatus
|
|
330
|
+
: "unavailable",
|
|
331
|
+
lintConfigured: verificationEvidence.lintConfigured === true,
|
|
358
332
|
...(repairAssessment ? { repairAssessment } : {}),
|
|
359
333
|
...(verifyFailure ? { verifyFailure } : {}),
|
|
360
334
|
...(lintAssessment ? { lintAssessment } : {}),
|
|
@@ -121,11 +121,11 @@ export function createFrontendReviewScopeProtocol(input) {
|
|
|
121
121
|
committedFacts: () => readCommittedEvents(input.getStore(), input.attemptId),
|
|
122
122
|
assertComplete() { const missing = input.inventory?.scopes.filter(s => !completedScopeIds().has(s.id)) ?? []; if (missing.length)
|
|
123
123
|
throw Object.assign(Error(`FRONTEND_REVIEW_SCOPE_INCOMPLETE: ${missing.map(s => s.id).join(", ")}`), { code: "FRONTEND_REVIEW_SCOPE_INCOMPLETE" }); },
|
|
124
|
-
customTools: !input.inventory ? [] : [{ name: "complete_review_scope", label: "complete_review_scope", description: "After reviewing the supplied complete scope and saving its findings, checkpoint its exact runtime id. This does not approve the implementation or design.", parameters: { type: "object", properties: { id: { type: "string", minLength: 1 } }, required: ["id"], additionalProperties: false },
|
|
124
|
+
customTools: !input.inventory ? [] : [{ name: "complete_review_scope", label: "complete_review_scope", description: "After reviewing the supplied complete scope and saving its findings, checkpoint its exact runtime id. This does not approve the implementation or design.", parameters: { type: "object", properties: { id: { type: "string", minLength: 1, enum: input.inventory.scopes.map(scope => scope.id) } }, required: ["id"], additionalProperties: false },
|
|
125
125
|
async execute(callId, params) {
|
|
126
126
|
const scope = input.inventory.scopes.find(s => s.id === params.id);
|
|
127
127
|
if (!scope || !active.has(params.id))
|
|
128
|
-
return receipt({ ok: false, code: "FRONTEND_INPUT_SCOPE_VIOLATION", error: "Only the supplied active review scope may be completed" });
|
|
128
|
+
return receipt({ ok: false, code: "FRONTEND_INPUT_SCOPE_VIOLATION", error: "Only the supplied active review scope may be completed; use an exact activeScopeIds entry, not the node name", activeScopeIds: [...active], completedScopeIds: [...completedScopeIds()] });
|
|
129
129
|
await input.inventory.validate();
|
|
130
130
|
const store = input.getStore();
|
|
131
131
|
const requestId = `${input.attemptId}:${callId}:${randomUUID()}`;
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* allowing its path is NOT dependency introduction by itself; the manifest
|
|
8
8
|
* path check in classifyFrontendRisk requires this language to escalate.
|
|
9
9
|
*/
|
|
10
|
-
const DEPENDENCY_INSTALL_LANGUAGE = /new\s+dependency|add\s+
|
|
11
|
-
|
|
10
|
+
const DEPENDENCY_INSTALL_LANGUAGE = /new\s+dependency|add\s+dependenc(?:y|ies)|install\s+dependenc(?:y|ies)|npm\s+(?:install|i)\b|yarn\s+(?:add|install)\b|pnpm\s+(?:add|install)\b|bun\s+(?:add|install)\b|新增依赖|添加依赖|安装依赖|引入依赖/i;
|
|
11
|
+
/** Shared vocabulary for risk and topology routing. Keep policy-specific
|
|
12
|
+
* signals (design conflict/state infrastructure) below this list. */
|
|
13
|
+
export const FRONTEND_SHARED_ESCALATION_PATTERNS = [
|
|
12
14
|
{
|
|
13
15
|
id: "new-route",
|
|
14
16
|
re: /new\s+route|新增路由|createBrowserRouter|add\s+route|新建页面路由/i,
|
|
@@ -45,6 +47,48 @@ const HIGH_RISK_PATTERNS = [
|
|
|
45
47
|
id: "cross-domain-paths",
|
|
46
48
|
re: /多个领域|cross-module|multiple\s+packages/i,
|
|
47
49
|
},
|
|
50
|
+
];
|
|
51
|
+
function isExplicitSharedExclusion(clause, pattern) {
|
|
52
|
+
const match = new RegExp(pattern.re.source, "i").exec(clause);
|
|
53
|
+
if (!match)
|
|
54
|
+
return false;
|
|
55
|
+
const prefix = clause.slice(0, match.index).trim();
|
|
56
|
+
const suffix = clause.slice(match.index + match[0].length).trim();
|
|
57
|
+
const actionInSignal = ["new-route", "new-dependency"].includes(pattern.id);
|
|
58
|
+
if (suffix && !(actionInSignal && /^no$/i.test(prefix) && /^is\s+(?:required|needed)$/i.test(suffix)))
|
|
59
|
+
return false;
|
|
60
|
+
if (/^(?:不涉及|不(?:修改|变更|调整|改变|新增|添加)|无需(?:修改|变更|调整|改变|新增|添加)|no\s+changes?\s+to|(?:do\s+not|don't)\s+(?:change|modify))$/i.test(prefix))
|
|
61
|
+
return true;
|
|
62
|
+
return actionInSignal && /^(?:不|禁止|无需|无须|不需要|no|without|do\s+not|don't)$/i.test(prefix);
|
|
63
|
+
}
|
|
64
|
+
/** Only a complete, independent scope exclusion can suppress a risk signal.
|
|
65
|
+
* Nested negation, conditions and trailing consequences remain risk evidence;
|
|
66
|
+
* an excluded occurrence never cancels another occurrence elsewhere. */
|
|
67
|
+
export function hasAffirmativeSharedSignal(blob, pattern) {
|
|
68
|
+
for (const sentence of blob.split(/[\n。.;;!?!?]/)) {
|
|
69
|
+
if (!pattern.re.test(sentence))
|
|
70
|
+
continue;
|
|
71
|
+
// Commas can attach a condition to an exclusion. A comma-separated list
|
|
72
|
+
// is independent only when every item is itself an explicit exclusion.
|
|
73
|
+
const clauses = sentence.split(/[,,、]/).map(part => part.trim().replace(/^(?:[-*+]|\d+\))\s+/, ""));
|
|
74
|
+
if (clauses.every(clause => FRONTEND_SHARED_ESCALATION_PATTERNS.some(candidate => isExplicitSharedExclusion(clause, candidate))))
|
|
75
|
+
continue;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
/** Extract the shared escalation vocabulary once. Shape routing reuses this
|
|
81
|
+
* exact result so risk and topology cannot drift on the same input. */
|
|
82
|
+
export function collectFrontendSharedSignals(blob, supervised = false) {
|
|
83
|
+
const signals = FRONTEND_SHARED_ESCALATION_PATTERNS
|
|
84
|
+
.filter((pattern) => hasAffirmativeSharedSignal(blob, pattern))
|
|
85
|
+
.map((pattern) => pattern.id);
|
|
86
|
+
if (supervised)
|
|
87
|
+
signals.push("supervised-profile");
|
|
88
|
+
return [...new Set(signals)];
|
|
89
|
+
}
|
|
90
|
+
const HIGH_RISK_PATTERNS = [
|
|
91
|
+
...FRONTEND_SHARED_ESCALATION_PATTERNS,
|
|
48
92
|
{
|
|
49
93
|
id: "new-state-infra",
|
|
50
94
|
re: /\bredux\b|\bzustand\b|\bpinia\b|\bjotai\b|\brecoil\b|新状态库|state\s+management\s+introduc/i,
|
|
@@ -77,6 +121,41 @@ function pathSpreadScore(paths) {
|
|
|
77
121
|
.filter(Boolean));
|
|
78
122
|
return tops.size;
|
|
79
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Narrow-product-surface test for genuinely small topology. A task qualifies
|
|
126
|
+
* only when its non-auxiliary (product) paths are at most two and all share the
|
|
127
|
+
* same two-segment prefix — `src/components/Button.tsx` + `test/components/*`
|
|
128
|
+
* is narrow, while `src/components/**` + `src/pages/**` spans two product
|
|
129
|
+
* domains and is not. Direct source files (`src/a.tsx`) collapse to their
|
|
130
|
+
* directory so siblings stay narrow. This is the structured replacement for the
|
|
131
|
+
* fragile "single component / local style" keyword gate: it measures the actual
|
|
132
|
+
* delivery surface instead of relying on how the author worded the request.
|
|
133
|
+
*/
|
|
134
|
+
export function isNarrowProductSurface(paths) {
|
|
135
|
+
const auxiliaryRoot = /^(?:test|tests|__tests__|e2e|cypress|spec|specs|docs|documentation)$/i;
|
|
136
|
+
const product = paths
|
|
137
|
+
.map((raw) => raw.replace(/\\/g, "/").split("/").filter(Boolean))
|
|
138
|
+
.filter((segs) => segs.length > 0 && !auxiliaryRoot.test(segs[0] ?? ""));
|
|
139
|
+
if (product.length === 0 || product.length > 2)
|
|
140
|
+
return false;
|
|
141
|
+
// Every product path must name a concrete file or a concrete second-level
|
|
142
|
+
// directory. A top-level wide glob (`src/**`, `src/*`) covers the whole
|
|
143
|
+
// source tree and is NOT a small delivery surface.
|
|
144
|
+
for (const segs of product) {
|
|
145
|
+
const second = segs[1] ?? "";
|
|
146
|
+
if (!second || second === "*" || second === "**")
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
const prefixOf = (segs) => {
|
|
150
|
+
const second = segs[1] ?? "";
|
|
151
|
+
const secondIsFile = /\.[A-Za-z0-9]+$/.test(second);
|
|
152
|
+
return segs.length >= 2 && second && !secondIsFile
|
|
153
|
+
? `${segs[0] ?? ""}/${segs[1]}`
|
|
154
|
+
: segs[0] ?? "";
|
|
155
|
+
};
|
|
156
|
+
const prefix = prefixOf(product[0] ?? []);
|
|
157
|
+
return product.every((segs) => prefixOf(segs) === prefix);
|
|
158
|
+
}
|
|
80
159
|
/**
|
|
81
160
|
* Classify frontend task risk. High-risk signals always win over small keywords.
|
|
82
161
|
* supervised / explicit high governance never selects small topology.
|
|
@@ -116,10 +195,14 @@ export function classifyFrontendRisk(input) {
|
|
|
116
195
|
if (pattern.re.test(blob))
|
|
117
196
|
smallHits.push(pattern.id);
|
|
118
197
|
}
|
|
119
|
-
// small only if: no high-risk,
|
|
198
|
+
// small only if: no high-risk, non-empty concentrated paths, no remote API
|
|
199
|
+
// signals, not supervised, and not an explicitly large task. Keyword and
|
|
200
|
+
// explicit "small/low" complexity hints are no longer required — they were
|
|
201
|
+
// a fragile proxy for "this is genuinely a small change" and silently
|
|
202
|
+
// forced genuinely-small tasks with different wording into full gates.
|
|
120
203
|
const hasApiSignal = /\b(api|fetch|axios|graphql|endpoint|远程|接口)\b/i.test(blob) &&
|
|
121
204
|
!/\b(no\s+api|无接口|not-needed)\b/i.test(blob);
|
|
122
|
-
const
|
|
205
|
+
const narrowSurface = isNarrowProductSurface(paths);
|
|
123
206
|
if (supervised) {
|
|
124
207
|
rejectedSignals.push(...smallHits.map((id) => `small:${id}:blocked-by-supervised`));
|
|
125
208
|
return {
|
|
@@ -142,19 +225,18 @@ export function classifyFrontendRisk(input) {
|
|
|
142
225
|
forceFullGates: true,
|
|
143
226
|
};
|
|
144
227
|
}
|
|
145
|
-
if (
|
|
146
|
-
concentrated &&
|
|
228
|
+
if (narrowSurface &&
|
|
147
229
|
!hasApiSignal &&
|
|
148
|
-
(input.complexity
|
|
230
|
+
(input.complexity ?? "").toLowerCase() !== "large") {
|
|
149
231
|
return {
|
|
150
232
|
selectedRisk: "small",
|
|
151
|
-
signals: smallHits,
|
|
233
|
+
signals: smallHits.length > 0 ? smallHits : ["structured-small"],
|
|
152
234
|
rejectedSignals,
|
|
153
|
-
reason:
|
|
235
|
+
reason: "narrow single-domain delivery surface without remote API, dependency, or escalation signals; small topology",
|
|
154
236
|
forceFullGates: false,
|
|
155
237
|
};
|
|
156
238
|
}
|
|
157
|
-
if (smallHits.length > 0 && (hasApiSignal || !
|
|
239
|
+
if (smallHits.length > 0 && (hasApiSignal || !narrowSurface)) {
|
|
158
240
|
rejectedSignals.push(...smallHits.map((id) => `small:${id}:blocked-by-${hasApiSignal ? "api-signal" : "path-spread"}`));
|
|
159
241
|
return {
|
|
160
242
|
selectedRisk: "standard",
|