@tea-agent/loop-agent 0.16.19 → 0.16.21
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 +28 -5
- package/README.md +8 -0
- package/dist/cli/command-definitions.js +4 -2
- package/dist/cli/program.js +2 -1
- package/dist/cli/update/init-surface-notifier.js +167 -0
- package/dist/cli/update/policy.js +36 -1
- package/dist/cli/update/runtime-activity.js +29 -0
- package/dist/cli.js +14 -2
- package/dist/commands/init.js +85 -2
- package/dist/executors/shell-executor.js +92 -66
- package/dist/executors/shell-write-guard.js +5 -0
- package/dist/shared/runtime-activity.js +6 -0
- package/dist/worker/observability/read-model.js +10 -7
- package/dist/worker/observe/static/views/session-timeline.js +1 -1
- package/dist/workflows/dag/backend-test-case-manifest.js +13 -3
- package/dist/workflows/dag/backend-test-classification-contract.js +38 -0
- package/dist/workflows/dag/backend-test-contract-envelope.js +167 -0
- package/dist/workflows/dag/backend-test-semantic-review-contract.js +2 -2
- package/dist/workflows/dag/init-hybrid.js +64 -409
- package/dist/workflows/dag/node-execution.js +4 -3
- package/dist/workflows/dag/types.js +1 -5
- package/docs/README.md +1 -0
- package/docs/templates/agent-dag.schema.json +1 -1
- package/docs/templates/backend-test-case-manifest.schema.json +35 -2
- package/docs/templates/backend-test-dag.json +39 -340
- package/docs/templates/backend-test-dag.review-cases.prompt.md +4 -4
- package/package.json +1 -1
- package/skills/loop-agent/references/command-reference.md +4 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/workflows/dag/backend-test-repair-contract.js +0 -94
|
@@ -18,7 +18,6 @@ import { resolveVerifyPreset } from "../../executors/shell-verification.js";
|
|
|
18
18
|
import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
|
|
19
19
|
import { normalizeTaskRequirementText, resolveTaskDagTemplateSelection, } from "./task-demand-routing.js";
|
|
20
20
|
import { BACKEND_TEST_EXECUTION_DEFAULT_TEST_ROOT, buildBackendTestExecutionPreflightShellSnippet, } from "./backend-test-execution-contract.js";
|
|
21
|
-
import { buildBackendTestEffectiveResultSelectorShellSnippet, buildBackendTestRepairEligibilityShellSnippet, buildBackendTestRepairSafetyShellSnippet, } from "./backend-test-repair-contract.js";
|
|
22
21
|
import { buildBackendTestOutcomeGateShellSnippet } from "./backend-test-result-contract.js";
|
|
23
22
|
import { buildFrontendTestOutcomeGateShellSnippet } from "./frontend-test-result-contract.js";
|
|
24
23
|
import { classifyFrontendRisk, } from "./frontend-risk.js";
|
|
@@ -900,6 +899,22 @@ function extractExplicitRequirementIds(...markdownInputs) {
|
|
|
900
899
|
}
|
|
901
900
|
return ids;
|
|
902
901
|
}
|
|
902
|
+
/**
|
|
903
|
+
* Prefer TaskSpec-scoped acceptance refs from the derived 需求.md section when
|
|
904
|
+
* present. Full Feature acceptance.yaml may list sibling ACs that this task is
|
|
905
|
+
* not responsible for covering (e.g. FE-TEST must not require AC-CONTRACT-*).
|
|
906
|
+
*/
|
|
907
|
+
export function extractTaskScopedRequirementIds(requirementMarkdown, ...fallbackMarkdown) {
|
|
908
|
+
// Only the explicit TaskSpec-derived section is authoritative for scope.
|
|
909
|
+
// Without it, preserve legacy full-scan behavior (requirement + references).
|
|
910
|
+
const sectionMatch = requirementMarkdown.match(/(?:^|\n)##\s*Acceptance References\s*\n([\s\S]*?)(?=\n##\s+|\n#\s+|$)/i);
|
|
911
|
+
if (sectionMatch?.[1]) {
|
|
912
|
+
const fromSection = extractExplicitRequirementIds(sectionMatch[1]);
|
|
913
|
+
if (fromSection.length > 0)
|
|
914
|
+
return fromSection;
|
|
915
|
+
}
|
|
916
|
+
return extractExplicitRequirementIds(requirementMarkdown, ...fallbackMarkdown);
|
|
917
|
+
}
|
|
903
918
|
function buildDagSourceBinding(sources) {
|
|
904
919
|
const sourceEntries = [
|
|
905
920
|
{
|
|
@@ -932,7 +947,7 @@ function buildDagSourceBinding(sources) {
|
|
|
932
947
|
.update(source.markdown, "utf8")
|
|
933
948
|
.digest("hex"),
|
|
934
949
|
})),
|
|
935
|
-
requirementIds:
|
|
950
|
+
requirementIds: extractTaskScopedRequirementIds(sources.requirementMarkdown, sources.constraintMarkdown, ...(sources.referenceDocuments ?? []).map((reference) => reference.markdown)),
|
|
936
951
|
};
|
|
937
952
|
}
|
|
938
953
|
function buildBackendTestAnalysisSourceBindingContract(sources) {
|
|
@@ -2682,7 +2697,7 @@ function buildGenerateBackendFunctionalCasesNode(sources) {
|
|
|
2682
2697
|
}
|
|
2683
2698
|
const BACKEND_TEST_CASE_MANIFEST_OUTPUT_INSTRUCTIONS = [
|
|
2684
2699
|
"The final fenced JSON block is authoritative and MUST conform exactly to Backend Test Case Manifest v1.",
|
|
2685
|
-
"Top-level keys MUST be exactly: schemaVersion, sourceBinding, cases, evidenceGaps
|
|
2700
|
+
"Top-level keys MUST be exactly: schemaVersion, sourceBinding, cases, evidenceGaps. Do NOT emit coverageSummary — the shell materializer always computes it from sourceBinding/cases/evidenceGaps. Set schemaVersion to numeric 1. Do NOT emit schemaId, manifestType, taskId, modules, acCoverage, brCoverage, dataIsolation, readiness, or other custom top-level keys.",
|
|
2686
2701
|
"Copy sourceBinding exactly from contracts/backend-test-analysis.json: taskId, requirementPath, requirementSha256, referencePaths, requirementIds. Preserve Unicode paths exactly; never replace characters in source/需求.md or other paths.",
|
|
2687
2702
|
"Each cases[] item MUST use exactly: caseId, non-empty acIds, title, category, automationStatus; optional endpointRef, ruleRefs, file, symbol, gapReason, evidenceRef. Do NOT use id, module, brIds, endpoint, or priority.",
|
|
2688
2703
|
"category MUST be exactly one of: positive, negative, boundary, state-transition, auth, timeout, concurrency, other.",
|
|
@@ -2690,7 +2705,7 @@ const BACKEND_TEST_CASE_MANIFEST_OUTPUT_INSTRUCTIONS = [
|
|
|
2690
2705
|
"Each evidenceGaps[] item MUST use exactly: optional acId, optional caseId, required description, optional evidenceRef. Every gap requires at least acId or caseId. Do NOT use requirementId, relatedBrIds, or sourceRef.",
|
|
2691
2706
|
"Every case must map to at least one semantically applicable explicit AC-* in acIds. If no AC applies, omit that case and bind an evidence gap to the nearest applicable acId or caseId; never emit an unbound informational gap.",
|
|
2692
2707
|
"acIds MUST exactly match the explicit AC-* values in the written case body; do not infer ACs from Business Rules or summary matrices.",
|
|
2693
|
-
"Use full BE-<MODULE>-<NNN> caseId strings. Do not invent coverage percentages
|
|
2708
|
+
"Use full BE-<MODULE>-<NNN> caseId strings. Do not invent coverage percentages and do not emit coverageSummary; shell always writes the canonical summary.",
|
|
2694
2709
|
"Minimal shape example: {\"schemaVersion\":1,\"sourceBinding\":{\"taskId\":\"...\",\"requirementPath\":\"source/需求.md\",\"requirementSha256\":\"<64 lowercase hex>\",\"referencePaths\":[],\"requirementIds\":[\"AC-001\"]},\"cases\":[{\"caseId\":\"BE-MODULE-001\",\"acIds\":[\"AC-001\"],\"title\":\"...\",\"category\":\"positive\",\"automationStatus\":\"planned\",\"evidenceRef\":\"testcase/md/module.md\"}],\"evidenceGaps\":[]}",
|
|
2695
2710
|
].join("\n\n");
|
|
2696
2711
|
function buildEmitBackendCaseManifestNode(sources, options) {
|
|
@@ -2769,10 +2784,8 @@ function buildBackendTestTraceabilityGateNode(sources, options = {}) {
|
|
|
2769
2784
|
};
|
|
2770
2785
|
}
|
|
2771
2786
|
function buildReviewBackendCasesNode(sources, options) {
|
|
2772
|
-
const phase = options?.phase ?? "first";
|
|
2773
|
-
const isFinal = phase === "final";
|
|
2774
2787
|
return {
|
|
2775
|
-
id:
|
|
2788
|
+
id: "review-backend-cases-pi",
|
|
2776
2789
|
depends_on: options?.dependsOn ?? [
|
|
2777
2790
|
"backend-test-case-manifest-shell",
|
|
2778
2791
|
"backend-test-analysis-contract-shell",
|
|
@@ -2783,13 +2796,9 @@ function buildReviewBackendCasesNode(sources, options) {
|
|
|
2783
2796
|
writePolicy: "read-only",
|
|
2784
2797
|
allowedPaths: commonReadOnlyPaths(sources),
|
|
2785
2798
|
forbiddenPaths: commonForbiddenPaths(sources),
|
|
2786
|
-
outputContract:
|
|
2787
|
-
? "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision after at most one revision; followed by Findings and Coverage Assessment. No file writes. Final gate accepts pass only."
|
|
2788
|
-
: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision; followed by Findings, Coverage Assessment, and Required revisions when requesting revision. No file writes.",
|
|
2799
|
+
outputContract: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision; followed by Findings and Coverage Assessment. No file writes. The deterministic gate accepts pass only; request-revision ends this run.",
|
|
2789
2800
|
subtask_prompt: [
|
|
2790
|
-
|
|
2791
|
-
? "Final review of backend functional cases under testcase/md/ and the re-validated Case Manifest v1 after at most one revision pass."
|
|
2792
|
-
: "Review the generated backend functional test cases under testcase/md/ and the validated Case Manifest v1.",
|
|
2801
|
+
"Review the generated backend functional test cases under testcase/md/ and the validated Case Manifest v1.",
|
|
2793
2802
|
"",
|
|
2794
2803
|
"## Mandatory First Line:",
|
|
2795
2804
|
"First non-empty line must be exactly: VERDICT: pass or VERDICT: request-revision",
|
|
@@ -2818,15 +2827,13 @@ function buildReviewBackendCasesNode(sources, options) {
|
|
|
2818
2827
|
"## Verdict Rules:",
|
|
2819
2828
|
"- All Critical checks pass + Important findings ≤ 2 → VERDICT: pass",
|
|
2820
2829
|
"- Any Critical fails OR Important > 2 → VERDICT: request-revision",
|
|
2821
|
-
|
|
2822
|
-
? "- This is the FINAL review after one revision opportunity; remaining Critical issues must still request-revision (final gate will stop the DAG)."
|
|
2823
|
-
: "- When requesting revision, list numbered Required revisions concrete enough for revise-backend-cases-pi to edit testcase/md/**.",
|
|
2830
|
+
"- Any request-revision verdict ends the current run at the deterministic gate; describe findings clearly for an independent follow-up task.",
|
|
2824
2831
|
"",
|
|
2825
2832
|
"## Output After Verdict:",
|
|
2826
2833
|
"1. Coverage Assessment table (AC → full BE-* case IDs) using manifest + MD",
|
|
2827
2834
|
"2. Findings list (Critical/Important/Informational)",
|
|
2828
2835
|
"3. Statistics (total cases, positive/negative/boundary breakdown)",
|
|
2829
|
-
|
|
2836
|
+
"4. Required follow-up actions (only when request-revision; no in-run writer)",
|
|
2830
2837
|
"",
|
|
2831
2838
|
"## Constraints:",
|
|
2832
2839
|
"- Read-only: do not modify files",
|
|
@@ -2837,69 +2844,6 @@ function buildReviewBackendCasesNode(sources, options) {
|
|
|
2837
2844
|
.join("\n\n"),
|
|
2838
2845
|
};
|
|
2839
2846
|
}
|
|
2840
|
-
function buildReviewBackendCasesBranchConditionNode(sources) {
|
|
2841
|
-
return {
|
|
2842
|
-
id: "review-backend-cases-branch-condition",
|
|
2843
|
-
depends_on: ["review-backend-cases-pi"],
|
|
2844
|
-
role: "verifier",
|
|
2845
|
-
executor: "static",
|
|
2846
|
-
complexity: "LOW",
|
|
2847
|
-
writePolicy: "none",
|
|
2848
|
-
allowedPaths: commonReadOnlyPaths(sources),
|
|
2849
|
-
forbiddenPaths: commonForbiddenPaths(sources),
|
|
2850
|
-
outputContract: "Dynamic condition: select direct pass vs single revision chain from first review firstVerdictLine; malformed VERDICT fails closed (no default). The pass target is the already-finished first review, so the effective gate remains outside the exclusive branch targets.",
|
|
2851
|
-
subtask_prompt: "Branch on review-backend-cases-pi VERDICT: pass → already-finished first review (revision chain skipped); request-revision → revise-backend-cases-pi.",
|
|
2852
|
-
static: {
|
|
2853
|
-
resultMarkdown: "Backend case review branch condition (direct pass vs single revision).",
|
|
2854
|
-
},
|
|
2855
|
-
dynamicCondition: {
|
|
2856
|
-
workflowNodeId: "review-backend-cases-branch-condition",
|
|
2857
|
-
cases: [
|
|
2858
|
-
{
|
|
2859
|
-
when: "$.nodes['review-backend-cases-pi'].firstVerdictLine == 'VERDICT: pass'",
|
|
2860
|
-
then: "review-backend-cases-pi",
|
|
2861
|
-
},
|
|
2862
|
-
{
|
|
2863
|
-
when: "$.nodes['review-backend-cases-pi'].firstVerdictLine == 'VERDICT: request-revision'",
|
|
2864
|
-
then: "revise-backend-cases-pi",
|
|
2865
|
-
},
|
|
2866
|
-
],
|
|
2867
|
-
},
|
|
2868
|
-
};
|
|
2869
|
-
}
|
|
2870
|
-
function buildReviseBackendCasesNode(sources) {
|
|
2871
|
-
return {
|
|
2872
|
-
id: "revise-backend-cases-pi",
|
|
2873
|
-
depends_on: ["review-backend-cases-branch-condition"],
|
|
2874
|
-
role: "implementer",
|
|
2875
|
-
executor: "pi",
|
|
2876
|
-
toolProfile: "write",
|
|
2877
|
-
complexity: "MED",
|
|
2878
|
-
writePolicy: "exclusive",
|
|
2879
|
-
writeSet: ["testcase/md/**"],
|
|
2880
|
-
allowedPaths: ["testcase/md/**"],
|
|
2881
|
-
forbiddenPaths: commonForbiddenPaths(sources),
|
|
2882
|
-
outputContract: "Only scheduled when first review is VERDICT: request-revision. Apply Required revisions under testcase/md/** then summarize changes (single revision pass).",
|
|
2883
|
-
subtask_prompt: [
|
|
2884
|
-
"You are the single backend case revision pass (max one per DAG run).",
|
|
2885
|
-
"This node is only scheduled when review-backend-cases-pi emitted VERDICT: request-revision.",
|
|
2886
|
-
"",
|
|
2887
|
-
"## Inputs",
|
|
2888
|
-
"- First review: review-backend-cases-pi Markdown (VERDICT + Findings + Required revisions)",
|
|
2889
|
-
"- Current cases: testcase/md/**",
|
|
2890
|
-
"- Validated analysis + case manifest under the current run contracts/",
|
|
2891
|
-
"",
|
|
2892
|
-
"## Required work",
|
|
2893
|
-
"1. Edit only testcase/md/** to address every Critical finding and Required revision item",
|
|
2894
|
-
"2. Fix AC matrices to list FULL BE-* ids matching case bodies; remove false 'all cases' AC claims",
|
|
2895
|
-
"3. Keep BE-<MODULE>-<NNN> ids stable when possible; do not invent out-of-scope AC coverage",
|
|
2896
|
-
"4. Stay within writeSet; do not write pytest or production code",
|
|
2897
|
-
"5. End with a short summary of files touched",
|
|
2898
|
-
"",
|
|
2899
|
-
"Downstream will re-emit and re-validate Case Manifest v1, then run a FINAL review (pass-only gate).",
|
|
2900
|
-
].join("\n\n"),
|
|
2901
|
-
};
|
|
2902
|
-
}
|
|
2903
2847
|
function buildReviewBackendCasesGateNode(sources) {
|
|
2904
2848
|
return {
|
|
2905
2849
|
id: "review-backend-cases-gate-shell",
|
|
@@ -3053,21 +2997,18 @@ function buildGenerateBackendPytestNode(sources) {
|
|
|
3053
2997
|
"- If a test filename exists, add suffix: test_order.py → test_order_01.py",
|
|
3054
2998
|
"- Do NOT re-read source documents — use reviewed cases under testcase/md/ and upstream analyze-inputs-pi output only",
|
|
3055
2999
|
"- Read existing conftest.py/pytest.ini to understand conventions, but do NOT modify them",
|
|
3056
|
-
"- Do NOT execute pytest/python -m pytest or npm test in this node;
|
|
3000
|
+
"- Do NOT execute pytest/python -m pytest or npm test in this node; the single execution is owned by the dedicated shell node. Local smoke runs create __pycache__/.pytest_cache and are unnecessary here.",
|
|
3057
3001
|
].join("\n\n"),
|
|
3058
3002
|
};
|
|
3059
3003
|
}
|
|
3060
3004
|
function buildBackendTestSemanticReviewNode(sources, options = {}) {
|
|
3061
|
-
const final = options.final ?? false;
|
|
3062
3005
|
return {
|
|
3063
|
-
id:
|
|
3006
|
+
id: "review-generated-backend-pytest-pi",
|
|
3064
3007
|
depends_on: options.dependsOn ?? [
|
|
3065
3008
|
"generate-backend-pytest-pi",
|
|
3066
3009
|
"backend-test-analysis-contract-shell",
|
|
3067
3010
|
"backend-test-case-manifest-shell",
|
|
3068
|
-
"backend-test-case-manifest-final-shell",
|
|
3069
3011
|
],
|
|
3070
|
-
dependsPolicy: "all-or-condition-skip",
|
|
3071
3012
|
role: "reviewer",
|
|
3072
3013
|
executor: "pi",
|
|
3073
3014
|
complexity: "MED",
|
|
@@ -3076,7 +3017,7 @@ function buildBackendTestSemanticReviewNode(sources, options = {}) {
|
|
|
3076
3017
|
forbiddenPaths: commonForbiddenPaths(sources),
|
|
3077
3018
|
outputContract: "Pure Backend Test Semantic Review v1 JSON: verdict, findings[], summary. No file writes.",
|
|
3078
3019
|
subtask_prompt: [
|
|
3079
|
-
|
|
3020
|
+
"Review generated pytest semantics before the single execution.",
|
|
3080
3021
|
"Use only compact authoritative inputs: contracts/backend-test-analysis.json, contracts/backend-test-case-manifest.json, testcase/md/**, and generated testcase/**/test_*.py/helpers/factories.",
|
|
3081
3022
|
"Return exactly one pure JSON object with only verdict, findings, summary; no Markdown fence or surrounding prose.",
|
|
3082
3023
|
"verdict must be pass or request-revision. Each findings[] item must contain exactly severity, caseId, testFile, testSymbol, contractRefs, issue, requiredChange.",
|
|
@@ -3089,98 +3030,6 @@ function buildBackendTestSemanticReviewNode(sources, options = {}) {
|
|
|
3089
3030
|
].join("\n\n"),
|
|
3090
3031
|
};
|
|
3091
3032
|
}
|
|
3092
|
-
function buildBackendTestSemanticReviewGateNode(sources, options = {}) {
|
|
3093
|
-
const fromNodeId = options.fromNodeId ?? "review-generated-backend-pytest-pi";
|
|
3094
|
-
return {
|
|
3095
|
-
id: options.id ?? "backend-test-semantic-review-shell",
|
|
3096
|
-
depends_on: options.dependsOn ?? [fromNodeId],
|
|
3097
|
-
role: "verifier",
|
|
3098
|
-
executor: "shell",
|
|
3099
|
-
complexity: "LOW",
|
|
3100
|
-
writePolicy: "read-only",
|
|
3101
|
-
allowedPaths: commonReadOnlyPaths(sources),
|
|
3102
|
-
forbiddenPaths: commonForbiddenPaths(sources),
|
|
3103
|
-
outputContract: "Validated run-owned Backend Test Semantic Review v1 artifact.",
|
|
3104
|
-
subtask_prompt: "Validate semantic review JSON before branch selection.",
|
|
3105
|
-
shell: {
|
|
3106
|
-
commands: [],
|
|
3107
|
-
jsonArtifactGate: {
|
|
3108
|
-
fromNodeId,
|
|
3109
|
-
schemaId: "backend-test-semantic-review-v1",
|
|
3110
|
-
artifactName: options.id?.includes("final") ? "backend-test-semantic-review-final.json" : "backend-test-semantic-review.json",
|
|
3111
|
-
outputDir: "contracts",
|
|
3112
|
-
},
|
|
3113
|
-
cwd: ".",
|
|
3114
|
-
timeoutMs: 60000,
|
|
3115
|
-
},
|
|
3116
|
-
};
|
|
3117
|
-
}
|
|
3118
|
-
function buildBackendTestSemanticReviewConditionNode(sources) {
|
|
3119
|
-
return {
|
|
3120
|
-
id: "backend-test-semantic-review-condition",
|
|
3121
|
-
depends_on: ["backend-test-semantic-review-shell"],
|
|
3122
|
-
role: "verifier",
|
|
3123
|
-
executor: "static",
|
|
3124
|
-
complexity: "LOW",
|
|
3125
|
-
writePolicy: "none",
|
|
3126
|
-
allowedPaths: [],
|
|
3127
|
-
forbiddenPaths: commonForbiddenPaths(sources),
|
|
3128
|
-
outputContract: "Select direct semantic pass or one testcase-only revision; the pass target is the already-finished validated semantic review so the final gate remains outside the exclusive branch targets.",
|
|
3129
|
-
subtask_prompt: "Branch deterministically from semantic review verdict.",
|
|
3130
|
-
static: { resultMarkdown: "Backend pytest semantic review selector." },
|
|
3131
|
-
dynamicCondition: {
|
|
3132
|
-
workflowNodeId: "backend-test-semantic-review-condition",
|
|
3133
|
-
cases: [
|
|
3134
|
-
{ when: "$.nodes['backend-test-semantic-review-shell'].json.verdict == 'pass'", then: "backend-test-semantic-review-shell" },
|
|
3135
|
-
],
|
|
3136
|
-
default: "revise-generated-backend-pytest-pi",
|
|
3137
|
-
},
|
|
3138
|
-
};
|
|
3139
|
-
}
|
|
3140
|
-
function buildReviseGeneratedBackendPytestNode(sources) {
|
|
3141
|
-
const writeSet = ["testcase/**/test_*.py", "testcase/**/helpers/**", "testcase/**/factories/**"];
|
|
3142
|
-
return {
|
|
3143
|
-
id: "revise-generated-backend-pytest-pi",
|
|
3144
|
-
depends_on: ["backend-test-semantic-review-condition"],
|
|
3145
|
-
role: "implementer",
|
|
3146
|
-
executor: "pi",
|
|
3147
|
-
toolProfile: "write",
|
|
3148
|
-
complexity: "MED",
|
|
3149
|
-
writePolicy: "exclusive",
|
|
3150
|
-
writeSet,
|
|
3151
|
-
allowedPaths: writeSet,
|
|
3152
|
-
forbiddenPaths: [...commonForbiddenPaths(sources), "apps/**", "src/**", "testcase/md/**", "**/conftest.py", "**/pytest.ini"],
|
|
3153
|
-
outputContract: "Apply one bounded semantic correction to generated pytest from validated findings only.",
|
|
3154
|
-
subtask_prompt: [
|
|
3155
|
-
"This is the only pre-execution generated-pytest semantic revision (1/1).",
|
|
3156
|
-
"Read contracts/backend-test-semantic-review.json and edit only the cited generated test files/helpers/factories.",
|
|
3157
|
-
"Preserve case IDs, symbols, test count, target mode, base URL and real-service path.",
|
|
3158
|
-
"Do not delete tests, add skip/xfail, swallow failures, substitute mocks, or weaken assertions beyond the formal comparison/shape contract.",
|
|
3159
|
-
"Do not modify product code, testcase/md/**, conftest.py, pytest.ini, or .harness/**.",
|
|
3160
|
-
].join("\n\n"),
|
|
3161
|
-
};
|
|
3162
|
-
}
|
|
3163
|
-
function buildBackendTestSemanticFinalGateNode(sources) {
|
|
3164
|
-
return {
|
|
3165
|
-
id: "backend-test-semantic-final-gate-shell",
|
|
3166
|
-
depends_on: ["backend-test-semantic-review-condition", "backend-test-semantic-review-final-shell"],
|
|
3167
|
-
dependsPolicy: "all-or-condition-skip",
|
|
3168
|
-
role: "verifier",
|
|
3169
|
-
executor: "shell",
|
|
3170
|
-
complexity: "LOW",
|
|
3171
|
-
writePolicy: "read-only",
|
|
3172
|
-
allowedPaths: commonReadOnlyPaths(sources),
|
|
3173
|
-
forbiddenPaths: commonForbiddenPaths(sources),
|
|
3174
|
-
outputContract: "Pass-only semantic authorization for traceability and initial pytest.",
|
|
3175
|
-
subtask_prompt: "Accept initial pass path or final semantic review pass; fail closed otherwise.",
|
|
3176
|
-
shell: {
|
|
3177
|
-
commands: [],
|
|
3178
|
-
backendTestPipeline: "semantic-effective",
|
|
3179
|
-
cwd: ".",
|
|
3180
|
-
timeoutMs: 60000,
|
|
3181
|
-
},
|
|
3182
|
-
};
|
|
3183
|
-
}
|
|
3184
3033
|
function collectBackendTestShellEnvAllowlist(sources) {
|
|
3185
3034
|
const names = new Set();
|
|
3186
3035
|
for (const verify of sources.taskConfig.verifyCommands) {
|
|
@@ -3228,7 +3077,7 @@ function buildExecuteBackendPytestNode(sources, options = {}) {
|
|
|
3228
3077
|
return {
|
|
3229
3078
|
id: nodeId,
|
|
3230
3079
|
depends_on: options.dependsOn ?? [
|
|
3231
|
-
"backend-test-semantic-
|
|
3080
|
+
"backend-test-semantic-gate-shell",
|
|
3232
3081
|
"backend-test-execution-contract-shell",
|
|
3233
3082
|
],
|
|
3234
3083
|
role: "verifier",
|
|
@@ -3254,41 +3103,10 @@ function buildExecuteBackendPytestNode(sources, options = {}) {
|
|
|
3254
3103
|
},
|
|
3255
3104
|
};
|
|
3256
3105
|
}
|
|
3257
|
-
function buildParseBackendTestResultNode(sources, options = {}) {
|
|
3258
|
-
const id = options.id ?? "parse-backend-test-result-shell";
|
|
3259
|
-
const fromNodeId = options.fromNodeId ?? "execute-backend-pytest-shell";
|
|
3260
|
-
const artifactName = options.artifactName ?? "backend-test-result.json";
|
|
3261
|
-
return {
|
|
3262
|
-
id,
|
|
3263
|
-
depends_on: options.dependsOn ?? [fromNodeId],
|
|
3264
|
-
role: "verifier",
|
|
3265
|
-
executor: "shell",
|
|
3266
|
-
complexity: "LOW",
|
|
3267
|
-
writePolicy: "read-only",
|
|
3268
|
-
allowedPaths: commonReadOnlyPaths(sources),
|
|
3269
|
-
forbiddenPaths: commonForbiddenPaths(sources),
|
|
3270
|
-
outputContract: "Validated run-owned Backend Test Result v1 at contracts/backend-test-result.json (schemaId backend-test-result-v1) with outcome/counts/failures from deterministic JUnit parse.",
|
|
3271
|
-
subtask_prompt: "Materialize Backend Test Result v1 from JUnit + pytestExitCode under the current DAG run (fail-closed on missing/corrupt report).",
|
|
3272
|
-
shell: {
|
|
3273
|
-
commands: [],
|
|
3274
|
-
jsonArtifactGate: {
|
|
3275
|
-
fromNodeId,
|
|
3276
|
-
schemaId: "backend-test-result-v1",
|
|
3277
|
-
artifactName,
|
|
3278
|
-
outputDir: "contracts",
|
|
3279
|
-
...(options.junitRelativePath
|
|
3280
|
-
? { junitRelativePath: options.junitRelativePath }
|
|
3281
|
-
: {}),
|
|
3282
|
-
},
|
|
3283
|
-
cwd: ".",
|
|
3284
|
-
timeoutMs: 60000,
|
|
3285
|
-
},
|
|
3286
|
-
};
|
|
3287
|
-
}
|
|
3288
3106
|
function buildClassifyBackendTestResultNode(sources) {
|
|
3289
3107
|
return {
|
|
3290
3108
|
id: "classify-backend-test-result-pi",
|
|
3291
|
-
depends_on: ["parse-backend-
|
|
3109
|
+
depends_on: ["execute-and-parse-backend-pytest-shell"],
|
|
3292
3110
|
role: "reviewer",
|
|
3293
3111
|
executor: "pi",
|
|
3294
3112
|
complexity: "MED",
|
|
@@ -3313,155 +3131,6 @@ function buildClassifyBackendTestResultNode(sources) {
|
|
|
3313
3131
|
].join("\n\n"),
|
|
3314
3132
|
};
|
|
3315
3133
|
}
|
|
3316
|
-
function buildBackendTestClassificationGateNode(sources) {
|
|
3317
|
-
return {
|
|
3318
|
-
id: "backend-test-classification-shell",
|
|
3319
|
-
depends_on: ["classify-backend-test-result-pi"],
|
|
3320
|
-
role: "verifier",
|
|
3321
|
-
executor: "shell",
|
|
3322
|
-
complexity: "LOW",
|
|
3323
|
-
writePolicy: "read-only",
|
|
3324
|
-
allowedPaths: commonReadOnlyPaths(sources),
|
|
3325
|
-
forbiddenPaths: commonForbiddenPaths(sources),
|
|
3326
|
-
outputContract: "Validated run-owned Backend Test Classification v1 at contracts/backend-test-classification.json.",
|
|
3327
|
-
subtask_prompt: "Validate and materialize the read-only backend-test classification for deterministic repair routing.",
|
|
3328
|
-
shell: {
|
|
3329
|
-
commands: [],
|
|
3330
|
-
jsonArtifactGate: {
|
|
3331
|
-
fromNodeId: "classify-backend-test-result-pi",
|
|
3332
|
-
schemaId: "backend-test-classification-v1",
|
|
3333
|
-
artifactName: "backend-test-classification.json",
|
|
3334
|
-
outputDir: "contracts",
|
|
3335
|
-
},
|
|
3336
|
-
cwd: ".",
|
|
3337
|
-
timeoutMs: 60000,
|
|
3338
|
-
},
|
|
3339
|
-
};
|
|
3340
|
-
}
|
|
3341
|
-
function buildBackendTestRepairEligibilityNode(sources) {
|
|
3342
|
-
const command = buildBackendTestRepairEligibilityShellSnippet();
|
|
3343
|
-
return {
|
|
3344
|
-
id: "backend-test-repair-eligibility-shell",
|
|
3345
|
-
depends_on: ["backend-test-classification-shell"],
|
|
3346
|
-
role: "verifier",
|
|
3347
|
-
executor: "shell",
|
|
3348
|
-
complexity: "LOW",
|
|
3349
|
-
writePolicy: "read-only",
|
|
3350
|
-
allowedPaths: commonReadOnlyPaths(sources),
|
|
3351
|
-
forbiddenPaths: commonForbiddenPaths(sources),
|
|
3352
|
-
outputContract: "Pure JSON {schemaVersion,eligible,reason,revisionAttempt,category,confidence}; eligible only for completed TestBug assertion failures with error=0.",
|
|
3353
|
-
subtask_prompt: "Deterministically decide whether this run may use its single testcase-only repair attempt.",
|
|
3354
|
-
shell: { commands: [command], cwd: ".", timeoutMs: 60000 },
|
|
3355
|
-
};
|
|
3356
|
-
}
|
|
3357
|
-
function buildBackendTestRepairConditionNode(sources) {
|
|
3358
|
-
return {
|
|
3359
|
-
id: "backend-test-repair-condition",
|
|
3360
|
-
depends_on: ["backend-test-repair-eligibility-shell"],
|
|
3361
|
-
role: "verifier",
|
|
3362
|
-
executor: "static",
|
|
3363
|
-
complexity: "LOW",
|
|
3364
|
-
writePolicy: "none",
|
|
3365
|
-
allowedPaths: [],
|
|
3366
|
-
forbiddenPaths: commonForbiddenPaths(sources),
|
|
3367
|
-
outputContract: "Select repair or skip repair exactly once; ineligible runs select the already-finished eligibility evidence so the effective-result shell remains outside the exclusive branch targets.",
|
|
3368
|
-
subtask_prompt: "Route eligible TestBug to one repair attempt; otherwise select the already-finished eligibility evidence and skip repair.",
|
|
3369
|
-
static: { resultMarkdown: "Backend-test repair branch selector." },
|
|
3370
|
-
dynamicCondition: {
|
|
3371
|
-
workflowNodeId: "backend-test-repair-condition",
|
|
3372
|
-
cases: [
|
|
3373
|
-
{
|
|
3374
|
-
when: "$.nodes['backend-test-repair-eligibility-shell'].json.eligible == true",
|
|
3375
|
-
then: "repair-backend-pytest-pi",
|
|
3376
|
-
},
|
|
3377
|
-
],
|
|
3378
|
-
default: "backend-test-repair-eligibility-shell",
|
|
3379
|
-
},
|
|
3380
|
-
};
|
|
3381
|
-
}
|
|
3382
|
-
function buildRepairBackendPytestNode(sources) {
|
|
3383
|
-
const writeSet = [
|
|
3384
|
-
"testcase/**/test_*.py",
|
|
3385
|
-
"testcase/**/helpers/**",
|
|
3386
|
-
"testcase/**/factories/**",
|
|
3387
|
-
];
|
|
3388
|
-
return {
|
|
3389
|
-
id: "repair-backend-pytest-pi",
|
|
3390
|
-
depends_on: ["backend-test-repair-condition"],
|
|
3391
|
-
role: "implementer",
|
|
3392
|
-
executor: "pi",
|
|
3393
|
-
toolProfile: "write",
|
|
3394
|
-
complexity: "HIGH",
|
|
3395
|
-
writePolicy: "exclusive",
|
|
3396
|
-
writeSet,
|
|
3397
|
-
allowedPaths: writeSet,
|
|
3398
|
-
forbiddenPaths: [
|
|
3399
|
-
...commonForbiddenPaths(sources),
|
|
3400
|
-
"apps/**",
|
|
3401
|
-
"src/**",
|
|
3402
|
-
".env*",
|
|
3403
|
-
"**/migrations/**",
|
|
3404
|
-
],
|
|
3405
|
-
outputContract: "Repair only existing generated pytest tests/helpers/factories for classified TestBug findings. No production/config/runtime evidence writes.",
|
|
3406
|
-
subtask_prompt: [
|
|
3407
|
-
"This is the only automatic TestBug repair attempt (1/1).",
|
|
3408
|
-
"Read contracts/backend-test-result-initial.json, contracts/backend-test-classification.json, contracts/backend-test-analysis.json, contracts/backend-test-case-manifest.json, and only the generated pytest files named in failures[].name or manifest mappings.",
|
|
3409
|
-
"Do not re-read the full task source tree: the run-owned analysis/manifest are the compact authoritative context.",
|
|
3410
|
-
"Repair only test implementation defects directly supported by reviewed Expected Results or formal API contract evidence.",
|
|
3411
|
-
"Never modify product code, migrations, service configuration, conftest.py, pytest.ini, pyproject.toml, setup.cfg, source requirements, testcase/md/**, or .harness/**.",
|
|
3412
|
-
"Never delete a test, remove a case mapping, add skip/skipif/xfail, swallow AssertionError/network exceptions, switch to a mock server, or weaken documented status/value assertions.",
|
|
3413
|
-
"Keep the full suite runnable and preserve each BE-* case ID and pytest symbol mapping.",
|
|
3414
|
-
"Stay within writeSet: testcase/**/test_*.py, helpers/**, factories/**.",
|
|
3415
|
-
].join("\n\n"),
|
|
3416
|
-
};
|
|
3417
|
-
}
|
|
3418
|
-
function buildBackendTestRepairSafetyGateNode(sources) {
|
|
3419
|
-
const command = buildBackendTestRepairSafetyShellSnippet();
|
|
3420
|
-
return {
|
|
3421
|
-
id: "backend-test-repair-safety-gate-shell",
|
|
3422
|
-
depends_on: ["repair-backend-pytest-pi"],
|
|
3423
|
-
role: "verifier",
|
|
3424
|
-
executor: "shell",
|
|
3425
|
-
complexity: "LOW",
|
|
3426
|
-
writePolicy: "read-only",
|
|
3427
|
-
allowedPaths: ["testcase/**"],
|
|
3428
|
-
forbiddenPaths: commonForbiddenPaths(sources),
|
|
3429
|
-
outputContract: "Fail-closed repair safety gate rejecting skip/xfail and broad failure swallowing before final traceability and pytest.",
|
|
3430
|
-
subtask_prompt: "Check that automatic TestBug repair did not manufacture success.",
|
|
3431
|
-
shell: { commands: [command], cwd: ".", timeoutMs: 60000 },
|
|
3432
|
-
};
|
|
3433
|
-
}
|
|
3434
|
-
function buildBackendTestEffectiveResultNode(sources) {
|
|
3435
|
-
const command = buildBackendTestEffectiveResultSelectorShellSnippet();
|
|
3436
|
-
// Inline retrospective context: relocated verbatim from the former
|
|
3437
|
-
// buildBackendTestRetrospectiveContextNode so test-retrospect-pi keeps a
|
|
3438
|
-
// single upstream. Runs after `command` which materializes
|
|
3439
|
-
// contracts/backend-test-result.json from the effective (final|initial) result.
|
|
3440
|
-
const retroCommand = [
|
|
3441
|
-
'test -n "${HARNESS_DAG_RUN_DIR:-}" || { echo "missing HARNESS_DAG_RUN_DIR for backend-test retrospective context" >&2; exit 2; }',
|
|
3442
|
-
'node -e \'const fs=require("fs"),path=require("path"); const root=process.env.HARNESS_DAG_RUN_DIR; const read=(name)=>JSON.parse(fs.readFileSync(path.join(root,"contracts",name),"utf8")); process.stdout.write(JSON.stringify({schemaVersion:1,result:read("backend-test-result.json"),initialResult:read("backend-test-result-initial.json"),finalResult:fs.existsSync(path.join(root,"contracts","backend-test-result-final.json"))?read("backend-test-result-final.json"):null,manifest:read("backend-test-case-manifest.json"),classification:read("backend-test-classification.json")},null,2));\'',
|
|
3443
|
-
].join("; ");
|
|
3444
|
-
return {
|
|
3445
|
-
id: "select-effective-backend-test-result-shell",
|
|
3446
|
-
depends_on: [
|
|
3447
|
-
"backend-test-repair-condition",
|
|
3448
|
-
"parse-backend-test-result-final-shell",
|
|
3449
|
-
"backend-test-case-manifest-shell",
|
|
3450
|
-
"backend-test-case-manifest-final-shell",
|
|
3451
|
-
"backend-test-classification-shell",
|
|
3452
|
-
],
|
|
3453
|
-
dependsPolicy: "all-or-condition-skip",
|
|
3454
|
-
role: "verifier",
|
|
3455
|
-
executor: "shell",
|
|
3456
|
-
complexity: "LOW",
|
|
3457
|
-
writePolicy: "read-only",
|
|
3458
|
-
allowedPaths: commonReadOnlyPaths(sources),
|
|
3459
|
-
forbiddenPaths: commonForbiddenPaths(sources),
|
|
3460
|
-
outputContract: "Materialize contracts/backend-test-result.json from final result when present, otherwise initial result; emit effective result metadata, and emit the complete retrospective context (Result v1 + Case Manifest v1 + classifier output) for downstream retrospective consumption.",
|
|
3461
|
-
subtask_prompt: "Select the effective backend-test result deterministically without changing initial/final evidence, then emit the complete retrospective context.",
|
|
3462
|
-
shell: { commands: [command, retroCommand], cwd: ".", timeoutMs: 60000 },
|
|
3463
|
-
};
|
|
3464
|
-
}
|
|
3465
3134
|
function buildTestRetrospectNode(sources) {
|
|
3466
3135
|
const canWriteReport = taskAllowsBackendTestReportWrite(sources);
|
|
3467
3136
|
return {
|
|
@@ -3567,10 +3236,11 @@ function buildBackendTestHybridDag(sources) {
|
|
|
3567
3236
|
const globalConstraints = [
|
|
3568
3237
|
...taskConfig.hardConstraints,
|
|
3569
3238
|
...STANDARD_GLOBAL_CONSTRAINTS,
|
|
3570
|
-
"backend-test-dag uses exactly
|
|
3571
|
-
"
|
|
3239
|
+
"backend-test-dag uses exactly 15 real top-level tasks and executes pytest exactly once.",
|
|
3240
|
+
"Case and semantic request-revision verdicts fail at deterministic gates; no in-run revision or repair writer is authorized.",
|
|
3241
|
+
"Analysis, execution, manifest, semantic review, single-run result, classification, canonical result, retrospective and outcome evidence remain run-owned and fail-closed.",
|
|
3572
3242
|
"Functional test case IDs must use BE-<MODULE>-<NNN> format.",
|
|
3573
|
-
"pytest writers may only
|
|
3243
|
+
"pytest writers may only create the initially declared testcase assets; production code, config, skip/xfail, swallowed failures and mock substitution are forbidden.",
|
|
3574
3244
|
];
|
|
3575
3245
|
const analyze = buildAnalyzeInputsNode(sources);
|
|
3576
3246
|
analyze.id = "analyze-and-discover-backend-test-pi";
|
|
@@ -3589,64 +3259,49 @@ function buildBackendTestHybridDag(sources) {
|
|
|
3589
3259
|
generateCases.subtask_prompt += `\n\nAfter writing Markdown, end assistant output with exactly one fenced json block containing Backend Test Case Manifest v1 derived from the written cases.\n\n${BACKEND_TEST_CASE_MANIFEST_OUTPUT_INSTRUCTIONS}`;
|
|
3590
3260
|
const manifest = buildBackendTestCaseManifestGateNode(sources, { dependsOn: [generateCases.id], fromNodeId: generateCases.id });
|
|
3591
3261
|
const reviewCases = buildReviewBackendCasesNode(sources, { dependsOn: [manifest.id, contracts.id] });
|
|
3592
|
-
const reviseCases = buildReviseBackendCasesNode(sources);
|
|
3593
|
-
reviseCases.depends_on = [reviewCases.id];
|
|
3594
|
-
reviseCases.runIf = "$.nodes['review-backend-cases-pi'].firstVerdictLine == 'VERDICT: request-revision'";
|
|
3595
|
-
reviseCases.outputContract = "Apply one case revision and end with one fenced json final Case Manifest v1 block matching the strict field contract.";
|
|
3596
|
-
reviseCases.subtask_prompt += `\n\nAfter edits, end assistant output with exactly one fenced json block containing the complete final Backend Test Case Manifest v1.\n\n${BACKEND_TEST_CASE_MANIFEST_OUTPUT_INSTRUCTIONS}`;
|
|
3597
|
-
const finalManifest = buildBackendTestCaseManifestGateNode(sources, { id: "backend-test-case-manifest-final-shell", dependsOn: [reviseCases.id], fromNodeId: reviseCases.id });
|
|
3598
|
-
finalManifest.runIf = reviseCases.runIf;
|
|
3599
|
-
const finalCaseReview = buildReviewBackendCasesNode(sources, { id: "review-backend-cases-final-pi", phase: "final", dependsOn: [finalManifest.id, contracts.id] });
|
|
3600
|
-
finalCaseReview.runIf = reviseCases.runIf;
|
|
3601
3262
|
const caseGate = buildReviewBackendCasesGateNode(sources);
|
|
3602
|
-
caseGate.depends_on = [reviewCases.id
|
|
3603
|
-
caseGate.
|
|
3263
|
+
caseGate.depends_on = [reviewCases.id];
|
|
3264
|
+
caseGate.dependsPolicy = undefined;
|
|
3265
|
+
caseGate.outputContract = "Deterministic backend case review gate: exit 0 only when the first and only review emits VERDICT: pass.";
|
|
3266
|
+
caseGate.subtask_prompt = "Block pytest generation when backend case review requests revision; do not authorize an in-run writer.";
|
|
3267
|
+
caseGate.shell.verdictGate = { fromNodeId: reviewCases.id, accept: ["VERDICT: pass"], label: "backend case review", lineMode: "first-verdict-line" };
|
|
3604
3268
|
const generatePytest = buildGenerateBackendPytestNode(sources);
|
|
3605
3269
|
generatePytest.depends_on = [caseGate.id, contracts.id];
|
|
3606
|
-
const semanticReview = buildBackendTestSemanticReviewNode(sources, { dependsOn: [generatePytest.id, contracts.id, manifest.id
|
|
3607
|
-
const
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
const
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
role: "verifier",
|
|
3619
|
-
executor: "shell",
|
|
3620
|
-
complexity: "LOW",
|
|
3621
|
-
writePolicy: "read-only",
|
|
3622
|
-
allowedPaths: commonReadOnlyPaths(sources),
|
|
3623
|
-
forbiddenPaths: commonForbiddenPaths(sources),
|
|
3624
|
-
outputContract: "Pass-only effective semantic review gate with final traceability after revision.",
|
|
3625
|
-
subtask_prompt: "Accept initial semantic pass or validate the single final review and traceability.",
|
|
3270
|
+
const semanticReview = buildBackendTestSemanticReviewNode(sources, { dependsOn: [generatePytest.id, contracts.id, manifest.id] });
|
|
3271
|
+
const semanticMaterialize = {
|
|
3272
|
+
id: "validate-semantic-review-and-traceability-shell", depends_on: [semanticReview.id, manifest.id], role: "verifier", executor: "shell", complexity: "LOW",
|
|
3273
|
+
writePolicy: "read-only", allowedPaths: commonReadOnlyPaths(sources), forbiddenPaths: commonForbiddenPaths(sources),
|
|
3274
|
+
outputContract: "Materialize the only semantic review and validate pytest traceability.", subtask_prompt: "Materialize semantic facts and traceability; verdict authorization is handled by the next deterministic gate.",
|
|
3275
|
+
shell: { commands: [], backendTestPipeline: "semantic-initial", cwd: ".", timeoutMs: 60000 },
|
|
3276
|
+
};
|
|
3277
|
+
const semanticGate = {
|
|
3278
|
+
id: "backend-test-semantic-gate-shell", depends_on: [semanticMaterialize.id, semanticReview.id], role: "verifier", executor: "shell", complexity: "LOW",
|
|
3279
|
+
writePolicy: "read-only", allowedPaths: commonReadOnlyPaths(sources), forbiddenPaths: commonForbiddenPaths(sources),
|
|
3280
|
+
outputContract: "Pass-only authorization by reading contracts/backend-test-semantic-review.json; only verdict=pass proceeds to the single pytest execution.",
|
|
3281
|
+
subtask_prompt: "Read the canonical semantic review artifact written by validate-semantic-review-and-traceability-shell. Authorize only when verdict is pass. Do not materialize, do not parse raw Pi Markdown or VERDICT lines, and do not authorize an in-run pytest writer.",
|
|
3626
3282
|
shell: {
|
|
3627
3283
|
commands: [],
|
|
3628
|
-
backendTestPipeline: "semantic-
|
|
3284
|
+
backendTestPipeline: "semantic-initial",
|
|
3629
3285
|
cwd: ".",
|
|
3630
3286
|
timeoutMs: 60000,
|
|
3631
3287
|
},
|
|
3632
3288
|
};
|
|
3633
|
-
const
|
|
3634
|
-
|
|
3289
|
+
const execute = buildExecuteBackendPytestNode(sources, { id: "execute-and-parse-backend-pytest-shell", dependsOn: [semanticGate.id, contracts.id], reportStem: "backend-test-initial" });
|
|
3290
|
+
execute.shell.backendTestPipeline = "execute-parse-initial";
|
|
3635
3291
|
const classify = buildClassifyBackendTestResultNode(sources);
|
|
3636
|
-
classify.depends_on = [
|
|
3637
|
-
const
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
finalize.shell.backendTestPipeline = "finalize-effective-result";
|
|
3645
|
-
finalize.outputContract = "If repaired, execute/parse final pytest; always materialize contracts/backend-test-result.json from final or initial result.";
|
|
3292
|
+
classify.depends_on = [execute.id];
|
|
3293
|
+
const context = {
|
|
3294
|
+
id: "materialize-classification-and-result-context-shell", depends_on: [classify.id, manifest.id], role: "verifier", executor: "shell", complexity: "LOW",
|
|
3295
|
+
writePolicy: "read-only", allowedPaths: commonReadOnlyPaths(sources), forbiddenPaths: commonForbiddenPaths(sources),
|
|
3296
|
+
outputContract: "Materialize Classification v1, copy the unique initial Result to canonical contracts/backend-test-result.json, and emit Result + Manifest + Classification context.",
|
|
3297
|
+
subtask_prompt: "Validate classification and materialize canonical single-run result context without repair eligibility or rerun.",
|
|
3298
|
+
shell: { commands: [], backendTestPipeline: "classification-result-context", cwd: ".", timeoutMs: 60000 },
|
|
3299
|
+
};
|
|
3646
3300
|
const retrospect = buildTestRetrospectNode(sources);
|
|
3647
|
-
retrospect.depends_on = [
|
|
3301
|
+
retrospect.depends_on = [context.id];
|
|
3302
|
+
retrospect.subtask_prompt = retrospect.subtask_prompt.replaceAll("select-effective-backend-test-result-shell", context.id);
|
|
3648
3303
|
const outcome = buildBackendTestOutcomeGateNode(sources);
|
|
3649
|
-
const tasks = [analyze, contracts, generateCases, manifest, reviewCases,
|
|
3304
|
+
const tasks = [analyze, contracts, generateCases, manifest, reviewCases, caseGate, generatePytest, semanticReview, semanticMaterialize, semanticGate, execute, classify, context, retrospect, outcome];
|
|
3650
3305
|
const spec = { version: 3, title: `Backend test DAG: ${taskConfig.title}`, runtimeContract: GENERATED_DAG_RUNTIME_CONTRACT, outputLanguage: sources.outputLanguage ?? DEFAULT_DAG_OUTPUT_LANGUAGE, objective: extractObjective(sources.requirementMarkdown, taskConfig.title), successCriteria: extractSuccessCriteria(sources.requirementMarkdown, sources.taskId), globalConstraints, defaults: { ...BACKEND_TEST_DEFAULTS, contextProfile: taskConfig.contextProfile }, skillsByRole: BACKEND_TEST_SKILLS_BY_ROLE, executorModels: sources.executorModelMatrix ?? DEFAULT_DAG_EXECUTOR_MODELS, tasks };
|
|
3651
3306
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
3652
3307
|
parseDagSpec(spec);
|