@tea-agent/loop-agent 0.41.1-beta.0 → 0.42.0-next.2
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/CHANGELOG.md +78 -206
- package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
- package/dist/adapters/context-transfer/pi-session.js +61 -0
- package/dist/application/dag/generate-task-dag.js +19 -75
- package/dist/application/task-lifecycle/advance.js +5 -24
- package/dist/application/task-lifecycle/observe.js +17 -171
- package/dist/application/task-lifecycle/plan-transitions.js +7 -42
- package/dist/application/task-lifecycle/recommendations.js +2 -13
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +12 -7
- package/dist/cli/program.js +23 -6
- package/dist/commands/client-recovery.js +0 -3
- package/dist/commands/dag-artifact.js +284 -0
- package/dist/commands/dag-context.js +184 -0
- package/dist/commands/dag-rerun.js +206 -1
- package/dist/commands/init.js +1 -27
- package/dist/commands/task-advance.js +0 -19
- package/dist/executors/dag-pi-executor.js +266 -3792
- package/dist/executors/pi-executor.js +4 -15
- package/dist/executors/pi-sdk-executor.js +3 -0
- package/dist/executors/shell-executor.js +217 -945
- package/dist/executors/shell-write-guard.js +0 -7
- package/dist/{worker → infrastructure}/console/app-data.js +4 -0
- package/dist/infrastructure/console/artifact-revision-store.js +430 -0
- package/dist/infrastructure/console/context-export-store.js +160 -0
- package/dist/infrastructure/console/dir-lock.js +132 -0
- package/dist/infrastructure/console/operation-store.js +197 -0
- package/dist/infrastructure/harness/artifact-store.js +10 -1
- package/dist/infrastructure/harness/atomic-write.js +12 -2
- package/dist/shared/context-transfer/artifact-revision.js +172 -0
- package/dist/shared/context-transfer.js +418 -0
- package/dist/shared/dag-failure-category.js +0 -12
- package/dist/shared/openspec-spec.js +4 -70
- package/dist/shared/operator/capabilities.js +14 -7
- package/dist/shared/operator/safe-run-summary.js +1 -0
- package/dist/shared/path-safety.js +93 -0
- package/dist/shared/preview.js +28 -4
- package/dist/task/config-types.js +80 -35
- package/dist/task/contract/adopt.js +0 -4
- package/dist/task/contract/import-revision.js +0 -4
- package/dist/task/contract/project.js +0 -3
- package/dist/task/contract/schema.js +1 -2
- package/dist/task/frontend-project-capability.js +20 -203
- package/dist/task/runtime.js +2 -5
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +9 -15
- package/dist/task/source-prepare/prepare.js +1 -89
- package/dist/task/source-prepare/semantic-intake.js +2 -6
- package/dist/task/source-references.js +1 -22
- package/dist/task/task-demand-routing.js +3 -0
- package/dist/worker/console/chat/chat-event-store.js +2 -2
- package/dist/worker/console/chat/pi-runtime.js +3 -2
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +13 -4
- package/dist/worker/console/chat/semantic-activity.js +12 -11
- package/dist/worker/console/chat/session-stats.js +96 -0
- package/dist/worker/console/chat/session-store.js +1 -1
- package/dist/worker/console/chat/turn-process.js +28 -4
- package/dist/worker/console/chat/user-questions.js +1 -1
- package/dist/worker/console/console-update-runtime.js +1 -1
- package/dist/worker/console/context-transfer-diagnostics.js +198 -0
- package/dist/worker/console/dag-confirmation.js +1 -1
- package/dist/worker/console/dag-execution-receipt.js +1 -1
- package/dist/worker/console/doctor.js +1 -1
- package/dist/worker/console/draft-store.js +1 -1
- package/dist/worker/console/human-gate-token.js +1 -1
- package/dist/worker/console/index.js +3 -6
- package/dist/worker/console/interview/assessment.js +1 -1
- package/dist/worker/console/interview/session.js +1 -1
- package/dist/worker/console/operation-runner.js +45 -4
- package/dist/worker/console/operation-sse.js +1 -1
- package/dist/worker/console/operation-wait.js +1 -1
- package/dist/worker/console/operator-actions.js +107 -168
- package/dist/worker/console/operator-surface-health.js +1 -1
- package/dist/worker/console/operator-user-error.js +169 -0
- package/dist/worker/console/pi-plugins.js +60 -1
- package/dist/worker/console/routes.js +637 -8
- package/dist/worker/console/security.js +35 -0
- package/dist/worker/console/server.js +6 -6
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bho4we4c.js → abnfDiagram-N423BO3Z-DZZ8m3PO.js} +1 -1
- package/dist/worker/console/static/assets/{arc-TBDkTeK0.js → arc-D6PvaVd-.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BF_fZrn2.js → architectureDiagram-T3A2C74G-B_OTOiI8.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CCnPgUe2.js → blockDiagram-VBNYF7ZC-Bv6rqHBg.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCYZJLAT.js → c4Diagram-5PPSVZJV-B8eHr0oz.js} +1 -1
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D8K6adhU.js → chunk-2GRJ4B5K-DYwR0im2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DkT1Q78b.js → chunk-2Q5K7J3B-D2WPGqXt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DuhvpIr7.js → chunk-5RXB4S5H-CQISJ_I7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BnK6dC49.js → chunk-5VM5RSS4-C0o2Du1e.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DPz__z7r.js → chunk-6Q2QTUOP-4f8kr-U7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-0kkppM2j.js → chunk-GF5L2VYU-D_OWvXzX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BB41iK5t.js → chunk-JWPE2WC7-CasdPz5X.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CiVrxKHU.js → chunk-KBJHAD2P-Cj8lRrla.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C2h9Xje1.js → chunk-RYQCIY6F-CUvD1FSd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-3iOSHdbM.js → chunk-XXDRQBXY-CqLw_eqB.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BN3A7tkk.js → cose-bilkent-JH36ORCC-ku-WqJPx.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CTuBy0Or.js → cynefin-VYW2F7L2-Bq_PYDhl.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-wb6tUlMN.js → cynefinDiagram-MW4NZA55-BiGZV641.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-GP1Ivx8B.js → dagre-VZM6K2ZE-C4JRlglF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ejrcK6L0.js → diagram-7IWD3JNH-B0dNeWYG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-YFkCCvUU.js → diagram-B4RE2ZJO-Cj35YvYM.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-B4rrvIaL.js → diagram-LBJQPF4R-uzQoJ2-8.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C-YIZcUf.js → diagram-Q27KOJAE-D1a-Buoz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPF5wcL1.js → diagram-UB23O5K3-XjRrLRSs.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CU5SzXjO.js → ebnfDiagram-BXEA7PRR-Da_O24cW.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CpL414DE.js → erDiagram-JOGREHBK-BLJ8jrYU.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-MrhLzNau.js → flowDiagram-UKHOOZJN-B9GrLjM0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Bg4qFuUo.js → ganttDiagram-PKOTCBZU-CeJ0TqiK.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-ColxSXLR.js → gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js} +1 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +1 -0
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +451 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czvtm6gc.js → infoDiagram-6WML65LV-DB26i3d8.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js → ishikawaDiagram-WSZJBQD7-BOGRyeZT.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DWYZcvph.js → journeyDiagram-NVQOT4AX-CZRoBO_6.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-KTLZRUjx.js → kanban-definition-27J2QSJJ-qlWhJyoB.js} +1 -1
- package/dist/worker/console/static/assets/{linear-_Exn7bfl.js → linear-CfUiDDB3.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Cxg8kX8V.js → mermaid.core-BQe6fpqj.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-oKrlo2Bt.js → mindmap-definition-FAOFIHXS-zFzWHw64.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-XAeteiNd.js → pegDiagram-VL7TDLO6-DFGUqjZO.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BTf1K-lp.js → pieDiagram-7S7Q4E2Y-BB5i0l8Z.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js → quadrantDiagram-CIZ2JOQS-obbVZ_X5.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BVbKtZhW.js → railroadDiagram-AXF67PYL-BwdaNzc1.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C5e4JFcr.js → requirementDiagram-LRYGKXZP-CkTJaugh.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cz30hc29.js → sankeyDiagram-W5VNT64P-BwJ-hIgq.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DS33n6d0.js → sequenceDiagram-SI44F4Z6-BzSgmm7w.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-46w7v-9T.js → sizeCapture-X5ZJPWSS-Bn3oj5Q1.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-pbcD5SsZ.js → stateDiagram-OKZ733FA-dxhGtL8K.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DuivYBSj.js → swimlanes-SLNWSIFB-BLpEInya.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cb9VZVIl.js → timeline-definition-Z64GVDOM-ChJGYcXy.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-D2-mfOgR.js → vennDiagram-T6HMQDX7-DK-qjRer.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DeS2aC76.js → wardleyDiagram-T6FBY63Y-3qeaWAg-.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-WGsrFjxH.js → xychartDiagram-ELKLHX3M-CootlyP9.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
- package/dist/worker/console/static-src/app/useRunProgress.js +9 -2
- package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +22 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +106 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
- package/dist/worker/console/workspace-context.js +28 -112
- package/dist/worker/console/workspace-registry.js +2 -2
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/observability/read-model.js +4 -0
- package/dist/worker/observe/health.js +1 -1
- package/dist/worker/observe/node-transparency.js +572 -0
- package/dist/worker/observe/routes.js +51 -1
- package/dist/worker/observe/static/api.js +69 -5
- package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
- package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
- package/dist/worker/observe/static/dag-node-purpose.js +0 -5
- package/dist/worker/observe/static/state.js +3 -1
- package/dist/worker/observe/static/styles.css +1037 -126
- package/dist/worker/observe/static/views/dag-inspector.js +1217 -65
- package/dist/worker/observe/static/views/session-timeline.js +121 -25
- package/dist/worker/outcomes/adapters.js +19 -0
- package/dist/worker/outcomes/types.js +1 -0
- package/dist/worker/pool/attempt-lease.js +97 -66
- package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
- package/dist/worker/pool/reconcile.js +46 -1
- package/dist/worker/pool/run-store.js +2 -0
- package/dist/worker/pool/state-projection.js +2 -0
- package/dist/worker/task-spec/workflow-routing.js +8 -3
- package/dist/workflows/dag/artifact-bindings.js +149 -0
- package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +99 -4
- package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-writer-completeness.js +45 -3
- package/dist/workflows/dag/budget-enforcement.js +10 -0
- package/dist/workflows/dag/context-receipt.js +305 -0
- package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
- package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
- package/dist/workflows/dag/context-transfer/renderers.js +93 -0
- package/dist/workflows/dag/contract-validator-registrations.js +2 -1
- package/dist/workflows/dag/frontend-implementation-contract.js +162 -932
- package/dist/workflows/dag/frontend-plan-render.js +1 -2
- package/dist/workflows/dag/frontend-prewrite-gate.js +349 -255
- package/dist/workflows/dag/frontend-recovery-plan.js +10 -17
- package/dist/workflows/dag/frontend-recovery-run.js +20 -33
- package/dist/workflows/dag/frontend-repair.js +432 -1
- package/dist/workflows/dag/frontend-review-context.js +15 -261
- package/dist/workflows/dag/frontend-verification-trace.js +24 -249
- package/dist/workflows/dag/frontend-worktree-diff.js +17 -250
- package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
- package/dist/workflows/dag/init-hybrid.js +740 -718
- package/dist/workflows/dag/interrupt-request.js +0 -7
- package/dist/workflows/dag/node-execution.js +136 -759
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +30 -38
- package/dist/workflows/dag/report.js +1 -37
- package/dist/workflows/dag/rerun-feedback.js +1 -256
- package/dist/workflows/dag/rerun-plan.js +557 -11
- package/dist/workflows/dag/rerun-run.js +601 -38
- package/dist/workflows/dag/rerun-task.js +0 -29
- package/dist/workflows/dag/retry-policy.js +109 -214
- package/dist/workflows/dag/runner.js +142 -430
- package/dist/workflows/dag/scheduler.js +16 -114
- package/dist/workflows/dag/skill-snapshot.js +17 -0
- package/dist/workflows/dag/types.js +195 -246
- package/dist/workflows/dag/validate.js +12 -28
- package/docs/architecture/runtime-boundaries.md +6 -6
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/init-surface.manifest.json +12 -30
- package/docs/skills/vetted-skill-registry.md +2 -4
- package/docs/templates/README.md +0 -2
- package/docs/templates/agent-dag-report.schema.json +2 -8
- package/docs/templates/agent-dag.schema.json +33 -1
- package/docs/templates/backend-test-dag.json +18 -18
- package/docs/templates/frontend-implementation-contract.schema.json +1 -4
- package/docs/templates/product-line/task.yaml +1 -1
- package/harness.json +3 -3
- package/package.json +3 -1
- package/skills/frontend-bounded-implement/SKILL.md +14 -15
- package/skills/frontend-design-review/SKILL.md +41 -22
- package/skills/frontend-implementation/SKILL.md +52 -0
- package/skills/frontend-implementation/references/code-standards.md +33 -0
- package/skills/frontend-implementation/references/design-spec.md +56 -0
- package/skills/frontend-implementation/references/node-contracts.md +31 -0
- package/skills/frontend-review/SKILL.md +15 -20
- package/skills/frontend-review/references/review-findings.md +7 -6
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +6 -1
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/commands/dag-follow-up.js +0 -138
- package/dist/executors/pi-read-budget-policy.js +0 -239
- package/dist/worker/console/frontend-human-decision-adapter.js +0 -19
- package/dist/worker/console/frontend-split-operation-adapter.js +0 -20
- package/dist/worker/console/operation-store.js +0 -389
- package/dist/worker/console/static/assets/channel-bnXW0U3C.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +0 -1
- package/dist/worker/console/static/assets/index-D3CC4eUz.css +0 -1
- package/dist/worker/console/static/assets/index-DLt4ZFvD.js +0 -437
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +0 -8
- package/dist/worker/console/static-src/active-run-badge.js +0 -17
- package/dist/worker/materialize/frontend-split-task-materializer.js +0 -72
- package/dist/workflows/dag/dag-retry-schema.js +0 -138
- package/dist/workflows/dag/frontend-closeout.js +0 -221
- package/dist/workflows/dag/frontend-design-policy.js +0 -400
- package/dist/workflows/dag/frontend-human-decision.js +0 -182
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +0 -159
- package/dist/workflows/dag/frontend-read-budgets.js +0 -12
- package/dist/workflows/dag/frontend-recovery-capsule.js +0 -455
- package/dist/workflows/dag/frontend-recovery-controller.js +0 -202
- package/dist/workflows/dag/frontend-recovery-lineage.js +0 -178
- package/dist/workflows/dag/frontend-review-findings.js +0 -270
- package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -914
- package/dist/workflows/dag/frontend-shape-capsule-store.js +0 -191
- package/dist/workflows/dag/frontend-shape-facts.js +0 -409
- package/dist/workflows/dag/frontend-shape.js +0 -427
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +0 -108
- package/dist/workflows/dag/frontend-split-application-service.js +0 -203
- package/dist/workflows/dag/frontend-split-orchestrator.js +0 -899
- package/dist/workflows/dag/frontend-typed-event-store.js +0 -452
- package/dist/workflows/dag/frontend-typed-event-transaction.js +0 -180
- package/dist/workflows/dag/frontend-writer-admission.js +0 -285
- package/dist/workflows/dag/frontend-writer-status.js +0 -256
- package/dist/workflows/dag/recovery-lease.js +0 -80
- package/docs/templates/frontend-implementation-dag.json +0 -89
- package/docs/templates/spec-registry.schema.json +0 -45
- package/skills/frontend-bounded-implement/references/code-standards.md +0 -19
- package/skills/frontend-contract/SKILL.md +0 -23
- package/skills/frontend-contract/references/contract-protocol.md +0 -34
- package/skills/frontend-plan/SKILL.md +0 -26
- package/skills/frontend-plan/references/decision-contract.md +0 -37
- package/skills/frontend-plan/references/design-decisions.md +0 -17
- package/skills/frontend-scout/SKILL.md +0 -25
- package/skills/frontend-scout/references/design-evidence.md +0 -16
- package/skills/frontend-scout/references/scout-evidence.md +0 -23
- /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
|
@@ -4,166 +4,11 @@ import path from "node:path";
|
|
|
4
4
|
import { writeDagRunJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
|
|
5
5
|
import { formatFrontendWorktreeDiffStdout, runFrontendWorktreeDiffGate, } from "./frontend-worktree-diff.js";
|
|
6
6
|
import { frontendLintAssessmentArtifactSchema } from "./frontend-lint-baseline.js";
|
|
7
|
-
import {
|
|
7
|
+
import { frontendImplementationContractSchema, } from "./frontend-implementation-contract.js";
|
|
8
|
+
import { frontendRepairAssessmentSchema } from "./frontend-repair.js";
|
|
8
9
|
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
9
|
-
import { ledgerStatsLine, parseLedgerJson } from "../../task/source-prepare/ledger.js";
|
|
10
|
-
export const FRONTEND_REVIEW_CONTEXT_SCHEMA_ID = "frontend-review-context-
|
|
11
|
-
/**
|
|
12
|
-
* Deterministically assemble the cumulative diff from a task baseline and the
|
|
13
|
-
* current candidate snapshot. Same input always yields the same output:
|
|
14
|
-
* paths are sorted, statuses are derived from a fixed decision table, and both
|
|
15
|
-
* digests use canonical JSON hashing.
|
|
16
|
-
*/
|
|
17
|
-
export function assembleCumulativeDiffContext(input) {
|
|
18
|
-
const baselineByPath = new Map(input.baselineEntries.map((entry) => [entry.path, entry.sha256]));
|
|
19
|
-
const currentByPath = new Map(input.currentEntries.map((entry) => [entry.path, entry.sha256]));
|
|
20
|
-
const allPaths = new Set([
|
|
21
|
-
...baselineByPath.keys(),
|
|
22
|
-
...currentByPath.keys(),
|
|
23
|
-
]);
|
|
24
|
-
const sortedPaths = [...allPaths].sort();
|
|
25
|
-
const perPath = sortedPaths
|
|
26
|
-
.map((p) => {
|
|
27
|
-
const baselineSha256 = baselineByPath.get(p) ?? null;
|
|
28
|
-
const hasCurrent = currentByPath.has(p);
|
|
29
|
-
const currentSha256 = hasCurrent
|
|
30
|
-
? (currentByPath.get(p) ?? null)
|
|
31
|
-
: null;
|
|
32
|
-
let status;
|
|
33
|
-
if (baselineSha256 === null) {
|
|
34
|
-
status = currentSha256 === null ? "deleted" : "added";
|
|
35
|
-
}
|
|
36
|
-
else if (currentSha256 === null) {
|
|
37
|
-
status = hasCurrent ? "deleted" : "unchanged";
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
status =
|
|
41
|
-
currentSha256 === baselineSha256 ? "unchanged" : "modified";
|
|
42
|
-
}
|
|
43
|
-
return { path: p, status, baselineSha256, currentSha256 };
|
|
44
|
-
})
|
|
45
|
-
// Cumulative diff carries only actual changes. The worktree baseline is
|
|
46
|
-
// the full repository inventory (git ls-files -co), so an unfiltered
|
|
47
|
-
// perPath enumerates thousands of unchanged hashes (e.g. 3000+ entries
|
|
48
|
-
// ≈ 700KB), ballooning the review context and blowing the review node
|
|
49
|
-
// read budget on a single read. Unchanged entries add no diff evidence.
|
|
50
|
-
.filter((entry) => entry.status !== "unchanged");
|
|
51
|
-
const baselineDigest = deterministicSha256([...input.baselineEntries].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
52
|
-
const cumulativeDigest = deterministicSha256(perPath);
|
|
53
|
-
return {
|
|
54
|
-
schemaId: "frontend-cumulative-diff-context-v1",
|
|
55
|
-
baselineDigest,
|
|
56
|
-
perPath,
|
|
57
|
-
cumulativeDigest,
|
|
58
|
-
empty: perPath.length === 0,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
async function sha256File(filePath) {
|
|
62
|
-
try {
|
|
63
|
-
const bytes = await readFile(filePath);
|
|
64
|
-
return createHash("sha256").update(bytes).digest("hex");
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Capture the cumulative diff from the frozen worktree baseline and the
|
|
72
|
-
* run-owned diff gate output. Pure status/digest logic lives in
|
|
73
|
-
* `assembleCumulativeDiffContext`; this wrapper only does IO.
|
|
74
|
-
*/
|
|
75
|
-
export async function captureCumulativeDiffContext(input) {
|
|
76
|
-
const root = path.resolve(input.workspaceRoot);
|
|
77
|
-
let baselineEntries = [];
|
|
78
|
-
try {
|
|
79
|
-
const raw = JSON.parse(await readFile(path.join(input.runDir, "contracts/frontend-worktree-baseline.json"), "utf8"));
|
|
80
|
-
baselineEntries = (raw.files ?? [])
|
|
81
|
-
.map((file) => ({ path: file, sha256: raw.hashes?.[file] ?? null }))
|
|
82
|
-
.filter((entry) => entry.sha256 !== null);
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
// Missing baseline → empty baseline entries (still deterministic).
|
|
86
|
-
}
|
|
87
|
-
const candidatePaths = [
|
|
88
|
-
...new Set([...input.diff.changedFiles, ...input.diff.untrackedFiles]),
|
|
89
|
-
];
|
|
90
|
-
const currentEntries = [];
|
|
91
|
-
for (const file of candidatePaths) {
|
|
92
|
-
currentEntries.push({
|
|
93
|
-
path: file,
|
|
94
|
-
sha256: await sha256File(path.join(root, file)),
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
return assembleCumulativeDiffContext({ baselineEntries, currentEntries });
|
|
98
|
-
}
|
|
99
|
-
const TYPED_TO_LEGACY = {
|
|
100
|
-
approve_review: "pass",
|
|
101
|
-
request_review_changes: "request-revision",
|
|
102
|
-
};
|
|
103
|
-
function isTypedReviewTerminalVerdict(value) {
|
|
104
|
-
return value === "approve_review" || value === "request_review_changes";
|
|
105
|
-
}
|
|
106
|
-
function isLegacyReviewVerdict(value) {
|
|
107
|
-
return value === "pass" || value === "request-revision";
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Deterministic, fail-closed equivalence between the typed review terminal
|
|
111
|
-
* verdict(s) observed in the attempt and the legacy JSON verdict.
|
|
112
|
-
*
|
|
113
|
-
* Fail-closed cases (all yield `match: false` with an explicit reason):
|
|
114
|
-
* - no typed terminal fact;
|
|
115
|
-
* - more than one typed terminal fact (conflicting);
|
|
116
|
-
* - an unknown typed terminal kind;
|
|
117
|
-
* - a missing/illegal legacy JSON verdict.
|
|
118
|
-
*/
|
|
119
|
-
export function compareTypedReviewToLegacyJsonVerdict(input) {
|
|
120
|
-
const kinds = [...input.typedKinds];
|
|
121
|
-
const legacyVerdict = isLegacyReviewVerdict(input.legacyVerdict ?? "")
|
|
122
|
-
? input.legacyVerdict
|
|
123
|
-
: undefined;
|
|
124
|
-
if (kinds.length === 0) {
|
|
125
|
-
return {
|
|
126
|
-
typedVerdict: undefined,
|
|
127
|
-
legacyVerdict,
|
|
128
|
-
match: false,
|
|
129
|
-
reason: "typed review terminal fact missing",
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
if (kinds.length > 1) {
|
|
133
|
-
return {
|
|
134
|
-
typedVerdict: undefined,
|
|
135
|
-
legacyVerdict,
|
|
136
|
-
match: false,
|
|
137
|
-
reason: `conflicting typed review terminal facts: ${[...new Set(kinds)].join(",")}`,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
const typedVerdict = kinds[0];
|
|
141
|
-
if (!isTypedReviewTerminalVerdict(typedVerdict)) {
|
|
142
|
-
return {
|
|
143
|
-
typedVerdict: undefined,
|
|
144
|
-
legacyVerdict,
|
|
145
|
-
match: false,
|
|
146
|
-
reason: `unknown typed review terminal fact kind: ${typedVerdict}`,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
if (!legacyVerdict) {
|
|
150
|
-
return {
|
|
151
|
-
typedVerdict,
|
|
152
|
-
legacyVerdict: undefined,
|
|
153
|
-
match: false,
|
|
154
|
-
reason: "legacy JSON verdict missing or not pass|request-revision",
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
const expected = TYPED_TO_LEGACY[typedVerdict];
|
|
158
|
-
return {
|
|
159
|
-
typedVerdict,
|
|
160
|
-
legacyVerdict,
|
|
161
|
-
match: expected === legacyVerdict,
|
|
162
|
-
reason: expected === legacyVerdict
|
|
163
|
-
? "typed review verdict matches legacy JSON verdict"
|
|
164
|
-
: `typed review verdict ${typedVerdict} maps to ${expected} but legacy JSON verdict is ${legacyVerdict}`,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
10
|
+
import { ledgerStatsLine, parseLedgerJson, } from "../../task/source-prepare/ledger.js";
|
|
11
|
+
export const FRONTEND_REVIEW_CONTEXT_SCHEMA_ID = "frontend-review-context-v1";
|
|
167
12
|
/**
|
|
168
13
|
* review-context 稳定失败枚举(AC-011 / AC-HARD-005):v2 drift / manifest 失败返回
|
|
169
14
|
* 结构化、可消费的 failure code(稳定枚举 + reason 的 envelope),既有调用方无需字符串
|
|
@@ -207,37 +52,6 @@ async function readOptionalLintAssessment(runDir) {
|
|
|
207
52
|
throw new Error(`frontend review context invalid ${relativePath}`);
|
|
208
53
|
}
|
|
209
54
|
}
|
|
210
|
-
async function readOptionalRepairAssessment(runDir) {
|
|
211
|
-
const relativePath = "contracts/frontend-repair-assessment.json";
|
|
212
|
-
try {
|
|
213
|
-
return JSON.parse(await readFile(path.join(runDir, relativePath), "utf8"));
|
|
214
|
-
}
|
|
215
|
-
catch (error) {
|
|
216
|
-
if (error &&
|
|
217
|
-
typeof error === "object" &&
|
|
218
|
-
"code" in error &&
|
|
219
|
-
error.code === "ENOENT") {
|
|
220
|
-
return undefined;
|
|
221
|
-
}
|
|
222
|
-
throw new Error(`frontend review context invalid ${relativePath}`);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
/** M8: verify failure ownership facts written by frontend-verify-shell. */
|
|
226
|
-
async function readOptionalVerifyFailure(runDir) {
|
|
227
|
-
const relativePath = "contracts/frontend-verify-failure.json";
|
|
228
|
-
try {
|
|
229
|
-
return JSON.parse(await readFile(path.join(runDir, relativePath), "utf8"));
|
|
230
|
-
}
|
|
231
|
-
catch (error) {
|
|
232
|
-
if (error &&
|
|
233
|
-
typeof error === "object" &&
|
|
234
|
-
"code" in error &&
|
|
235
|
-
error.code === "ENOENT") {
|
|
236
|
-
return undefined;
|
|
237
|
-
}
|
|
238
|
-
throw new Error(`frontend review context invalid ${relativePath}`);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
55
|
function assertReviewEvidence(input) {
|
|
242
56
|
const contract = frontendImplementationContractSchema.safeParse(input.contract);
|
|
243
57
|
if (!contract.success) {
|
|
@@ -280,105 +94,47 @@ function assertReviewEvidence(input) {
|
|
|
280
94
|
trace.mockStrategy !== contract.data.mockApi.strategy) {
|
|
281
95
|
throw new FrontendReviewContextFailure("review-context-verification-unbound", "frontend review context verification trace is not bound to a passing contract");
|
|
282
96
|
}
|
|
97
|
+
const assessment = frontendRepairAssessmentSchema.safeParse(input.repairAssessment);
|
|
98
|
+
if (!assessment.success) {
|
|
99
|
+
throw new FrontendReviewContextFailure("review-context-invalid-contract", "frontend review context invalid repair assessment");
|
|
100
|
+
}
|
|
283
101
|
}
|
|
284
102
|
export async function runFrontendReviewContextGate(input) {
|
|
285
|
-
const
|
|
286
|
-
let contractRaw;
|
|
287
|
-
try {
|
|
288
|
-
contractRaw = await readFile(path.join(input.runDir, contractRelativePath), "utf8");
|
|
289
|
-
}
|
|
290
|
-
catch (error) {
|
|
291
|
-
throw new FrontendReviewContextFailure("review-context-manifest-missing", `frontend review context missing or invalid ${contractRelativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
292
|
-
}
|
|
293
|
-
let contract;
|
|
294
|
-
try {
|
|
295
|
-
contract = JSON.parse(contractRaw);
|
|
296
|
-
}
|
|
297
|
-
catch (error) {
|
|
298
|
-
throw new FrontendReviewContextFailure("review-context-manifest-missing", `frontend review context missing or invalid ${contractRelativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
299
|
-
}
|
|
103
|
+
const contract = await readRequiredJson(input.runDir, "contracts/frontend-implementation-contract.json");
|
|
300
104
|
const parsedContract = frontendImplementationContractSchema.safeParse(contract);
|
|
301
105
|
if (!parsedContract.success) {
|
|
302
106
|
throw new FrontendReviewContextFailure("review-context-invalid-contract", "frontend review context invalid implementation contract");
|
|
303
107
|
}
|
|
304
|
-
// Authorized diff surface = deliverable files + every verification-target
|
|
305
|
-
// file the contract references. The writer legitimately writes VT test
|
|
306
|
-
// artifacts (behavior verification), and the reviewer must audit their
|
|
307
|
-
// diffs; scoping this to targets.files alone failed the baseline-overlap
|
|
308
|
-
// check on app.test.js (r19 extreme smoke).
|
|
309
|
-
const authorizedChangedPaths = [
|
|
310
|
-
...new Set([
|
|
311
|
-
...parsedContract.data.targets.files,
|
|
312
|
-
...parsedContract.data.verificationTargets.map((target) => target.file),
|
|
313
|
-
]),
|
|
314
|
-
];
|
|
315
108
|
const diff = await runFrontendWorktreeDiffGate({
|
|
316
109
|
...input,
|
|
317
|
-
authorizedChangedPaths,
|
|
110
|
+
authorizedChangedPaths: parsedContract.data.targets.files,
|
|
318
111
|
});
|
|
319
112
|
const verificationTrace = await readRequiredJson(input.runDir, "contracts/frontend-verification-trace.json");
|
|
320
|
-
const repairAssessment = await
|
|
321
|
-
const verifyFailure = await readOptionalVerifyFailure(input.runDir);
|
|
113
|
+
const repairAssessment = await readRequiredJson(input.runDir, "contracts/frontend-repair-assessment.json");
|
|
322
114
|
const lintAssessment = await readOptionalLintAssessment(input.runDir);
|
|
323
|
-
const cumulativeDiff = await captureCumulativeDiffContext({
|
|
324
|
-
runDir: input.runDir,
|
|
325
|
-
workspaceRoot: input.workspaceRoot,
|
|
326
|
-
diff,
|
|
327
|
-
});
|
|
328
115
|
assertReviewEvidence({ contract, verificationTrace, repairAssessment });
|
|
329
116
|
// Source fidelity ledger 核对(AC-008 / AC-FIDELITY-008):ledger 缺失/漂移即阻断;
|
|
330
117
|
// 同时把核对清单注入 review-context 供最终 reviewer 机械化核对。
|
|
331
118
|
const sourceFidelity = await buildReviewSourceFidelityChecklist(input, parsedContract.data);
|
|
332
|
-
const contractReviewArtifacts = await writeFrontendContractReviewArtifacts({
|
|
333
|
-
runDir: input.runDir,
|
|
334
|
-
outputDir: "contracts",
|
|
335
|
-
artifactName: "frontend-implementation-contract.json",
|
|
336
|
-
canonical: parsedContract.data,
|
|
337
|
-
canonicalArtifact: {
|
|
338
|
-
path: path.join(input.runDir, contractRelativePath),
|
|
339
|
-
sha256: createHash("sha256").update(contractRaw).digest("hex"),
|
|
340
|
-
bytes: Buffer.byteLength(contractRaw, "utf8"),
|
|
341
|
-
},
|
|
342
|
-
});
|
|
343
119
|
const payload = {
|
|
344
|
-
schemaVersion:
|
|
120
|
+
schemaVersion: 1,
|
|
345
121
|
schemaId: FRONTEND_REVIEW_CONTEXT_SCHEMA_ID,
|
|
346
|
-
|
|
347
|
-
path: contractRelativePath,
|
|
348
|
-
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID,
|
|
349
|
-
sha256: contractReviewArtifacts.index.contract.sha256,
|
|
350
|
-
bytes: contractReviewArtifacts.index.contract.bytes,
|
|
351
|
-
indexPath: contractReviewArtifacts.index.relativePath,
|
|
352
|
-
indexSha256: contractReviewArtifacts.index.artifactSha256,
|
|
353
|
-
capacityDiagnosticPath: contractReviewArtifacts.capacity.relativePath,
|
|
354
|
-
capacityDiagnosticSha256: contractReviewArtifacts.capacity.artifactSha256,
|
|
355
|
-
sections: contractReviewArtifacts.index.sections,
|
|
356
|
-
},
|
|
122
|
+
contract,
|
|
357
123
|
verificationTrace,
|
|
358
|
-
|
|
359
|
-
...(verifyFailure ? { verifyFailure } : {}),
|
|
124
|
+
repairAssessment,
|
|
360
125
|
...(lintAssessment ? { lintAssessment } : {}),
|
|
361
126
|
sourceFidelity,
|
|
362
127
|
diff: {
|
|
363
128
|
schemaId: diff.schemaId,
|
|
364
129
|
patchPath: diff.patchPath,
|
|
365
130
|
patchSha256: diff.patchSha256,
|
|
366
|
-
reviewSummaryPath: diff.reviewSummaryPath,
|
|
367
|
-
reviewIndexPath: diff.reviewIndexPath,
|
|
368
131
|
changedFiles: diff.changedFiles,
|
|
369
132
|
untrackedFiles: diff.untrackedFiles,
|
|
370
133
|
empty: diff.empty,
|
|
371
134
|
},
|
|
372
|
-
cumulativeDiff,
|
|
373
135
|
};
|
|
374
136
|
const artifactPath = await writeDagRunJsonArtifact(input.runDir, "contracts/frontend-review-context.json", payload);
|
|
375
|
-
return {
|
|
376
|
-
ok: true,
|
|
377
|
-
artifactPath,
|
|
378
|
-
diff,
|
|
379
|
-
contractRef: payload.contractRef,
|
|
380
|
-
capacity: contractReviewArtifacts.capacity,
|
|
381
|
-
};
|
|
137
|
+
return { ok: true, artifactPath, diff };
|
|
382
138
|
}
|
|
383
139
|
/**
|
|
384
140
|
* 最终 review 核对清单(机械化):ledger 缺失/漂移、canonical requirement 漏项、
|
|
@@ -437,8 +193,6 @@ export function formatFrontendReviewContextStdout(result) {
|
|
|
437
193
|
return [
|
|
438
194
|
"Frontend review context: pass",
|
|
439
195
|
`Artifact: ${result.artifactPath}`,
|
|
440
|
-
`Contract index: ${result.contractRef.indexPath}`,
|
|
441
|
-
`Capacity: ${result.capacity.overallLevel} (${result.capacity.relativePath}, non-blocking)`,
|
|
442
196
|
formatFrontendWorktreeDiffStdout(result.diff),
|
|
443
197
|
].join("\n");
|
|
444
198
|
}
|
|
@@ -49,236 +49,15 @@ function symbolEvidenceCandidates(symbol) {
|
|
|
49
49
|
return [symbol, describeTitle, "describe("];
|
|
50
50
|
return [symbol];
|
|
51
51
|
}
|
|
52
|
-
|
|
52
|
+
function isConfigurationVerificationFile(file) {
|
|
53
53
|
return /(?:^|\/)(?:package\.json|tsconfig(?:\.[^/]+)?\.json|(?:vite|webpack|rollup|docusaurus)\.config\.[cm]?[jt]s)$/.test(file.replace(/\\/g, "/"));
|
|
54
54
|
}
|
|
55
|
-
function isIdentifierStart(char) {
|
|
56
|
-
return char !== undefined && /[A-Za-z_$]/.test(char);
|
|
57
|
-
}
|
|
58
|
-
function isIdentifierPart(char) {
|
|
59
|
-
return char !== undefined && /[A-Za-z0-9_$]/.test(char);
|
|
60
|
-
}
|
|
61
|
-
function readQuotedString(content, start) {
|
|
62
|
-
const quote = content[start];
|
|
63
|
-
if (quote !== "'" && quote !== '"' && quote !== "`")
|
|
64
|
-
return null;
|
|
65
|
-
let value = "";
|
|
66
|
-
for (let index = start + 1; index < content.length; index += 1) {
|
|
67
|
-
const char = content[index];
|
|
68
|
-
if (char === "\\") {
|
|
69
|
-
const escaped = content[index + 1];
|
|
70
|
-
if (escaped === undefined)
|
|
71
|
-
return null;
|
|
72
|
-
value += escaped;
|
|
73
|
-
index += 1;
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (quote === "`" && char === "$" && content[index + 1] === "{") {
|
|
77
|
-
// Interpolated templates are not literal titles: their runtime value is
|
|
78
|
-
// not deterministic from source text alone.
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
if (char === quote)
|
|
82
|
-
return { end: index + 1, value };
|
|
83
|
-
value += char;
|
|
84
|
-
}
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
function skipQuotedSource(content, start) {
|
|
88
|
-
const quote = content[start];
|
|
89
|
-
for (let index = start + 1; index < content.length; index += 1) {
|
|
90
|
-
if (content[index] === "\\") {
|
|
91
|
-
index += 1;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
if (content[index] === quote)
|
|
95
|
-
return index + 1;
|
|
96
|
-
}
|
|
97
|
-
return content.length;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Small JS/TS lexer for test-title extraction. It intentionally emits only
|
|
101
|
-
* identifiers, string literals, and punctuation while skipping comments. This
|
|
102
|
-
* prevents prose in comments or ordinary strings from becoming trace evidence
|
|
103
|
-
* without adding a parser as a production dependency.
|
|
104
|
-
*/
|
|
105
|
-
function lexFrontendTestSource(content) {
|
|
106
|
-
const tokens = [];
|
|
107
|
-
let index = 0;
|
|
108
|
-
while (index < content.length) {
|
|
109
|
-
const char = content[index];
|
|
110
|
-
if (/\s/.test(char ?? "")) {
|
|
111
|
-
index += 1;
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
if (char === "/" && content[index + 1] === "/") {
|
|
115
|
-
index += 2;
|
|
116
|
-
while (index < content.length && content[index] !== "\n")
|
|
117
|
-
index += 1;
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (char === "/" && content[index + 1] === "*") {
|
|
121
|
-
const end = content.indexOf("*/", index + 2);
|
|
122
|
-
index = end < 0 ? content.length : end + 2;
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
const quoted = readQuotedString(content, index);
|
|
126
|
-
if (quoted) {
|
|
127
|
-
tokens.push({ type: "string", value: quoted.value });
|
|
128
|
-
index = quoted.end;
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
if (char === "'" || char === '"' || char === "`") {
|
|
132
|
-
// Unterminated or interpolated strings are skipped instead of being
|
|
133
|
-
// tokenized as executable source.
|
|
134
|
-
index = skipQuotedSource(content, index);
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
if (isIdentifierStart(char)) {
|
|
138
|
-
let end = index + 1;
|
|
139
|
-
while (isIdentifierPart(content[end]))
|
|
140
|
-
end += 1;
|
|
141
|
-
tokens.push({ type: "identifier", value: content.slice(index, end) });
|
|
142
|
-
index = end;
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
tokens.push({ type: "punctuation", value: char ?? "" });
|
|
146
|
-
index += 1;
|
|
147
|
-
}
|
|
148
|
-
return tokens;
|
|
149
|
-
}
|
|
150
|
-
/** Extract literal titles from real describe/it/test calls in JS/TS source. */
|
|
151
|
-
export function extractFrontendTestTitles(content) {
|
|
152
|
-
const tokens = lexFrontendTestSource(content);
|
|
153
|
-
const titles = [];
|
|
154
|
-
const configurationModifiers = new Set(["each", "runIf", "skipIf"]);
|
|
155
|
-
function closingParenIndex(openIndex) {
|
|
156
|
-
let depth = 0;
|
|
157
|
-
for (let cursor = openIndex; cursor < tokens.length; cursor += 1) {
|
|
158
|
-
if (tokens[cursor]?.type !== "punctuation")
|
|
159
|
-
continue;
|
|
160
|
-
if (tokens[cursor]?.value === "(")
|
|
161
|
-
depth += 1;
|
|
162
|
-
if (tokens[cursor]?.value === ")") {
|
|
163
|
-
depth -= 1;
|
|
164
|
-
if (depth === 0)
|
|
165
|
-
return cursor;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
for (let index = 0; index < tokens.length; index += 1) {
|
|
171
|
-
const token = tokens[index];
|
|
172
|
-
if (token?.type !== "identifier" ||
|
|
173
|
-
!["describe", "it", "test"].includes(token.value) ||
|
|
174
|
-
(tokens[index - 1]?.type === "punctuation" &&
|
|
175
|
-
tokens[index - 1]?.value === ".")) {
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
let cursor = index + 1;
|
|
179
|
-
let hasConfigurationModifier = false;
|
|
180
|
-
while (tokens[cursor]?.type === "punctuation" &&
|
|
181
|
-
tokens[cursor]?.value === "." &&
|
|
182
|
-
tokens[cursor + 1]?.type === "identifier") {
|
|
183
|
-
if (tokens[cursor + 1]?.type === "identifier" &&
|
|
184
|
-
configurationModifiers.has(tokens[cursor + 1].value)) {
|
|
185
|
-
hasConfigurationModifier = true;
|
|
186
|
-
}
|
|
187
|
-
cursor += 2;
|
|
188
|
-
}
|
|
189
|
-
if (tokens[cursor]?.type !== "punctuation" ||
|
|
190
|
-
tokens[cursor]?.value !== "(") {
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
if (hasConfigurationModifier) {
|
|
194
|
-
const close = closingParenIndex(cursor);
|
|
195
|
-
if (close === null ||
|
|
196
|
-
tokens[close + 1]?.type !== "punctuation" ||
|
|
197
|
-
tokens[close + 1]?.value !== "(") {
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
cursor = close + 1;
|
|
201
|
-
}
|
|
202
|
-
if (tokens[cursor + 1]?.type !== "string")
|
|
203
|
-
continue;
|
|
204
|
-
titles.push(tokens[cursor + 1].value);
|
|
205
|
-
}
|
|
206
|
-
return titles;
|
|
207
|
-
}
|
|
208
|
-
function traceKeyMatchesTitle(traceKey, title) {
|
|
209
|
-
const escaped = traceKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
210
|
-
return new RegExp(`(?:^|[^A-Za-z0-9_-])${escaped}(?:$|[^A-Za-z0-9_-])`).test(title);
|
|
211
|
-
}
|
|
212
|
-
function formatObservedTestTitles(titles) {
|
|
213
|
-
if (titles.length === 0)
|
|
214
|
-
return "(none)";
|
|
215
|
-
const limit = 8;
|
|
216
|
-
const preview = titles
|
|
217
|
-
.slice(0, limit)
|
|
218
|
-
.map((title) => title.length > 120 ? `${title.slice(0, 117)}...` : title)
|
|
219
|
-
.join(" | ");
|
|
220
|
-
return titles.length > limit
|
|
221
|
-
? `${preview} | ... (${titles.length - limit} more)`
|
|
222
|
-
: preview;
|
|
223
|
-
}
|
|
224
|
-
async function evaluateTargetFileTrace(input) {
|
|
225
|
-
const fileIssues = await assertFileAndSymbol({
|
|
226
|
-
workspaceRoot: input.workspaceRoot,
|
|
227
|
-
file: input.target.file,
|
|
228
|
-
});
|
|
229
|
-
if (fileIssues.length || input.target.type === "static") {
|
|
230
|
-
return { issues: fileIssues };
|
|
231
|
-
}
|
|
232
|
-
const absolute = path.resolve(input.workspaceRoot, input.target.file);
|
|
233
|
-
const content = await readFile(absolute, "utf8");
|
|
234
|
-
const observedTestTitles = extractFrontendTestTitles(content);
|
|
235
|
-
if (observedTestTitles.some((title) => traceKeyMatchesTitle(input.target.id, title))) {
|
|
236
|
-
return { issues: [], observedTestTitles };
|
|
237
|
-
}
|
|
238
|
-
const observed = formatObservedTestTitles(observedTestTitles);
|
|
239
|
-
return {
|
|
240
|
-
issues: [
|
|
241
|
-
`trace key not found in test title: ${input.target.id} in ${input.target.file}; observed test titles: ${observed}`,
|
|
242
|
-
],
|
|
243
|
-
observedTestTitles,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Lightweight writer-boundary check. It validates file existence and stable
|
|
248
|
-
* behavior trace tokens only; frozen commands remain the final trace gate's
|
|
249
|
-
* responsibility after command execution.
|
|
250
|
-
*/
|
|
251
|
-
export async function evaluateFrontendWriterTracePreflight(input) {
|
|
252
|
-
const targets = [];
|
|
253
|
-
const failures = [];
|
|
254
|
-
for (const target of input.contract.verificationTargets) {
|
|
255
|
-
const fileTrace = await evaluateTargetFileTrace({
|
|
256
|
-
workspaceRoot: input.workspaceRoot,
|
|
257
|
-
target,
|
|
258
|
-
});
|
|
259
|
-
for (const detail of fileTrace.issues) {
|
|
260
|
-
failures.push({ category: "coverage-gap", targetId: target.id, detail });
|
|
261
|
-
}
|
|
262
|
-
targets.push({
|
|
263
|
-
id: target.id,
|
|
264
|
-
commandLabel: target.commandLabel,
|
|
265
|
-
file: target.file,
|
|
266
|
-
...(target.symbol ? { symbol: target.symbol } : {}),
|
|
267
|
-
...(target.type !== "static" ? { traceKey: target.id } : {}),
|
|
268
|
-
status: fileTrace.issues.length ? "failed" : "ok",
|
|
269
|
-
matchedNodeIds: [],
|
|
270
|
-
issues: fileTrace.issues,
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
return { ok: failures.length === 0, targets, failures };
|
|
274
|
-
}
|
|
275
55
|
/**
|
|
276
56
|
* Pure verification-target evaluation shared by the trace gate and the repair
|
|
277
57
|
* assess gate. It derives, for each contract verification target, whether the
|
|
278
58
|
* target bound to frozen evidence (commandLabel owners), and whether the file
|
|
279
|
-
* and
|
|
280
|
-
*
|
|
281
|
-
* per-target `status`.
|
|
59
|
+
* and optional symbol exist in the workspace. It performs no writes and throws
|
|
60
|
+
* nothing: failures are returned as `hardIssues` plus per-target `status`.
|
|
282
61
|
*/
|
|
283
62
|
export async function evaluateVerificationTargets(input) {
|
|
284
63
|
const targets = [];
|
|
@@ -296,11 +75,17 @@ export async function evaluateVerificationTargets(input) {
|
|
|
296
75
|
if (!owners?.length) {
|
|
297
76
|
issues.push(`commandLabel not executed in current-run frozen evidence: ${target.commandLabel}`);
|
|
298
77
|
}
|
|
299
|
-
const
|
|
78
|
+
const fileIssues = await assertFileAndSymbol({
|
|
300
79
|
workspaceRoot: input.workspaceRoot,
|
|
301
|
-
target,
|
|
80
|
+
file: target.file,
|
|
81
|
+
// Configuration files prove that the command's entrypoint exists;
|
|
82
|
+
// they do not expose source symbols. LLMs sometimes derive a
|
|
83
|
+
// filename fragment such as "build" from tsconfig.build.json.
|
|
84
|
+
symbol: isConfigurationVerificationFile(target.file)
|
|
85
|
+
? undefined
|
|
86
|
+
: target.symbol,
|
|
302
87
|
});
|
|
303
|
-
issues.push(...
|
|
88
|
+
issues.push(...fileIssues);
|
|
304
89
|
const status = issues.length ? "failed" : "ok";
|
|
305
90
|
if (issues.length)
|
|
306
91
|
hardIssues.push(...issues.map((i) => `${target.id}: ${i}`));
|
|
@@ -309,7 +94,6 @@ export async function evaluateVerificationTargets(input) {
|
|
|
309
94
|
commandLabel: target.commandLabel,
|
|
310
95
|
file: target.file,
|
|
311
96
|
symbol: target.symbol,
|
|
312
|
-
...(target.type !== "static" ? { traceKey: target.id } : {}),
|
|
313
97
|
status,
|
|
314
98
|
matchedNodeIds: owners ?? [],
|
|
315
99
|
issues,
|
|
@@ -317,25 +101,6 @@ export async function evaluateVerificationTargets(input) {
|
|
|
317
101
|
}
|
|
318
102
|
return { targets, hardIssues };
|
|
319
103
|
}
|
|
320
|
-
/**
|
|
321
|
-
* Legacy deterministic symbol resolvability check retained by canonical
|
|
322
|
-
* normalization (`dropUnresolvedVerificationSymbols`). New plan tools do not
|
|
323
|
-
* expose symbol and runtime trace binding uses the stable target id. This
|
|
324
|
-
* helper still handles old `all describe blocks`, `describe('title')`, and
|
|
325
|
-
* slash-delimited labels so existing run artifacts normalize consistently.
|
|
326
|
-
*/
|
|
327
|
-
export function verificationSymbolMatchesContent(symbol, content) {
|
|
328
|
-
if (symbol.trim().toLowerCase() === "all describe blocks")
|
|
329
|
-
return content.includes("describe(");
|
|
330
|
-
const symbols = symbol.includes(" / ")
|
|
331
|
-
? symbol
|
|
332
|
-
.split(/[((]/, 1)[0]
|
|
333
|
-
.split("/")
|
|
334
|
-
.map((item) => item.trim())
|
|
335
|
-
.filter(Boolean)
|
|
336
|
-
: [symbol];
|
|
337
|
-
return symbols.every((item) => symbolEvidenceCandidates(item).some((candidate) => content.includes(candidate)));
|
|
338
|
-
}
|
|
339
104
|
async function assertFileAndSymbol(input) {
|
|
340
105
|
const issues = [];
|
|
341
106
|
const absolute = path.resolve(input.workspaceRoot, input.file);
|
|
@@ -359,8 +124,18 @@ async function assertFileAndSymbol(input) {
|
|
|
359
124
|
}
|
|
360
125
|
if (input.symbol) {
|
|
361
126
|
const content = await readFile(absolute, "utf8");
|
|
362
|
-
|
|
363
|
-
|
|
127
|
+
const symbols = input.symbol.includes(" / ")
|
|
128
|
+
? input.symbol
|
|
129
|
+
.split(/[((]/, 1)[0]
|
|
130
|
+
.split("/")
|
|
131
|
+
.map((symbol) => symbol.trim())
|
|
132
|
+
.filter(Boolean)
|
|
133
|
+
: [input.symbol];
|
|
134
|
+
for (const symbol of symbols) {
|
|
135
|
+
const matched = symbolEvidenceCandidates(symbol).some((candidate) => content.includes(candidate));
|
|
136
|
+
if (!matched) {
|
|
137
|
+
issues.push(`symbol not found: ${symbol} in ${input.file}`);
|
|
138
|
+
}
|
|
364
139
|
}
|
|
365
140
|
}
|
|
366
141
|
return issues;
|