@tea-agent/loop-agent 0.33.6-beta.0 → 0.33.7-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/dist/executors/shell-executor.js +91 -4
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +435 -31
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +25 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
- package/dist/workflows/dag/backend-test-scenario-param.js +269 -82
- package/dist/workflows/dag/init-hybrid.js +28 -24
- package/docs/templates/backend-test-dag.json +23 -20
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.33.7-beta.0] - 2026-08-11
|
|
6
|
+
|
|
7
|
+
### 重点更新
|
|
8
|
+
|
|
9
|
+
- backend-test 收口 source-fidelity P0:绑定 source coverage、有界 Markdown/pytest 同步、item-level eligibility 与单次 eligible 业务执行;my-webapp R107 16 节点 finished
|
|
10
|
+
|
|
11
|
+
### 新增
|
|
12
|
+
|
|
13
|
+
- backend-test 新增绑定 source references 的来源覆盖事实、Markdown 一轮增量同步和 pytest correspondence 一轮有界修复,在业务测试体执行前保留 initial/effective/residual 证据
|
|
14
|
+
- backend-test 新增 item-level execution eligibility:只授权 payload-safe 的 pytest items,excluded items 与真实业务失败分开展示
|
|
15
|
+
|
|
16
|
+
### 改进
|
|
17
|
+
|
|
18
|
+
- 将 backend-test Markdown/pytest map 的默认总 token 预算从 `600000` 提升到 `3000000`,降低多模块生成时过早触发 `dynamic map token budget exhausted` 的概率;仍保持预算耗尽 fail-closed
|
|
19
|
+
- Coverage Matrix 接受 `R400001` 等错误码 Rule Key,并保留单元格内 `ACTIVE|ARCHIVED` 未转义枚举管道符,避免合法行被拆成 10 列后整表丢弃
|
|
20
|
+
- scenario-param 按 TP 精确匹配多行 `场景意图`,保留 `ACTIVE` 大小写字面量,识别 `"a" * N` 长度表达式与空白 `empty`,request-level `nominal` 允许 `None` 查询过滤;避免把合法 payload 误判为 MISMATCH 并错误排除 eligible items
|
|
21
|
+
- backend-test 支持 evidence-only 元用例:Markdown 显式声明 `脚本/primary symbol=无` 且仅绑定 assertion/cross-cutting TP 时,不再强制 1:1 pytest 符号,也不再把其算作 MISSING_PYTEST/SCRIPT_MISMATCH
|
|
22
|
+
- generated pytest 内的 `ScopeMismatch`/setup fixture 失败若完全归因于 `testcase/**` 映射脚本,则归为 REPAIRABLE 并进入唯一一轮 collection repair,不再直接 BLOCKED
|
|
23
|
+
- scenario-param 观测跳过 `pytest.param("TP-...", value, id="TP-...")` 的前导 TP 标签,并支持长度意图用数字位置参数表达
|
|
24
|
+
|
|
25
|
+
### 修复
|
|
26
|
+
|
|
27
|
+
- 修复 readiness 错误信息截断与无效 Coverage Matrix 状态语义,避免超长错误或 advisory 矩阵状态误阻断后续节点
|
|
28
|
+
- 修复 scenario 排除项未绑定 payload 字段、request-level 不确定性被误升级为硬失败的问题
|
|
29
|
+
|
|
5
30
|
## [0.33.6-beta.0] - 2026-08-11
|
|
6
31
|
|
|
7
32
|
### 修复
|
|
@@ -31,7 +31,7 @@ import { analyzeBackendTestCaseCoverage, analyzeBackendTestMarkdownPytestCorresp
|
|
|
31
31
|
import { materializeBackendTestResultFromPytestHtml, materializeBackendTestResultFromRunDir, parsePytestHtmlReport, } from "../workflows/dag/backend-test-result-contract.js";
|
|
32
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";
|
|
33
33
|
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";
|
|
34
|
+
import { assessBackendPytestCollection, assessMissingBackendPytestScripts, assessPriorityOnlyBackendPytestModules, assertBackendTestExecutionReadinessFresh, buildBackendPytestAssetInventory, buildBackendTestItemEligibility, materializeBackendTestExecutionReadiness, materializeEffectiveBackendPytestCollection, readBackendPytestCollectionFacts, readBackendTestExecutionReadiness, writeBackendPytestCollectionArtifacts, } from "../workflows/dag/backend-test-pytest-collection.js";
|
|
35
35
|
import { computeL5ReportMetrics } from "../workflows/dag/l5-report-metrics.js";
|
|
36
36
|
import { buildBackendTestCanonicalResultFromInitialShellSnippet, materializeBackendTestClassification, } from "../workflows/dag/backend-test-classification-contract.js";
|
|
37
37
|
import { backendTestSemanticReviewSchema, materializeBackendTestSemanticReview, } from "../workflows/dag/backend-test-semantic-review-contract.js";
|
|
@@ -678,6 +678,42 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
678
678
|
fixtureResolution,
|
|
679
679
|
});
|
|
680
680
|
}
|
|
681
|
+
try {
|
|
682
|
+
const correspondence = await analyzeBackendTestMarkdownPytestCorrespondence({
|
|
683
|
+
workspaceRoot: input.cwd,
|
|
684
|
+
taskId: meta.spec.sourceBinding?.taskId ?? "unbound",
|
|
685
|
+
});
|
|
686
|
+
const correspondenceReportPath = await writeRunReport(meta.runDir, "backend-test-markdown-pytest-correspondence-initial.md", correspondence.markdown);
|
|
687
|
+
const contractsDir = path.join(meta.runDir, "contracts");
|
|
688
|
+
await mkdir(contractsDir, { recursive: true });
|
|
689
|
+
const correspondenceFactsPath = path.join(contractsDir, "backend-test-markdown-pytest-correspondence-initial.json");
|
|
690
|
+
await writeFile(correspondenceFactsPath, JSON.stringify(correspondence.facts, null, 2), "utf8");
|
|
691
|
+
outputs.push(`initialCorrespondence=${correspondenceReportPath}`, `initialCorrespondenceFacts=${correspondenceFactsPath}`);
|
|
692
|
+
if (correspondence.facts.status === "FAIL" && facts.status === "PASS") {
|
|
693
|
+
const correspondenceRepairPaths = Array.from(new Set(correspondence.facts.entries
|
|
694
|
+
.filter((entry) => entry.status !== "EXACT_1_TO_1")
|
|
695
|
+
.flatMap((entry) => [entry.expectedScript, ...entry.actualScripts])
|
|
696
|
+
.filter((candidate) => candidate.startsWith("testcase/") && candidate.endsWith(".py")))).sort();
|
|
697
|
+
if (correspondenceRepairPaths.length > 0) {
|
|
698
|
+
facts.status = "REPAIRABLE";
|
|
699
|
+
facts.repairEligible = true;
|
|
700
|
+
facts.repairPaths = Array.from(new Set([...facts.repairPaths, ...correspondenceRepairPaths])).sort();
|
|
701
|
+
facts.findings.push({
|
|
702
|
+
kind: "markdown-pytest-correspondence",
|
|
703
|
+
classification: "test-asset-defect",
|
|
704
|
+
repairability: "repairable",
|
|
705
|
+
detail: correspondence.facts.entries
|
|
706
|
+
.filter((entry) => entry.status !== "EXACT_1_TO_1")
|
|
707
|
+
.flatMap((entry) => entry.findings)
|
|
708
|
+
.join("; ")
|
|
709
|
+
.slice(0, 12_000) || "Markdown-to-pytest correspondence is incomplete",
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
catch (error) {
|
|
715
|
+
outputs.push(`initialCorrespondence=UNAVAILABLE: ${error instanceof Error ? error.message : String(error)}`);
|
|
716
|
+
}
|
|
681
717
|
const artifacts = await writeBackendPytestCollectionArtifacts({
|
|
682
718
|
runDir: meta.runDir,
|
|
683
719
|
stem: "initial",
|
|
@@ -776,12 +812,56 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
776
812
|
facts: effective,
|
|
777
813
|
});
|
|
778
814
|
const scenarioFacts = await readBackendScenarioParamFacts(path.join(meta.runDir, "contracts", "backend-test-scenario-param-consistency-facts.json"));
|
|
815
|
+
const correspondence = meta.spec.sourceBinding
|
|
816
|
+
? await analyzeBackendTestMarkdownPytestCorrespondence({
|
|
817
|
+
workspaceRoot: input.cwd,
|
|
818
|
+
taskId: meta.spec.sourceBinding.taskId,
|
|
819
|
+
})
|
|
820
|
+
: undefined;
|
|
821
|
+
const eligibility = correspondence
|
|
822
|
+
? buildBackendTestItemEligibility(effective.collectedItemIds, {
|
|
823
|
+
correspondenceEntries: correspondence.facts.entries,
|
|
824
|
+
scenarioEntries: scenarioFacts.entries,
|
|
825
|
+
})
|
|
826
|
+
: { eligibleItemIds: effective.collectedItemIds, excludedItems: [] };
|
|
827
|
+
const eligibilityInputHashes = Object.fromEntries((correspondence?.facts.inputFiles ?? []).map((item) => [item.path, item.sha256]));
|
|
828
|
+
const eligibilityFactsPath = path.join(meta.runDir, "contracts", "backend-test-execution-eligibility.json");
|
|
829
|
+
await mkdir(path.dirname(eligibilityFactsPath), { recursive: true });
|
|
830
|
+
await writeFile(eligibilityFactsPath, `${JSON.stringify({
|
|
831
|
+
schemaId: "backend-test-execution-eligibility-v1",
|
|
832
|
+
status: eligibility.eligibleItemIds.length === 0 ? "BLOCKED" : eligibility.excludedItems.length > 0 ? "PARTIAL" : "PASS",
|
|
833
|
+
collectedItemCount: effective.collectedItemIds.length,
|
|
834
|
+
eligibleItemCount: eligibility.eligibleItemIds.length,
|
|
835
|
+
excludedItemCount: eligibility.excludedItems.length,
|
|
836
|
+
eligibleItemIds: eligibility.eligibleItemIds,
|
|
837
|
+
excludedItems: eligibility.excludedItems,
|
|
838
|
+
inputHashes: eligibilityInputHashes,
|
|
839
|
+
}, null, 2)}\n`, "utf8");
|
|
840
|
+
const eligibilityReportPath = await writeRunReport(meta.runDir, "backend-test-execution-eligibility.md", [
|
|
841
|
+
"# Backend Test Execution Eligibility",
|
|
842
|
+
"",
|
|
843
|
+
"## Status",
|
|
844
|
+
"",
|
|
845
|
+
eligibility.eligibleItemIds.length === 0 ? "BLOCKED" : eligibility.excludedItems.length > 0 ? "PARTIAL" : "PASS",
|
|
846
|
+
"",
|
|
847
|
+
`- Collected items: ${effective.collectedItemIds.length}`,
|
|
848
|
+
`- Eligible items: ${eligibility.eligibleItemIds.length}`,
|
|
849
|
+
`- Excluded items: ${eligibility.excludedItems.length}`,
|
|
850
|
+
"",
|
|
851
|
+
"## Excluded Items",
|
|
852
|
+
"",
|
|
853
|
+
...(eligibility.excludedItems.length > 0 ? eligibility.excludedItems.map((item) => `- \`${item.itemId}\` (${item.caseId ?? "unknown Case"}): ${item.reasons.join("; ")}`) : ["- None"]),
|
|
854
|
+
"",
|
|
855
|
+
].join("\n"));
|
|
856
|
+
outputs.push(`executionEligibility=${eligibilityReportPath}`, `executionEligibilityFacts=${eligibilityFactsPath}`);
|
|
779
857
|
const readiness = await materializeBackendTestExecutionReadiness({
|
|
780
858
|
runDir: meta.runDir,
|
|
781
859
|
workspaceRoot: input.cwd,
|
|
782
860
|
effective,
|
|
783
861
|
scenarioParamStatus: scenarioFacts.overallStatus,
|
|
784
862
|
scenarioParamRepairAttempt: scenarioFacts.repairAttempt,
|
|
863
|
+
eligibility,
|
|
864
|
+
eligibilityInputHashes,
|
|
785
865
|
});
|
|
786
866
|
return {
|
|
787
867
|
ok: true,
|
|
@@ -792,6 +872,8 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
792
872
|
collectedItemCount: effective.collectedItemCount,
|
|
793
873
|
fixtureResolutionStatus: effective.fixtureResolutionStatus,
|
|
794
874
|
executionReadinessStatus: readiness.status,
|
|
875
|
+
eligibleItemCount: readiness.eligibleItemIds.length,
|
|
876
|
+
excludedItemCount: readiness.excludedItems.length,
|
|
795
877
|
factsPath: "contracts/backend-test-pytest-collection-effective.json",
|
|
796
878
|
readinessPath: "contracts/backend-test-execution-readiness.json",
|
|
797
879
|
reportPath: artifacts.reportPath,
|
|
@@ -890,7 +972,8 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
890
972
|
await assertBackendTestExecutionReadinessFresh(input.cwd, executionReadiness);
|
|
891
973
|
const mappedScripts = await collectBackendTestMappedPytestScripts(input.cwd);
|
|
892
974
|
const shellQuote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
893
|
-
const
|
|
975
|
+
const eligibleItemIds = executionReadiness.eligibleItemIds;
|
|
976
|
+
const pytestTargets = eligibleItemIds.map(shellQuote).join(" ");
|
|
894
977
|
// Split into short bash -c commands (aligned with markdown-environment).
|
|
895
978
|
// A single ultra-long compound command has been observed on Windows to
|
|
896
979
|
// exit 0xC0000142 (STATUS_DLL_INIT_FAILED) in ~40ms with empty stdout/
|
|
@@ -967,7 +1050,9 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
967
1050
|
'PYTHON_BIN="$(command -v python || command -v python3 || true)"',
|
|
968
1051
|
'if [ -z "${PYTHON_BIN}" ]; then echo "python/python3 is required for backend-test execution" | tee -a "${DIAG_FILE}" >&2; exit 127; fi',
|
|
969
1052
|
'echo "STEP=pytest-run"',
|
|
970
|
-
`echo "- pytestCommand: python -m pytest
|
|
1053
|
+
`echo "- pytestCommand: python -m pytest <${eligibleItemIds.length} eligibility-authorized-items> -v -p no:cacheprovider --html=reports/backend-test.html --self-contained-html" >> "\${DIAG_FILE}"`,
|
|
1054
|
+
`echo "- eligibleItemCount: ${eligibleItemIds.length}" >> "\${DIAG_FILE}"`,
|
|
1055
|
+
`echo "- excludedItemCount: ${executionReadiness.excludedItems.length}" >> "\${DIAG_FILE}"`,
|
|
971
1056
|
`PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 "\${PYTHON_BIN}" -m pytest ${pytestTargets} -v -p no:cacheprovider --html="\${HARNESS_DAG_RUN_DIR}/reports/backend-test.html" --self-contained-html`,
|
|
972
1057
|
"STATUS=$?",
|
|
973
1058
|
'printf "%s" "${STATUS}" > "${HARNESS_DAG_RUN_DIR}/reports/backend-test-pytest-exit.txt"',
|
|
@@ -1057,7 +1142,9 @@ async function executeBackendTestPipeline(input, meta) {
|
|
|
1057
1142
|
const caseValidationSummary = await readRequiredRunReport(reportsDir, "backend-md-case-validation.md");
|
|
1058
1143
|
const caseCoverageSummary = await readAdvisoryRunReport(reportsDir, "backend-test-case-coverage-analysis.md", "Backend Test Case Coverage Analysis");
|
|
1059
1144
|
const traceabilitySummary = await readRequiredRunReport(reportsDir, "backend-test-traceability.md");
|
|
1060
|
-
const
|
|
1145
|
+
const correspondenceSummaryBase = await readAdvisoryRunReport(reportsDir, "backend-test-markdown-pytest-correspondence.md", "Backend Test Markdown → pytest Correspondence");
|
|
1146
|
+
const eligibilitySummary = await readAdvisoryRunReport(reportsDir, "backend-test-execution-eligibility.md", "Backend Test Execution Eligibility");
|
|
1147
|
+
const correspondenceSummary = `${correspondenceSummaryBase.trim()}\n\n${eligibilitySummary.trim()}\n`;
|
|
1061
1148
|
const htmlContent = renderBackendTestHtml({
|
|
1062
1149
|
title: meta.spec.title,
|
|
1063
1150
|
parsed,
|