@tea-agent/loop-agent 0.38.1 → 0.38.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +126 -12
- package/README.md +2 -0
- package/bin/loop-agent.js +7 -3
- package/dist/application/dag/generate-task-dag.js +9 -0
- package/dist/application/task-lifecycle/advance.js +4 -0
- package/dist/build-stamp.json +3 -3
- package/dist/commands/init.js +2 -14
- package/dist/commands/task-advance.js +31 -0
- package/dist/executors/dag-pi-executor.js +99 -5
- 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-sdk-executor.js +187 -59
- package/dist/executors/shell-executor.js +324 -16
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/governance/manifest-types.js +0 -2
- package/dist/shared/operator/capabilities.js +87 -22
- package/dist/task/config-types.js +22 -0
- package/dist/task/contract/constants.js +1 -0
- package/dist/task/contract/diff.js +26 -0
- package/dist/task/contract/project.js +5 -0
- package/dist/task/contract/schema.js +6 -1
- package/dist/task/source-prepare/build-draft.js +15 -0
- package/dist/worker/console/chat/deferred-turn.js +91 -0
- package/dist/worker/console/chat/pi-runtime.js +10 -5
- package/dist/worker/console/chat/routes.js +458 -47
- package/dist/worker/console/chat/semantic-activity.js +13 -13
- package/dist/worker/console/chat/session-store.js +146 -2
- package/dist/worker/console/harness-pi-model-matrix.js +2 -11
- package/dist/worker/console/operator-actions.js +5 -4
- package/dist/worker/console/pi-readiness.js +5 -4
- package/dist/worker/console/recovery-cta.js +4 -4
- package/dist/worker/console/recovery-error-copy.js +4 -4
- package/dist/worker/console/server.js +6 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Hfsot6rP.js → abnfDiagram-N423BO3Z-C4kVYweA.js} +1 -1
- package/dist/worker/console/static/assets/{arc-YGwEASin.js → arc-Bj2M8iO5.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CmOM0F92.js → architectureDiagram-T3A2C74G-CLlXe4-6.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DDLuaHC5.js → blockDiagram-VBNYF7ZC-psEp5xH0.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B0Um43W4.js → c4Diagram-5PPSVZJV-DeKcOCGJ.js} +1 -1
- package/dist/worker/console/static/assets/channel-CPF4N7pf.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DvBN4JZc.js → chunk-2GRJ4B5K-K0C744p1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B1UVgW4E.js → chunk-2Q5K7J3B-xbNw4J9-.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Cf0GgXzX.js → chunk-5RXB4S5H-DjLaSDUO.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DbNA24sj.js → chunk-5VM5RSS4-CtiPlKel.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Bi_0Be06.js → chunk-6Q2QTUOP-DZtolir7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-C8pPGvRd.js → chunk-GF5L2VYU-CPcdNeew.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BitQY8Z-.js → chunk-JWPE2WC7-NgHc6V37.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-U67GCiEo.js → chunk-KBJHAD2P-DQ_T-hg8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-IoJwXPBA.js → chunk-RYQCIY6F-DgLsxcVP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-YDZ_UHgE.js → chunk-XXDRQBXY-UrVoM9z1.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CUZgsiRC.js → cose-bilkent-JH36ORCC-CLkYvTb3.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-COmCh4pT.js → cynefin-VYW2F7L2-BwT_xKrE.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Ta6FSm68.js → cynefinDiagram-MW4NZA55-Bi9tuzif.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-UdcR-VW9.js → dagre-VZM6K2ZE-BCIqkWBV.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DybcO3Et.js → diagram-7IWD3JNH-Dip6l9_Z.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-wErDFWLw.js → diagram-B4RE2ZJO-B-xkh_wK.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DkVHZWqz.js → diagram-LBJQPF4R-DZO0kTt_.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DdqWs7QN.js → diagram-Q27KOJAE-D6ZplNbZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-C9dMVmDo.js → diagram-UB23O5K3-BPekbhoS.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-B2T2WcBK.js → ebnfDiagram-BXEA7PRR-BtmaJpK5.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Bt9rjDFq.js → erDiagram-JOGREHBK-6DS0Xc44.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-U8ic_tvi.js → flowDiagram-UKHOOZJN-CihnROSm.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BfQJdQoO.js → ganttDiagram-PKOTCBZU-CZC4zOE2.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CpMftH4O.js → gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js} +1 -1
- package/dist/worker/console/static/assets/index-DTZOKgAn.js +404 -0
- package/dist/worker/console/static/assets/index-Ya5FE7cD.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DzqWXNO_.js → infoDiagram-6WML65LV-DlSl4BGx.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CJmGQ5Yp.js → ishikawaDiagram-WSZJBQD7-snnzFl_U.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-ChPtKFLz.js → journeyDiagram-NVQOT4AX-BDE7qpMx.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-PRe7wB5u.js → kanban-definition-27J2QSJJ-CD2Ci04G.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DpKX6QNp.js → linear-DdnapKIH.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Dnl76b7Y.js → mermaid.core-BVjAT9b8.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-S9PCXc3J.js → mindmap-definition-FAOFIHXS-D0yJk-zA.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Du8e-FSX.js → pegDiagram-VL7TDLO6-BVo9tFP-.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CY6OmHTc.js → pieDiagram-7S7Q4E2Y-DnOV-mZ_.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C65GPhpj.js → quadrantDiagram-CIZ2JOQS-CzUJo58i.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-U6IUiTCL.js → railroadDiagram-AXF67PYL-Bvikrolh.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-I_9Qhi.js → requirementDiagram-LRYGKXZP-DtaSVCap.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B57Fhed0.js → sankeyDiagram-W5VNT64P-C2c9A0wW.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CJOtiw21.js → sequenceDiagram-SI44F4Z6-DmXJcU7r.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-FyX8zxSh.js → sizeCapture-X5ZJPWSS-B4GUFW92.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-eF-pbs9t.js → stateDiagram-OKZ733FA-Dsad2MXf.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CcfkA-Dq.js → swimlanes-SLNWSIFB-DGq48Fbi.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DH8FjnOK.js → timeline-definition-Z64GVDOM-CSSi6OFf.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-beoTagJg.js → vennDiagram-T6HMQDX7-BqyzPrWv.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CQSfw4cK.js → wardleyDiagram-T6FBY63Y-DlznVb6S.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CQgzQ6jn.js → xychartDiagram-ELKLHX3M-CfBcgJ_K.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +0 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +1 -1
- package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +38 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -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 +42 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +204 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +23 -1
- package/dist/worker/observe/node-input.js +72 -3
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/state.js +2 -2
- package/dist/worker/observe/static/views/dag-inspector.js +51 -0
- package/dist/worker/observe/static/views/session-timeline.js +18 -5
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +809 -53
- 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 +167 -32
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +368 -33
- 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 +100 -56
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +2 -1
- package/dist/workflows/dag/convergence/controller.js +141 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
- package/dist/workflows/dag/failure-category.js +4 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +355 -26
- package/dist/workflows/dag/frontend-prewrite-gate.js +96 -84
- package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
- package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +10 -18
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
- package/dist/workflows/dag/init-hybrid.js +720 -177
- package/dist/workflows/dag/node-execution.js +164 -26
- package/dist/workflows/dag/prompt.js +60 -1
- package/dist/workflows/dag/rerun-feedback.js +212 -0
- package/dist/workflows/dag/rerun-plan.js +112 -27
- package/dist/workflows/dag/rerun-run.js +3 -0
- package/dist/workflows/dag/rerun-task.js +61 -2
- package/dist/workflows/dag/retry-policy.js +18 -0
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +121 -0
- package/dist/workflows/dag/validate.js +41 -1
- package/docs/operations/local-development-environment.md +1 -5
- package/docs/skills/vetted-skill-registry.md +14 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +44 -0
- package/docs/templates/backend-test-dag.json +60 -35
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/harness.schema.json +0 -8
- package/harness.json +1 -8
- package/package.json +8 -4
- 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/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/dist/worker/console/static/assets/channel-Dp_2Aqf1.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL--CODy37j.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE--CODy37j.js +0 -1
- package/dist/worker/console/static/assets/index-BA5-N5ER.css +0 -1
- package/dist/worker/console/static/assets/index-DKPXBrdc.js +0 -325
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B-mvnLEc.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-JtpBmIv4.js +0 -8
|
@@ -15,6 +15,8 @@ import { materializeBackendTestAnalysisContract } from "../workflows/dag/backend
|
|
|
15
15
|
import { extractBackendTestContractEnvelope } from "../workflows/dag/backend-test-contract-envelope.js";
|
|
16
16
|
import { materializeFrontendImplementationContract } from "../workflows/dag/frontend-implementation-contract.js";
|
|
17
17
|
import { materializeFrontendTestResult, validateFrontendCaseEvidence, } from "../workflows/dag/frontend-test-result-contract.js";
|
|
18
|
+
import { copyFrontendTestStandardScenarios } from "../workflows/dag/frontend-test-standard-scenarios.js";
|
|
19
|
+
import { probeFrontendTestEnvironment } from "../workflows/dag/frontend-test-environment-probe.js";
|
|
18
20
|
import { renderFrontendTestL5Report } from "../workflows/dag/frontend-test-l5-report.js";
|
|
19
21
|
import { validateFrontendCaseChecklist } from "../workflows/dag/frontend-test-case-checklist.js";
|
|
20
22
|
import { materializeFrontendTestCaseManifest } from "../workflows/dag/frontend-test-case-manifest.js";
|
|
@@ -27,11 +29,13 @@ import { formatFrontendReviewContextStdout, runFrontendReviewContextGate, } from
|
|
|
27
29
|
import { materializeFrontendLintAssessment, materializeFrontendLintBaseline, } from "../workflows/dag/frontend-lint-baseline.js";
|
|
28
30
|
import { formatTraceabilityGateStdout, materializeBackendTestCaseManifest, runBackendTestTraceabilityGate, } from "../workflows/dag/backend-test-case-manifest.js";
|
|
29
31
|
import { materializeBackendTestExecutionContract } from "../workflows/dag/backend-test-execution-contract.js";
|
|
32
|
+
import { resolveBackendTestLayout } from "../workflows/dag/backend-test-layout.js";
|
|
33
|
+
import { backendTestGapDocSchema, ingestBackendTestGap, } from "../workflows/dag/backend-test-gap-fill.js";
|
|
30
34
|
import { analyzeBackendTestCaseCoverage, analyzeBackendTestMarkdownPytestCorrespondence, materializeBackendTestCaseManifestFromFacts, } from "../workflows/dag/backend-test-case-coverage-analysis.js";
|
|
31
35
|
import { materializeBackendTestResultFromPytestHtml, materializeBackendTestResultFromRunDir, parsePytestHtmlReport, } from "../workflows/dag/backend-test-result-contract.js";
|
|
32
|
-
import { collectBackendTestHumanCaseCatalog, collectBackendTestMappedPytestScripts, resolveBackendTestMappedPytestScripts, collectJacocoCoverage, hasBlockingBackendMarkdownSafetyFindings, hasBlockingBackendMarkdownModuleStemFindings, inspectBackendTestEnvironment, requiredBackendMarkdownCaseAcIds, renderBackendTestFacts, renderBackendTestHtml, renderBackendTestL5Dashboard, redactBackendTestOutput, validateBackendMarkdownCases, validateBackendMarkdownTraceability, writeRunReport, } from "../workflows/dag/backend-test-markdown-workflow.js";
|
|
36
|
+
import { collectBackendTestHumanCaseCatalog, collectBackendTestMappedPytestScripts, resolveBackendTestMappedPytestScripts, collectJacocoCoverage, hasBlockingBackendMarkdownSafetyFindings, hasBlockingBackendMarkdownModuleStemFindings, inspectBackendTestEnvironment, markdownFiles, requiredBackendMarkdownCaseAcIds, renderBackendTestFacts, renderBackendTestHtml, renderBackendTestL5Dashboard, redactBackendTestOutput, validateBackendMarkdownCases, validateBackendMarkdownTraceability, writeRunReport, } from "../workflows/dag/backend-test-markdown-workflow.js";
|
|
33
37
|
import { applyDeterministicScenarioParamRepairs, assessBackendScenarioParamConsistency, classifyBackendTestFailureWithScenarioParam, readBackendScenarioParamFacts, renderBackendTestFailureAnalysis, writeBackendScenarioParamArtifacts, writeScenarioParamRepairAudit, } from "../workflows/dag/backend-test-scenario-param.js";
|
|
34
|
-
import { assessBackendPytestCollection, assessMissingBackendPytestScripts, assessPriorityOnlyBackendPytestModules, assertBackendTestExecutionReadinessFresh, buildBackendPytestAssetInventory, materializeBackendTestExecutionReadiness, materializeEffectiveBackendPytestCollection, readBackendPytestCollectionFacts, readBackendTestExecutionReadiness, writeBackendPytestCollectionArtifacts, } from "../workflows/dag/backend-test-pytest-collection.js";
|
|
38
|
+
import { assessBackendPytestCollection, assessMissingBackendPytestScripts, assessPriorityOnlyBackendPytestModules, assertBackendTestExecutionReadinessFresh, buildBackendPytestAssetInventory, buildBackendTestItemEligibility, materializeBackendTestExecutionReadiness, materializeEffectiveBackendPytestCollection, readBackendPytestCollectionFacts, readBackendTestExecutionReadiness, writeBackendPytestCollectionArtifacts, } from "../workflows/dag/backend-test-pytest-collection.js";
|
|
35
39
|
import { computeL5ReportMetrics } from "../workflows/dag/l5-report-metrics.js";
|
|
36
40
|
import { buildBackendTestCanonicalResultFromInitialShellSnippet, materializeBackendTestClassification, } from "../workflows/dag/backend-test-classification-contract.js";
|
|
37
41
|
import { backendTestSemanticReviewSchema, materializeBackendTestSemanticReview, } from "../workflows/dag/backend-test-semantic-review-contract.js";
|
|
@@ -48,6 +52,7 @@ async function runBackendScenarioParamPreflight(input) {
|
|
|
48
52
|
phase: "initial",
|
|
49
53
|
repairAttempt: 0,
|
|
50
54
|
strictScenarioParamGate: input.strictScenarioParamGate,
|
|
55
|
+
layout: input.layout,
|
|
51
56
|
});
|
|
52
57
|
const initialArtifacts = await writeBackendScenarioParamArtifacts({
|
|
53
58
|
runDir: input.runDir,
|
|
@@ -73,6 +78,7 @@ async function runBackendScenarioParamPreflight(input) {
|
|
|
73
78
|
phase: "final",
|
|
74
79
|
repairAttempt: 1,
|
|
75
80
|
strictScenarioParamGate: input.strictScenarioParamGate,
|
|
81
|
+
layout: input.layout,
|
|
76
82
|
});
|
|
77
83
|
finalFacts = reassessed.facts;
|
|
78
84
|
finalMarkdown = reassessed.markdown;
|
|
@@ -492,11 +498,114 @@ async function readAdvisoryRunReport(reportsDir, filename, title) {
|
|
|
492
498
|
async function executeBackendTestPipeline(input, meta) {
|
|
493
499
|
const pipeline = input.task.shell?.backendTestPipeline;
|
|
494
500
|
const started = Date.now();
|
|
501
|
+
// Plan A: prefer the frozen layout carried by the compiled spec; fall back
|
|
502
|
+
// to the historical default for hand-written legacy DAGs.
|
|
503
|
+
const backendLayout = resolveBackendTestLayout(meta.spec.backendTestLayout
|
|
504
|
+
? {
|
|
505
|
+
testRoot: meta.spec.backendTestLayout.testRoot,
|
|
506
|
+
markdownRel: meta.spec.backendTestLayout.markdownRel,
|
|
507
|
+
scriptRel: meta.spec.backendTestLayout.scriptRel,
|
|
508
|
+
}
|
|
509
|
+
: undefined);
|
|
495
510
|
try {
|
|
496
511
|
if (!pipeline)
|
|
497
512
|
throw new Error("missing backend-test pipeline id");
|
|
498
513
|
const outputs = [];
|
|
499
|
-
if (pipeline === "
|
|
514
|
+
if (pipeline === "ingest-backend-test-gap") {
|
|
515
|
+
// Plan D: deterministic gap ingest. Identity (taskId + requirement
|
|
516
|
+
// sha256) is fail-closed; a mismatched plan blocks every writer.
|
|
517
|
+
const gapDocRel = meta.spec.globalConstraints
|
|
518
|
+
?.map((item) => /^backendTestGapDoc=(.+)$/i.exec(item.trim())?.[1])
|
|
519
|
+
.find(Boolean);
|
|
520
|
+
const binding = meta.spec.sourceBinding;
|
|
521
|
+
const requirementSha256 = binding?.sources.find((s) => s.kind === "requirement")?.sha256 ?? "";
|
|
522
|
+
if (!gapDocRel || !binding || !requirementSha256) {
|
|
523
|
+
return {
|
|
524
|
+
ok: false,
|
|
525
|
+
stdout: outputs.join("\n"),
|
|
526
|
+
stderr: "backend-test gap ingest requires the bound gap document path and the task source binding with a requirement sha256",
|
|
527
|
+
failureCategory: "invalid-output",
|
|
528
|
+
durationMs: Date.now() - started,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
const gapDocAbs = path.join(input.cwd, ...gapDocRel.split("/"));
|
|
532
|
+
let parsed;
|
|
533
|
+
try {
|
|
534
|
+
parsed = JSON.parse(await readFile(gapDocAbs, "utf8"));
|
|
535
|
+
}
|
|
536
|
+
catch (error) {
|
|
537
|
+
return {
|
|
538
|
+
ok: false,
|
|
539
|
+
stdout: outputs.join("\n"),
|
|
540
|
+
stderr: `backend-test gap document is unreadable JSON at ${gapDocRel}: ${error instanceof Error ? error.message : String(error)}`,
|
|
541
|
+
failureCategory: "invalid-output",
|
|
542
|
+
durationMs: Date.now() - started,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
const doc = backendTestGapDocSchema.safeParse(parsed);
|
|
546
|
+
if (!doc.success) {
|
|
547
|
+
return {
|
|
548
|
+
ok: false,
|
|
549
|
+
stdout: outputs.join("\n"),
|
|
550
|
+
stderr: `invalid backend-test-gap-v1 document: ${doc.error.issues
|
|
551
|
+
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
|
552
|
+
.join("; ")}`,
|
|
553
|
+
failureCategory: "invalid-output",
|
|
554
|
+
durationMs: Date.now() - started,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
// previous N6 facts (may be absent on a fresh checkout)
|
|
558
|
+
const factsPath = path.join(input.cwd, "contracts", "backend-test-case-coverage-facts.json");
|
|
559
|
+
let previousMissingSlots;
|
|
560
|
+
let previousCoveredSlots;
|
|
561
|
+
try {
|
|
562
|
+
const facts = JSON.parse(await readFile(factsPath, "utf8"));
|
|
563
|
+
previousMissingSlots = facts.scenarioPartitions?.missingSlots;
|
|
564
|
+
previousCoveredSlots = (facts.rules ?? []).flatMap((rule) => rule.coveredTestPoints ?? []);
|
|
565
|
+
}
|
|
566
|
+
catch {
|
|
567
|
+
// no previous facts in the workspace: the gap doc alone drives the plan
|
|
568
|
+
}
|
|
569
|
+
// module index comes from the README on disk
|
|
570
|
+
let existingModuleStems = [];
|
|
571
|
+
try {
|
|
572
|
+
const files = await markdownFiles(input.cwd, backendLayout);
|
|
573
|
+
existingModuleStems = files
|
|
574
|
+
.map((file) => path.basename(file, ".md"))
|
|
575
|
+
.filter((stem) => stem.toLowerCase() !== "readme");
|
|
576
|
+
}
|
|
577
|
+
catch {
|
|
578
|
+
// no markdown tree: every slot becomes a conflict
|
|
579
|
+
}
|
|
580
|
+
const plan = ingestBackendTestGap({
|
|
581
|
+
gapDoc: doc.data,
|
|
582
|
+
previousMissingSlots,
|
|
583
|
+
previousCoveredSlots,
|
|
584
|
+
existingModuleStems,
|
|
585
|
+
sharedSetupPath: meta.spec.backendTestSharedSetup?.path ?? null,
|
|
586
|
+
layout: backendLayout,
|
|
587
|
+
taskId: binding.taskId,
|
|
588
|
+
requirementSha256,
|
|
589
|
+
});
|
|
590
|
+
const contractsDir = path.join(meta.runDir, "contracts");
|
|
591
|
+
await mkdir(contractsDir, { recursive: true });
|
|
592
|
+
const planPath = path.join(contractsDir, "backend-test-gap-plan-v1.json");
|
|
593
|
+
await writeFile(planPath, JSON.stringify(plan, null, 2), "utf8");
|
|
594
|
+
outputs.push(`gapPlan=${planPath}`, `gapPlanStatus=${plan.status}`);
|
|
595
|
+
if (plan.status === "error") {
|
|
596
|
+
return {
|
|
597
|
+
ok: false,
|
|
598
|
+
stdout: outputs.join("\n"),
|
|
599
|
+
stderr: [
|
|
600
|
+
"backend-test gap ingest failed closed (identity/conflicts):",
|
|
601
|
+
...plan.conflicts,
|
|
602
|
+
].join("\n"),
|
|
603
|
+
failureCategory: "invalid-output",
|
|
604
|
+
durationMs: Date.now() - started,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
else if (pipeline === "markdown-environment") {
|
|
500
609
|
const results = await executePipelineCommands(input, meta);
|
|
501
610
|
if (!results.every((result) => result.ok)) {
|
|
502
611
|
const failure = results.find((result) => !result.ok);
|
|
@@ -530,6 +639,8 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
530
639
|
environmentMarkdown,
|
|
531
640
|
requiredRequirementIds,
|
|
532
641
|
sourceBinding: meta.spec.sourceBinding,
|
|
642
|
+
layout: backendLayout,
|
|
643
|
+
sharedSetup: meta.spec.backendTestSharedSetup ?? null,
|
|
533
644
|
});
|
|
534
645
|
}
|
|
535
646
|
catch (error) {
|
|
@@ -583,6 +694,7 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
583
694
|
const coverage = await analyzeBackendTestCaseCoverage({
|
|
584
695
|
workspaceRoot: input.cwd,
|
|
585
696
|
sourceBinding: normalizedBinding,
|
|
697
|
+
layout: backendLayout,
|
|
586
698
|
});
|
|
587
699
|
const coveragePath = await writeRunReport(meta.runDir, "backend-test-case-coverage-analysis.md", coverage.markdown);
|
|
588
700
|
const contractsDir = path.join(meta.runDir, "contracts");
|
|
@@ -590,6 +702,23 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
590
702
|
const factsPath = path.join(contractsDir, "backend-test-case-coverage-facts.json");
|
|
591
703
|
await writeFile(factsPath, JSON.stringify(coverage.facts, null, 2), "utf8");
|
|
592
704
|
outputs.push(`caseCoverage=${coveragePath}`, `caseCoverageFacts=${factsPath}`, coverage.markdown);
|
|
705
|
+
// Plan C: scenario-partition slot coverage is fail-closed. General
|
|
706
|
+
// coverage findings stay advisory, but a declared filter axis missing
|
|
707
|
+
// an each-value/omitted slot or its complement slot must block pytest
|
|
708
|
+
// generation until the Markdown declares the slot.
|
|
709
|
+
const partitionBlocking = coverage.facts.findings.filter((finding) => /^(?:MISSING_PARTITION|MISSING_PARTITION_SLOT|MISSING_COMPLEMENT_SLOT|MISSING_PARTITION_TABLE|INVALID_PARTITION_DOMAIN|NO_FINITE_DOMAIN|PARTITION_SOURCE_MISMATCH)/.test(finding));
|
|
710
|
+
if (partitionBlocking.length > 0) {
|
|
711
|
+
return {
|
|
712
|
+
ok: false,
|
|
713
|
+
stdout: outputs.join("\n\n"),
|
|
714
|
+
stderr: [
|
|
715
|
+
"backend-test scenario partitions are incomplete (fail-closed):",
|
|
716
|
+
...partitionBlocking,
|
|
717
|
+
].join("\n"),
|
|
718
|
+
failureCategory: "invalid-output",
|
|
719
|
+
durationMs: Date.now() - started,
|
|
720
|
+
};
|
|
721
|
+
}
|
|
593
722
|
}
|
|
594
723
|
catch (error) {
|
|
595
724
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -606,9 +735,9 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
606
735
|
}
|
|
607
736
|
}
|
|
608
737
|
else if (pipeline === "markdown-collection-assess") {
|
|
609
|
-
const resolution = await resolveBackendTestMappedPytestScripts(input.cwd);
|
|
738
|
+
const resolution = await resolveBackendTestMappedPytestScripts(input.cwd, backendLayout);
|
|
610
739
|
let facts;
|
|
611
|
-
const preflightInventory = await buildBackendPytestAssetInventory(input.cwd, resolution.existingScripts, { requireMappedScripts: false });
|
|
740
|
+
const preflightInventory = await buildBackendPytestAssetInventory(input.cwd, resolution.existingScripts, { requireMappedScripts: false, layout: backendLayout });
|
|
612
741
|
const priorityOnlyFacts = assessPriorityOnlyBackendPytestModules({
|
|
613
742
|
phase: "initial",
|
|
614
743
|
mappedScripts: resolution.expectedScripts,
|
|
@@ -618,7 +747,7 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
618
747
|
facts = priorityOnlyFacts;
|
|
619
748
|
}
|
|
620
749
|
else if (resolution.missingScripts.length > 0) {
|
|
621
|
-
const inventory = await buildBackendPytestAssetInventory(input.cwd, resolution.existingScripts, { requireMappedScripts: false });
|
|
750
|
+
const inventory = await buildBackendPytestAssetInventory(input.cwd, resolution.existingScripts, { requireMappedScripts: false, layout: backendLayout });
|
|
622
751
|
facts = assessMissingBackendPytestScripts({
|
|
623
752
|
mappedScripts: resolution.expectedScripts,
|
|
624
753
|
existingMappedScripts: resolution.existingScripts,
|
|
@@ -632,10 +761,11 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
632
761
|
const scenario = await runBackendScenarioParamPreflight({
|
|
633
762
|
workspaceRoot: input.cwd,
|
|
634
763
|
runDir: meta.runDir,
|
|
764
|
+
layout: backendLayout,
|
|
635
765
|
strictScenarioParamGate: Boolean((meta.spec.globalConstraints ?? []).some((item) => /strictScenarioParamGate\s*=\s*true/i.test(item))),
|
|
636
766
|
});
|
|
637
767
|
outputs.push(...scenario.outputs);
|
|
638
|
-
const inventory = await buildBackendPytestAssetInventory(input.cwd, mappedScripts);
|
|
768
|
+
const inventory = await buildBackendPytestAssetInventory(input.cwd, mappedScripts, { layout: backendLayout });
|
|
639
769
|
const shellQuote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
640
770
|
const targets = mappedScripts.map(shellQuote).join(" ");
|
|
641
771
|
const command = `PYTHONDONTWRITEBYTECODE=1 python -m pytest --collect-only -q -p no:cacheprovider ${targets}`;
|
|
@@ -669,6 +799,7 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
669
799
|
fixtureResolution = { exitCode: fixtureResult.exitCode ?? 2, stdout: fixtureResult.stdout, stderr: fixtureResult.stderr };
|
|
670
800
|
}
|
|
671
801
|
facts = assessBackendPytestCollection({
|
|
802
|
+
layout: backendLayout,
|
|
672
803
|
phase: "initial",
|
|
673
804
|
mappedScripts,
|
|
674
805
|
inventory,
|
|
@@ -678,6 +809,43 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
678
809
|
fixtureResolution,
|
|
679
810
|
});
|
|
680
811
|
}
|
|
812
|
+
try {
|
|
813
|
+
const correspondence = await analyzeBackendTestMarkdownPytestCorrespondence({
|
|
814
|
+
workspaceRoot: input.cwd,
|
|
815
|
+
layout: backendLayout,
|
|
816
|
+
taskId: meta.spec.sourceBinding?.taskId ?? "unbound",
|
|
817
|
+
});
|
|
818
|
+
const correspondenceReportPath = await writeRunReport(meta.runDir, "backend-test-markdown-pytest-correspondence-initial.md", correspondence.markdown);
|
|
819
|
+
const contractsDir = path.join(meta.runDir, "contracts");
|
|
820
|
+
await mkdir(contractsDir, { recursive: true });
|
|
821
|
+
const correspondenceFactsPath = path.join(contractsDir, "backend-test-markdown-pytest-correspondence-initial.json");
|
|
822
|
+
await writeFile(correspondenceFactsPath, JSON.stringify(correspondence.facts, null, 2), "utf8");
|
|
823
|
+
outputs.push(`initialCorrespondence=${correspondenceReportPath}`, `initialCorrespondenceFacts=${correspondenceFactsPath}`);
|
|
824
|
+
if (correspondence.facts.status === "FAIL" && facts.status === "PASS") {
|
|
825
|
+
const correspondenceRepairPaths = Array.from(new Set(correspondence.facts.entries
|
|
826
|
+
.filter((entry) => entry.status !== "EXACT_1_TO_1")
|
|
827
|
+
.flatMap((entry) => [entry.expectedScript, ...entry.actualScripts])
|
|
828
|
+
.filter((candidate) => candidate.startsWith("testcase/") && candidate.endsWith(".py")))).sort();
|
|
829
|
+
if (correspondenceRepairPaths.length > 0) {
|
|
830
|
+
facts.status = "REPAIRABLE";
|
|
831
|
+
facts.repairEligible = true;
|
|
832
|
+
facts.repairPaths = Array.from(new Set([...facts.repairPaths, ...correspondenceRepairPaths])).sort();
|
|
833
|
+
facts.findings.push({
|
|
834
|
+
kind: "markdown-pytest-correspondence",
|
|
835
|
+
classification: "test-asset-defect",
|
|
836
|
+
repairability: "repairable",
|
|
837
|
+
detail: correspondence.facts.entries
|
|
838
|
+
.filter((entry) => entry.status !== "EXACT_1_TO_1")
|
|
839
|
+
.flatMap((entry) => entry.findings)
|
|
840
|
+
.join("; ")
|
|
841
|
+
.slice(0, 12_000) || "Markdown-to-pytest correspondence is incomplete",
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
catch (error) {
|
|
847
|
+
outputs.push(`initialCorrespondence=UNAVAILABLE: ${error instanceof Error ? error.message : String(error)}`);
|
|
848
|
+
}
|
|
681
849
|
const artifacts = await writeBackendPytestCollectionArtifacts({
|
|
682
850
|
runDir: meta.runDir,
|
|
683
851
|
stem: "initial",
|
|
@@ -711,14 +879,15 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
711
879
|
});
|
|
712
880
|
}
|
|
713
881
|
else if (initial.status === "REPAIRABLE") {
|
|
714
|
-
const mappedScripts = await collectBackendTestMappedPytestScripts(input.cwd);
|
|
882
|
+
const mappedScripts = await collectBackendTestMappedPytestScripts(input.cwd, backendLayout);
|
|
715
883
|
const scenario = await runBackendScenarioParamPreflight({
|
|
716
884
|
workspaceRoot: input.cwd,
|
|
717
885
|
runDir: meta.runDir,
|
|
886
|
+
layout: backendLayout,
|
|
718
887
|
strictScenarioParamGate: Boolean((meta.spec.globalConstraints ?? []).some((item) => /strictScenarioParamGate\s*=\s*true/i.test(item))),
|
|
719
888
|
});
|
|
720
889
|
outputs.push(...scenario.outputs);
|
|
721
|
-
const inventory = await buildBackendPytestAssetInventory(input.cwd, mappedScripts);
|
|
890
|
+
const inventory = await buildBackendPytestAssetInventory(input.cwd, mappedScripts, { layout: backendLayout });
|
|
722
891
|
const shellQuote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
723
892
|
const targets = mappedScripts.map(shellQuote).join(" ");
|
|
724
893
|
const command = `PYTHONDONTWRITEBYTECODE=1 python -m pytest --collect-only -q -p no:cacheprovider ${targets}`;
|
|
@@ -753,6 +922,7 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
753
922
|
fixtureResolution = { exitCode: fixtureResult.exitCode ?? 2, stdout: fixtureResult.stdout, stderr: fixtureResult.stderr };
|
|
754
923
|
}
|
|
755
924
|
const finalFacts = assessBackendPytestCollection({
|
|
925
|
+
layout: backendLayout,
|
|
756
926
|
phase: "final",
|
|
757
927
|
mappedScripts,
|
|
758
928
|
inventory,
|
|
@@ -776,12 +946,57 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
776
946
|
facts: effective,
|
|
777
947
|
});
|
|
778
948
|
const scenarioFacts = await readBackendScenarioParamFacts(path.join(meta.runDir, "contracts", "backend-test-scenario-param-consistency-facts.json"));
|
|
949
|
+
const correspondence = meta.spec.sourceBinding
|
|
950
|
+
? await analyzeBackendTestMarkdownPytestCorrespondence({
|
|
951
|
+
workspaceRoot: input.cwd,
|
|
952
|
+
layout: backendLayout,
|
|
953
|
+
taskId: meta.spec.sourceBinding.taskId,
|
|
954
|
+
})
|
|
955
|
+
: undefined;
|
|
956
|
+
const eligibility = correspondence
|
|
957
|
+
? buildBackendTestItemEligibility(effective.collectedItemIds, {
|
|
958
|
+
correspondenceEntries: correspondence.facts.entries,
|
|
959
|
+
scenarioEntries: scenarioFacts.entries,
|
|
960
|
+
})
|
|
961
|
+
: { eligibleItemIds: effective.collectedItemIds, excludedItems: [] };
|
|
962
|
+
const eligibilityInputHashes = Object.fromEntries((correspondence?.facts.inputFiles ?? []).map((item) => [item.path, item.sha256]));
|
|
963
|
+
const eligibilityFactsPath = path.join(meta.runDir, "contracts", "backend-test-execution-eligibility.json");
|
|
964
|
+
await mkdir(path.dirname(eligibilityFactsPath), { recursive: true });
|
|
965
|
+
await writeFile(eligibilityFactsPath, `${JSON.stringify({
|
|
966
|
+
schemaId: "backend-test-execution-eligibility-v1",
|
|
967
|
+
status: eligibility.eligibleItemIds.length === 0 ? "BLOCKED" : eligibility.excludedItems.length > 0 ? "PARTIAL" : "PASS",
|
|
968
|
+
collectedItemCount: effective.collectedItemIds.length,
|
|
969
|
+
eligibleItemCount: eligibility.eligibleItemIds.length,
|
|
970
|
+
excludedItemCount: eligibility.excludedItems.length,
|
|
971
|
+
eligibleItemIds: eligibility.eligibleItemIds,
|
|
972
|
+
excludedItems: eligibility.excludedItems,
|
|
973
|
+
inputHashes: eligibilityInputHashes,
|
|
974
|
+
}, null, 2)}\n`, "utf8");
|
|
975
|
+
const eligibilityReportPath = await writeRunReport(meta.runDir, "backend-test-execution-eligibility.md", [
|
|
976
|
+
"# Backend Test Execution Eligibility",
|
|
977
|
+
"",
|
|
978
|
+
"## Status",
|
|
979
|
+
"",
|
|
980
|
+
eligibility.eligibleItemIds.length === 0 ? "BLOCKED" : eligibility.excludedItems.length > 0 ? "PARTIAL" : "PASS",
|
|
981
|
+
"",
|
|
982
|
+
`- Collected items: ${effective.collectedItemIds.length}`,
|
|
983
|
+
`- Eligible items: ${eligibility.eligibleItemIds.length}`,
|
|
984
|
+
`- Excluded items: ${eligibility.excludedItems.length}`,
|
|
985
|
+
"",
|
|
986
|
+
"## Excluded Items",
|
|
987
|
+
"",
|
|
988
|
+
...(eligibility.excludedItems.length > 0 ? eligibility.excludedItems.map((item) => `- \`${item.itemId}\` (${item.caseId ?? "unknown Case"}): ${item.reasons.join("; ")}`) : ["- None"]),
|
|
989
|
+
"",
|
|
990
|
+
].join("\n"));
|
|
991
|
+
outputs.push(`executionEligibility=${eligibilityReportPath}`, `executionEligibilityFacts=${eligibilityFactsPath}`);
|
|
779
992
|
const readiness = await materializeBackendTestExecutionReadiness({
|
|
780
993
|
runDir: meta.runDir,
|
|
781
994
|
workspaceRoot: input.cwd,
|
|
782
995
|
effective,
|
|
783
996
|
scenarioParamStatus: scenarioFacts.overallStatus,
|
|
784
997
|
scenarioParamRepairAttempt: scenarioFacts.repairAttempt,
|
|
998
|
+
eligibility,
|
|
999
|
+
eligibilityInputHashes,
|
|
785
1000
|
});
|
|
786
1001
|
return {
|
|
787
1002
|
ok: true,
|
|
@@ -792,6 +1007,8 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
792
1007
|
collectedItemCount: effective.collectedItemCount,
|
|
793
1008
|
fixtureResolutionStatus: effective.fixtureResolutionStatus,
|
|
794
1009
|
executionReadinessStatus: readiness.status,
|
|
1010
|
+
eligibleItemCount: readiness.eligibleItemIds.length,
|
|
1011
|
+
excludedItemCount: readiness.excludedItems.length,
|
|
795
1012
|
factsPath: "contracts/backend-test-pytest-collection-effective.json",
|
|
796
1013
|
readinessPath: "contracts/backend-test-execution-readiness.json",
|
|
797
1014
|
reportPath: artifacts.reportPath,
|
|
@@ -826,6 +1043,7 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
826
1043
|
try {
|
|
827
1044
|
const correspondence = await analyzeBackendTestMarkdownPytestCorrespondence({
|
|
828
1045
|
workspaceRoot: input.cwd,
|
|
1046
|
+
layout: backendLayout,
|
|
829
1047
|
taskId: sourceBinding.taskId,
|
|
830
1048
|
});
|
|
831
1049
|
const correspondencePath = await writeRunReport(meta.runDir, "backend-test-markdown-pytest-correspondence.md", correspondence.markdown);
|
|
@@ -888,9 +1106,32 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
888
1106
|
else if (pipeline === "markdown-execute-html") {
|
|
889
1107
|
const executionReadiness = await readBackendTestExecutionReadiness(path.join(meta.runDir, "contracts", "backend-test-execution-readiness.json"));
|
|
890
1108
|
await assertBackendTestExecutionReadinessFresh(input.cwd, executionReadiness);
|
|
891
|
-
const mappedScripts = await collectBackendTestMappedPytestScripts(input.cwd);
|
|
892
|
-
|
|
893
|
-
|
|
1109
|
+
const mappedScripts = await collectBackendTestMappedPytestScripts(input.cwd, backendLayout);
|
|
1110
|
+
let eligibleItemIds = executionReadiness.eligibleItemIds;
|
|
1111
|
+
// Plan D gap-fill executeScope=affected: when a gap plan exists in
|
|
1112
|
+
// this run, prefer the newly planned items (pytest node ids contain
|
|
1113
|
+
// the TP slot id) plus every collected item of the patched modules;
|
|
1114
|
+
// scope=all keeps the full eligible set.
|
|
1115
|
+
const affectedScope = meta.spec.globalConstraints?.some((item) => /^backendTestExecuteScope=affected$/i.test(item.trim())) ?? false;
|
|
1116
|
+
if (affectedScope) {
|
|
1117
|
+
try {
|
|
1118
|
+
const plan = JSON.parse(await readFile(path.join(meta.runDir, "contracts", "backend-test-gap-plan-v1.json"), "utf8"));
|
|
1119
|
+
const newPoints = new Set(plan.newTestPoints ?? []);
|
|
1120
|
+
const modules = new Set((plan.targetModules ?? []).map((stem) => `test_${stem}.py`));
|
|
1121
|
+
const affected = eligibleItemIds.filter((id) => modules.size === 0 ||
|
|
1122
|
+
[...modules].some((script) => id.includes(script)) ||
|
|
1123
|
+
[...newPoints].some((point) => id.includes(point)));
|
|
1124
|
+
if (affected.length > 0)
|
|
1125
|
+
eligibleItemIds = affected;
|
|
1126
|
+
}
|
|
1127
|
+
catch {
|
|
1128
|
+
// no gap plan in this run: execute the full eligible set
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
const pytestTargetsRelative = "reports/backend-test-pytest-targets.txt";
|
|
1132
|
+
const pytestTargetsAbsolute = path.join(meta.runDir, pytestTargetsRelative);
|
|
1133
|
+
await writeFile(pytestTargetsAbsolute, `${eligibleItemIds.join("\n")}\n`, "utf8");
|
|
1134
|
+
const pytestTargetsArg = `@\"\${HARNESS_DAG_RUN_DIR}/${pytestTargetsRelative}\"`;
|
|
894
1135
|
// Split into short bash -c commands (aligned with markdown-environment).
|
|
895
1136
|
// A single ultra-long compound command has been observed on Windows to
|
|
896
1137
|
// exit 0xC0000142 (STATUS_DLL_INIT_FAILED) in ~40ms with empty stdout/
|
|
@@ -967,8 +1208,10 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
967
1208
|
'PYTHON_BIN="$(command -v python || command -v python3 || true)"',
|
|
968
1209
|
'if [ -z "${PYTHON_BIN}" ]; then echo "python/python3 is required for backend-test execution" | tee -a "${DIAG_FILE}" >&2; exit 127; fi',
|
|
969
1210
|
'echo "STEP=pytest-run"',
|
|
970
|
-
`echo "- pytestCommand: python -m pytest
|
|
971
|
-
`
|
|
1211
|
+
`echo "- pytestCommand: python -m pytest <${eligibleItemIds.length} eligibility-authorized-items> -v -p no:cacheprovider --html=reports/backend-test.html --self-contained-html" >> "\${DIAG_FILE}"`,
|
|
1212
|
+
`echo "- eligibleItemCount: ${eligibleItemIds.length}" >> "\${DIAG_FILE}"`,
|
|
1213
|
+
`echo "- excludedItemCount: ${executionReadiness.excludedItems.length}" >> "\${DIAG_FILE}"`,
|
|
1214
|
+
`PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 "\${PYTHON_BIN}" -m pytest ${pytestTargetsArg} -v -p no:cacheprovider --html="\${HARNESS_DAG_RUN_DIR}/reports/backend-test.html" --self-contained-html`,
|
|
972
1215
|
"STATUS=$?",
|
|
973
1216
|
'printf "%s" "${STATUS}" > "${HARNESS_DAG_RUN_DIR}/reports/backend-test-pytest-exit.txt"',
|
|
974
1217
|
'echo "- pytestExitCode: ${STATUS}" >> "${DIAG_FILE}"',
|
|
@@ -1053,11 +1296,13 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
1053
1296
|
htmlContent: pytestHtmlContent,
|
|
1054
1297
|
pytestExitCode,
|
|
1055
1298
|
});
|
|
1056
|
-
const cases = await collectBackendTestHumanCaseCatalog(input.cwd);
|
|
1299
|
+
const cases = await collectBackendTestHumanCaseCatalog(input.cwd, backendLayout);
|
|
1057
1300
|
const caseValidationSummary = await readRequiredRunReport(reportsDir, "backend-md-case-validation.md");
|
|
1058
1301
|
const caseCoverageSummary = await readAdvisoryRunReport(reportsDir, "backend-test-case-coverage-analysis.md", "Backend Test Case Coverage Analysis");
|
|
1059
1302
|
const traceabilitySummary = await readRequiredRunReport(reportsDir, "backend-test-traceability.md");
|
|
1060
|
-
const
|
|
1303
|
+
const correspondenceSummaryBase = await readAdvisoryRunReport(reportsDir, "backend-test-markdown-pytest-correspondence.md", "Backend Test Markdown → pytest Correspondence");
|
|
1304
|
+
const eligibilitySummary = await readAdvisoryRunReport(reportsDir, "backend-test-execution-eligibility.md", "Backend Test Execution Eligibility");
|
|
1305
|
+
const correspondenceSummary = `${correspondenceSummaryBase.trim()}\n\n${eligibilitySummary.trim()}\n`;
|
|
1061
1306
|
const htmlContent = renderBackendTestHtml({
|
|
1062
1307
|
title: meta.spec.title,
|
|
1063
1308
|
parsed,
|
|
@@ -1245,6 +1490,7 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
1245
1490
|
fromNodeId: "backend-test-execution-envelope",
|
|
1246
1491
|
artifactName: "backend-test-execution.json",
|
|
1247
1492
|
outputDir: "contracts",
|
|
1493
|
+
allowedTestRoot: backendLayout.testRoot,
|
|
1248
1494
|
});
|
|
1249
1495
|
outputs.push(`analysis=${analysis.path}`, `execution=${execution.path}`);
|
|
1250
1496
|
}
|
|
@@ -1360,6 +1606,7 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
1360
1606
|
artifactName: "backend-test-result-initial.json",
|
|
1361
1607
|
outputDir: "contracts",
|
|
1362
1608
|
junitRelativePath: "reports/backend-test-initial-junit.xml",
|
|
1609
|
+
testRoot: backendLayout.testRoot,
|
|
1363
1610
|
});
|
|
1364
1611
|
outputs.push(...results.map((result) => result.stdout), `result=${artifact.path}`);
|
|
1365
1612
|
}
|
|
@@ -1816,6 +2063,52 @@ async function executeFrontendTestHtmlReport(input, meta) {
|
|
|
1816
2063
|
};
|
|
1817
2064
|
}
|
|
1818
2065
|
}
|
|
2066
|
+
async function executeFrontendTestStandardScenarios(input) {
|
|
2067
|
+
const started = Date.now();
|
|
2068
|
+
try {
|
|
2069
|
+
const result = await copyFrontendTestStandardScenarios({
|
|
2070
|
+
workspaceRoot: input.cwd,
|
|
2071
|
+
});
|
|
2072
|
+
return {
|
|
2073
|
+
ok: true,
|
|
2074
|
+
stdout: JSON.stringify(result),
|
|
2075
|
+
stderr: "",
|
|
2076
|
+
failureCategory: "success",
|
|
2077
|
+
durationMs: Date.now() - started,
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
catch (error) {
|
|
2081
|
+
return {
|
|
2082
|
+
ok: false,
|
|
2083
|
+
stdout: "",
|
|
2084
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
2085
|
+
failureCategory: "invalid-output",
|
|
2086
|
+
durationMs: Date.now() - started,
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
async function executeFrontendTestEnvironmentProbe(input) {
|
|
2091
|
+
const started = Date.now();
|
|
2092
|
+
const result = await probeFrontendTestEnvironment({
|
|
2093
|
+
workspaceRoot: input.cwd,
|
|
2094
|
+
});
|
|
2095
|
+
if (result.ok) {
|
|
2096
|
+
return {
|
|
2097
|
+
ok: true,
|
|
2098
|
+
stdout: result.stdout,
|
|
2099
|
+
stderr: "",
|
|
2100
|
+
failureCategory: "success",
|
|
2101
|
+
durationMs: Date.now() - started,
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
return {
|
|
2105
|
+
ok: false,
|
|
2106
|
+
stdout: "",
|
|
2107
|
+
stderr: result.stderr,
|
|
2108
|
+
failureCategory: "nonzero-exit",
|
|
2109
|
+
durationMs: Date.now() - started,
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
1819
2112
|
async function executeFrontendTestEvidenceValidation(input) {
|
|
1820
2113
|
const started = Date.now();
|
|
1821
2114
|
try {
|
|
@@ -2094,6 +2387,12 @@ export async function executeDagShellNode(input, meta) {
|
|
|
2094
2387
|
};
|
|
2095
2388
|
}
|
|
2096
2389
|
}
|
|
2390
|
+
if (shell?.frontendTestStandardScenarios) {
|
|
2391
|
+
return executeFrontendTestStandardScenarios(input);
|
|
2392
|
+
}
|
|
2393
|
+
if (shell?.frontendTestEnvironmentProbe) {
|
|
2394
|
+
return executeFrontendTestEnvironmentProbe(input);
|
|
2395
|
+
}
|
|
2097
2396
|
if (shell?.frontendBrowserToolPreflight) {
|
|
2098
2397
|
const started = Date.now();
|
|
2099
2398
|
try {
|
|
@@ -2246,6 +2545,13 @@ export async function executeDagShellNode(input, meta) {
|
|
|
2246
2545
|
}
|
|
2247
2546
|
if (shell?.jsonArtifactGate) {
|
|
2248
2547
|
const started = Date.now();
|
|
2548
|
+
const backendLayout = resolveBackendTestLayout(meta.spec.backendTestLayout
|
|
2549
|
+
? {
|
|
2550
|
+
testRoot: meta.spec.backendTestLayout.testRoot,
|
|
2551
|
+
markdownRel: meta.spec.backendTestLayout.markdownRel,
|
|
2552
|
+
scriptRel: meta.spec.backendTestLayout.scriptRel,
|
|
2553
|
+
}
|
|
2554
|
+
: undefined);
|
|
2249
2555
|
try {
|
|
2250
2556
|
const gate = shell.jsonArtifactGate;
|
|
2251
2557
|
const sourceNodeId = gate.fallbackFromNodeIds?.length
|
|
@@ -2273,6 +2579,7 @@ export async function executeDagShellNode(input, meta) {
|
|
|
2273
2579
|
fromNodeId: sourceNodeId,
|
|
2274
2580
|
artifactName: gate.artifactName,
|
|
2275
2581
|
outputDir: gate.outputDir,
|
|
2582
|
+
allowedTestRoot: backendLayout.testRoot,
|
|
2276
2583
|
});
|
|
2277
2584
|
break;
|
|
2278
2585
|
case "backend-test-result-v1":
|
|
@@ -2282,6 +2589,7 @@ export async function executeDagShellNode(input, meta) {
|
|
|
2282
2589
|
artifactName: gate.artifactName,
|
|
2283
2590
|
outputDir: gate.outputDir,
|
|
2284
2591
|
junitRelativePath: gate.junitRelativePath,
|
|
2592
|
+
testRoot: backendLayout.testRoot,
|
|
2285
2593
|
});
|
|
2286
2594
|
break;
|
|
2287
2595
|
case "backend-test-classification-v1":
|
|
@@ -98,6 +98,13 @@ export function isEphemeralToolCachePath(filePath) {
|
|
|
98
98
|
const normalized = normalizePath(filePath);
|
|
99
99
|
if (!normalized)
|
|
100
100
|
return false;
|
|
101
|
+
// CodeGraph extension sync side effect (plan 2026-08-21 D7): a loaded
|
|
102
|
+
// pi-codegraph extension keeps an existing .codegraph/ index fresh at
|
|
103
|
+
// session start; those index files are extension-owned run state, not
|
|
104
|
+
// writer output, so they never count as write-guard violations.
|
|
105
|
+
if (normalized === ".codegraph" || normalized.startsWith(".codegraph/")) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
101
108
|
if (normalized === ".pytest_cache" ||
|
|
102
109
|
normalized.startsWith(".pytest_cache/")) {
|
|
103
110
|
return true;
|
|
@@ -78,8 +78,6 @@ export const executorManifestSchema = z
|
|
|
78
78
|
LOW: executorTierModelSchema.optional(),
|
|
79
79
|
MED: executorTierModelSchema.optional(),
|
|
80
80
|
HIGH: executorTierModelSchema.optional(),
|
|
81
|
-
/** Console 模型矩阵下拉专用列表;仅供 UI 使用,不参与 DAG 模型路由。 */
|
|
82
|
-
modelOptions: z.array(z.string().min(1)).optional(),
|
|
83
81
|
})
|
|
84
82
|
.strict();
|
|
85
83
|
export const workflowPolicyProfileNameSchema = z.enum([
|