@tea-agent/loop-agent 0.39.0-next.9 → 0.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -1
- package/CHANGELOG.md +269 -69
- package/README.md +11 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/dag/generate-task-dag.js +9 -0
- package/dist/application/task-lifecycle/advance.js +16 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +16 -5
- package/dist/cli/program.js +16 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/cli.js +2 -1
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +428 -27
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +31 -0
- package/dist/executors/dag-pi-executor.js +124 -18
- package/dist/executors/pi-event-serializer.js +42 -1
- package/dist/executors/pi-executor.js +51 -0
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +74 -28
- package/dist/executors/pi-sdk-executor.js +187 -59
- package/dist/executors/shell-executor.js +555 -34
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/shared/dag-failure-category.js +138 -0
- package/dist/shared/dag-prompt-override.js +27 -0
- package/dist/shared/operator/capabilities.js +181 -27
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +70 -15
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/config-types.js +28 -0
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +3 -0
- package/dist/task/contract/diff.js +26 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +20 -1
- package/dist/task/contract/schema.js +8 -1
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/source-prepare/build-draft.js +15 -0
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +474 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/task/source-prepare/prepare.js +262 -1
- package/dist/task/source-prepare/semantic-intake.js +19 -3
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/worker/cli.js +18 -2
- package/dist/worker/console/chat/deferred-turn.js +91 -0
- package/dist/worker/console/chat/mcp-inventory.js +221 -0
- package/dist/worker/console/chat/operation-card.js +8 -1
- package/dist/worker/console/chat/pi-console-config.js +81 -10
- package/dist/worker/console/chat/pi-runtime.js +165 -83
- package/dist/worker/console/chat/repo-walk.js +10 -1
- package/dist/worker/console/chat/routes.js +480 -55
- package/dist/worker/console/chat/semantic-activity.js +20 -8
- package/dist/worker/console/chat/session-store.js +146 -2
- package/dist/worker/console/chat/turn-process.js +10 -10
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/inspect-split.js +18 -0
- package/dist/worker/console/operation-run-facts.js +19 -3
- package/dist/worker/console/operation-store.js +1 -1
- package/dist/worker/console/operator-actions.js +320 -4
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/pi-readiness.js +5 -4
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/recovery-cta.js +4 -4
- package/dist/worker/console/recovery-error-copy.js +4 -4
- package/dist/worker/console/routes.js +46 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +111 -7
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BLXFe4qI.js → abnfDiagram-N423BO3Z-CTx1TrNa.js} +1 -1
- package/dist/worker/console/static/assets/{arc-7iOJeqho.js → arc-B4i9cVxX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CYoyj5K7.js → architectureDiagram-T3A2C74G-BAXE8_Rl.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-fK01JNlR.js → blockDiagram-VBNYF7ZC-C5Xh59at.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-bzbw5D_Z.js → c4Diagram-5PPSVZJV-B_asOzAu.js} +1 -1
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Cf6hJFFG.js → chunk-2GRJ4B5K-wRO_iRNc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DSFXR-et.js → chunk-2Q5K7J3B-ByOaSh_G.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BagJechl.js → chunk-5RXB4S5H-DjLzudan.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-E7QNMFaW.js → chunk-5VM5RSS4-JCNGkXYT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BG_eOBrC.js → chunk-6Q2QTUOP-BhrPXXMZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bo42dqol.js → chunk-GF5L2VYU-TJ5JuEBT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-dibM-87v.js → chunk-JWPE2WC7-VVthn_mD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Bytrp3Oe.js → chunk-KBJHAD2P-D60-Mny4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DBABILrr.js → chunk-RYQCIY6F-zXv7aVye.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DCUk5Pf9.js → chunk-XXDRQBXY-By9VQoi1.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BlGE9v6E.js → cose-bilkent-JH36ORCC-C4zQLe-M.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DGl5ewph.js → cynefin-VYW2F7L2-wh5N2ReN.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DsJ_SVH_.js → cynefinDiagram-MW4NZA55-Dlu5wIqs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BIhjFdzv.js → dagre-VZM6K2ZE-CrDgthc6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dmy1ctTb.js → diagram-7IWD3JNH-zN1clg27.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DYCtWUF3.js → diagram-B4RE2ZJO-BN-o7J7-.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BMZFHVd2.js → diagram-LBJQPF4R-C8SuPt0d.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-b_U8gfaO.js → diagram-Q27KOJAE-BoizuHbZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dzy_bn-I.js → diagram-UB23O5K3-BzUzXixG.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DJCiMNEI.js → ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-28lD016B.js → erDiagram-JOGREHBK-GALUzRbs.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DGyHJmIw.js → flowDiagram-UKHOOZJN-CZJN65Sb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CzdwUjrO.js → ganttDiagram-PKOTCBZU-JLB9OESl.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-D9bGwuo8.js → gitGraphDiagram-DS77QQ5N-CcvCr4GD.js} +1 -1
- package/dist/worker/console/static/assets/index-CW88Fjbw.js +406 -0
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-LJgJUQFC.js → infoDiagram-6WML65LV-HG5z7aVg.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-98LaB8zb.js → ishikawaDiagram-WSZJBQD7-BHEu9chR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-ISzwlQ.js → journeyDiagram-NVQOT4AX-Cp_Up8-a.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-gfYOI-EL.js → kanban-definition-27J2QSJJ-DvVeY-em.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CyntZgKV.js → linear-aGmcjgTn.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BBOR7dg0.js → mermaid.core-Biyo-tqA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CQAgwPMk.js → mindmap-definition-FAOFIHXS-DyxY2pIb.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DkMiZFuV.js → pegDiagram-VL7TDLO6-CkdGuc50.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DLNXa_0J.js → pieDiagram-7S7Q4E2Y-NNQI9xVR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Dc7bdzjm.js → quadrantDiagram-CIZ2JOQS-PAJkuz_G.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BxMB0QqW.js → railroadDiagram-AXF67PYL-ZwrQod0O.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-FjumoKn4.js → requirementDiagram-LRYGKXZP-D3ZfrzM2.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CkhKc_E3.js → sankeyDiagram-W5VNT64P-DDGFmWk5.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DoBWqTTH.js → sequenceDiagram-SI44F4Z6-DO6mmXTA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CQeh_YeE.js → sizeCapture-X5ZJPWSS-DMbCQiJt.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-LuHmFW-I.js → stateDiagram-OKZ733FA-Di_YUEfC.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CHwbDctY.js → swimlanes-SLNWSIFB-Bn2PphNJ.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CoMngk0X.js → timeline-definition-Z64GVDOM-DPh8pRsp.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CURYbtbO.js → vennDiagram-T6HMQDX7-GMfl26pW.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BdqSECjU.js → wardleyDiagram-T6FBY63Y-DTlmVrmF.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DbMUYuAg.js → xychartDiagram-ELKLHX3M-YGBjKAS5.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +40 -1
- package/dist/worker/console/static-src/app/useOperatorActions.js +6 -1
- package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +38 -2
- package/dist/worker/console/static-src/app/useRunProgress.js +46 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +73 -1
- package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +74 -26
- package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
- package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +54 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +237 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
- package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
- package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
- package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/materialize/harness-task-lineage.js +5 -2
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observability/read-model.js +22 -0
- package/dist/worker/observe/node-input.js +160 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +224 -3
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +67 -1
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/format.js +60 -0
- package/dist/worker/observe/static/index.html +5 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.js +4 -0
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/state.js +13 -2
- package/dist/worker/observe/static/styles.css +85 -0
- package/dist/worker/observe/static/views/dag-inspector.js +912 -8
- package/dist/worker/observe/static/views/dag.js +8 -1
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/worker/observe/static/views/task.js +55 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +856 -54
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
- package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
- package/dist/workflows/dag/backend-test-intake-context.js +32 -0
- package/dist/workflows/dag/backend-test-layout.js +133 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +343 -33
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +454 -36
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
- package/dist/workflows/dag/backend-test-scenario-partitions.js +321 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +110 -61
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +3 -1
- package/dist/workflows/dag/convergence/controller.js +141 -0
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
- package/dist/workflows/dag/dynamic-runtime/map.js +25 -9
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
- package/dist/workflows/dag/failure-category.js +7 -116
- package/dist/workflows/dag/frontend-implementation-contract.js +465 -6
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +293 -84
- package/dist/workflows/dag/frontend-repair.js +14 -0
- package/dist/workflows/dag/frontend-review-context.js +92 -10
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
- package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
- package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
- package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
- package/dist/workflows/dag/frontend-test-html-report.js +3 -1
- package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
- package/dist/workflows/dag/frontend-test-layout.js +159 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
- package/dist/workflows/dag/frontend-verification-trace.js +29 -4
- package/dist/workflows/dag/init-hybrid.js +1019 -209
- package/dist/workflows/dag/node-execution.js +228 -15
- package/dist/workflows/dag/prompt.js +60 -1
- package/dist/workflows/dag/rerun-feedback.js +212 -0
- package/dist/workflows/dag/rerun-plan.js +224 -27
- package/dist/workflows/dag/rerun-run.js +61 -6
- package/dist/workflows/dag/rerun-task.js +61 -2
- package/dist/workflows/dag/retry-policy.js +109 -0
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +185 -12
- package/dist/workflows/dag/validate.js +64 -20
- package/docs/README.md +1 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +6 -0
- package/docs/operations/README.md +1 -1
- package/docs/operations/local-development-environment.md +1 -5
- package/docs/skills/vetted-skill-registry.md +14 -0
- package/docs/templates/README.md +2 -2
- package/docs/templates/agent-dag.schema.json +44 -0
- package/docs/templates/backend-test-dag.json +60 -35
- package/docs/templates/frontend-implementation-contract.schema.json +681 -24
- package/docs/templates/frontend-test-case-checklist.md +2 -2
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/harness.json +6 -4
- package/package.json +12 -4
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/analyze-product-requirements/SKILL.md +8 -5
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
- package/skills/analyze-product-requirements/references/example.md +50 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
- package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/frontend-implementation/references/node-contracts.md +2 -2
- package/skills/improve-codebase-architecture/SKILL.md +81 -0
- package/skills/improve-codebase-architecture/deepening.md +37 -0
- package/skills/improve-codebase-architecture/html-report.md +123 -0
- package/skills/improve-codebase-architecture/interface-design.md +44 -0
- package/skills/improve-codebase-architecture/language.md +53 -0
- package/skills/loop-agent/references/command-reference.md +11 -4
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/skills/playwright-cli/SKILL.md +23 -1
- package/skills/playwright-cli-case-generator/SKILL.md +29 -8
- package/dist/worker/console/static/assets/channel-BBOTJHvD.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cm8Yf8tD.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cm8Yf8tD.js +0 -1
- package/dist/worker/console/static/assets/index-B-qWGThd.js +0 -325
- package/dist/worker/console/static/assets/index-C-xRgY_0.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DZihfdKc.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CKnXCjHX.js +0 -8
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Plan C: deterministic query/filter axis scenario partitions.
|
|
4
|
+
*
|
|
5
|
+
* A README `## Scenario Partitions` table declares one partition per
|
|
6
|
+
* filterable enum/classification axis on an affected list/query operation.
|
|
7
|
+
* The deterministic expander turns each declared partition into machine
|
|
8
|
+
* slots — the model never invents slot IDs:
|
|
9
|
+
*
|
|
10
|
+
* each-value one slot per legal domain value (intent=nominal-filter)
|
|
11
|
+
* omitted only when the contract marks the parameter optional
|
|
12
|
+
* not-in-set exactly one complement slot (intent=enum-invalid)
|
|
13
|
+
*
|
|
14
|
+
* `not-in-set` expected semantics must come from the bound source; when the
|
|
15
|
+
* source does not document the complement expectation, the partition row
|
|
16
|
+
* keeps the slot but the expected column stays GAP so N6 reports it instead
|
|
17
|
+
* of guessing "empty list" or "400".
|
|
18
|
+
*/
|
|
19
|
+
export const scenarioPartitionRowSchema = z.object({
|
|
20
|
+
partitionId: z.string().min(1),
|
|
21
|
+
operation: z.string().min(1),
|
|
22
|
+
axis: z.string().min(1),
|
|
23
|
+
/** Legal values copied verbatim from the bound OpenAPI enum / requirement sentence. */
|
|
24
|
+
domain: z.array(z.string().min(1)).min(1),
|
|
25
|
+
/** true when the parameter is optional and the omitted slot is required. */
|
|
26
|
+
optional: z.boolean(),
|
|
27
|
+
/**
|
|
28
|
+
* Complement expectation sourced from the contract. `unknown` keeps the
|
|
29
|
+
* not-in-set slot but marks expected as GAP (never guessed).
|
|
30
|
+
*/
|
|
31
|
+
complementExpected: z.enum(["empty-result", "excluded-result", "unknown"]),
|
|
32
|
+
/** Which bind rule this partition enforces (advisory provenance). */
|
|
33
|
+
bindRule: z.string().min(1),
|
|
34
|
+
}).strict();
|
|
35
|
+
const PARTITION_HEADING = /^##\s+Scenario Partitions\s*$/;
|
|
36
|
+
const PARTITION_TABLE_HEADER = /^\|\s*Partition ID\s*\|\s*Operation\s*\|\s*Axis\s*\|\s*Domain\s*\|\s*Required Slots\s*\|\s*Expected by Slot\s*\|\s*Bind Rule\s*\|\s*$/;
|
|
37
|
+
const TABLE_ROW = /^\|(.+)\|$/;
|
|
38
|
+
const FINITE_DOMAIN_MEMBER = /^[A-Za-z0-9]+(?:[._\- ][A-Za-z0-9]+)*$/;
|
|
39
|
+
const RESERVED_SLOT_TOKENS = new Set(["OMITTED", "NOT-IN-SET"]);
|
|
40
|
+
function splitCells(raw) {
|
|
41
|
+
return raw
|
|
42
|
+
.split("|")
|
|
43
|
+
.map((cell) => cell.trim())
|
|
44
|
+
.filter((cell, index, cells) => !(cell === "" && (index === 0 || index === cells.length - 1)));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parse the `## Scenario Partitions` section of the README into declared
|
|
48
|
+
* partitions. Returns { partitions, issues }; issues are fail-closed parse
|
|
49
|
+
* problems (malformed table), while semantic gaps surface in expansion.
|
|
50
|
+
*/
|
|
51
|
+
export function parseScenarioPartitions(readmeMarkdown) {
|
|
52
|
+
const issues = [];
|
|
53
|
+
const partitions = [];
|
|
54
|
+
const lines = readmeMarkdown.split("\n");
|
|
55
|
+
const headingIndex = lines.findIndex((line) => PARTITION_HEADING.test(line));
|
|
56
|
+
if (headingIndex === -1)
|
|
57
|
+
return { partitions, issues };
|
|
58
|
+
// find table header within the section (before the next ## heading)
|
|
59
|
+
let headerIndex = -1;
|
|
60
|
+
for (let i = headingIndex + 1; i < lines.length; i++) {
|
|
61
|
+
if (/^##\s+/.test(lines[i]) && !PARTITION_HEADING.test(lines[i]))
|
|
62
|
+
break;
|
|
63
|
+
if (PARTITION_TABLE_HEADER.test(lines[i])) {
|
|
64
|
+
headerIndex = i;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (headerIndex === -1) {
|
|
69
|
+
issues.push("MISSING_PARTITION_TABLE: Scenario Partitions section has no canonical header row");
|
|
70
|
+
return { partitions, issues };
|
|
71
|
+
}
|
|
72
|
+
const seen = new Set();
|
|
73
|
+
for (let i = headerIndex + 2; i < lines.length; i++) {
|
|
74
|
+
const line = lines[i];
|
|
75
|
+
if (!TABLE_ROW.test(line)) {
|
|
76
|
+
if (/^##\s+/.test(line) || /^#\s+/.test(line))
|
|
77
|
+
break;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const cells = splitCells(line);
|
|
81
|
+
if (cells.length !== 7) {
|
|
82
|
+
issues.push(`MISSING_PARTITION: row has ${cells.length} cells, expected 7: ${line.trim()}`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const [partitionId, operation, axis, domainRaw, requiredSlotsRaw, expectedBySlotRaw, bindRule,] = cells;
|
|
86
|
+
const domain = domainRaw
|
|
87
|
+
.split(/[;,,;]/)
|
|
88
|
+
.map((value) => value.trim())
|
|
89
|
+
.filter((value) => value.length > 0);
|
|
90
|
+
if (domain.length === 0) {
|
|
91
|
+
issues.push(`MISSING_PARTITION: empty domain: ${partitionId}`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const optional = /omit/i.test(requiredSlotsRaw);
|
|
95
|
+
const complementExpected = /empty[- ]?result/i.test(expectedBySlotRaw)
|
|
96
|
+
? "empty-result"
|
|
97
|
+
: /excluded[- ]?result/i.test(expectedBySlotRaw)
|
|
98
|
+
? "excluded-result"
|
|
99
|
+
: "unknown";
|
|
100
|
+
if (seen.has(partitionId)) {
|
|
101
|
+
issues.push(`MISSING_PARTITION: duplicate partition id ${partitionId}`);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
seen.add(partitionId);
|
|
105
|
+
const domainIssue = describeInvalidPartitionDomain(domain);
|
|
106
|
+
if (domainIssue) {
|
|
107
|
+
issues.push(`INVALID_PARTITION_DOMAIN: partition ${partitionId} ${domainIssue}`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
partitions.push({
|
|
111
|
+
partitionId,
|
|
112
|
+
operation,
|
|
113
|
+
axis,
|
|
114
|
+
domain,
|
|
115
|
+
optional,
|
|
116
|
+
complementExpected,
|
|
117
|
+
bindRule,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return { partitions, issues };
|
|
121
|
+
}
|
|
122
|
+
function slotToken(value) {
|
|
123
|
+
return value
|
|
124
|
+
.trim()
|
|
125
|
+
.toUpperCase()
|
|
126
|
+
.replace(/[^A-Z0-9]+/g, "-")
|
|
127
|
+
.replace(/^-+|-+$/g, "");
|
|
128
|
+
}
|
|
129
|
+
function describeInvalidPartitionDomain(domain) {
|
|
130
|
+
const tokens = [];
|
|
131
|
+
const seenTokens = new Set();
|
|
132
|
+
for (const value of domain) {
|
|
133
|
+
if (!FINITE_DOMAIN_MEMBER.test(value)) {
|
|
134
|
+
return `value is not a finite identifier member: ${value}`;
|
|
135
|
+
}
|
|
136
|
+
const token = slotToken(value);
|
|
137
|
+
if (!token) {
|
|
138
|
+
return `value cannot produce a stable non-empty slot token: ${value}`;
|
|
139
|
+
}
|
|
140
|
+
if (RESERVED_SLOT_TOKENS.has(token)) {
|
|
141
|
+
return `value maps to reserved slot token ${token}: ${value}`;
|
|
142
|
+
}
|
|
143
|
+
if (seenTokens.has(token)) {
|
|
144
|
+
return `values collapse to duplicate slot token ${token}`;
|
|
145
|
+
}
|
|
146
|
+
seenTokens.add(token);
|
|
147
|
+
tokens.push(token);
|
|
148
|
+
}
|
|
149
|
+
return tokens.length === 0 ? "domain produced no slot tokens" : undefined;
|
|
150
|
+
}
|
|
151
|
+
function normalizePartitionAxisName(value) {
|
|
152
|
+
return value.trim().replace(/^(?:query|path)\s*:\s*/i, "").toLowerCase();
|
|
153
|
+
}
|
|
154
|
+
function normalizePartitionOperation(value) {
|
|
155
|
+
return value.trim().replace(/\s+/g, " ").toUpperCase();
|
|
156
|
+
}
|
|
157
|
+
function sameFiniteSet(left, right) {
|
|
158
|
+
if (left.length !== right.length)
|
|
159
|
+
return false;
|
|
160
|
+
const expected = new Set(left);
|
|
161
|
+
return right.every((value) => expected.has(value));
|
|
162
|
+
}
|
|
163
|
+
const STABLE_PARTITION_ID = /^SP-[A-Z0-9][A-Z0-9._-]*$/i;
|
|
164
|
+
/**
|
|
165
|
+
* Slot IDs are `TP-<Partition ID>-<VALUE|OMITTED|NOT-IN-SET>`.
|
|
166
|
+
* The declared Partition ID is the single source of truth so model-written
|
|
167
|
+
* `TP-SP-GET-API-...` IDs match the expander (never drop the HTTP method).
|
|
168
|
+
*/
|
|
169
|
+
function partitionToken(row) {
|
|
170
|
+
const id = row.partitionId.trim();
|
|
171
|
+
if (STABLE_PARTITION_ID.test(id))
|
|
172
|
+
return `TP-${id.toUpperCase()}`;
|
|
173
|
+
return `TP-SP-${slotToken(row.operation)}-${slotToken(row.axis)}`;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Deterministically expand declared partitions into slots. Pure function:
|
|
177
|
+
* same input rows always produce the same slot IDs.
|
|
178
|
+
*/
|
|
179
|
+
export function expandScenarioPartitions(partitions) {
|
|
180
|
+
const slots = [];
|
|
181
|
+
for (const row of partitions) {
|
|
182
|
+
if (describeInvalidPartitionDomain(row.domain))
|
|
183
|
+
continue;
|
|
184
|
+
const prefix = partitionToken(row);
|
|
185
|
+
for (const value of row.domain) {
|
|
186
|
+
const token = slotToken(value);
|
|
187
|
+
slots.push({
|
|
188
|
+
partitionId: row.partitionId,
|
|
189
|
+
operation: row.operation,
|
|
190
|
+
axis: row.axis,
|
|
191
|
+
slotId: `${prefix}-${token}`,
|
|
192
|
+
kind: "each-value",
|
|
193
|
+
value,
|
|
194
|
+
intent: "nominal-filter",
|
|
195
|
+
expected: "domain-value",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (row.optional) {
|
|
199
|
+
slots.push({
|
|
200
|
+
partitionId: row.partitionId,
|
|
201
|
+
operation: row.operation,
|
|
202
|
+
axis: row.axis,
|
|
203
|
+
slotId: `${prefix}-OMITTED`,
|
|
204
|
+
kind: "omitted",
|
|
205
|
+
value: "",
|
|
206
|
+
intent: "omitted",
|
|
207
|
+
expected: "default-behavior",
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
slots.push({
|
|
211
|
+
partitionId: row.partitionId,
|
|
212
|
+
operation: row.operation,
|
|
213
|
+
axis: row.axis,
|
|
214
|
+
slotId: `${prefix}-NOT-IN-SET`,
|
|
215
|
+
kind: "not-in-set",
|
|
216
|
+
value: "",
|
|
217
|
+
intent: "enum-invalid",
|
|
218
|
+
expected: row.complementExpected === "unknown" ? "GAP" : "complement",
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return slots;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Bind parsed partitions to GET query/path OpenAPI axes.
|
|
225
|
+
* Unconfirmed axes keep identifier-valid Domain rows (requirement finite sets).
|
|
226
|
+
*/
|
|
227
|
+
export function applyOpenApiPartitionDomainPolicy(partitions, axes) {
|
|
228
|
+
const issues = [];
|
|
229
|
+
const valid = [];
|
|
230
|
+
for (const row of partitions) {
|
|
231
|
+
const operation = normalizePartitionOperation(row.operation);
|
|
232
|
+
const axisName = normalizePartitionAxisName(row.axis);
|
|
233
|
+
const matched = axes.find((axis) => normalizePartitionOperation(axis.operation) === operation &&
|
|
234
|
+
normalizePartitionAxisName(axis.name) === axisName);
|
|
235
|
+
if (!matched) {
|
|
236
|
+
valid.push(row);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (matched.enumValues === null) {
|
|
240
|
+
issues.push(`NO_FINITE_DOMAIN: partition ${row.partitionId} axis ${row.axis} exists on ${row.operation} without a documented enum`);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (!sameFiniteSet(matched.enumValues, row.domain)) {
|
|
244
|
+
issues.push(`PARTITION_SOURCE_MISMATCH: partition ${row.partitionId} domain does not match OpenAPI enum for ${row.operation} ${row.axis}`);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
valid.push(row);
|
|
248
|
+
}
|
|
249
|
+
return { partitions: valid, issues };
|
|
250
|
+
}
|
|
251
|
+
/** Facts projection consumed by N6 and plan D gap-fill. */
|
|
252
|
+
export const scenarioPartitionFactsSchema = z.object({
|
|
253
|
+
partitions: z.array(z.object({
|
|
254
|
+
partitionId: z.string(),
|
|
255
|
+
operation: z.string(),
|
|
256
|
+
axis: z.string(),
|
|
257
|
+
domain: z.array(z.string()),
|
|
258
|
+
optional: z.boolean(),
|
|
259
|
+
complementExpected: z.string(),
|
|
260
|
+
bindRule: z.string(),
|
|
261
|
+
slotIds: z.array(z.string()),
|
|
262
|
+
})),
|
|
263
|
+
slots: z.array(z.object({
|
|
264
|
+
slotId: z.string(),
|
|
265
|
+
partitionId: z.string(),
|
|
266
|
+
kind: z.string(),
|
|
267
|
+
intent: z.string(),
|
|
268
|
+
expected: z.string(),
|
|
269
|
+
})),
|
|
270
|
+
missingSlots: z.array(z.string()),
|
|
271
|
+
});
|
|
272
|
+
/**
|
|
273
|
+
* Compare expanded slots against the Case test-point universe. A slot is
|
|
274
|
+
* covered only when its exact slot ID is declared in some Case's 测试点 or
|
|
275
|
+
* binding lists. Missing each-value/omitted slots and the complement slot
|
|
276
|
+
* produce fail-closed finding codes; a GAP-expected complement that IS
|
|
277
|
+
* covered still reports a coverage gap (expected semantics undocumented).
|
|
278
|
+
*/
|
|
279
|
+
export function assessScenarioPartitionCoverage(partitions, declaredTestPointIds) {
|
|
280
|
+
const slots = expandScenarioPartitions(partitions);
|
|
281
|
+
const findings = [];
|
|
282
|
+
const missingSlots = [];
|
|
283
|
+
for (const row of partitions) {
|
|
284
|
+
const rowSlots = slots.filter((slot) => slot.partitionId === row.partitionId);
|
|
285
|
+
for (const slot of rowSlots) {
|
|
286
|
+
if (!declaredTestPointIds.has(slot.slotId)) {
|
|
287
|
+
missingSlots.push(slot.slotId);
|
|
288
|
+
const code = slot.kind === "not-in-set" ? "MISSING_COMPLEMENT_SLOT" : "MISSING_PARTITION_SLOT";
|
|
289
|
+
findings.push(`${code}: partition ${row.partitionId} slot ${slot.slotId} (${slot.kind}${slot.value ? `=${slot.value}` : ""}) is not covered by any Case test point`);
|
|
290
|
+
}
|
|
291
|
+
else if (slot.kind === "not-in-set" && slot.expected === "GAP") {
|
|
292
|
+
findings.push(`PARTITION_COMPLEMENT_EXPECTED_GAP: partition ${row.partitionId} complement slot ${slot.slotId} has no documented expected semantics in the bound source; expected stays GAP instead of guessing`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
facts: {
|
|
298
|
+
partitions: partitions.map((row) => ({
|
|
299
|
+
partitionId: row.partitionId,
|
|
300
|
+
operation: row.operation,
|
|
301
|
+
axis: row.axis,
|
|
302
|
+
domain: row.domain,
|
|
303
|
+
optional: row.optional,
|
|
304
|
+
complementExpected: row.complementExpected,
|
|
305
|
+
bindRule: row.bindRule,
|
|
306
|
+
slotIds: slots
|
|
307
|
+
.filter((slot) => slot.partitionId === row.partitionId)
|
|
308
|
+
.map((slot) => slot.slotId),
|
|
309
|
+
})),
|
|
310
|
+
slots: slots.map((slot) => ({
|
|
311
|
+
slotId: slot.slotId,
|
|
312
|
+
partitionId: slot.partitionId,
|
|
313
|
+
kind: slot.kind,
|
|
314
|
+
intent: slot.intent,
|
|
315
|
+
expected: slot.expected,
|
|
316
|
+
})),
|
|
317
|
+
missingSlots,
|
|
318
|
+
},
|
|
319
|
+
findings,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
@@ -2,7 +2,8 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { access, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { isPriorityOnlyBackendTestModuleStem } from "./backend-test-module-stem.js";
|
|
5
|
+
import { BACKEND_TEST_MAX_MODULE_COUNT, isOpaqueHashBackendTestModuleStem, isPriorityOnlyBackendTestModuleStem, } from "./backend-test-module-stem.js";
|
|
6
|
+
import { resolveBackendTestLayout, } from "./backend-test-layout.js";
|
|
6
7
|
/**
|
|
7
8
|
* Maps a backend-test generation writer task id to its writer-progress role.
|
|
8
9
|
* Returns undefined for nodes that are not completeness-gated generators.
|
|
@@ -103,6 +104,8 @@ function moduleStemInvalidReason(raw) {
|
|
|
103
104
|
const stem = normalizeBackendTestModuleStem(raw);
|
|
104
105
|
if (isPriorityOnlyBackendTestModuleStem(stem))
|
|
105
106
|
return "priority-only-module-stem";
|
|
107
|
+
if (isOpaqueHashBackendTestModuleStem(stem))
|
|
108
|
+
return "opaque-hash-module-stem";
|
|
106
109
|
if (stem.toLowerCase() === "readme")
|
|
107
110
|
return "reserved-module-stem";
|
|
108
111
|
if (!/^[a-z][a-z0-9_]*$/.test(stem))
|
|
@@ -114,28 +117,40 @@ function moduleStemInvalidReason(raw) {
|
|
|
114
117
|
function looksLikeValidModuleStem(raw) {
|
|
115
118
|
return moduleStemInvalidReason(raw) === undefined;
|
|
116
119
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const candidates = [];
|
|
120
|
-
const tableRowLines = readme
|
|
120
|
+
function extractCanonicalModuleIndexSection(readme) {
|
|
121
|
+
const lines = readme
|
|
121
122
|
.replaceAll("\r\n", "\n")
|
|
122
123
|
.replaceAll("\r", "\n")
|
|
123
|
-
.split("\n")
|
|
124
|
-
|
|
124
|
+
.split("\n");
|
|
125
|
+
const headings = lines.flatMap((line, index) => line.trim() === "## Module Index" ? [index] : []);
|
|
126
|
+
if (headings.length === 0) {
|
|
127
|
+
return { section: "", structureIssues: ["missing-module-index"] };
|
|
128
|
+
}
|
|
129
|
+
if (headings.length > 1) {
|
|
130
|
+
return { section: "", structureIssues: ["duplicate-module-index"] };
|
|
131
|
+
}
|
|
132
|
+
const start = headings[0] + 1;
|
|
133
|
+
const relativeEnd = lines.slice(start).findIndex((line) => /^##\s+\S/.test(line.trim()));
|
|
134
|
+
const end = relativeEnd < 0 ? lines.length : start + relativeEnd;
|
|
135
|
+
return { section: lines.slice(start, end).join("\n"), structureIssues: [] };
|
|
136
|
+
}
|
|
137
|
+
/** Inspect only candidates inside the unique exact `## Module Index` section. */
|
|
138
|
+
export function inspectModuleStemsFromReadme(readme) {
|
|
139
|
+
const extracted = extractCanonicalModuleIndexSection(readme);
|
|
140
|
+
const candidates = [];
|
|
141
|
+
const tableRowLines = extracted.section.split("\n").filter((line) => line.includes("|"));
|
|
125
142
|
for (const line of tableRowLines) {
|
|
126
143
|
for (const match of line.matchAll(/`?testcase\/md\/([A-Za-z0-9_.-]+)\.md`?/g)) {
|
|
127
144
|
candidates.push(match[1]);
|
|
128
145
|
}
|
|
129
146
|
for (const match of line.matchAll(/\|\s*`?([A-Za-z0-9_.-]+)`?\s*\|\s*`?testcase\/test_/g)) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (looksLikeValidModuleStem(match[1]) || isPriorityOnlyBackendTestModuleStem(match[1])) {
|
|
134
|
-
candidates.push(match[1]);
|
|
147
|
+
const raw = match[1];
|
|
148
|
+
if (looksLikeValidModuleStem(raw) || moduleStemInvalidReason(raw) !== "invalid-syntax") {
|
|
149
|
+
candidates.push(raw);
|
|
135
150
|
}
|
|
136
151
|
}
|
|
137
152
|
}
|
|
138
|
-
for (const match of
|
|
153
|
+
for (const match of extracted.section.matchAll(/\[[^\]]+\]\(\.\/([A-Za-z0-9_.-]+)\.md\)/g)) {
|
|
139
154
|
candidates.push(match[1]);
|
|
140
155
|
}
|
|
141
156
|
const uniqueCandidates = orderedUnique(candidates);
|
|
@@ -145,12 +160,18 @@ export function inspectModuleStemsFromReadme(readme) {
|
|
|
145
160
|
? [{ raw, normalized: normalizeBackendTestModuleStem(raw), reasonCode }]
|
|
146
161
|
: [];
|
|
147
162
|
});
|
|
163
|
+
const validStems = orderedUnique(uniqueCandidates
|
|
164
|
+
.filter(looksLikeValidModuleStem)
|
|
165
|
+
.map((stem) => normalizeBackendTestModuleStem(stem)));
|
|
166
|
+
const structureIssues = [...extracted.structureIssues];
|
|
167
|
+
if (validStems.length > BACKEND_TEST_MAX_MODULE_COUNT) {
|
|
168
|
+
structureIssues.push(`excessive-module-count: ${validStems.length} > ${BACKEND_TEST_MAX_MODULE_COUNT}`);
|
|
169
|
+
}
|
|
148
170
|
return {
|
|
149
171
|
candidates: uniqueCandidates,
|
|
150
|
-
validStems
|
|
151
|
-
.filter(looksLikeValidModuleStem)
|
|
152
|
-
.map((stem) => normalizeBackendTestModuleStem(stem))),
|
|
172
|
+
validStems,
|
|
153
173
|
invalid,
|
|
174
|
+
structureIssues,
|
|
154
175
|
};
|
|
155
176
|
}
|
|
156
177
|
/**
|
|
@@ -161,8 +182,9 @@ export function inspectModuleStemsFromReadme(readme) {
|
|
|
161
182
|
export function extractModuleStemsFromReadme(readme) {
|
|
162
183
|
return inspectModuleStemsFromReadme(readme).validStems;
|
|
163
184
|
}
|
|
164
|
-
async function listMarkdownModules(workspaceRoot) {
|
|
165
|
-
const
|
|
185
|
+
async function listMarkdownModules(workspaceRoot, layout) {
|
|
186
|
+
const resolved = layout ?? resolveBackendTestLayout(undefined);
|
|
187
|
+
const root = path.join(workspaceRoot, ...resolved.markdownDir.split("/"));
|
|
166
188
|
if (!(await exists(root)))
|
|
167
189
|
return [];
|
|
168
190
|
const entries = await readdir(root, { withFileTypes: true });
|
|
@@ -170,7 +192,7 @@ async function listMarkdownModules(workspaceRoot) {
|
|
|
170
192
|
.filter((entry) => entry.isFile() &&
|
|
171
193
|
entry.name.toLowerCase().endsWith(".md") &&
|
|
172
194
|
entry.name.toLowerCase() !== "readme.md")
|
|
173
|
-
.map((entry) => path.posix.join(
|
|
195
|
+
.map((entry) => path.posix.join(resolved.markdownDir, entry.name.replaceAll("\\", "/"))));
|
|
174
196
|
}
|
|
175
197
|
/**
|
|
176
198
|
* Recursively list `.py` file paths relative to `dirRoot` (forward slashes).
|
|
@@ -363,12 +385,17 @@ export const FORBIDDEN_GENERATED_IMPORT_NAMESPACES = [
|
|
|
363
385
|
"testcase.helpers",
|
|
364
386
|
"testcase.factories",
|
|
365
387
|
];
|
|
366
|
-
const FORBIDDEN_GENERATED_IMPORT_PATTERN = /^\s*(?:from\s+(testcase_helpers|testcase\.helpers|testcase\.factories)\b|import\s+(testcase_helpers|testcase\.helpers|testcase\.factories)\b)/gm;
|
|
388
|
+
const FORBIDDEN_GENERATED_IMPORT_PATTERN = /^\s*(?:from\s+(testcase_helpers|testcase\.helpers|testcase\.factories|__\d+[A-Za-z0-9_]*)\b|import\s+(testcase_helpers|testcase\.helpers|testcase\.factories|__\d+[A-Za-z0-9_]*)\b)/gm;
|
|
367
389
|
/**
|
|
368
|
-
* Scan Python source for imports from forbidden generated shared namespaces
|
|
369
|
-
* Anchored at line start with a word
|
|
370
|
-
* substring inside a docstring
|
|
371
|
-
*
|
|
390
|
+
* Scan Python source for imports from forbidden generated shared namespaces
|
|
391
|
+
* or bare generated-local helper modules. Anchored at line start with a word
|
|
392
|
+
* boundary so `testcase.helpers_other` or a substring inside a docstring is not
|
|
393
|
+
* falsely flagged. In addition to the `testcase_helpers`/`testcase.helpers`/
|
|
394
|
+
* `testcase.factories` namespaces, bare `__<digits>...` helper modules (for
|
|
395
|
+
* example `from __551_manager_helpers import ...` / `import __551_manager_helpers`)
|
|
396
|
+
* are generated-local shared dependencies: a self-contained pytest child must
|
|
397
|
+
* inline or remove them rather than depend on a generated shared module.
|
|
398
|
+
* Returns the list of offending module names (deduplicated, order-preserving).
|
|
372
399
|
*/
|
|
373
400
|
export function findForbiddenGeneratedImports(source) {
|
|
374
401
|
const hits = [];
|
|
@@ -550,56 +577,58 @@ export async function assessBackendTestShardChildCompleteness(input) {
|
|
|
550
577
|
issues,
|
|
551
578
|
};
|
|
552
579
|
}
|
|
553
|
-
export async function assessBackendTestMdWriterCompleteness(workspaceRoot) {
|
|
580
|
+
export async function assessBackendTestMdWriterCompleteness(workspaceRoot, layout) {
|
|
581
|
+
const resolved = layout ?? resolveBackendTestLayout(undefined);
|
|
582
|
+
const readmeRel = resolved.readmePath;
|
|
554
583
|
const issues = [];
|
|
555
|
-
const expectedPaths = [
|
|
584
|
+
const expectedPaths = [readmeRel];
|
|
556
585
|
const actualPaths = [];
|
|
557
586
|
const missingPaths = [];
|
|
558
587
|
const brokenPaths = [];
|
|
559
|
-
const readmePath = path.join(workspaceRoot, "
|
|
588
|
+
const readmePath = path.join(workspaceRoot, ...readmeRel.split("/"));
|
|
560
589
|
let readme = "";
|
|
561
590
|
if (!(await exists(readmePath))) {
|
|
562
|
-
missingPaths.push(
|
|
591
|
+
missingPaths.push(readmeRel);
|
|
563
592
|
issues.push({
|
|
564
593
|
code: "T3",
|
|
565
|
-
path:
|
|
594
|
+
path: readmeRel,
|
|
566
595
|
detail: "README.md is missing",
|
|
567
596
|
recoverable: true,
|
|
568
597
|
});
|
|
569
598
|
}
|
|
570
599
|
else {
|
|
571
|
-
actualPaths.push(
|
|
600
|
+
actualPaths.push(readmeRel);
|
|
572
601
|
readme = await readFile(readmePath, "utf8");
|
|
573
602
|
if (!readme.includes("## Coverage Scope")) {
|
|
574
|
-
brokenPaths.push(
|
|
603
|
+
brokenPaths.push(readmeRel);
|
|
575
604
|
issues.push({
|
|
576
605
|
code: "T5",
|
|
577
|
-
path:
|
|
606
|
+
path: readmeRel,
|
|
578
607
|
detail: "README.md is missing ## Coverage Scope",
|
|
579
608
|
recoverable: true,
|
|
580
609
|
});
|
|
581
610
|
}
|
|
582
611
|
if (!readme.includes("## Coverage Matrix") ||
|
|
583
612
|
!hasMarkdownTable(readme, "Rule Key")) {
|
|
584
|
-
if (!brokenPaths.includes(
|
|
585
|
-
brokenPaths.push(
|
|
613
|
+
if (!brokenPaths.includes(readmeRel)) {
|
|
614
|
+
brokenPaths.push(readmeRel);
|
|
586
615
|
}
|
|
587
616
|
issues.push({
|
|
588
617
|
code: "T5",
|
|
589
|
-
path:
|
|
618
|
+
path: readmeRel,
|
|
590
619
|
detail: "README.md Coverage Matrix table is missing or broken",
|
|
591
620
|
recoverable: true,
|
|
592
621
|
});
|
|
593
622
|
}
|
|
594
623
|
}
|
|
595
624
|
const indexedStems = extractModuleStemsFromReadme(readme);
|
|
596
|
-
const existingModules = await listMarkdownModules(workspaceRoot);
|
|
625
|
+
const existingModules = await listMarkdownModules(workspaceRoot, resolved);
|
|
597
626
|
const existingByStem = new Map(existingModules.map((rel) => [
|
|
598
627
|
normalizeBackendTestModuleStem(path.posix.basename(rel)),
|
|
599
628
|
rel,
|
|
600
629
|
]));
|
|
601
630
|
for (const stem of indexedStems) {
|
|
602
|
-
const rel =
|
|
631
|
+
const rel = `${resolved.markdownDir}/${stem}.md`;
|
|
603
632
|
expectedPaths.push(rel);
|
|
604
633
|
const existing = existingByStem.get(stem);
|
|
605
634
|
if (!existing) {
|
|
@@ -668,15 +697,16 @@ export async function assessBackendTestMdWriterCompleteness(workspaceRoot) {
|
|
|
668
697
|
issues,
|
|
669
698
|
};
|
|
670
699
|
}
|
|
671
|
-
export async function assessBackendTestPytestWriterCompleteness(workspaceRoot) {
|
|
700
|
+
export async function assessBackendTestPytestWriterCompleteness(workspaceRoot, layout) {
|
|
701
|
+
const resolved = layout ?? resolveBackendTestLayout(undefined);
|
|
672
702
|
const issues = [];
|
|
673
703
|
const expectedPaths = [];
|
|
674
704
|
const actualPaths = [];
|
|
675
705
|
const missingPaths = [];
|
|
676
706
|
const brokenPaths = [];
|
|
677
|
-
const modules = await listMarkdownModules(workspaceRoot);
|
|
707
|
+
const modules = await listMarkdownModules(workspaceRoot, resolved);
|
|
678
708
|
for (const moduleRel of modules) {
|
|
679
|
-
const expected = expectedBackendTestPytestScriptForMarkdownModule(path.posix.basename(moduleRel));
|
|
709
|
+
const expected = expectedBackendTestPytestScriptForMarkdownModule(path.posix.basename(moduleRel), resolved);
|
|
680
710
|
expectedPaths.push(expected);
|
|
681
711
|
const absolute = path.join(workspaceRoot, expected);
|
|
682
712
|
if (!(await exists(absolute))) {
|
|
@@ -836,6 +866,11 @@ export function isBackendTestCompletenessRetryCandidate(task) {
|
|
|
836
866
|
task.id === "generate-backend-pytest-pi" ||
|
|
837
867
|
task.id === "generate-backend-md-plan-pi" ||
|
|
838
868
|
task.id === "generate-backend-pytest-plan-pi" ||
|
|
869
|
+
// Plan D gap-fill patch writers share the same Completeness Gate retry
|
|
870
|
+
// contract as full-chain writers.
|
|
871
|
+
task.id === "patch-backend-readme-matrix-pi" ||
|
|
872
|
+
(task.id?.startsWith("patch-backend-md-case-") ?? false) ||
|
|
873
|
+
(task.id?.startsWith("patch-backend-pytest-case-") ?? false) ||
|
|
839
874
|
(task.id?.startsWith("generate-backend-md-case-") ?? false) ||
|
|
840
875
|
(task.id?.startsWith("generate-backend-pytest-case-") ?? false));
|
|
841
876
|
}
|
|
@@ -872,42 +907,44 @@ export function isBackendTestPytestPlanTask(task) {
|
|
|
872
907
|
* index), WITHOUT requiring the indexed module files to exist yet — those are
|
|
873
908
|
* written by the downstream map_agent shard children.
|
|
874
909
|
*/
|
|
875
|
-
export async function assessBackendTestMdPlanCompleteness(workspaceRoot) {
|
|
910
|
+
export async function assessBackendTestMdPlanCompleteness(workspaceRoot, layout) {
|
|
911
|
+
const resolvedPlan = layout ?? resolveBackendTestLayout(undefined);
|
|
912
|
+
const planReadmeRel = resolvedPlan.readmePath;
|
|
876
913
|
const issues = [];
|
|
877
|
-
const expectedPaths = [
|
|
914
|
+
const expectedPaths = [planReadmeRel];
|
|
878
915
|
const actualPaths = [];
|
|
879
916
|
const missingPaths = [];
|
|
880
917
|
const brokenPaths = [];
|
|
881
|
-
const readmePath = path.join(workspaceRoot, "
|
|
918
|
+
const readmePath = path.join(workspaceRoot, ...planReadmeRel.split("/"));
|
|
882
919
|
if (!(await exists(readmePath))) {
|
|
883
|
-
missingPaths.push(
|
|
920
|
+
missingPaths.push(planReadmeRel);
|
|
884
921
|
issues.push({
|
|
885
922
|
code: "T3",
|
|
886
|
-
path:
|
|
923
|
+
path: planReadmeRel,
|
|
887
924
|
detail: "README.md is missing",
|
|
888
925
|
recoverable: true,
|
|
889
926
|
});
|
|
890
927
|
}
|
|
891
928
|
else {
|
|
892
|
-
actualPaths.push(
|
|
929
|
+
actualPaths.push(planReadmeRel);
|
|
893
930
|
const readme = await readFile(readmePath, "utf8");
|
|
894
931
|
if (!readme.includes("## Coverage Scope")) {
|
|
895
|
-
brokenPaths.push(
|
|
932
|
+
brokenPaths.push(planReadmeRel);
|
|
896
933
|
issues.push({
|
|
897
934
|
code: "T5",
|
|
898
|
-
path:
|
|
935
|
+
path: planReadmeRel,
|
|
899
936
|
detail: "README.md is missing ## Coverage Scope",
|
|
900
937
|
recoverable: true,
|
|
901
938
|
});
|
|
902
939
|
}
|
|
903
940
|
if (!readme.includes("## Coverage Matrix") ||
|
|
904
941
|
!hasMarkdownTable(readme, "Rule Key")) {
|
|
905
|
-
if (!brokenPaths.includes(
|
|
906
|
-
brokenPaths.push(
|
|
942
|
+
if (!brokenPaths.includes(planReadmeRel)) {
|
|
943
|
+
brokenPaths.push(planReadmeRel);
|
|
907
944
|
}
|
|
908
945
|
issues.push({
|
|
909
946
|
code: "T5",
|
|
910
|
-
path:
|
|
947
|
+
path: planReadmeRel,
|
|
911
948
|
detail: "README.md Coverage Matrix table is missing or broken",
|
|
912
949
|
recoverable: true,
|
|
913
950
|
});
|
|
@@ -917,24 +954,35 @@ export async function assessBackendTestMdPlanCompleteness(workspaceRoot) {
|
|
|
917
954
|
// are produced by map_agent children and are NOT required here.
|
|
918
955
|
const stemInspection = inspectModuleStemsFromReadme(readme);
|
|
919
956
|
const indexedStems = stemInspection.validStems;
|
|
957
|
+
for (const structureIssue of stemInspection.structureIssues) {
|
|
958
|
+
if (!brokenPaths.includes(planReadmeRel)) {
|
|
959
|
+
brokenPaths.push(planReadmeRel);
|
|
960
|
+
}
|
|
961
|
+
issues.push({
|
|
962
|
+
code: "T5",
|
|
963
|
+
path: planReadmeRel,
|
|
964
|
+
detail: structureIssue,
|
|
965
|
+
recoverable: true,
|
|
966
|
+
});
|
|
967
|
+
}
|
|
920
968
|
for (const invalid of stemInspection.invalid) {
|
|
921
|
-
if (!brokenPaths.includes(
|
|
922
|
-
brokenPaths.push(
|
|
969
|
+
if (!brokenPaths.includes(planReadmeRel)) {
|
|
970
|
+
brokenPaths.push(planReadmeRel);
|
|
923
971
|
}
|
|
924
972
|
issues.push({
|
|
925
973
|
code: "T5",
|
|
926
|
-
path:
|
|
974
|
+
path: planReadmeRel,
|
|
927
975
|
detail: `${invalid.reasonCode}: ${invalid.normalized}; use a stable lowercase business resource/domain stem`,
|
|
928
976
|
recoverable: true,
|
|
929
977
|
});
|
|
930
978
|
}
|
|
931
979
|
if (indexedStems.length === 0) {
|
|
932
|
-
if (!brokenPaths.includes(
|
|
933
|
-
brokenPaths.push(
|
|
980
|
+
if (!brokenPaths.includes(planReadmeRel)) {
|
|
981
|
+
brokenPaths.push(planReadmeRel);
|
|
934
982
|
}
|
|
935
983
|
issues.push({
|
|
936
984
|
code: "T5",
|
|
937
|
-
path:
|
|
985
|
+
path: planReadmeRel,
|
|
938
986
|
detail: "README.md module index is empty; downstream map_agent has no stems to shard",
|
|
939
987
|
recoverable: true,
|
|
940
988
|
});
|
|
@@ -960,16 +1008,17 @@ export async function assessBackendTestMdPlanCompleteness(workspaceRoot) {
|
|
|
960
1008
|
* requiring per-module pytest scripts to exist yet — those are written by the
|
|
961
1009
|
* downstream map_agent shard children.
|
|
962
1010
|
*/
|
|
963
|
-
export async function assessBackendTestPytestPlanCompleteness(workspaceRoot) {
|
|
1011
|
+
export async function assessBackendTestPytestPlanCompleteness(workspaceRoot, layout) {
|
|
1012
|
+
const resolvedPlan = layout ?? resolveBackendTestLayout(undefined);
|
|
964
1013
|
const issues = [];
|
|
965
1014
|
const expectedPaths = [];
|
|
966
1015
|
const actualPaths = [];
|
|
967
1016
|
const missingPaths = [];
|
|
968
1017
|
const brokenPaths = [];
|
|
969
|
-
// Discover shared pytest resources under
|
|
970
|
-
//
|
|
1018
|
+
// Discover shared pytest resources under <testRoot>/**/helpers/** and
|
|
1019
|
+
// <testRoot>/**/factories/** (the plan node writeSet globs).
|
|
971
1020
|
const candidates = [];
|
|
972
|
-
for (const dir of [
|
|
1021
|
+
for (const dir of [`${resolvedPlan.testRoot}/helpers`, `${resolvedPlan.testRoot}/factories`]) {
|
|
973
1022
|
const absolute = path.join(workspaceRoot, dir);
|
|
974
1023
|
if (await exists(absolute)) {
|
|
975
1024
|
for (const file of await listPythonFiles(absolute)) {
|