@tea-agent/loop-agent 0.41.1 → 0.42.0-next.10
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 +397 -5
- 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 +75 -19
- package/dist/application/dag/run-dag.js +41 -0
- package/dist/application/task-lifecycle/advance.js +24 -5
- package/dist/application/task-lifecycle/observe.js +171 -17
- package/dist/application/task-lifecycle/plan-transitions.js +42 -7
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +19 -0
- package/dist/cli/program.js +29 -2
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-artifact.js +284 -0
- package/dist/commands/dag-context.js +184 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/dag-rerun-task.js +2 -0
- package/dist/commands/dag-rerun.js +206 -1
- package/dist/commands/init.js +27 -1
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +4013 -86
- package/dist/executors/pi-executor.js +15 -4
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/pi-sdk-executor.js +3 -0
- package/dist/executors/shell-executor.js +978 -188
- package/dist/executors/shell-write-guard.js +7 -0
- 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 +417 -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 +12 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +21 -0
- 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 +107 -6
- package/dist/task/contract/adopt.js +4 -0
- package/dist/task/contract/import-revision.js +4 -0
- package/dist/task/contract/project.js +3 -0
- package/dist/task/contract/schema.js +2 -1
- package/dist/task/frontend-project-capability.js +203 -20
- package/dist/task/runtime.js +5 -2
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +64 -25
- package/dist/task/source-prepare/prepare.js +126 -21
- package/dist/task/source-prepare/semantic-intake.js +6 -2
- package/dist/task/source-references.js +22 -1
- 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/model-resolver.js +7 -4
- package/dist/worker/console/chat/pi-runtime.js +86 -5
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +136 -53
- package/dist/worker/console/chat/sdd-data-alignment.js +222 -0
- package/dist/worker/console/chat/session-catalog.js +2 -0
- package/dist/worker/console/chat/session-store.js +43 -1
- package/dist/worker/console/chat/shortcuts.js +18 -2
- 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/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/human-gate-token.js +1 -1
- package/dist/worker/console/index.js +6 -3
- 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 +273 -10
- 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 +57 -0
- package/dist/worker/console/pi-readiness.js +3 -2
- package/dist/worker/console/routes.js +637 -8
- package/dist/worker/console/security.js +35 -0
- package/dist/worker/console/server.js +29 -4
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-D7oU983t.js → abnfDiagram-N423BO3Z-CcS17TBr.js} +1 -1
- package/dist/worker/console/static/assets/{arc-BrKSRdFS.js → arc-COptKq2S.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CiXV7L56.js → architectureDiagram-T3A2C74G-h4LKMHjP.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D_Hs_gIG.js → blockDiagram-VBNYF7ZC-COA1MOH0.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DbM-Lqqh.js → c4Diagram-5PPSVZJV-DJUf0QPm.js} +1 -1
- package/dist/worker/console/static/assets/channel-DdBCaOJ6.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D1M8uqJw.js → chunk-2GRJ4B5K-mtWfKrUX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-4D3RFHT6.js → chunk-2Q5K7J3B-B8pXsxDQ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CdwpY_n9.js → chunk-5RXB4S5H-ipKzByl1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BLeLZ9PG.js → chunk-5VM5RSS4-DYi3Ald_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-CeRNEMvc.js → chunk-6Q2QTUOP-DQtGYoty.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CjkHP8Vy.js → chunk-GF5L2VYU-BU0qS2YV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-B54ZUQY-.js → chunk-JWPE2WC7-DVH9dIGE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-4bKaMVmx.js → chunk-KBJHAD2P-Dt60SmgM.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-Cyjfznu_.js → chunk-RYQCIY6F-BoaUGuEY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CDN-zGFV.js → chunk-XXDRQBXY-_WHFDTp2.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DZFra1GO.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DZFra1GO.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-HxJmz411.js → cose-bilkent-JH36ORCC-BxkbTIRd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DtSt5K2w.js → cynefin-VYW2F7L2-Bf2UVnoG.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-VtkSpRmG.js → cynefinDiagram-MW4NZA55-Bo23q1J_.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bj4DT4du.js → dagre-VZM6K2ZE-Dd_UU49i.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Bpuk6BmP.js → diagram-7IWD3JNH-ebUa1a9y.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BHYGv_yO.js → diagram-B4RE2ZJO-BucthU8r.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DEx3WwnT.js → diagram-LBJQPF4R-BgqNpAkm.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-Cj0dS5e8.js → diagram-Q27KOJAE-DC4q5NGa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-B97h8l21.js → diagram-UB23O5K3-CBqCaMeb.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DMSFAhBd.js → ebnfDiagram-BXEA7PRR-CGtnbQ3-.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DhVOCbt6.js → erDiagram-JOGREHBK-CG3LUao5.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-GlakITWL.js → flowDiagram-UKHOOZJN-D2ZVgoFS.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-VxPpu9eQ.js → ganttDiagram-PKOTCBZU-DoYFZiKt.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bs_e5DRT.js → gitGraphDiagram-DS77QQ5N-CKoP1s6j.js} +1 -1
- package/dist/worker/console/static/assets/index-CAZ2fC_X.css +1 -0
- package/dist/worker/console/static/assets/index-vbTcFnFs.js +449 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BXYuU4Uw.js → infoDiagram-6WML65LV-Duofv8p2.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C34erCbU.js → ishikawaDiagram-WSZJBQD7-D2nlCkA1.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DZCAiOU3.js → journeyDiagram-NVQOT4AX-Dd4IHdus.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C9gaTdUm.js → kanban-definition-27J2QSJJ-Bi20AOdt.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CvI0Z17U.js → linear-D59YJ9kB.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DMMSIx7j.js → mermaid.core-Bl13LpNa.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BKKP0EO6.js → mindmap-definition-FAOFIHXS-C6DLP5eY.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-C3LiFbl5.js → pegDiagram-VL7TDLO6-BjO4cy64.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-Bb3Om2Dz.js → pieDiagram-7S7Q4E2Y-D7i2qZTG.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DNthMjzc.js → quadrantDiagram-CIZ2JOQS-Dx8o_h0y.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-miFsr4Tv.js → railroadDiagram-AXF67PYL-B55orI_t.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-yXUncmn6.js → requirementDiagram-LRYGKXZP-zWaehp6f.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DLeWKLak.js → sankeyDiagram-W5VNT64P-CcA-pjvD.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CI3he1R3.js → sequenceDiagram-SI44F4Z6-BOFbzNHI.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B7jfDkS5.js → sizeCapture-X5ZJPWSS-BjAejah1.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BmrZod7J.js → stateDiagram-OKZ733FA-DXYwxJwZ.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Clg3V9t1.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CDOlHVSY.js → swimlanes-SLNWSIFB-OYiOch8n.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DX1dxAAW.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cz-XX1gy.js → timeline-definition-Z64GVDOM-cZfH3nmU.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-caV4cI8C.js → vennDiagram-T6HMQDX7-BDE7b3E1.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-dImPlnIO.js → wardleyDiagram-T6FBY63Y-kyyGy9WJ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CtRYVJcn.js → xychartDiagram-ELKLHX3M-CJj6VTog.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -5
- package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +258 -23
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +112 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -4
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
- package/dist/worker/console/static-src/shell/console-update-reload.js +52 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
- package/dist/worker/console/workspace-context.js +142 -2
- package/dist/worker/console/workspace-registry.js +2 -2
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observe/health.js +1 -1
- package/dist/worker/observe/node-transparency.js +572 -0
- package/dist/worker/observe/routes.js +55 -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-helpers.js +4 -2
- package/dist/worker/observe/static/dag-node-purpose.js +5 -0
- package/dist/worker/observe/static/inspect-workspace.js +23 -0
- package/dist/worker/observe/static/kpi.js +2 -2
- package/dist/worker/observe/static/operator-chrome.d.ts +10 -2
- package/dist/worker/observe/static/operator-chrome.js +37 -23
- package/dist/worker/observe/static/relations.js +2 -2
- package/dist/worker/observe/static/router.d.ts +12 -1
- package/dist/worker/observe/static/router.js +48 -6
- package/dist/worker/observe/static/run-processing.js +4 -2
- package/dist/worker/observe/static/shell-chrome.js +2 -2
- package/dist/worker/observe/static/state.js +8 -1
- package/dist/worker/observe/static/styles.css +1092 -86
- package/dist/worker/observe/static/views/dag-inspector.js +1228 -64
- package/dist/worker/observe/static/views/dag.js +10 -2
- package/dist/worker/observe/static/views/dags.js +3 -1
- package/dist/worker/observe/static/views/dashboard.js +14 -6
- package/dist/worker/observe/static/views/pool.js +7 -2
- package/dist/worker/observe/static/views/run.js +12 -3
- package/dist/worker/observe/static/views/session-timeline.js +142 -18
- package/dist/worker/observe/static/views/task.js +7 -2
- 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 +215 -28
- package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
- package/dist/workflows/dag/backend-test-plan-protocol.js +106 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +172 -25
- package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
- package/dist/workflows/dag/backend-test-writer-completeness.js +104 -12
- 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 +1 -2
- package/dist/workflows/dag/dag-retry-schema.js +138 -0
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +1237 -192
- package/dist/workflows/dag/frontend-plan-render.js +2 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +256 -350
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +226 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +21 -10
- package/dist/workflows/dag/frontend-recovery-run.js +166 -34
- package/dist/workflows/dag/frontend-repair.js +1 -432
- package/dist/workflows/dag/frontend-review-context.js +261 -15
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +941 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +419 -0
- package/dist/workflows/dag/frontend-shape.js +435 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +252 -25
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +319 -0
- package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
- package/dist/workflows/dag/frontend-writer-status.js +256 -0
- package/dist/workflows/dag/init-hybrid.js +1174 -562
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +940 -31
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +99 -7
- package/dist/workflows/dag/recovery-lease.js +170 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +315 -1
- package/dist/workflows/dag/rerun-plan.js +568 -3
- package/dist/workflows/dag/rerun-run.js +642 -18
- package/dist/workflows/dag/rerun-task.js +251 -13
- package/dist/workflows/dag/retry-policy.js +219 -104
- package/dist/workflows/dag/runner.js +596 -121
- package/dist/workflows/dag/scheduler.js +133 -20
- package/dist/workflows/dag/skill-snapshot.js +17 -0
- package/dist/workflows/dag/types.js +426 -22
- package/dist/workflows/dag/validate.js +28 -12
- package/docs/architecture/runtime-boundaries.md +6 -6
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/examples/README.md +5 -0
- package/docs/init-surface.manifest.json +30 -12
- package/docs/skills/vetted-skill-registry.md +4 -2
- package/docs/templates/README.md +2 -0
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +33 -1
- package/docs/templates/backend-test-dag.json +23 -20
- package/docs/templates/frontend-implementation-contract.schema.json +4 -1
- package/docs/templates/frontend-implementation-dag.json +89 -0
- package/docs/templates/product-line/task.yaml +1 -1
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +1 -1
- package/package.json +6 -3
- package/skills/frontend-bounded-implement/SKILL.md +15 -14
- package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
- package/skills/frontend-contract/SKILL.md +23 -0
- package/skills/frontend-contract/references/contract-protocol.md +34 -0
- package/skills/frontend-design-review/SKILL.md +22 -41
- package/skills/frontend-plan/SKILL.md +26 -0
- package/skills/frontend-plan/references/decision-contract.md +37 -0
- package/skills/frontend-plan/references/design-decisions.md +17 -0
- package/skills/frontend-review/SKILL.md +20 -15
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/frontend-scout/SKILL.md +25 -0
- package/skills/frontend-scout/references/design-evidence.md +16 -0
- package/skills/frontend-scout/references/scout-evidence.md +23 -0
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +7 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/operation-store.js +0 -169
- package/dist/worker/console/static/assets/channel-D1xajGpe.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BmuGTfRr.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BmuGTfRr.js +0 -1
- package/dist/worker/console/static/assets/index-BLzcAykx.js +0 -451
- package/dist/worker/console/static/assets/index-Cs9JUSPs.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-D9ykKEWP.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BvPOXALT.js +0 -8
- package/skills/frontend-implementation/SKILL.md +0 -52
- package/skills/frontend-implementation/references/code-standards.md +0 -33
- package/skills/frontend-implementation/references/design-spec.md +0 -56
- package/skills/frontend-implementation/references/node-contracts.md +0 -31
- /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
|
@@ -59,6 +59,7 @@ const CLOSED_SET = new Set([
|
|
|
59
59
|
"null",
|
|
60
60
|
"min-1",
|
|
61
61
|
"min",
|
|
62
|
+
"max-1",
|
|
62
63
|
"max",
|
|
63
64
|
"max+1",
|
|
64
65
|
"pattern-invalid",
|
|
@@ -155,14 +156,19 @@ function parseScenarioBoundToken(raw) {
|
|
|
155
156
|
function parseScenarioIntentLine(line) {
|
|
156
157
|
// Target may contain commas (body.title,body.status). Intent may contain `|` / spaces.
|
|
157
158
|
// Stop field values only at known trailing keys or end-of-line.
|
|
158
|
-
const
|
|
159
|
+
const unfenced = line
|
|
160
|
+
.trim()
|
|
161
|
+
.replace(/^[-*+]\s+/, "")
|
|
162
|
+
.replace(/^`(.+)`$/, "$1")
|
|
163
|
+
.trim();
|
|
164
|
+
const precise = /场景意图\s*[::]\s*(TP-[A-Z0-9-]+)\s*[|;]\s*operation\s*=\s*([^;\n]+?)\s*[|;]\s*target\s*=\s*([^;\n]+?)\s*[|;]\s*intent\s*=\s*([^;\n]+?)(?:\s*[|;]\s*bound\s*=\s*([^\s;`|\n]+))?(?:\s*[|;]\s*example\s*=\s*([^;\n]*))?(?:\s*[|;]\s*expectedCode\s*=\s*[^;\n]+)?\s*$/i.exec(unfenced);
|
|
159
165
|
if (precise) {
|
|
160
166
|
return {
|
|
161
167
|
tpId: precise[1],
|
|
162
168
|
rawIntent: (precise[4] ?? "unknown").trim(),
|
|
163
169
|
target: precise[3]?.trim(),
|
|
164
170
|
bound: parseScenarioBoundToken(precise[5]),
|
|
165
|
-
example: precise[6]
|
|
171
|
+
example: precise[6] === undefined ? undefined : precise[6].trim(),
|
|
166
172
|
legacy: false,
|
|
167
173
|
};
|
|
168
174
|
}
|
|
@@ -192,6 +198,53 @@ function resolveScenarioIntentField(target, field) {
|
|
|
192
198
|
target.split(/\s*,\s*/)[0];
|
|
193
199
|
return preferred?.split(".").at(-1);
|
|
194
200
|
}
|
|
201
|
+
function normalizeLengthIntentAlias(rawIntent) {
|
|
202
|
+
const lower = rawIntent.trim().toLowerCase();
|
|
203
|
+
if (lower === "max-1" || lower === "maxminus1" || lower === "max_minus_1") {
|
|
204
|
+
return "max-1";
|
|
205
|
+
}
|
|
206
|
+
if (lower === "min-1" || lower === "minminus1" || lower === "min_minus_1") {
|
|
207
|
+
return "min-1";
|
|
208
|
+
}
|
|
209
|
+
return lower;
|
|
210
|
+
}
|
|
211
|
+
function proseLengthBound(haystack, field) {
|
|
212
|
+
const fieldAlt = field ? field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") : undefined;
|
|
213
|
+
const scoped = fieldAlt ? new RegExp(`${fieldAlt}[^\\n]{0,160}`, "ig") : undefined;
|
|
214
|
+
const windows = [];
|
|
215
|
+
if (scoped) {
|
|
216
|
+
for (const match of haystack.matchAll(scoped)) {
|
|
217
|
+
windows.push(match[0] ?? "");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
windows.push(haystack);
|
|
221
|
+
let max;
|
|
222
|
+
let min;
|
|
223
|
+
const proseMaxRe = /(\d{1,4})\s*个字符的最大/i;
|
|
224
|
+
for (const window of windows) {
|
|
225
|
+
const proseMax = proseMaxRe.exec(window);
|
|
226
|
+
if (proseMax && max === undefined)
|
|
227
|
+
max = Number(proseMax[1]);
|
|
228
|
+
const nums = [...window.matchAll(/(?<![+\-])\d{1,4}/g)]
|
|
229
|
+
.map((item) => Number(item[0]))
|
|
230
|
+
.filter((item) => Number.isFinite(item));
|
|
231
|
+
for (let i = 0; i + 2 < nums.length; i += 1) {
|
|
232
|
+
const a = nums[i];
|
|
233
|
+
const b = nums[i + 1];
|
|
234
|
+
const c = nums[i + 2];
|
|
235
|
+
// Prefer n-1,n,n+1 around a documented max (100/2000), not 0,1,99 or 99,100,101's first two plus leftover.
|
|
236
|
+
if (b === a + 1 && c === b + 1 && b >= 10) {
|
|
237
|
+
max ??= b;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (nums.includes(0) && nums.includes(1))
|
|
242
|
+
min ??= 1;
|
|
243
|
+
if (max !== undefined && min !== undefined)
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
return { max, min };
|
|
247
|
+
}
|
|
195
248
|
function resolveCustomLiteralIntent(rawIntent, field) {
|
|
196
249
|
const lower = rawIntent.toLowerCase();
|
|
197
250
|
if (!lower.startsWith("custom-literal:")) {
|
|
@@ -250,7 +303,7 @@ export function inferScenarioParamIntent(input) {
|
|
|
250
303
|
const field = resolveScenarioIntentField(machine.target, machine.field);
|
|
251
304
|
const rawIntent = machine.rawIntent.trim();
|
|
252
305
|
const example = machine.example;
|
|
253
|
-
const lower = rawIntent
|
|
306
|
+
const lower = normalizeLengthIntentAlias(rawIntent);
|
|
254
307
|
let normalizedIntent = lower === "nominal-operation" ? "nominal" : lower;
|
|
255
308
|
// Writers sometimes mark a short "typical" length sample as min/max with bound=LEN<nominal>.
|
|
256
309
|
// Treat that as nominal so a short helper/string is not forced to equal maxLength.
|
|
@@ -273,16 +326,16 @@ export function inferScenarioParamIntent(input) {
|
|
|
273
326
|
new RegExp(`(?:minLength|min(?:imum)?|下限|最小长度)\\s*[=::]?\\s*(\\d{1,4})[^\\n]{0,80}?${fieldEsc}`, "i").exec(haystack) ??
|
|
274
327
|
/(?:minLength|min(?:imum)?|下限|最小长度)\s*[=::]?\s*(\d{1,4})/i.exec(haystack)
|
|
275
328
|
: /(?:minLength|min(?:imum)?|下限|最小长度)\s*[=::]?\s*(\d{1,4})/i.exec(haystack);
|
|
329
|
+
const proseBound = proseLengthBound(haystack, field);
|
|
276
330
|
const prefersMinBound = normalizedIntent === "min" ||
|
|
277
331
|
normalizedIntent === "min-1" ||
|
|
278
332
|
/(?:MIN|LEN1|LENGTH-?1|最短|最小)/i.test(tpId);
|
|
279
|
-
const boundFromBody = prefersMinBound
|
|
280
|
-
? minBoundFromBody ?? maxBoundFromBody
|
|
281
|
-
: maxBoundFromBody ?? minBoundFromBody;
|
|
333
|
+
const boundFromBody = prefersMinBound ? minBoundFromBody : maxBoundFromBody ?? minBoundFromBody;
|
|
282
334
|
const boundFromTp = /(?:LEN(?:GTH)?|长度)[_-]?(\d{1,5})(?:[_-]|$)/i.exec(tpId) ??
|
|
283
|
-
/(?:MIN|MAX)[_-]
|
|
335
|
+
/(?:MIN|MAX)[_-](\d{2,5})(?:[_-]|$)/i.exec(tpId);
|
|
284
336
|
const lengthIntent = normalizedIntent === "min" ||
|
|
285
337
|
normalizedIntent === "min-1" ||
|
|
338
|
+
normalizedIntent === "max-1" ||
|
|
286
339
|
normalizedIntent === "max" ||
|
|
287
340
|
normalizedIntent === "max+1";
|
|
288
341
|
// Length intents: explicit machine bound >0, then TP-encoded LEN1/LEN100,
|
|
@@ -294,13 +347,35 @@ export function inferScenarioParamIntent(input) {
|
|
|
294
347
|
: normalizedIntent === "max+1"
|
|
295
348
|
? Math.max(0, boundFromTpNumber - 1)
|
|
296
349
|
: boundFromTpNumber;
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
350
|
+
const boundFromProse = prefersMinBound ? proseBound.min : proseBound.max;
|
|
351
|
+
const offsetIntent = normalizedIntent === "max-1" ||
|
|
352
|
+
normalizedIntent === "max+1" ||
|
|
353
|
+
normalizedIntent === "min-1";
|
|
354
|
+
const contractBound = boundFromBody
|
|
355
|
+
? Number(boundFromBody[1])
|
|
356
|
+
: boundFromProse !== undefined && boundFromProse >= 0
|
|
357
|
+
? boundFromProse
|
|
358
|
+
: undefined;
|
|
359
|
+
const machineBoundPositive = typeof machine.bound === "number" && Number.isFinite(machine.bound) && machine.bound > 0;
|
|
360
|
+
const minLengthFromBody = prefersMinBound && minBoundFromBody ? Number(minBoundFromBody[1]) : undefined;
|
|
361
|
+
const machineBoundZeroMin = prefersMinBound &&
|
|
362
|
+
machine.bound === 0 &&
|
|
363
|
+
minLengthFromBody === undefined &&
|
|
364
|
+
boundFromTpNormalized === undefined;
|
|
365
|
+
const bound = offsetIntent && contractBound !== undefined && contractBound > 0
|
|
366
|
+
? contractBound
|
|
367
|
+
: machineBoundPositive && !offsetIntent
|
|
368
|
+
? machine.bound
|
|
369
|
+
: lengthIntent && boundFromTpNormalized !== undefined
|
|
370
|
+
? boundFromTpNormalized
|
|
371
|
+
: minLengthFromBody !== undefined && minLengthFromBody >= 0
|
|
372
|
+
? minLengthFromBody
|
|
373
|
+
: machineBoundZeroMin
|
|
374
|
+
? 0
|
|
375
|
+
: contractBound !== undefined &&
|
|
376
|
+
(prefersMinBound ? contractBound >= 0 : contractBound > 0)
|
|
377
|
+
? contractBound
|
|
378
|
+
: undefined;
|
|
304
379
|
if (rawIntent.toLowerCase().startsWith("custom-literal:")) {
|
|
305
380
|
const resolved = resolveCustomLiteralIntent(rawIntent, field);
|
|
306
381
|
const rawLiteral = rawIntent.slice(rawIntent.indexOf(":") + 1).trim();
|
|
@@ -387,13 +462,15 @@ export function inferScenarioParamIntent(input) {
|
|
|
387
462
|
const boundMatch = fieldScopedBound ??
|
|
388
463
|
/(?:maxLength|max(?:imum)?|上限|最大长度)\s*[=::]?\s*(\d{1,4})/i.exec(`${caseBody}\n${dataSection}`) ??
|
|
389
464
|
/(?:minLength|min(?:imum)?|下限|最小长度)\s*[=::]?\s*(\d{1,4})/i.exec(`${caseBody}\n${dataSection}`);
|
|
390
|
-
const
|
|
465
|
+
const proseBound = proseLengthBound(`${caseBody}
|
|
466
|
+
${dataSection}`, field);
|
|
467
|
+
const bound = boundMatch ? Number(boundMatch[1]) : proseBound.max ?? proseBound.min;
|
|
391
468
|
const exampleMatch = /(?:反例|invalid example|example)\s*[=::]\s*[`"]?([^`"\n]+)[`"]?/i.exec(caseBody);
|
|
392
469
|
const example = exampleMatch?.[1]?.trim();
|
|
393
470
|
if (/EMPTY|空白|空串|空字符串/.test(upper) || /空字符串|空串|empty string/i.test(caseBody)) {
|
|
394
471
|
return { intent: "empty", field, bound, example, intentSource: "tp-fallback" };
|
|
395
472
|
}
|
|
396
|
-
if (/MISSING|缺省|缺失|省略/.test(upper) || /缺少字段|缺失字段|missing field/i.test(caseBody)) {
|
|
473
|
+
if (/MISSING|OMITTED|\bOMIT\b|缺省|缺失|省略/.test(upper) || /缺少字段|缺失字段|missing field/i.test(caseBody)) {
|
|
397
474
|
return { intent: "missing", field, bound, example, intentSource: "tp-fallback" };
|
|
398
475
|
}
|
|
399
476
|
if (/\bNULL\b|空值/.test(upper) || /\bnull\b/i.test(caseBody)) {
|
|
@@ -402,6 +479,9 @@ export function inferScenarioParamIntent(input) {
|
|
|
402
479
|
if (/MAX[-_]?PLUS[-_]?1|MAX\+1|超长|越界|LENGTH[-_]?OVER|OVER[-_]?LENGTH|OVER/.test(upper)) {
|
|
403
480
|
return { intent: "max+1", field, bound, example, intentSource: "tp-fallback" };
|
|
404
481
|
}
|
|
482
|
+
if (/MAX[-_]1(?:$|[-_])|max-1/i.test(upper)) {
|
|
483
|
+
return { intent: "max-1", field, bound, example, intentSource: "tp-fallback" };
|
|
484
|
+
}
|
|
405
485
|
if (/\bMAX\b|最大/.test(upper)) {
|
|
406
486
|
return { intent: "max", field, bound, example, intentSource: "tp-fallback" };
|
|
407
487
|
}
|
|
@@ -425,7 +505,7 @@ export function inferScenarioParamIntent(input) {
|
|
|
425
505
|
}
|
|
426
506
|
return { intent: "unknown", field, bound, example, intentSource: "tp-fallback" };
|
|
427
507
|
}
|
|
428
|
-
const OMIT_SENTINEL_RE = /^(?:_OMIT|OMIT|MISSING|Ellipsis|\.\.\.)\b/;
|
|
508
|
+
const OMIT_SENTINEL_RE = /^(?:_OMIT|_MISSING|OMIT|MISSING|Ellipsis|\.\.\.)\b/;
|
|
429
509
|
const HTTP_EXPECTED_CODE_RE = /^(?:2xx|3xx|4xx|5xx|[1-5]\d{2}|HTTP_[1-5]\d{2})$/i;
|
|
430
510
|
/** Semantic custom-literal tokens that describe a value class, not a raw string. */
|
|
431
511
|
function isWhitespaceSemanticLiteral(token) {
|
|
@@ -774,6 +854,10 @@ export function observeParamFeatures(block, field, constants, parameterNames) {
|
|
|
774
854
|
(/["'][A-Za-z_][A-Za-z0-9_]*["']\s*:/.test(dictText) ||
|
|
775
855
|
/(?:payload|body|data|json)\s*=/.test(block));
|
|
776
856
|
if (dictLooksLikePayload && !field) {
|
|
857
|
+
const emptyField = /["']([A-Za-z_][A-Za-z0-9_.]*)["']\s*:\s*(?:""|'')/.exec(dictText);
|
|
858
|
+
if (emptyField?.[1]) {
|
|
859
|
+
return { kind: "empty-string", text: '""', length: 0, literal: "" };
|
|
860
|
+
}
|
|
777
861
|
return { kind: "dict", text: dictText.replace(/\s+/g, " ").slice(0, 160) };
|
|
778
862
|
}
|
|
779
863
|
if (dictLooksLikePayload && field) {
|
|
@@ -803,7 +887,7 @@ export function observeParamFeatures(block, field, constants, parameterNames) {
|
|
|
803
887
|
};
|
|
804
888
|
}
|
|
805
889
|
// Bare omit sentinel before id=...
|
|
806
|
-
if (/pytest\.param\s*\(\s*(?:_OMIT|OMIT|MISSING|\.\.\.|Ellipsis)\s*(?:,|\)|$)/.test(block)) {
|
|
890
|
+
if (/pytest\.param\s*\(\s*(?:_OMIT|_MISSING|OMIT|MISSING|\.\.\.|Ellipsis)\s*(?:,|\)|$)/.test(block)) {
|
|
807
891
|
return {
|
|
808
892
|
kind: "missing-key",
|
|
809
893
|
text: field ? `missing:${field}` : "missing-key",
|
|
@@ -865,7 +949,7 @@ export function observeParamFeatures(block, field, constants, parameterNames) {
|
|
|
865
949
|
if (paramOpen) {
|
|
866
950
|
const afterOpen = block.slice((paramOpen.index ?? 0) + paramOpen[0].length);
|
|
867
951
|
const positionalTokens = [];
|
|
868
|
-
const tokenRe = /\s*(_OMIT|OMIT|MISSING|Ellipsis|\.\.\.|None|True|False|-?\d+(?:\.\d+)?|"""[\s\S]*?"""|\'\'\'[\s\S]*?\'\'\'|(?:["'][^"'\\]["']\s*\*\s*(?:\d{1,5}|[A-Za-z_][A-Za-z0-9_]*))|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[A-Za-z_][A-Za-z0-9_]*)\s*(?:,|\)|$)/g;
|
|
952
|
+
const tokenRe = /\s*(_OMIT|_MISSING|OMIT|MISSING|Ellipsis|\.\.\.|None|True|False|-?\d+(?:\.\d+)?|"""[\s\S]*?"""|\'\'\'[\s\S]*?\'\'\'|(?:["'][^"'\\]["']\s*\*\s*(?:\d{1,5}|[A-Za-z_][A-Za-z0-9_]*))|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[A-Za-z_][A-Za-z0-9_]*)\s*(?:,|\)|$)/g;
|
|
869
953
|
let match;
|
|
870
954
|
while ((match = tokenRe.exec(afterOpen)) !== null) {
|
|
871
955
|
const between = afterOpen.slice(0, match.index);
|
|
@@ -1008,12 +1092,25 @@ function compareIntent(intent, observed, bound, example, field) {
|
|
|
1008
1092
|
return "MATCH";
|
|
1009
1093
|
return len !== undefined ? "MISMATCH" : "UNDETERMINED";
|
|
1010
1094
|
}
|
|
1095
|
+
case "max-1":
|
|
1096
|
+
if (bound === undefined)
|
|
1097
|
+
return "UNDETERMINED";
|
|
1098
|
+
{
|
|
1099
|
+
const expectedMax1 = Math.max(0, bound - 1);
|
|
1100
|
+
const len = observedLengthValue(observed);
|
|
1101
|
+
if (len === expectedMax1)
|
|
1102
|
+
return "MATCH";
|
|
1103
|
+
return len !== undefined ? "MISMATCH" : "UNDETERMINED";
|
|
1104
|
+
}
|
|
1011
1105
|
case "min":
|
|
1012
1106
|
if (bound === undefined)
|
|
1013
1107
|
return "UNDETERMINED";
|
|
1014
1108
|
{
|
|
1015
1109
|
if (isMinLengthHelper(observed))
|
|
1016
1110
|
return "MATCH";
|
|
1111
|
+
// Optional min=0 may omit the key instead of sending "".
|
|
1112
|
+
if (bound === 0 && observed.kind === "missing-key")
|
|
1113
|
+
return "MATCH";
|
|
1017
1114
|
const len = observedLengthValue(observed);
|
|
1018
1115
|
if (len === bound)
|
|
1019
1116
|
return "MATCH";
|
|
@@ -1116,7 +1213,11 @@ function compareIntent(intent, observed, bound, example, field) {
|
|
|
1116
1213
|
? "UNDETERMINED"
|
|
1117
1214
|
: "MISMATCH";
|
|
1118
1215
|
}
|
|
1119
|
-
|
|
1216
|
+
const comparableObserved = observed.kind === "boolean"
|
|
1217
|
+
? (observed.literal === "True" ? "true" : observed.literal === "False" ? "false" : literal)
|
|
1218
|
+
: literal;
|
|
1219
|
+
const comparableExpected = expected === "True" ? "true" : expected === "False" ? "false" : expected;
|
|
1220
|
+
return comparableObserved === comparableExpected
|
|
1120
1221
|
? "MATCH"
|
|
1121
1222
|
: observed.kind === "unknown"
|
|
1122
1223
|
? "UNDETERMINED"
|
|
@@ -1163,6 +1264,10 @@ function suggestedFixFor(intent, field, bound, example) {
|
|
|
1163
1264
|
return field && bound !== undefined
|
|
1164
1265
|
? `set ${field}="x"*${bound + 1}`
|
|
1165
1266
|
: undefined;
|
|
1267
|
+
case "max-1":
|
|
1268
|
+
return field && bound !== undefined
|
|
1269
|
+
? `set ${field}="x"*${Math.max(0, bound - 1)}`
|
|
1270
|
+
: undefined;
|
|
1166
1271
|
case "min":
|
|
1167
1272
|
return field && bound !== undefined
|
|
1168
1273
|
? `set ${field}="x"*${bound}`
|
|
@@ -1207,6 +1312,12 @@ function boundedScenarioText(value, logicalLength) {
|
|
|
1207
1312
|
const suffix = `<truncated length=${logicalLength ?? value.length}>`;
|
|
1208
1313
|
return `${value.slice(0, Math.max(0, MAX_PERSISTED_SCENARIO_TEXT - suffix.length - 3))}...${suffix}`;
|
|
1209
1314
|
}
|
|
1315
|
+
/** Facts schema forbids negative lengths. Internal helper sentinels stay in-memory only. */
|
|
1316
|
+
function persistedObservedLength(length) {
|
|
1317
|
+
return typeof length === "number" && Number.isFinite(length) && length >= 0
|
|
1318
|
+
? Math.trunc(length)
|
|
1319
|
+
: undefined;
|
|
1320
|
+
}
|
|
1210
1321
|
function localPythonFunctionRegion(source, functionName) {
|
|
1211
1322
|
const escaped = functionName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1212
1323
|
const startMatch = new RegExp(`^def\\s+${escaped}\\s*\\(`, "m").exec(source);
|
|
@@ -1365,6 +1476,32 @@ function hasSafeSameModulePayloadHelper(source, block, parameterNames) {
|
|
|
1365
1476
|
return false;
|
|
1366
1477
|
return Boolean(safeSameModuleDictHelper(source, call.name));
|
|
1367
1478
|
}
|
|
1479
|
+
function observeBranchPayloadField(input) {
|
|
1480
|
+
const region = scenarioFunctionRegion(input.source, input.tpId);
|
|
1481
|
+
if (!region || !input.block)
|
|
1482
|
+
return undefined;
|
|
1483
|
+
const args = extractPytestParamTopLevelArgs(input.block);
|
|
1484
|
+
const escapedField = input.field.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&");
|
|
1485
|
+
for (let index = 0; index < input.parameterNames.length; index += 1) {
|
|
1486
|
+
const parameter = input.parameterNames[index];
|
|
1487
|
+
const raw = args[index]?.trim();
|
|
1488
|
+
if (!raw || !/^['\"](?:[^'\"\\\\]|\\\\.)*['\"]$/.test(raw))
|
|
1489
|
+
continue;
|
|
1490
|
+
const branchValue = raw.slice(1, -1).replace(/\\\\([\\\"'])/g, "$1");
|
|
1491
|
+
const escapedParameter = parameter.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&");
|
|
1492
|
+
const escapedBranchValue = branchValue.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&");
|
|
1493
|
+
const branch = new RegExp(`(?:^|\\n)([ \\t]*)if\\s+${escapedParameter}\\s*==\\s*['\"]${escapedBranchValue}['\"]\\s*:\\s*([\\s\\S]*?)(?=\\n\\1(?:elif\\b|else\\s*:)|\\n\\1(?:if\\b|def\\b)|$)`, "m").exec(region)?.[2];
|
|
1494
|
+
if (!branch)
|
|
1495
|
+
continue;
|
|
1496
|
+
const value = new RegExp(`['\"]${escapedField}['\"]\\s*:\\s*([^,}\\n]+)`).exec(branch)?.[1];
|
|
1497
|
+
if (!value)
|
|
1498
|
+
continue;
|
|
1499
|
+
const observed = observeLiteralToken(value.trim(), input.field, input.constants);
|
|
1500
|
+
if (observed)
|
|
1501
|
+
return observed;
|
|
1502
|
+
}
|
|
1503
|
+
return undefined;
|
|
1504
|
+
}
|
|
1368
1505
|
function observeDeterministicFunctionFieldOverride(input) {
|
|
1369
1506
|
const region = scenarioFunctionRegion(input.source, input.tpId);
|
|
1370
1507
|
if (!region)
|
|
@@ -1449,11 +1586,12 @@ function scenarioParamDiagnosticValues(intent, observed) {
|
|
|
1449
1586
|
const expectedRaw = intent.startsWith("custom-literal:")
|
|
1450
1587
|
? intent.slice("custom-literal:".length)
|
|
1451
1588
|
: intent === "unknown" ? undefined : intent;
|
|
1589
|
+
const persistedLength = persistedObservedLength(observed.length);
|
|
1452
1590
|
return {
|
|
1453
1591
|
expectedRaw,
|
|
1454
|
-
observedRaw: boundedScenarioText(observed.text,
|
|
1592
|
+
observedRaw: boundedScenarioText(observed.text, persistedLength),
|
|
1455
1593
|
expectedNormalized: expectedRaw === undefined ? undefined : normalizeCustomLiteralExpected(expectedRaw).slice(0, 512),
|
|
1456
|
-
observedNormalized: observed.literal === undefined ? undefined : boundedScenarioText(normalizeScenarioLiteralText(observed.literal),
|
|
1594
|
+
observedNormalized: observed.literal === undefined ? undefined : boundedScenarioText(normalizeScenarioLiteralText(observed.literal), persistedLength),
|
|
1457
1595
|
};
|
|
1458
1596
|
}
|
|
1459
1597
|
export async function assessBackendScenarioParamConsistency(input) {
|
|
@@ -1496,7 +1634,10 @@ export async function assessBackendScenarioParamConsistency(input) {
|
|
|
1496
1634
|
constants,
|
|
1497
1635
|
})
|
|
1498
1636
|
: undefined;
|
|
1499
|
-
const
|
|
1637
|
+
const branchFieldObserved = source && inferred.field
|
|
1638
|
+
? observeBranchPayloadField({ source, tpId, block, parameterNames, field: inferred.field, constants })
|
|
1639
|
+
: undefined;
|
|
1640
|
+
const observed = functionFieldOverride ?? helperFieldObserved ?? branchFieldObserved ?? observeParamFeatures(block, inferred.field, constants, parameterNames);
|
|
1500
1641
|
// Request-level / health checks often have no pytest.param payload row,
|
|
1501
1642
|
// or only a TP-id label positional (no field value).
|
|
1502
1643
|
const requestLevelNoParam = Boolean(source) &&
|
|
@@ -1539,8 +1680,8 @@ export async function assessBackendScenarioParamConsistency(input) {
|
|
|
1539
1680
|
tpId,
|
|
1540
1681
|
field: inferred.field,
|
|
1541
1682
|
intent: inferred.intent,
|
|
1542
|
-
observed: boundedScenarioText(observed.text, observed.length),
|
|
1543
|
-
observedLength: observed.length,
|
|
1683
|
+
observed: boundedScenarioText(observed.text, persistedObservedLength(observed.length)),
|
|
1684
|
+
observedLength: persistedObservedLength(observed.length),
|
|
1544
1685
|
status,
|
|
1545
1686
|
repairability,
|
|
1546
1687
|
reasonCode: status === "MATCH" ? "SCENARIO_PARAM_MATCH" : status === "MISMATCH" ? "SCENARIO_PARAM_MISMATCH" : "SCENARIO_PARAM_UNDETERMINED",
|
|
@@ -1709,6 +1850,12 @@ export function deterministicRewriteScenarioParam(input) {
|
|
|
1709
1850
|
}
|
|
1710
1851
|
valueLiteral = `"x" * ${bound + 1}`;
|
|
1711
1852
|
break;
|
|
1853
|
+
case "max-1":
|
|
1854
|
+
if (bound === undefined) {
|
|
1855
|
+
return { ok: false, source: input.source, detail: "missing bound for max-1" };
|
|
1856
|
+
}
|
|
1857
|
+
valueLiteral = `"x" * ${Math.max(0, bound - 1)}`;
|
|
1858
|
+
break;
|
|
1712
1859
|
case "min-1":
|
|
1713
1860
|
if (bound === undefined) {
|
|
1714
1861
|
return { ok: false, source: input.source, detail: "missing bound for min-1" };
|
|
@@ -43,6 +43,67 @@ function splitCells(raw) {
|
|
|
43
43
|
.map((cell) => cell.trim())
|
|
44
44
|
.filter((cell, index, cells) => !(cell === "" && (index === 0 || index === cells.length - 1)));
|
|
45
45
|
}
|
|
46
|
+
function isExactSlotInventory(extras, partitionId) {
|
|
47
|
+
const tokens = extras
|
|
48
|
+
.join(";")
|
|
49
|
+
.split(/[;,,;]/)
|
|
50
|
+
.map((token) => token.trim().replace(/`/g, ""))
|
|
51
|
+
.filter((token) => token.length > 0);
|
|
52
|
+
if (tokens.length === 0)
|
|
53
|
+
return false;
|
|
54
|
+
const prefix = `TP-${partitionId.trim().toUpperCase()}-`;
|
|
55
|
+
return tokens.every((token) => /^TP-[A-Z0-9-]+$/i.test(token) &&
|
|
56
|
+
(token.toUpperCase().startsWith(prefix) || token.toUpperCase().startsWith("TP-SP-")));
|
|
57
|
+
}
|
|
58
|
+
function trimExactSlotInventoryCells(cells) {
|
|
59
|
+
if (cells.length <= 7)
|
|
60
|
+
return { cells, trimmed: false };
|
|
61
|
+
const partitionId = cells[0] ?? "";
|
|
62
|
+
if (!isExactSlotInventory(cells.slice(7), partitionId)) {
|
|
63
|
+
return { cells, trimmed: false };
|
|
64
|
+
}
|
|
65
|
+
return { cells: cells.slice(0, 7), trimmed: true };
|
|
66
|
+
}
|
|
67
|
+
export function canonicalizeScenarioPartitionTable(markdown) {
|
|
68
|
+
const lines = markdown.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
|
|
69
|
+
const headingIndex = lines.findIndex((line) => PARTITION_HEADING.test(line.trim()));
|
|
70
|
+
if (headingIndex === -1) {
|
|
71
|
+
return { markdown: lines.join("\n"), changed: false, trimmedRows: 0 };
|
|
72
|
+
}
|
|
73
|
+
let headerIndex = -1;
|
|
74
|
+
let sectionEnd = lines.length;
|
|
75
|
+
for (let i = headingIndex + 1; i < lines.length; i++) {
|
|
76
|
+
if (/^##\s+/.test(lines[i].trim()) && !PARTITION_HEADING.test(lines[i].trim())) {
|
|
77
|
+
sectionEnd = i;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
if (PARTITION_TABLE_HEADER.test(lines[i])) {
|
|
81
|
+
headerIndex = i;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (headerIndex === -1) {
|
|
85
|
+
return { markdown: lines.join("\n"), changed: false, trimmedRows: 0 };
|
|
86
|
+
}
|
|
87
|
+
let trimmedRows = 0;
|
|
88
|
+
for (let i = headerIndex + 2; i < sectionEnd; i++) {
|
|
89
|
+
const line = lines[i];
|
|
90
|
+
if (!TABLE_ROW.test(line)) {
|
|
91
|
+
if (/^##\s+/.test(line) || /^#\s+/.test(line))
|
|
92
|
+
break;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const { cells, trimmed } = trimExactSlotInventoryCells(splitCells(line));
|
|
96
|
+
if (!trimmed)
|
|
97
|
+
continue;
|
|
98
|
+
lines[i] = `| ${cells.join(" | ")} |`;
|
|
99
|
+
trimmedRows += 1;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
markdown: lines.join("\n"),
|
|
103
|
+
changed: trimmedRows > 0,
|
|
104
|
+
trimmedRows,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
46
107
|
/**
|
|
47
108
|
* Parse the `## Scenario Partitions` section of the README into declared
|
|
48
109
|
* partitions. Returns { partitions, issues }; issues are fail-closed parse
|
|
@@ -77,7 +138,7 @@ export function parseScenarioPartitions(readmeMarkdown) {
|
|
|
77
138
|
break;
|
|
78
139
|
continue;
|
|
79
140
|
}
|
|
80
|
-
const cells = splitCells(line);
|
|
141
|
+
const { cells } = trimExactSlotInventoryCells(splitCells(line));
|
|
81
142
|
if (cells.length !== 7) {
|
|
82
143
|
issues.push(`MISSING_PARTITION: row has ${cells.length} cells, expected 7: ${line.trim()}`);
|
|
83
144
|
continue;
|
|
@@ -4,7 +4,8 @@ import path from "node:path";
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { BACKEND_TEST_MAX_MODULE_COUNT, isOpaqueHashBackendTestModuleStem, isPriorityOnlyBackendTestModuleStem, } from "./backend-test-module-stem.js";
|
|
6
6
|
import { resolveBackendTestLayout, } from "./backend-test-layout.js";
|
|
7
|
-
import { inspectBackendTestMarkdownTestPointBindings } from "./backend-test-case-coverage-analysis.js";
|
|
7
|
+
import { canonicalizeBackendTestMarkdownTestPointBindings, inspectBackendTestMarkdownTestPointBindings, } from "./backend-test-case-coverage-analysis.js";
|
|
8
|
+
import { BACKEND_TEST_SCENARIO_PARTITION_SLOTS_SCHEMA_ID, canonicalizeBackendTestPlanProtocolHeadings, collectMarkdownPartitionSlotCoverage, lookupRequiredVariantSlotsForMarkdownPath, } from "./backend-test-plan-protocol.js";
|
|
8
9
|
/**
|
|
9
10
|
* Maps a backend-test generation writer task id to its writer-progress role.
|
|
10
11
|
* Returns undefined for nodes that are not completeness-gated generators.
|
|
@@ -47,8 +48,16 @@ const progressSchema = z
|
|
|
47
48
|
path: z.string().optional(),
|
|
48
49
|
detail: z.string(),
|
|
49
50
|
recoverable: z.boolean(),
|
|
51
|
+
failureSubreason: z.literal("markdown-test-point-binding-format").optional(),
|
|
52
|
+
affectedCases: z.array(z.string()).optional(),
|
|
53
|
+
testPointIds: z.array(z.string()).optional(),
|
|
54
|
+
canonicalizationEligible: z.boolean().optional(),
|
|
55
|
+
canonicalizationOutcome: z.enum(["not-attempted", "changed", "already-canonical", "unsafe", "revalidation-failed"]).optional(),
|
|
50
56
|
})
|
|
51
57
|
.strict()),
|
|
58
|
+
canonicalizationOutcome: z.enum(["not-attempted", "changed", "already-canonical", "unsafe", "revalidation-failed"]).optional(),
|
|
59
|
+
canonicalizedTestPointCount: z.number().int().min(0).optional(),
|
|
60
|
+
lightweightRepairEligible: z.boolean().optional(),
|
|
52
61
|
})
|
|
53
62
|
.strict();
|
|
54
63
|
async function exists(filePath) {
|
|
@@ -119,10 +128,8 @@ function looksLikeValidModuleStem(raw) {
|
|
|
119
128
|
return moduleStemInvalidReason(raw) === undefined;
|
|
120
129
|
}
|
|
121
130
|
function extractCanonicalModuleIndexSection(readme) {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
.replaceAll("\r", "\n")
|
|
125
|
-
.split("\n");
|
|
131
|
+
const canonical = canonicalizeBackendTestPlanProtocolHeadings(readme).markdown;
|
|
132
|
+
const lines = canonical.split("\n");
|
|
126
133
|
const headings = lines.flatMap((line, index) => line.trim() === "## Module Index" ? [index] : []);
|
|
127
134
|
if (headings.length === 0) {
|
|
128
135
|
return { section: "", structureIssues: ["missing-module-index"] };
|
|
@@ -362,8 +369,6 @@ function pythonParseable(source) {
|
|
|
362
369
|
const normalized = source.replace(/\r\n/g, "\n");
|
|
363
370
|
if (!normalized.trim())
|
|
364
371
|
return false;
|
|
365
|
-
if (/^\s*(?:def|class|import|from)\b/m.test(normalized) === false)
|
|
366
|
-
return false;
|
|
367
372
|
const structural = pythonStructureOutsideStrings(normalized);
|
|
368
373
|
if (!structural.stringsClosed)
|
|
369
374
|
return false;
|
|
@@ -380,7 +385,13 @@ function pythonParseable(source) {
|
|
|
380
385
|
return false;
|
|
381
386
|
if (openBraces !== closeBraces)
|
|
382
387
|
return false;
|
|
383
|
-
|
|
388
|
+
// Business modules still need a statement. Evidence-only mapped scripts may
|
|
389
|
+
// be a closed module docstring + Case-ID with no `def test_` (R10 health).
|
|
390
|
+
if (/^\s*(?:def|class|import|from|__all__\s*=)/m.test(normalized))
|
|
391
|
+
return true;
|
|
392
|
+
return (/^\s*(?:'''|""")/.test(normalized) &&
|
|
393
|
+
/\bCase-ID\s*:/i.test(normalized) &&
|
|
394
|
+
!/^\s*(?:async\s+)?def\s+test_/m.test(normalized));
|
|
384
395
|
}
|
|
385
396
|
/**
|
|
386
397
|
* Forbidden generated shared helper/factory namespaces for the self-contained
|
|
@@ -511,6 +522,19 @@ export async function loadBackendTestWriterProgressForRetry(runDir, taskId) {
|
|
|
511
522
|
* child's own target file(s): md children are checked for structural
|
|
512
523
|
* completeness, pytest children for parseability. Sibling modules are ignored.
|
|
513
524
|
*/
|
|
525
|
+
async function loadScenarioPartitionSlotInventory(runDir) {
|
|
526
|
+
if (!runDir)
|
|
527
|
+
return undefined;
|
|
528
|
+
try {
|
|
529
|
+
const raw = JSON.parse(await readFile(path.join(runDir, "contracts", "backend-test-scenario-partition-slots.json"), "utf8"));
|
|
530
|
+
if (raw?.schemaId !== BACKEND_TEST_SCENARIO_PARTITION_SLOTS_SCHEMA_ID)
|
|
531
|
+
return undefined;
|
|
532
|
+
return raw;
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
return undefined;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
514
538
|
export async function assessBackendTestShardChildCompleteness(input) {
|
|
515
539
|
const role = input.role;
|
|
516
540
|
const targetPaths = input.writeSet
|
|
@@ -520,6 +544,12 @@ export async function assessBackendTestShardChildCompleteness(input) {
|
|
|
520
544
|
const actualPaths = [];
|
|
521
545
|
const missingPaths = [];
|
|
522
546
|
const brokenPaths = [];
|
|
547
|
+
let canonicalizationOutcome = role === "md-generate" ? "already-canonical" : undefined;
|
|
548
|
+
let canonicalizedTestPointCount = 0;
|
|
549
|
+
let lightweightRepairEligible = false;
|
|
550
|
+
const slotInventory = role === "md-generate"
|
|
551
|
+
? await loadScenarioPartitionSlotInventory(input.runDir)
|
|
552
|
+
: undefined;
|
|
523
553
|
for (const target of targetPaths) {
|
|
524
554
|
// Rendered map-child writeSet entries are concrete (e.g.
|
|
525
555
|
// testcase/md/health.md / testcase/test_orders.py); glob-only entries are
|
|
@@ -538,16 +568,65 @@ export async function assessBackendTestShardChildCompleteness(input) {
|
|
|
538
568
|
continue;
|
|
539
569
|
}
|
|
540
570
|
actualPaths.push(target);
|
|
541
|
-
|
|
571
|
+
let body = await readFile(absolute, "utf8");
|
|
542
572
|
if (role === "md-generate") {
|
|
573
|
+
const beforeBindings = inspectBackendTestMarkdownTestPointBindings(body);
|
|
574
|
+
const canonicalization = canonicalizeBackendTestMarkdownTestPointBindings(body);
|
|
575
|
+
if (canonicalization.changed) {
|
|
576
|
+
await writeFile(absolute, canonicalization.markdown, "utf8");
|
|
577
|
+
body = canonicalization.markdown;
|
|
578
|
+
canonicalizationOutcome = "changed";
|
|
579
|
+
canonicalizedTestPointCount += canonicalization.canonicalizedTestPoints.length;
|
|
580
|
+
}
|
|
581
|
+
const afterBindings = inspectBackendTestMarkdownTestPointBindings(body);
|
|
582
|
+
const unresolved = afterBindings.flatMap((entry) => entry.unclassifiedTestPoints);
|
|
583
|
+
const bindingCases = afterBindings
|
|
584
|
+
.filter((entry) => entry.unclassifiedTestPoints.length > 0 || entry.duplicateBindingTestPoints.length > 0)
|
|
585
|
+
.map((entry) => entry.caseId);
|
|
543
586
|
const structural = moduleStructurallyComplete(body);
|
|
587
|
+
const requiredSlots = lookupRequiredVariantSlotsForMarkdownPath(slotInventory, target);
|
|
588
|
+
const slotCoverage = requiredSlots.length > 0
|
|
589
|
+
? collectMarkdownPartitionSlotCoverage(body, requiredSlots)
|
|
590
|
+
: undefined;
|
|
591
|
+
const missingSlots = orderedUnique([
|
|
592
|
+
...(slotCoverage?.missingFromDeclared ?? []),
|
|
593
|
+
...(slotCoverage?.missingFromVariant ?? []),
|
|
594
|
+
]);
|
|
595
|
+
if (missingSlots.length > 0) {
|
|
596
|
+
lightweightRepairEligible = true;
|
|
597
|
+
if (!brokenPaths.includes(target))
|
|
598
|
+
brokenPaths.push(target);
|
|
599
|
+
issues.push({
|
|
600
|
+
code: "T5",
|
|
601
|
+
path: target,
|
|
602
|
+
detail: `incomplete-partition-slot-set: missing ${missingSlots.join(", ")}; Scheme A requires each exact slot as an independent variant Test Point, not Case count or SINGLE/MULTIPLE aliases`,
|
|
603
|
+
recoverable: true,
|
|
604
|
+
affectedCases: afterBindings.map((entry) => entry.caseId),
|
|
605
|
+
testPointIds: missingSlots,
|
|
606
|
+
});
|
|
607
|
+
}
|
|
544
608
|
if (!structural.ok) {
|
|
609
|
+
lightweightRepairEligible = bindingCases.length > 0;
|
|
610
|
+
if (bindingCases.length > 0)
|
|
611
|
+
canonicalizationOutcome = canonicalization.changed ? "revalidation-failed" : "unsafe";
|
|
545
612
|
brokenPaths.push(target);
|
|
613
|
+
const bindingFormatIssue = bindingCases.length > 0;
|
|
546
614
|
issues.push({
|
|
547
615
|
code: "T5",
|
|
548
616
|
path: target,
|
|
549
617
|
detail: moduleStructuralDetail(structural),
|
|
550
618
|
recoverable: true,
|
|
619
|
+
...(bindingFormatIssue
|
|
620
|
+
? {
|
|
621
|
+
failureSubreason: "markdown-test-point-binding-format",
|
|
622
|
+
affectedCases: orderedUnique(bindingCases),
|
|
623
|
+
testPointIds: orderedUnique(unresolved),
|
|
624
|
+
canonicalizationEligible: canonicalization.canonicalizedTestPoints.length > 0,
|
|
625
|
+
canonicalizationOutcome: canonicalization.changed
|
|
626
|
+
? (unresolved.length === 0 ? "changed" : "revalidation-failed")
|
|
627
|
+
: (beforeBindings.some((entry) => entry.unclassifiedTestPoints.length > 0) ? "unsafe" : "already-canonical"),
|
|
628
|
+
}
|
|
629
|
+
: {}),
|
|
551
630
|
});
|
|
552
631
|
}
|
|
553
632
|
}
|
|
@@ -586,6 +665,9 @@ export async function assessBackendTestShardChildCompleteness(input) {
|
|
|
586
665
|
brokenPaths: orderedUnique(brokenPaths),
|
|
587
666
|
targetPaths: recoverableTargets,
|
|
588
667
|
issues,
|
|
668
|
+
...(role === "md-generate"
|
|
669
|
+
? { canonicalizationOutcome, canonicalizedTestPointCount, lightweightRepairEligible }
|
|
670
|
+
: {}),
|
|
589
671
|
};
|
|
590
672
|
}
|
|
591
673
|
export async function assessBackendTestMdWriterCompleteness(workspaceRoot, layout) {
|
|
@@ -609,7 +691,7 @@ export async function assessBackendTestMdWriterCompleteness(workspaceRoot, layou
|
|
|
609
691
|
}
|
|
610
692
|
else {
|
|
611
693
|
actualPaths.push(readmeRel);
|
|
612
|
-
readme = await readFile(readmePath, "utf8");
|
|
694
|
+
readme = canonicalizeBackendTestPlanProtocolHeadings(await readFile(readmePath, "utf8")).markdown;
|
|
613
695
|
if (!readme.includes("## Coverage Scope")) {
|
|
614
696
|
brokenPaths.push(readmeRel);
|
|
615
697
|
issues.push({
|
|
@@ -853,7 +935,7 @@ export async function writeBackendTestWriterProgressArtifacts(input) {
|
|
|
853
935
|
"## Issues",
|
|
854
936
|
"",
|
|
855
937
|
...(input.progress.issues.length
|
|
856
|
-
? input.progress.issues.map((issue) => `- [${issue.code}] ${issue.path ?? "(workspace)"}: ${issue.detail}`)
|
|
938
|
+
? input.progress.issues.map((issue) => `- [${issue.code}] ${issue.path ?? "(workspace)"}: ${issue.detail}${issue.failureSubreason ? `; failureSubreason=${issue.failureSubreason}; canonicalizationOutcome=${issue.canonicalizationOutcome ?? "not-attempted"}; canonicalizationEligible=${issue.canonicalizationEligible ?? false}; affectedCases=${(issue.affectedCases ?? []).join(",") || "none"}; testPointIds=${(issue.testPointIds ?? []).join(",") || "none"}` : ""}`)
|
|
857
939
|
: ["- None"]),
|
|
858
940
|
"",
|
|
859
941
|
`<!-- sha256:${createHash("sha256").update(JSON.stringify(parsed)).digest("hex")} -->`,
|
|
@@ -862,6 +944,16 @@ export async function writeBackendTestWriterProgressArtifacts(input) {
|
|
|
862
944
|
await writeFile(reportPath, body, "utf8");
|
|
863
945
|
return { factsPath, reportPath };
|
|
864
946
|
}
|
|
947
|
+
export function isBackendTestPytestCollectionRepairOutcomeRecoveryCandidate(task) {
|
|
948
|
+
return (task.id === "repair-backend-pytest-collection-pi" &&
|
|
949
|
+
task.executor === "pi" &&
|
|
950
|
+
task.role === "implementer" &&
|
|
951
|
+
task.toolProfile === "write" &&
|
|
952
|
+
task.writePolicy === "exclusive" &&
|
|
953
|
+
(task.writeSet?.length ?? 0) > 0 &&
|
|
954
|
+
task.writerOutcomePolicy?.type === "implementation-outcome-v1" &&
|
|
955
|
+
task.writerOutcomePolicy.requireChangedFiles === true);
|
|
956
|
+
}
|
|
865
957
|
export function isBackendTestCompletenessRetryCandidate(task) {
|
|
866
958
|
if (task.executor !== "pi" || task.role !== "implementer")
|
|
867
959
|
return false;
|
|
@@ -933,7 +1025,7 @@ export async function assessBackendTestMdPlanCompleteness(workspaceRoot, layout)
|
|
|
933
1025
|
}
|
|
934
1026
|
else {
|
|
935
1027
|
actualPaths.push(planReadmeRel);
|
|
936
|
-
const readme = await readFile(readmePath, "utf8");
|
|
1028
|
+
const readme = canonicalizeBackendTestPlanProtocolHeadings(await readFile(readmePath, "utf8")).markdown;
|
|
937
1029
|
if (!readme.includes("## Coverage Scope")) {
|
|
938
1030
|
brokenPaths.push(planReadmeRel);
|
|
939
1031
|
issues.push({
|