@tea-agent/loop-agent 0.25.4 → 0.25.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -0
- package/CHANGELOG.md +60 -0
- package/dist/commands/client-recovery.js +209 -62
- package/dist/commands/init.js +68 -129
- package/dist/executors/dag-pi-executor.js +80 -15
- package/dist/executors/model-routing.js +1 -1
- package/dist/executors/shell-executor.js +127 -0
- package/dist/executors/shell-write-guard.js +21 -7
- package/dist/worker/console/repo-fingerprint.js +7 -1
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +1281 -0
- package/dist/workflows/dag/backend-test-case-manifest.js +59 -1
- package/dist/workflows/dag/backend-test-markdown-workflow.js +236 -16
- package/dist/workflows/dag/convergence/controller.js +134 -9
- package/dist/workflows/dag/frontend-test-l5-report.js +138 -0
- package/dist/workflows/dag/init-hybrid.js +270 -80
- package/dist/workflows/dag/node-execution.js +64 -11
- package/dist/workflows/dag/prompt.js +118 -4
- package/dist/workflows/dag/retry-policy.js +5 -4
- package/dist/workflows/dag/scheduler.js +32 -5
- package/dist/workflows/dag/types.js +7 -4
- package/dist/workflows/dag/validate.js +3 -2
- package/docs/architecture/dag-execution.md +7 -4
- package/docs/architecture/runtime-boundaries.md +1 -1
- package/docs/init-surface.manifest.json +3 -1
- package/docs/templates/README.md +1 -0
- package/docs/templates/agent-dag.base.json +1 -1
- package/docs/templates/agent-dag.final-verification.json +1 -1
- package/docs/templates/agent-dag.supervised-implementation.json +1 -1
- package/docs/templates/backend-test-dag.json +41 -14
- package/docs/templates/frontend-test-dag.json +32 -2
- package/docs/templates/hybrid-dag.json +1 -1
- package/docs/templates/init-managed-agents.md +137 -0
- package/examples/decision-gate-agent-dag.json +1 -1
- package/examples/example-dag.json +1 -1
- package/examples/hybrid-loop-agent-dag.json +1 -1
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/loop-agent/references/command-reference.md +5 -4
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/skills/loop-agent/references/model-routing.md +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { access, readdir, readFile, realpath
|
|
2
|
+
import { access, readdir, readFile, realpath } from "node:fs/promises";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
@@ -896,8 +896,7 @@ function chooseFrontendVerifyCommands(input) {
|
|
|
896
896
|
return { commandSource: "inline" };
|
|
897
897
|
}
|
|
898
898
|
function classifyFrontendVerifyCommand(command) {
|
|
899
|
-
return /\b(typecheck|check-types|lint|eslint|tsc|build|check)\b/i.test(command) ||
|
|
900
|
-
/\bscripts[\\/]+ci(?:-tests)?\.sh\b/i.test(command)
|
|
899
|
+
return /\b(typecheck|check-types|lint|eslint|tsc|build|check)\b/i.test(command) || /\bscripts[\\/]+ci(?:-tests)?\.sh\b/i.test(command)
|
|
901
900
|
? "static"
|
|
902
901
|
: "behavior";
|
|
903
902
|
}
|
|
@@ -925,16 +924,18 @@ function buildExplicitFrontendVerifyCommands(taskConfig, repoRoot) {
|
|
|
925
924
|
function verifyCommandKey(command) {
|
|
926
925
|
const normalizedArgs = normalizeVerifyCommandArgs(command.args);
|
|
927
926
|
const normalizedCwd = path.resolve(command.cwd).replace(/\\/g, "/");
|
|
928
|
-
const cwdKey = process.platform === "win32"
|
|
929
|
-
? normalizedCwd.toLowerCase()
|
|
930
|
-
: normalizedCwd;
|
|
927
|
+
const cwdKey = process.platform === "win32" ? normalizedCwd.toLowerCase() : normalizedCwd;
|
|
931
928
|
const envKey = Object.entries(command.env ?? {})
|
|
932
929
|
.filter((entry) => entry[1] !== undefined)
|
|
933
930
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
934
931
|
.map(([key, value]) => `${key}=${value}`)
|
|
935
932
|
.join("\0");
|
|
936
|
-
return [
|
|
937
|
-
|
|
933
|
+
return [
|
|
934
|
+
cwdKey,
|
|
935
|
+
normalizedArgs.join("\0"),
|
|
936
|
+
envKey,
|
|
937
|
+
command.timeoutMs ?? "",
|
|
938
|
+
].join("\u0001");
|
|
938
939
|
}
|
|
939
940
|
function normalizeVerifyCommandArgs(args) {
|
|
940
941
|
if (args.length === 3 &&
|
|
@@ -1008,7 +1009,10 @@ function isFullSuiteVerifyCommand(command) {
|
|
|
1008
1009
|
let index = 0;
|
|
1009
1010
|
while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(args[index] ?? ""))
|
|
1010
1011
|
index += 1;
|
|
1011
|
-
const executable = path
|
|
1012
|
+
const executable = path
|
|
1013
|
+
.basename(args[index] ?? "")
|
|
1014
|
+
.toLowerCase()
|
|
1015
|
+
.replace(/\.(?:exe|cmd)$/, "");
|
|
1012
1016
|
const rest = args.slice(index + 1);
|
|
1013
1017
|
if (["npm", "pnpm", "yarn", "bun"].includes(executable)) {
|
|
1014
1018
|
return ((rest.length === 1 && rest[0] === "test") ||
|
|
@@ -1017,7 +1021,8 @@ function isFullSuiteVerifyCommand(command) {
|
|
|
1017
1021
|
if (["bash", "sh"].includes(executable) && rest.length === 1) {
|
|
1018
1022
|
return /(?:^|\/)scripts\/ci\.sh$/i.test(rest[0].replace(/\\/g, "/"));
|
|
1019
1023
|
}
|
|
1020
|
-
return rest.length === 0 &&
|
|
1024
|
+
return (rest.length === 0 &&
|
|
1025
|
+
/(?:^|\/)scripts\/ci\.sh$/i.test((args[index] ?? "").replace(/\\/g, "/")));
|
|
1021
1026
|
}
|
|
1022
1027
|
function resolveDagVerifyStrategy(taskConfig, defaultIntermediateQuotaWhenFull = "full") {
|
|
1023
1028
|
const explicitIntermediateQuota = taskConfig.dagVerifyStrategy?.intermediateQuota;
|
|
@@ -1038,7 +1043,8 @@ function buildVerifyEvidence(input) {
|
|
|
1038
1043
|
quota: input.quota,
|
|
1039
1044
|
commandSource: input.commandSource,
|
|
1040
1045
|
commandCount,
|
|
1041
|
-
commandLabels: selectedCommands?.map((command) => command.label) ??
|
|
1046
|
+
commandLabels: selectedCommands?.map((command) => command.label) ??
|
|
1047
|
+
input.fallbackCommands,
|
|
1042
1048
|
commandTexts: input.commandTexts ?? [],
|
|
1043
1049
|
commandTimeoutMs: input.commandTimeoutMs,
|
|
1044
1050
|
totalTimeoutBudgetMs: commandCount * input.commandTimeoutMs,
|
|
@@ -1674,6 +1680,7 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
1674
1680
|
{
|
|
1675
1681
|
id: "closeout-pi",
|
|
1676
1682
|
depends_on: ["verify-pi"],
|
|
1683
|
+
failureAwareDependsOn: ["verify-pi"],
|
|
1677
1684
|
role: "closeout",
|
|
1678
1685
|
executor: "pi",
|
|
1679
1686
|
complexity: "MED",
|
|
@@ -1685,6 +1692,7 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
1685
1692
|
outputContract: "Plain Markdown closeout summary; no file writes.",
|
|
1686
1693
|
subtask_prompt: [
|
|
1687
1694
|
"Return closeout summary: what changed, verification status, risks, and handoff notes.",
|
|
1695
|
+
"If upstream failure evidence or a failure-derived skip is present, report the run as failed/partial_failed and never infer a passing result.",
|
|
1688
1696
|
"Read-only: do not modify code, docs, artifacts, or .harness/dag-runs/.",
|
|
1689
1697
|
sourceContext,
|
|
1690
1698
|
].join("\n\n"),
|
|
@@ -1874,7 +1882,8 @@ function buildBlockedFrontendMockDag(sources, readOnlyPaths, forbiddenPaths, glo
|
|
|
1874
1882
|
},
|
|
1875
1883
|
skillsByRole: FRONTEND_SKILLS_BY_ROLE,
|
|
1876
1884
|
executorModels: sources.executorModelMatrix ?? DEFAULT_DAG_EXECUTOR_MODELS,
|
|
1877
|
-
tasks: [
|
|
1885
|
+
tasks: [
|
|
1886
|
+
{
|
|
1878
1887
|
id: "frontend-mock-blocked-shell",
|
|
1879
1888
|
depends_on: [],
|
|
1880
1889
|
role: "verifier",
|
|
@@ -1892,7 +1901,8 @@ function buildBlockedFrontendMockDag(sources, readOnlyPaths, forbiddenPaths, glo
|
|
|
1892
1901
|
cwd: ".",
|
|
1893
1902
|
timeoutMs: 60000,
|
|
1894
1903
|
},
|
|
1895
|
-
}
|
|
1904
|
+
},
|
|
1905
|
+
],
|
|
1896
1906
|
};
|
|
1897
1907
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
1898
1908
|
parseDagSpec(spec);
|
|
@@ -2156,7 +2166,10 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2156
2166
|
constraintMarkdown: sources.constraintMarkdown,
|
|
2157
2167
|
});
|
|
2158
2168
|
const explicitFrontendVerifyCommands = buildExplicitFrontendVerifyCommands(taskConfig, sources.repoRoot);
|
|
2159
|
-
const explicitCommandKeys = new Set([
|
|
2169
|
+
const explicitCommandKeys = new Set([
|
|
2170
|
+
...explicitFrontendVerifyCommands.staticCommands,
|
|
2171
|
+
...explicitFrontendVerifyCommands.behaviorCommands,
|
|
2172
|
+
].map(verifyCommandKey));
|
|
2160
2173
|
const adapterVerifyCommands = (sources.verifyCommands?.final ?? []).filter((command) => !explicitCommandKeys.has(verifyCommandKey(command)));
|
|
2161
2174
|
const staticVerifyCommands = chooseFrontendVerifyCommands({
|
|
2162
2175
|
explicitCommands: explicitFrontendVerifyCommands.staticCommands,
|
|
@@ -2285,10 +2298,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2285
2298
|
},
|
|
2286
2299
|
{
|
|
2287
2300
|
id: "frontend-plan-pi",
|
|
2288
|
-
depends_on: [
|
|
2289
|
-
"frontend-contract-pi",
|
|
2290
|
-
"frontend-scout-pi",
|
|
2291
|
-
],
|
|
2301
|
+
depends_on: ["frontend-contract-pi", "frontend-scout-pi"],
|
|
2292
2302
|
role: "planner",
|
|
2293
2303
|
executor: "pi",
|
|
2294
2304
|
complexity: "MED",
|
|
@@ -2337,10 +2347,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2337
2347
|
},
|
|
2338
2348
|
{
|
|
2339
2349
|
id: "frontend-plan-revision-pi",
|
|
2340
|
-
depends_on: [
|
|
2341
|
-
"frontend-plan-pi",
|
|
2342
|
-
"frontend-design-review-pi",
|
|
2343
|
-
],
|
|
2350
|
+
depends_on: ["frontend-plan-pi", "frontend-design-review-pi"],
|
|
2344
2351
|
runIf: "$.nodes['frontend-design-review-pi'].firstVerdictLine == 'VERDICT: request-revision'",
|
|
2345
2352
|
role: "planner",
|
|
2346
2353
|
executor: "pi",
|
|
@@ -2425,12 +2432,18 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2425
2432
|
? ["not-needed"]
|
|
2426
2433
|
: taskConfig.frontendMock?.policy === "required"
|
|
2427
2434
|
? ["native", "browser-intercept", "request-adapter"]
|
|
2428
|
-
: [
|
|
2435
|
+
: [
|
|
2436
|
+
"native",
|
|
2437
|
+
"browser-intercept",
|
|
2438
|
+
"request-adapter",
|
|
2439
|
+
"not-needed",
|
|
2440
|
+
],
|
|
2429
2441
|
artifactName: "frontend-implementation-contract.json",
|
|
2430
2442
|
outputDir: "contracts",
|
|
2431
2443
|
requireSourceFreshness: true,
|
|
2432
2444
|
implementationWriteSet: implementPaths.writeSet,
|
|
2433
|
-
openspecCandidatePaths: sources.frontendProjectCapability?.designEvidence
|
|
2445
|
+
openspecCandidatePaths: sources.frontendProjectCapability?.designEvidence
|
|
2446
|
+
.normativePaths ?? [],
|
|
2434
2447
|
},
|
|
2435
2448
|
cwd: ".",
|
|
2436
2449
|
timeoutMs: 60000,
|
|
@@ -2482,7 +2495,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2482
2495
|
writerOutcomePolicy: {
|
|
2483
2496
|
type: "implementation-outcome-v1",
|
|
2484
2497
|
},
|
|
2485
|
-
outputContract: "First non-empty line: IMPLEMENTATION_OUTCOME: changed
|
|
2498
|
+
outputContract: "First non-empty line must be exactly one of: IMPLEMENTATION_OUTCOME: changed; IMPLEMENTATION_OUTCOME: already-satisfied; IMPLEMENTATION_OUTCOME: blocked. Then a Markdown delivery summary with Contract Ref (path/schema/hash), Changed Files, Requirements Implemented, UI States, Tests Changed, Verification Attempts, Deviations, and Residual Risks. Follow fixed stages: contract confirm → tests → component/state → API/Mock → focused checks → diff cleanup.",
|
|
2486
2499
|
subtask_prompt: [
|
|
2487
2500
|
"Implement against the validated run-owned Frontend Implementation Contract from frontend-prewrite-gate-shell (path/schema/hash). Do not rebuild the contract from Markdown alone.",
|
|
2488
2501
|
"The canonical contract already contains the approved requirement, target-file, UI-state, verification, design, and Mock/API decisions. Do not re-open task sources, OpenSpec, AI workspace, plan/revision, or design-review prose, and do not repeat broad repository research. Inspect only contract target files and directly related local code needed to implement them.",
|
|
@@ -2547,7 +2560,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2547
2560
|
writerOutcomePolicy: {
|
|
2548
2561
|
type: "implementation-outcome-v1",
|
|
2549
2562
|
},
|
|
2550
|
-
outputContract: "First non-empty line: IMPLEMENTATION_OUTCOME: changed
|
|
2563
|
+
outputContract: "First non-empty line must be exactly one of: IMPLEMENTATION_OUTCOME: changed; IMPLEMENTATION_OUTCOME: already-satisfied; IMPLEMENTATION_OUTCOME: blocked. Then a repair summary for an eligible repairable assessment. Must not expand writeSet, re-interpret requirements, skip tests, or enable Mock by default.",
|
|
2551
2564
|
subtask_prompt: [
|
|
2552
2565
|
"Read contracts/frontend-repair-assessment.json and the validated frontend implementation contract.",
|
|
2553
2566
|
"This node runs only for eligible=true. Apply the smallest fix for the classified repairable failure inside the original implement writeSet only.",
|
|
@@ -3454,107 +3467,190 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3454
3467
|
const forbidden = commonForbiddenPaths(sources);
|
|
3455
3468
|
const intake = await buildBackendTestIntakeContext(sources);
|
|
3456
3469
|
const shellNode = (id, depends_on, pipeline, prompt, outputContract, commands = [], timeoutMs = 60000) => ({
|
|
3457
|
-
id,
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3470
|
+
id,
|
|
3471
|
+
depends_on,
|
|
3472
|
+
role: "verifier",
|
|
3473
|
+
executor: "shell",
|
|
3474
|
+
complexity: "LOW",
|
|
3475
|
+
writePolicy: "read-only",
|
|
3476
|
+
allowedPaths: ro,
|
|
3477
|
+
forbiddenPaths: forbidden,
|
|
3478
|
+
outputContract,
|
|
3479
|
+
subtask_prompt: prompt,
|
|
3480
|
+
shell: {
|
|
3481
|
+
commands,
|
|
3482
|
+
backendTestPipeline: pipeline,
|
|
3483
|
+
cwd: ".",
|
|
3484
|
+
timeoutMs,
|
|
3485
|
+
...(commands.length
|
|
3486
|
+
? { envAllowlist: collectBackendTestShellEnvAllowlist(sources) }
|
|
3487
|
+
: {}),
|
|
3488
|
+
},
|
|
3462
3489
|
});
|
|
3463
|
-
const environment = shellNode("validate-backend-test-environment-shell", [], "markdown-environment", "Fail fast before model work when Python/pytest cannot run in the clean shell. Inspect only bounded common config, conftest, test-root and server-entry candidates; never read .env values or credentials.", "Run-owned reports/backend-test-environment.md with PASS/FAIL runtime, bounded project discovery, fixture and HTML-renderer facts; no secret values.", [
|
|
3490
|
+
const environment = shellNode("validate-backend-test-environment-shell", [], "markdown-environment", "Fail fast before model work when Python/pytest cannot run in the clean shell. Inspect only bounded common config, conftest, test-root and server-entry candidates; never read .env values or credentials.", "Run-owned reports/backend-test-environment.md with PASS/FAIL runtime, bounded project discovery, fixture and HTML-renderer facts; no secret values.", [
|
|
3491
|
+
"python --version",
|
|
3492
|
+
"python -m pytest --version",
|
|
3493
|
+
"python -m pytest --help",
|
|
3494
|
+
]);
|
|
3464
3495
|
const generateCases = {
|
|
3465
|
-
id: "generate-backend-md-cases-pi",
|
|
3466
|
-
|
|
3467
|
-
|
|
3496
|
+
id: "generate-backend-md-cases-pi",
|
|
3497
|
+
depends_on: [environment.id],
|
|
3498
|
+
role: "implementer",
|
|
3499
|
+
executor: "pi",
|
|
3500
|
+
toolProfile: "write",
|
|
3501
|
+
complexity: "MED",
|
|
3502
|
+
writePolicy: "exclusive",
|
|
3503
|
+
writeSet: ["testcase/md/**"],
|
|
3504
|
+
allowedPaths: ["testcase/md/**"],
|
|
3505
|
+
forbiddenPaths: forbidden,
|
|
3468
3506
|
outputContract: "Write a Chinese, human-readable testcase/md/README.md plus module Markdown case cards using BE-<MODULE>-<NNN>; keep machine IDs/literals exact and do not execute pytest or modify production code/config.",
|
|
3469
3507
|
subtask_prompt: [
|
|
3470
3508
|
"Read the upstream environment report. Generate a Markdown-first backend test strategy and cases under testcase/md/**.",
|
|
3471
3509
|
"Write human-readable content in Simplified Chinese by default. Keep English only for machine-readable IDs and technical literals such as Case/AC/REQ/BR IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and exact source citations.",
|
|
3472
3510
|
"Create testcase/md/README.md as the concise entry page: test objective, target/environment, isolation/cleanup, module summary and a linked case index table with Case ID, Chinese case name, scenario type, endpoint and expected status/result. Avoid repeating every case body in README.",
|
|
3473
|
-
"
|
|
3511
|
+
"Before the Coverage Matrix, write a mandatory machine-readable `## Coverage Scope` section in README using exactly `| Field | Value |`, immediately followed by the separator row `|---|---|`, and these six unique rows: `Change Classification`, `Coverage Policy`, `Affected Operations`, `Affected Rule Keys`, `Regression Floor`, `Scope Evidence`. Classify from authoritative task/reference evidence, not merely whether a route already exists. Use only these pairs: `new-operation` → `full-contract`; `contract-change` → `affected-contract-full`; `behavior-change` → `affected-behavior-full`; `bugfix` → `reproduction-plus-neighbors`; `implementation-optimization` → `change-focused-plus-regression-floor`. List affected operations exactly as `METHOD /path`, stable rule keys separated by semicolons, and precise source pointers as Scope Evidence.",
|
|
3512
|
+
"Coverage depth follows the declared change scope. For `new-operation`, fully cover every documented status, request/response field rule, requiredness, enum, boundary, format, auth and business state of each affected new operation, but do not re-test unrelated existing operations. For contract/behavior changes, fully cover the changed contract or behavior and its directly affected operations. For bugfix, cover exact reproduction, adjacent boundary/equivalence cases and a normal path. For `implementation-optimization`, cover explicit ACs, deterministic affected operations and a minimum regression floor; do not exhaustively regenerate unrelated POST/PUT/GET/DELETE rules. Every non-new classification must include `main-success-path` and `unchanged-response-shape`; contract changes also include `changed-contract-boundaries`, behavior changes `affected-state-transition`, and bugfixes `defect-reproduction` plus `adjacent-boundary`. Inspect shared validator/helper/DTO/query builder evidence and expand Affected Operations when the same changed path can affect them; unresolved impact stays visible as GAP/CONFLICT.",
|
|
3513
|
+
"Before writing cases, build the mandatory machine-readable Coverage Matrix inside `testcase/md/README.md` itself. Its section heading line must be exactly `## Coverage Matrix` with no numeric prefix/suffix; never place the canonical Matrix only in a module file. Use this exact header: `| Rule Key | Priority | Source | Endpoint/Field | Dimension | Rule | Required Test Points | Case IDs | Status |`. Every data row must contain exactly 9 pipe-delimited cells and must never omit `Dimension`; use concise dimensions such as requirement, operation, response-status, requiredness, enum, boundary, format, business-state or error. Use only P0/P1/P2 and COVERED/PARTIAL/GAP/CONFLICT. Use stable `TP-<UPPERCASE-HYPHENATED-ID>` test points separated by semicolons.",
|
|
3514
|
+
"Each Rule Key must appear in exactly one Matrix row. Preserve each AC/REQ/BR Rule Key as one row; if one product rule spans multiple dimensions, use a concise composite Dimension in that single row instead of duplicating the key. Derive OpenAPI Rule Keys exactly as the deterministic analyzer does: operation token is `<HTTP-METHOD>-<PATH>` with braces removed and every non-alphanumeric run replaced by a hyphen, uppercase (for example POST `/api/resource-notes` → `POST-API-RESOURCE-NOTES`); response statuses use `API-<OPERATION>-RESPONSE-STATUS`; body/parameter fields use `API-<OPERATION>-<FIELD>-REQUIRED|ENUM|MIN-LENGTH|MAX-LENGTH|MINIMUM|MAXIMUM|PATTERN|FORMAT`. Do not invent aliases such as API-CREATE-FIELDS when a deterministic key applies.",
|
|
3515
|
+
"Coverage priority is strict inside the declared scope: P0 product requirements/task hard constraints always remain in scope; P1 exhaustively supplements documented operations, fields, business rules, statuses and errors only for Affected Operations; P2 adds bounded protocol robustness only when it is relevant to the change and does not invent product behavior. Coverage percentages describe the declared affected scope, never whole-API completeness unless every operation is explicitly listed. Conflicts or undefined expectations must stay visible as GAP/CONFLICT with precise source pointers, never guessed.",
|
|
3516
|
+
"For uniqueness/lifecycle rules cover absent, active-existing, deleted-existing, create-delete-recreate, restore-then-recreate and documented scope/case-normalization states. For every enum cover every valid value plus bounded invalid equivalence classes (unknown, case variant, whitespace, empty, null/missing and wrong types as applicable). For every length/number rule cover min-1, min, nominal, max and max+1. For format rules cover each allowed class separately plus a valid mixed value, and representative forbidden classes including uppercase, internal/leading/trailing whitespace, tab/newline, unsupported punctuation, slash, emoji or control characters when the source contract supports that expectation.",
|
|
3517
|
+
"Write each module as readable case cards. Every case starts with `## BE-<MODULE>-<NNN>|<中文用例名称>`. `<NNN>` is exactly three zero-padded digits (`001`, `002`, ...), never two digits (`01`), a bare number, or an alphabetic suffix such as `011A`. Every case must include `### 覆盖规则`, `### 测试点`, `### 场景类型`, `### 前置条件`, `### 操作步骤`, `### 预期结果`, and `### 自动化映射`; `覆盖规则` and `测试点` must reference exact Matrix Rule Keys/Test Points. Add `测试目的`, `验收标准`, `需求依据`, and `测试数据` for readable evidence. The `验收标准` section must list the exact applicable `AC-...` IDs, and every explicit task AC must appear in at least one Case. Every automatable case explicitly names its target pytest script and exactly one primary symbol so traceability scans only that script/symbol.",
|
|
3474
3518
|
"Name each module file with a stable lowercase business stem such as `testcase/md/health.md` or `testcase/md/resource_notes.md`. Do not use Case-ID-like module filenames such as `BE-HEALTH.md` or `BE-NOTES.md`. For every automatable case, `自动化映射` must name exactly `testcase/test_<module>.py`, where <module> is that Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `testcase/md/health.md` → `testcase/test_health.py`; `testcase/md/resource_notes.md` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.",
|
|
3475
|
-
"
|
|
3476
|
-
"In `自动化映射`,
|
|
3477
|
-
intake.boundedSourceContext,
|
|
3519
|
+
"Every Case must keep at least one numbered executable line under `### 操作步骤`; a compact variant/result table may follow but must not replace the numbered action anchor. Keep numbered/bulleted independently assertable results under `### 预期结果`. The exact `### 操作步骤` and `### 预期结果` headings must remain present for every Case, including compact/table-based Cases; never compress later Cases by dropping required headings. Every result must name the observable HTTP status, response field/value, state transition or membership condition, never vague wording such as ‘符合预期’.",
|
|
3520
|
+
"In every `自动化映射`, use exactly these machine-readable list labels: `脚本`, `primary symbol`, `变体测试点`, `场景断言测试点`, `横切证据测试点`. Each Test Point from `### 测试点` must appear in exactly one binding list, and every Test Point named in any binding list must also be declared in that Case's `### 测试点`; write `无` for an empty list. A variant Test Point is atomic: one exact endpoint/input/precondition/outcome row equals one exact pytest item and one exact TP ID. If a parameter table has five rows, declare five distinct variant TP IDs in Markdown; never declare one family TP and append row suffixes only in pytest. Classify as `variant` only when endpoint, request input, precondition business state, or expected outcome genuinely changes and therefore needs an independent pytest parameter item. Classify CRUD checkpoints, status/body/header/schema assertions and multiple checks over the same response/journey as `assertion`; classify shared HTTP logging/redaction/truncation evidence as `cross-cutting`. Never create a Test Point merely to parameterize a checkpoint. Every non-cross-cutting TP ID is owned by exactly one Case; when the same response/schema/error assertion is needed in different Cases, use distinct Case-specific TP IDs instead of reusing one assertion TP across Cases. Keep the script path identical to the module one-to-one path and declare exactly one primary symbol named with the canonical Case prefix, for example `BE-RN-003` → `test_BE_RN_003_<description>`; non-Case-prefixed primary symbols are forbidden because parameterized item association must remain deterministic. For redaction scenarios, list sensitive header/field key names only. Never write any header-name-and-value pair, credential placeholder, fake token, anti-example, or other secret-shaped literal in Markdown; state only that a test-only value is supplied at runtime and omitted. Put implementation-only restrictions in a concise `<details>` block rather than dominating the main case flow. Before finalizing Markdown, calculate the predicted collected-item count as `sum(max(1, number of variant Test Points in each Case))`. If the task declares an item budget, the prediction must not exceed it. Reduce excess only by removing duplicate execution and converting same-request checkpoints to assertions; never drop required rules, boundaries, enums, operation-specific inputs, or business states. Record the prediction in README. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.",
|
|
3521
|
+
intake.boundedSourceContext,
|
|
3522
|
+
"## Authoritative reference index",
|
|
3523
|
+
JSON.stringify(intake.referenceIndex, null, 2),
|
|
3478
3524
|
"For each index entry, use `readPath` for Pi read-tool calls and copy `path` exactly into Markdown Source References. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.",
|
|
3479
3525
|
"Read only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text.",
|
|
3480
3526
|
].join("\n\n"),
|
|
3481
3527
|
};
|
|
3482
3528
|
const reviewCases = {
|
|
3483
|
-
id: "review-and-revise-backend-md-cases-pi",
|
|
3484
|
-
|
|
3485
|
-
|
|
3529
|
+
id: "review-and-revise-backend-md-cases-pi",
|
|
3530
|
+
depends_on: [generateCases.id],
|
|
3531
|
+
role: "reviewer",
|
|
3532
|
+
executor: "pi",
|
|
3533
|
+
toolProfile: "write",
|
|
3534
|
+
complexity: "MED",
|
|
3535
|
+
writePolicy: "exclusive",
|
|
3536
|
+
writeSet: ["testcase/md/**"],
|
|
3537
|
+
allowedPaths: ["testcase/md/**"],
|
|
3538
|
+
forbiddenPaths: forbidden,
|
|
3486
3539
|
outputContract: "Review source fidelity and directly revise only testcase/md/**; return concise Markdown, never JSON.",
|
|
3487
3540
|
subtask_prompt: [
|
|
3488
3541
|
"Independently review generated Markdown cases against the task requirements and environment evidence. Treat the files as human-facing test documentation: require clear preconditions, executable steps and assertable expected results; improve names, purpose, metadata and automation mapping where useful while preserving exact machine IDs and technical literals.",
|
|
3489
|
-
"
|
|
3490
|
-
"
|
|
3542
|
+
"Independently reconstruct the change classification, affected operations/rules, P0 product scenarios and applicable P1 documented API rules from authoritative sources before trusting the generated Coverage Scope or Coverage Matrix. Perform an explicit coverage-scope review: reject `new-operation` when the task only optimizes an existing implementation without contract change; reject narrow optimization scope when shared validator/helper/DTO/query builder evidence directly affects more operations; reject full-contract expansion across unrelated operations. For every in-scope operation, check applicable lifecycle/uniqueness states (including deleted-existing when in scope), valid enum values, bounded invalid classes, min-1/min/nominal/max/max+1, allowed/forbidden format classes, required/null/missing/wrong-type semantics, status/error codes, auth and state transitions. Directly add in-scope omissions; undefined impact remains GAP/CONFLICT rather than invented behavior.",
|
|
3543
|
+
"Check AC completeness/meaning, endpoint, fields/shape, status/error codes, rules, states, documented boundaries/auth, positive/negative coverage, executable steps and assertable results. Require the exact `## Coverage Scope` Field/Value table with the `|---|---|` separator row, a valid classification-policy pair, non-empty Affected Operations/Rule Keys/Scope Evidence, and the classification-specific Regression Floor. Require the exact unnumbered `## Coverage Matrix` heading in `testcase/md/README.md`, exact headers, exactly 9 cells in every data row (including a non-empty Dimension), deterministic OpenAPI Rule Keys for every in-scope affected operation, exactly one Matrix row per Rule Key (merge multi-dimension product rows), and bidirectional Matrix Rule/Test Point ↔ Case bindings. Never describe affected-scope coverage as whole-API completeness. Every explicit AC ID must appear in at least one Case `验收标准`; every explicit in-scope AC/REQ/BR Rule Key cited by a Case must have exactly one Coverage Matrix row, and no Case may cite a source Rule Key omitted from the Matrix. Every Matrix Case ID must share at least one of that row's Required Test Points and the Case must cite that Rule Key. Perform an explicit execution-redundancy review: merge checkpoint-only parameter rows, repeated default/read-back assertions, DELETE status/body/follow-up-read checks, response schema/Content-Type checks, PUT full-update/timestamp checks, repeated list setup and identical null/empty inputs when endpoint, input partition, precondition state and expected outcome are the same. Preserve separate POST/PUT, boundary, enum, wrong-type, role/tenant and distinct business-state variants. Directly repair malformed headings/rows/keys and binding modes rather than merely commenting on them. Reject avoidable English prose, duplicated bilingual wording, repeated boilerplate, oversized unstructured sections, a `### 操作步骤` section that contains only a table without any numbered executable line, vague results such as ‘符合预期’, Case-ID-like module filenames (for example `BE-HEALTH.md`), dropped exact `### 操作步骤`/`### 预期结果` headings, and missing or drifted script/function mapping where it can be derived.",
|
|
3544
|
+
"Correct testcase/md/** directly: add documented omissions, remove unsupported cases, rename module files to stable lowercase stems when needed, normalize every Case ID to hyphen-separated module segments plus exactly three zero-padded digits (`BE-RESOURCE_NOTES-01` → `BE-RESOURCE-NOTES-001`; `BE-RN-011A` must be renumbered or merged) consistently across headings/index/mappings, fix automation mappings so each case points at `testcase/test_<module>.py` derived from that module filename and declares exactly one primary symbol, assign every Test Point exactly one of `变体测试点`/`场景断言测试点`/`横切证据测试点`, ensure every binding-list Test Point is also present in that Case's `### 测试点`, expand every variant parameter row into its own atomic TP ID, make every non-cross-cutting TP Case-specific and owned by exactly one Case, require every primary symbol to start with the canonical Case prefix, ensure every explicit AC ID appears in an applicable Case `验收标准`, merge execution duplicates, improve navigation/tables/Chinese wording, or record gaps in Chinese. Remove every credential/header value, placeholder, fake token and anti-example from Markdown. Sensitive key names may remain only as a plain list; values must be described as runtime-only and omitted, with no colon/value pair or literal example anywhere, including details blocks and explanatory text. Keep Case IDs, AC/REQ/BR IDs, HTTP methods, paths, fields, enum values, filenames, code symbols and source citations as exact machine-readable identifiers; only normalize Case ID separator/sequence formatting as specified above. Recalculate predicted collected items as `sum(max(1, variant count per Case))`; when the task declares a budget, directly merge redundant journeys/reclassify same-request checkpoints until the prediction is within budget, while preserving all required coverage. The validator accepts Chinese and legacy English section aliases; retain or converge to the Chinese human-readable headings without losing structure.",
|
|
3491
3545
|
"Read only precise referenced source paths plus requirement sections needed for uncovered ACs. Do not scan the repository, modify source/**, generate pytest, execute tests, or emit JSON.",
|
|
3492
|
-
intake.boundedSourceContext,
|
|
3546
|
+
intake.boundedSourceContext,
|
|
3547
|
+
"## Authoritative reference index",
|
|
3548
|
+
JSON.stringify(intake.referenceIndex, null, 2),
|
|
3493
3549
|
"For each index entry, use `readPath` for Pi read-tool calls and keep `path` as the exact Markdown Source References citation. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.",
|
|
3494
3550
|
].join("\n\n"),
|
|
3495
3551
|
};
|
|
3496
|
-
const validateCases = shellNode("validate-backend-md-cases-shell", [reviewCases.id], "markdown-cases", "Record advisory findings for
|
|
3552
|
+
const validateCases = shellNode("validate-backend-md-cases-shell", [reviewCases.id], "markdown-cases", "Record advisory findings for Markdown structure and deterministically analyze the final README Coverage Scope and Coverage Matrix against final Case rule/test-point bindings. Validate the classification-policy pair, affected operations/rules, scope evidence and regression floor; require documented OpenAPI completeness only for declared affected operations, while all explicit AC/REQ/BR remain in scope. Detect missing in-scope product/API rules, enum values, invalid equivalence classes, boundaries, format classes, business lifecycle states, GAP/CONFLICT, bidirectional Matrix/Case drift, non-canonical Case IDs, unclassified Test Points, duplicate binding modes and non-cross-cutting Test Points bound by multiple Cases. Do not validate source-reference existence. Write human and machine evidence from the same facts. Keep quality findings advisory, but fail closed after writing the report when secret-shaped values are detected. Coverage FAIL stays advisory.", "Run-owned reports/backend-md-case-validation.md, reports/backend-test-case-coverage-analysis.md and contracts/backend-test-case-coverage-facts.json v3 with Coverage Scope plus PASS/FAIL/UNAVAILABLE advisory facts; downstream execution continues.");
|
|
3497
3553
|
const generatePytest = {
|
|
3498
|
-
id: "generate-backend-pytest-pi",
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3554
|
+
id: "generate-backend-pytest-pi",
|
|
3555
|
+
depends_on: [validateCases.id],
|
|
3556
|
+
role: "implementer",
|
|
3557
|
+
executor: "pi",
|
|
3558
|
+
toolProfile: "write",
|
|
3559
|
+
complexity: "HIGH",
|
|
3560
|
+
writePolicy: "exclusive",
|
|
3561
|
+
writeSet: [
|
|
3562
|
+
"testcase/**/test_*.py",
|
|
3563
|
+
"testcase/**/helpers/**",
|
|
3564
|
+
"testcase/**/factories/**",
|
|
3565
|
+
],
|
|
3566
|
+
allowedPaths: Array.from(new Set([...ro, "testcase/**"])),
|
|
3567
|
+
forbiddenPaths: forbidden,
|
|
3502
3568
|
outputContract: "Convert every final automatable Markdown case into pytest assets whose actual test function region contains the exact Case ID, preferably in the function name or docstring. Each testcase/md/<module>.md (excluding README.md) maps one-to-one to testcase/test_<module>.py; never merge or split modules. No JSON and no pytest execution.",
|
|
3503
3569
|
subtask_prompt: [
|
|
3504
3570
|
"Convert testcase/md/** to pytest using upstream environment and advisory validation evidence plus only bounded pytest config/conftest. A FAIL advisory report does not authorize inventing missing behavior; use the final Markdown facts that are present.",
|
|
3505
|
-
"Ensure every final Markdown Case ID appears in
|
|
3571
|
+
"Ensure every final Markdown Case ID appears in exactly one primary pytest test function or pytest test class method region, using the exact `primary symbol` declared by Markdown. The symbol must start with `test_BE_<MODULE>_<NNN>_` so every parameterized collected item remains associated with its Case. Module-level functions and class-based pytest methods are both supported. Only `变体测试点` may use stable `pytest.param(..., id=\"TP-...\")` IDs, and every atomic variant ID must appear exactly once with a genuine input/state/outcome change. Use `pytest.param(..., id=...)` for every row; do not use decorator-level `ids=[...]`, generated suffixes, or IDs that extend/shorten the exact Markdown TP. Do not parameterize `场景断言测试点` or `横切证据测试点`; execute all assertion checkpoints within the same business journey/item and use shared helpers for cross-cutting evidence. The primary symbol docstring must contain exact metadata lines `Case-ID: BE-...`, `Assertion-Test-Points: TP-...;TP-...` and `Cross-Cutting-Test-Points: TP-...;TP-...` (use `none` when empty). No Test Point may be invented, renamed, omitted or bound in two modes. The generated pytest collection shape must equal the Markdown prediction `sum(max(1, variant count per Case))`; keep it at or below the task's explicit budget by removing duplicate execution, never by collapsing multiple parameter rows under a coarse family TP. Assertions come only from 预期结果 and setup comes only from 前置条件/测试数据/自动化映射.",
|
|
3506
3572
|
"Name each generated pytest file so it corresponds one-to-one with its source Markdown module file: for each `testcase/md/<module>.md` (excluding README.md), emit exactly one `testcase/test_<module>.py`. The <module> stem is the Markdown filename without the `.md` extension, lowercased and with non-alphanumeric characters replaced by underscores. For example, `testcase/md/resource_notes.md` maps to `testcase/test_resource_notes.py`, `testcase/md/health.md` maps to `testcase/test_health.py`, `testcase/md/BE-HEALTH.md` maps to `testcase/test_be_health.py`, and `testcase/md/order-api.md` maps to `testcase/test_order_api.py`. If Markdown automation mapping names a different path than this module stem path, still write the module stem path and do not invent prefixes such as `test_be_*` unless the module filename itself normalizes to that stem. Never merge multiple Markdown modules into one pytest file, never split one module across several files, and never invent pytest filenames unrelated to the Markdown modules.",
|
|
3507
3573
|
"Generate a reusable HTTP logging helper (or equivalent client wrapper) and call it for every interface request. The request log must include method, URL/path, and request parameters (query plus JSON/body/payload summary). The response log must include status code and response result (JSON/text/body summary), and both records must be visible in pytest stdout/stderr without changing assertions.",
|
|
3574
|
+
"HTTP response header names are case-insensitive. If the helper stores a lower-case normalized header map, every Content-Type or other header assertion must query the lower-case key (for example `content-type`) or use an explicitly case-insensitive accessor; never call a case-sensitive plain dict with `Content-Type` when the stored key is lower-case. Preserve the actual media-type assertion rather than dropping it.",
|
|
3508
3575
|
"Compare timestamps and other semantically equivalent protocol values by parsed meaning, not byte-for-byte serialization. In particular, normalize valid ISO-8601 instants before equality/order assertions so differences such as omitted trailing fractional seconds do not create TestBug failures; preserve exact-string assertions only when the Markdown explicitly requires representation equality.",
|
|
3509
3576
|
"Before logging, recursively redact sensitive keys and header values including authorization, proxy-authorization, cookie, set-cookie, token, password, secret, api key and credentials. Never print full Authorization/Cookie values. Apply bounded truncation to serialized request and response bodies (with an explicit truncation marker) so large payloads cannot flood pytest or report artifacts.",
|
|
3510
3577
|
"Do not read source/**, add cases, reassign ACs, modify conftest/config/production code, use skip/xfail, swallow assertions, execute pytest, or emit JSON. For best-effort cleanup, catch only the narrow transport exception actually raised by the selected HTTP client (for example `requests.RequestException` or `urllib.error.URLError`); never use bare `except`, `Exception`, or `BaseException` with `pass`.",
|
|
3511
3578
|
].join("\n\n"),
|
|
3512
3579
|
};
|
|
3513
|
-
const traceability = shellNode("backend-test-traceability-gate-shell", [generatePytest.id], "markdown-traceability", "
|
|
3580
|
+
const traceability = shellNode("backend-test-traceability-gate-shell", [generatePytest.id], "markdown-traceability", "Deterministically scan only Markdown-mapped pytest scripts. Keep the existing traceability/logging checks and produce a bidirectional Markdown module/Case/Test Point ↔ pytest file/primary symbol correspondence analysis. Map variant Test Points from stable parameter IDs, assertion Test Points from the primary symbol docstring, and cross-cutting Test Points from the primary symbol evidence binding. Report 1:1, 1:0, 1:N, 0:1, script/primary-symbol mismatch, missing Case ID, missing variant parameter IDs, missing assertion/cross-cutting bindings, duplicate modes and extra bindings. Human and machine evidence must come from the same facts. Findings are advisory and never block pytest.", "Run-owned reports/backend-test-traceability.md, reports/backend-test-markdown-pytest-correspondence.md and contracts/backend-test-markdown-pytest-correspondence-facts.json with PASS/FAIL/UNAVAILABLE correspondence facts.");
|
|
3581
|
+
const manifest = shellNode("backend-test-case-manifest-shell", [traceability.id], "markdown-manifest", "Materialize the canonical Backend Test Case Manifest only from contracts/backend-test-case-coverage-facts.json and contracts/backend-test-markdown-pytest-correspondence-facts.json. Validate schema, task binding, input hashes and freshness; never re-read source semantics, re-analyze Coverage Matrix, rescan pytest symbols or recompute a second set of metrics. Missing/stale/conflicting facts produce partial/unavailable diagnostics rather than fabricated zeros.", "Run-owned contracts/backend-test-case-manifest.json with materializationStatus, sourceFactsIssues, validated coverageScope, coverageSummary, ruleCoverageSummary and correspondenceSummary; this is the single machine input for L-5 and closeout.");
|
|
3514
3582
|
const pytestCommand = [
|
|
3515
3583
|
'mkdir -p "${HARNESS_DAG_RUN_DIR}/reports"',
|
|
3516
3584
|
'echo "pytest targets are resolved at runtime from final Markdown 自动化映射"',
|
|
3517
3585
|
].join("; ");
|
|
3518
|
-
const execute = shellNode("execute-backend-pytest-and-html-report-shell", [
|
|
3586
|
+
const execute = shellNode("execute-backend-pytest-and-html-report-shell", [manifest.id], "markdown-execute-html", "Resolve the final Markdown Automation Notes/自动化映射 to a unique, safe set of testcase/**/test_*.py targets and execute only those scripts exactly once. Prefer the deterministic module one-to-one path when a mapped script is missing but the module stem file exists. Generate a native pytest-html self-contained report, then render the primary self-contained Chinese HTML report from the same pytest-html plus final Markdown case metadata without rerun. Keep 测试结论 and quality status; make node 4 Markdown validation + case coverage and node 6 traceability + Markdown-to-pytest correspondence expandable to their full escaped details; show each failure overview item with its original pytest message plus deterministic evidence-based reason analysis; list failure/error case cards before the remaining cases while preserving stable order. Each polished per-case result card includes concise scenario, automation test name, result, duration, and redacted bounded HTTP request parameters/response results for both passed and failed cases. Do not render a technical/execution evidence section in HTML; retain auditable paths and hashes in facts.", "One scoped pytest execution over Markdown-mapped scripts producing a valid pytest-html report with per-case captured output, self-contained reports/backend-test.html, reports/backend-test.md, reports/backend-test-facts.md, a deterministic self-contained reports/backend-test-l5-dashboard.html (machine-computed L-5 metrics, no JSON), and an optional contracts/code-coverage-v1.json when jacocoCoverage is configured (JaCoCo TCP dump → jacoco.xml → parsed; failure-safe); exit 0/1 with valid evidence continues.", [pytestCommand], 300000);
|
|
3519
3587
|
if (execute.shell) {
|
|
3520
3588
|
execute.shell.envAllowlist = collectBackendTestShellEnvAllowlist(sources);
|
|
3521
3589
|
}
|
|
3522
3590
|
const canWriteReport = taskAllowsBackendTestReportWrite(sources);
|
|
3523
3591
|
const report = {
|
|
3524
|
-
id: "backend-test-report-and-l5-pi",
|
|
3592
|
+
id: "backend-test-report-and-l5-pi",
|
|
3593
|
+
depends_on: [execute.id],
|
|
3594
|
+
role: "closeout",
|
|
3595
|
+
executor: "pi",
|
|
3596
|
+
complexity: "MED",
|
|
3525
3597
|
...(canWriteReport
|
|
3526
|
-
? {
|
|
3598
|
+
? {
|
|
3599
|
+
toolProfile: "write",
|
|
3600
|
+
writePolicy: "exclusive",
|
|
3601
|
+
writeSet: ["docs/test-reports/**"],
|
|
3602
|
+
allowedPaths: ["docs/test-reports/**"],
|
|
3603
|
+
}
|
|
3527
3604
|
: { writePolicy: "read-only", allowedPaths: ro }),
|
|
3528
3605
|
forbiddenPaths: forbidden,
|
|
3529
|
-
outputContract: canWriteReport
|
|
3606
|
+
outputContract: canWriteReport
|
|
3607
|
+
? "Final Markdown report and L-5 conclusion under docs/test-reports/**; the deterministic L-5 dashboard at reports/backend-test-l5-dashboard.html is the authoritative visualization and must be linked, not re-rendered; no JSON."
|
|
3608
|
+
: "Final Markdown report and L-5 conclusion in assistant output; the deterministic L-5 dashboard at reports/backend-test-l5-dashboard.html is the authoritative visualization and must be linked; no JSON or writes.",
|
|
3530
3609
|
subtask_prompt: [
|
|
3531
|
-
"Generate the final Markdown report from
|
|
3610
|
+
"Generate the final Markdown report only from authoritative run-owned artifacts. Read node 1 reports/backend-test-environment.md; node 4 backend-md-case-validation.md and backend-test-case-coverage-analysis.md; node 6 backend-test-traceability.md and backend-test-markdown-pytest-correspondence.md; node 7 contracts/backend-test-case-manifest.json; and node 8 backend-test-result.json, backend-test-facts.md, pytest-html/HTML and L-5 dashboard. Do not use node 2/3/5 assistant prose as facts. Do not emit JSON.",
|
|
3532
3611
|
"Use this exact human-facing section order: 测试结论 → 执行概览 → 质量校验 → 失败分析 → 风险与建议 → 证据与 L-5. Put the decision and key numbers first, use compact tables/bullets, and keep headings concise. Do not paste entire upstream reports, duplicate per-case tables already present in facts, or repeat the same evidence in multiple sections; link to paths/hashes and quote only the findings needed for the conclusion.",
|
|
3533
|
-
"The L-5 metrics and visualization are
|
|
3534
|
-
"Always state the exact PASS/FAIL status and findings from
|
|
3535
|
-
"Include environment, case quality/review, automation mapping, exact pytest facts, failure classification/analysis, risks, regression recommendations, evidence paths/hashes, coverage availability, and L-5 READY/NOT READY.",
|
|
3612
|
+
"The L-5 metrics and visualization are produced deterministically by node 8 at reports/backend-test-l5-dashboard.html. Link to that dashboard as the authoritative L-5 view. Pytest execution facts come from node 8; coverage/correspondence numbers and materializationStatus come from node 7; detailed coverage findings come from node 4; detailed mapping findings come from node 6. Never recompute these values. If machine manifest and human reports disagree, report evidence inconsistency rather than silently choosing.",
|
|
3613
|
+
"Always state the exact Coverage Scope classification, policy, affected operations, regression floor, completeness claim, PASS/FAIL/UNAVAILABLE status and findings from node 4 case validation + coverage, plus node 6 traceability + correspondence. Affected-scope or affected-operations-full coverage must never be described as whole-API completeness unless every operation is explicitly listed. Their FAIL status does not block pytest, but it must remain visible and must never be rewritten as PASS.",
|
|
3614
|
+
"Include environment, case quality/review, automation mapping, exact pytest facts, failure classification/analysis, risks, regression recommendations, evidence paths/hashes, coverage availability, and L-5 READY/NOT READY. Distinguish Markdown Case count, primary pytest symbol count, collected pytest item count, variant/assertion/cross-cutting Test Point counts and execution amplification; never describe pytest item count as the number of business scenarios.",
|
|
3536
3615
|
"Never override Shell/pytest-html facts. L-5 requires pass=100%, AC=100%, automation>=90%, line>=80%, branch>=70%, skipped=0 and no blocking Critical risk.",
|
|
3537
|
-
canWriteReport
|
|
3616
|
+
canWriteReport
|
|
3617
|
+
? "Write only under docs/test-reports/**."
|
|
3618
|
+
: "Keep the full report in assistant output.",
|
|
3538
3619
|
].join("\n\n"),
|
|
3539
3620
|
};
|
|
3540
3621
|
const spec = {
|
|
3541
|
-
version: 3,
|
|
3622
|
+
version: 3,
|
|
3623
|
+
title: `Backend test DAG: ${taskConfig.title}`,
|
|
3542
3624
|
runtimeContract: GENERATED_DAG_RUNTIME_CONTRACT,
|
|
3543
3625
|
outputLanguage: sources.outputLanguage ?? DEFAULT_DAG_OUTPUT_LANGUAGE,
|
|
3544
3626
|
objective: extractObjective(sources.requirementMarkdown, taskConfig.title),
|
|
3545
3627
|
successCriteria: extractSuccessCriteria(sources.requirementMarkdown, sources.taskId),
|
|
3546
3628
|
globalConstraints: [
|
|
3547
|
-
...taskConfig.hardConstraints,
|
|
3548
|
-
|
|
3629
|
+
...taskConfig.hardConstraints,
|
|
3630
|
+
...STANDARD_GLOBAL_CONSTRAINTS,
|
|
3631
|
+
"backend-test-dag uses exactly 9 real top-level tasks and executes pytest exactly once over only the safe scripts explicitly mapped by final Markdown cases.",
|
|
3549
3632
|
"Model nodes produce Markdown and pytest assets, never backend-test business JSON envelopes.",
|
|
3550
|
-
"Environment, advisory Markdown validation, advisory traceability, pytest-html, HTML and execution facts are deterministic evidence. Nodes 4 and 6 record findings without blocking nodes 5, 7 or 8.",
|
|
3633
|
+
"Environment, advisory Markdown validation/coverage, advisory traceability/correspondence, canonical manifest, pytest-html, HTML and execution facts are deterministic evidence. Nodes 4 and 6 record findings without blocking nodes 5, 7 or 8; node 7 partial/unavailable does not block node 8.",
|
|
3551
3634
|
"Only Markdown case generation/review may read source facts; pytest generation must not read source/**.",
|
|
3552
|
-
"Functional case IDs use BE-<MODULE>-<NNN
|
|
3635
|
+
"Functional case IDs use canonical BE-<MODULE>-<NNN> with exactly three digits and no alphabetic suffix. Every Test Point has exactly one variant/assertion/cross-cutting binding; only variant bindings create pytest parameter items. Production code/config, skip/xfail, repair and rerun are forbidden.",
|
|
3553
3636
|
],
|
|
3554
|
-
defaults: {
|
|
3637
|
+
defaults: {
|
|
3638
|
+
...BACKEND_TEST_DEFAULTS,
|
|
3639
|
+
contextProfile: taskConfig.contextProfile,
|
|
3640
|
+
},
|
|
3555
3641
|
skillsByRole: BACKEND_TEST_SKILLS_BY_ROLE,
|
|
3556
3642
|
executorModels: sources.executorModelMatrix ?? DEFAULT_DAG_EXECUTOR_MODELS,
|
|
3557
|
-
tasks: [
|
|
3643
|
+
tasks: [
|
|
3644
|
+
environment,
|
|
3645
|
+
generateCases,
|
|
3646
|
+
reviewCases,
|
|
3647
|
+
validateCases,
|
|
3648
|
+
generatePytest,
|
|
3649
|
+
traceability,
|
|
3650
|
+
manifest,
|
|
3651
|
+
execute,
|
|
3652
|
+
report,
|
|
3653
|
+
],
|
|
3558
3654
|
};
|
|
3559
3655
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
3560
3656
|
parseDagSpec(spec);
|
|
@@ -3604,7 +3700,8 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
3604
3700
|
"const issues=[];",
|
|
3605
3701
|
"const openRe=/playwright-cli\\s+open\\s+--browser=chrome\\s+--headed\\s+https?:\\/\\/\\S+/i;",
|
|
3606
3702
|
"const prodRe=/(?:^|\\/\\/)(?:www\\.)?[^\\s\\/]*(?:prod|production)/i;",
|
|
3607
|
-
"const caseIdRe=/^FE-[A-Za-z0-9][A-Za-z0-9-]*$/;",
|
|
3703
|
+
"const caseIdRe=/^FE-[A-Za-z0-9][A-Za-z0-9-]*$/;",
|
|
3704
|
+
,
|
|
3608
3705
|
"const acIdRe=/^AC(?:-[A-Z0-9]+)+$/i;",
|
|
3609
3706
|
"for(const c of manifest.cases){",
|
|
3610
3707
|
" const id=c&&c.caseId||'?';",
|
|
@@ -3614,7 +3711,8 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
3614
3711
|
" if(!casePath||!fs.existsSync(casePath)){issues.push({ruleId:'case-file-missing',caseId:id,detail:String(casePath)});continue;}",
|
|
3615
3712
|
" if(typeof c.caseId==='string'&&casePath!=='testcase/frontend/cases/'+c.caseId+'.md')issues.push({ruleId:'case-path-mismatch',caseId:id,detail:casePath+' must equal testcase/frontend/cases/'+c.caseId+'.md'});",
|
|
3616
3713
|
" const body=fs.readFileSync(casePath,'utf8');",
|
|
3617
|
-
" if(!openRe.test(body))issues.push({ruleId:'open-prefix',caseId:id,detail:'missing playwright-cli open --browser=chrome --headed <absolute-url>; playwright-cli is strongly recommended for browser execution'});",
|
|
3714
|
+
" if(!openRe.test(body))issues.push({ruleId:'open-prefix',caseId:id,detail:'missing playwright-cli open --browser=chrome --headed <absolute-url>; playwright-cli is strongly recommended for browser execution'});",
|
|
3715
|
+
,
|
|
3618
3716
|
" const m=body.match(/playwright-cli\\s+open\\s+--browser=chrome\\s+--headed\\s+(https?:\\/\\/\\S+)/i);",
|
|
3619
3717
|
" if(m){const url=m[1].replace(/[)\\]},.\"']+$/,''); if(prodRe.test(url))issues.push({ruleId:'production-url',caseId:id,detail:url});}",
|
|
3620
3718
|
" if(!Array.isArray(c.acIds)||c.acIds.length===0)issues.push({ruleId:'ac-mapping',caseId:id,detail:'acIds required (AC-FE-* acceptance ids, not caseId)'});",
|
|
@@ -3847,7 +3945,12 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
3847
3945
|
forbiddenPaths: forbidden,
|
|
3848
3946
|
outputContract: "Mechanical checklist: manifest/case paths, Case ID, non-production playwright-cli open prefix, and AC mapping; alternative executable tool commands are not inspected or blocked.",
|
|
3849
3947
|
subtask_prompt: "Scan generated cases/manifest for structural and safety rules only. Strongly recommend playwright-cli for browser execution, but do not inspect or reject alternative executable tool commands and do not use free-form LLM verdicts.",
|
|
3850
|
-
shell: {
|
|
3948
|
+
shell: {
|
|
3949
|
+
commands: [],
|
|
3950
|
+
frontendTestCaseChecklist: {},
|
|
3951
|
+
cwd: ".",
|
|
3952
|
+
timeoutMs: 120000,
|
|
3953
|
+
},
|
|
3851
3954
|
}, {
|
|
3852
3955
|
id: "materialize-frontend-case-manifest-shell",
|
|
3853
3956
|
depends_on: ["frontend-case-checklist-shell"],
|
|
@@ -3923,7 +4026,12 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
3923
4026
|
forbiddenPaths: forbidden,
|
|
3924
4027
|
outputContract: "Deterministic evidence gate: missing/malformed evidence is advisory; only unsafe evidenceDir or evidence paths hard-fail. Does not block retrospect.",
|
|
3925
4028
|
subtask_prompt: "Validate frontend case evidence before result materialization. Keep missing or malformed evidence as advisory findings; only path-escape failures abort the node.",
|
|
3926
|
-
shell: {
|
|
4029
|
+
shell: {
|
|
4030
|
+
commands: [],
|
|
4031
|
+
frontendTestEvidenceValidation: {},
|
|
4032
|
+
cwd: ".",
|
|
4033
|
+
timeoutMs: 120000,
|
|
4034
|
+
},
|
|
3927
4035
|
}, {
|
|
3928
4036
|
id: "materialize-frontend-test-result-shell",
|
|
3929
4037
|
depends_on: ["validate-frontend-case-evidence-shell"],
|
|
@@ -3947,10 +4055,24 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
3947
4055
|
timeoutMs: 120000,
|
|
3948
4056
|
},
|
|
3949
4057
|
});
|
|
4058
|
+
tasks.push({
|
|
4059
|
+
id: "frontend-test-l5-report-shell",
|
|
4060
|
+
depends_on: ["materialize-frontend-test-result-shell"],
|
|
4061
|
+
role: "verifier",
|
|
4062
|
+
executor: "shell",
|
|
4063
|
+
complexity: "LOW",
|
|
4064
|
+
writePolicy: "exclusive",
|
|
4065
|
+
writeSet: ["testcase/frontend/reports/**"],
|
|
4066
|
+
allowedPaths: ["testcase/frontend/**"],
|
|
4067
|
+
forbiddenPaths: forbidden,
|
|
4068
|
+
outputContract: "Deterministic frontend L-5 Markdown and self-contained HTML dashboard derived only from frontend-test-result-v1.",
|
|
4069
|
+
subtask_prompt: "Render the authoritative frontend L-5 report from frontend-test-result-v1. Do not use Pi prose or invent code coverage. Missing line/branch coverage remains unavailable and makes L-5 NOT READY.",
|
|
4070
|
+
shell: { frontendTestL5Report: {}, commands: [], cwd: ".", timeoutMs: 120000 },
|
|
4071
|
+
});
|
|
3950
4072
|
if (strictOutcomeGate) {
|
|
3951
4073
|
tasks.push({
|
|
3952
4074
|
id: "frontend-test-result-outcome-gate-shell",
|
|
3953
|
-
depends_on: ["materialize-frontend-test-result-shell"],
|
|
4075
|
+
depends_on: ["materialize-frontend-test-result-shell", "frontend-test-l5-report-shell"],
|
|
3954
4076
|
role: "verifier",
|
|
3955
4077
|
executor: "shell",
|
|
3956
4078
|
complexity: "LOW",
|
|
@@ -3968,7 +4090,7 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
3968
4090
|
}
|
|
3969
4091
|
tasks.push({
|
|
3970
4092
|
id: "frontend-test-retrospect-pi",
|
|
3971
|
-
depends_on: ["materialize-frontend-test-result-shell"],
|
|
4093
|
+
depends_on: ["materialize-frontend-test-result-shell", "frontend-test-l5-report-shell"],
|
|
3972
4094
|
role: "closeout",
|
|
3973
4095
|
executor: "pi",
|
|
3974
4096
|
toolProfile: "write",
|
|
@@ -3978,7 +4100,7 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
3978
4100
|
allowedPaths: ["testcase/frontend/**"],
|
|
3979
4101
|
forbiddenPaths: forbidden,
|
|
3980
4102
|
outputContract: "Write frontend-test retrospective under testcase/frontend/reports/frontend-test-retrospect-<date>.md with coverage, pass/fail/blocked, execution evidence review, risks, findings, and A/B/C/D rating — even when outcome is failed/incomplete. Pipeline acceptance = this report exists (not case 100% pass).",
|
|
3981
|
-
subtask_prompt: "Write the frontend test retrospective under testcase/frontend/reports/ after result materialization
|
|
4103
|
+
subtask_prompt: "Write the frontend test retrospective under testcase/frontend/reports/ after result materialization and L-5 report generation. Combine AC→case→browser-evidence review with the closeout report: coverage, the exact L-5 READY/NOT READY decision, passed/failed/blocked (including token-budget-exhausted / executor-auth-unavailable), evidence gaps, browser anomalies, residual risks, and A/B/C/D rating. Passed cases need assertion plus screenshot or equivalent evidence when available; failed/blocked need explicit reasons. Blocked cases never count as passed. Do not recompute L-5 metrics or replace browser evidence with model conclusions. Do not write docs/**. Pipeline success is report production, not case full green.",
|
|
3982
4104
|
});
|
|
3983
4105
|
tasks.push({
|
|
3984
4106
|
id: "frontend-test-html-report-shell",
|
|
@@ -3992,7 +4114,12 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
3992
4114
|
forbiddenPaths: forbidden,
|
|
3993
4115
|
outputContract: "Write testcase/frontend/reports/frontend-test-report.md and frontend-test-report.html from frontend-test-result-v1, containing only case execution results, case content, and failed/blocked error analysis.",
|
|
3994
4116
|
subtask_prompt: "Render the formal frontend test Markdown and HTML report from the current run frontend-test-result-v1. Do not include evidence chains, evidence paths or hashes, advisory findings, quality suggestions, improvement suggestions, or ratings.",
|
|
3995
|
-
shell: {
|
|
4117
|
+
shell: {
|
|
4118
|
+
commands: [],
|
|
4119
|
+
frontendTestHtmlReport: {},
|
|
4120
|
+
cwd: ".",
|
|
4121
|
+
timeoutMs: 120000,
|
|
4122
|
+
},
|
|
3996
4123
|
});
|
|
3997
4124
|
const globalConstraints = [
|
|
3998
4125
|
...sources.taskConfig.hardConstraints,
|
|
@@ -5114,7 +5241,7 @@ function cloneTask(task, patch = {}) {
|
|
|
5114
5241
|
}
|
|
5115
5242
|
/**
|
|
5116
5243
|
* Apply the default read-only Pi retry policy to safe planner/scout/reviewer/
|
|
5117
|
-
* verifier/closeout Pi nodes in the generated DAG. Writers,
|
|
5244
|
+
* verifier/supervisor/closeout Pi nodes in the generated DAG. Writers,
|
|
5118
5245
|
* dynamic, shell, static, and decision-gate nodes are skipped. Idempotent:
|
|
5119
5246
|
* never overwrites an explicit retryPolicy a task already declares.
|
|
5120
5247
|
*/
|
|
@@ -5233,6 +5360,14 @@ function buildReviewGateNode(sources) {
|
|
|
5233
5360
|
}
|
|
5234
5361
|
function enableProjectGovernanceOnNode(task) {
|
|
5235
5362
|
task.governanceStandardReview = true;
|
|
5363
|
+
task.outputProtocol = REVIEW_VERDICT_OUTPUT_PROTOCOL;
|
|
5364
|
+
task.retryPolicy = PROTOCOL_AWARE_PI_RETRY_POLICY;
|
|
5365
|
+
task.outputContract =
|
|
5366
|
+
"Plain Markdown whose first non-empty line is exactly VERDICT: pass or VERDICT: request-revision; unresolved mandatory governance violations force request-revision. No file writes.";
|
|
5367
|
+
const protocolInstruction = "First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.";
|
|
5368
|
+
if (!task.subtask_prompt.includes(protocolInstruction)) {
|
|
5369
|
+
task.subtask_prompt = `${protocolInstruction}\n\n${task.subtask_prompt}`;
|
|
5370
|
+
}
|
|
5236
5371
|
}
|
|
5237
5372
|
/**
|
|
5238
5373
|
* Apply governance only to general implementation DAGs, and only when the
|
|
@@ -5309,6 +5444,7 @@ function insertGovernanceStandardGate(spec, sources) {
|
|
|
5309
5444
|
const closeoutIndex = spec.tasks.findIndex((task) => task.id === "closeout-pi");
|
|
5310
5445
|
spec.tasks.splice(closeoutIndex, 0, gate);
|
|
5311
5446
|
closeout.depends_on = ["governance-standard-gate-shell"];
|
|
5447
|
+
closeout.failureAwareDependsOn = ["governance-standard-gate-shell"];
|
|
5312
5448
|
}
|
|
5313
5449
|
function buildReviewGatedHybridDag(standard, sources) {
|
|
5314
5450
|
const spec = {
|
|
@@ -5322,7 +5458,10 @@ function buildReviewGatedHybridDag(standard, sources) {
|
|
|
5322
5458
|
tasks: standard.tasks.map((task) => ({ ...task })),
|
|
5323
5459
|
};
|
|
5324
5460
|
const closeout = getTaskOrThrow(spec, "closeout-pi");
|
|
5325
|
-
replaceTask(spec, cloneTask(closeout, {
|
|
5461
|
+
replaceTask(spec, cloneTask(closeout, {
|
|
5462
|
+
depends_on: ["review-gate-shell"],
|
|
5463
|
+
failureAwareDependsOn: ["review-gate-shell"],
|
|
5464
|
+
}));
|
|
5326
5465
|
spec.tasks.splice(spec.tasks.length - 1, 0, buildReviewNode(sources), buildReviewVerdictRecoveryNode(sources), buildReviewGateNode(sources));
|
|
5327
5466
|
applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
|
|
5328
5467
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
@@ -5528,6 +5667,7 @@ function buildProcessSupervisorNode(sources) {
|
|
|
5528
5667
|
allowedPaths: commonReadOnlyPaths(sources),
|
|
5529
5668
|
forbiddenPaths: commonForbiddenPaths(sources),
|
|
5530
5669
|
outputContract: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by a REPAIR_ARTIFACT_JSON fenced block. Audit writeSet coverage, boundary drift, verification gaps, repair scope. No file writes.",
|
|
5670
|
+
outputProtocol: REVIEW_VERDICT_OUTPUT_PROTOCOL,
|
|
5531
5671
|
subtask_prompt: [
|
|
5532
5672
|
"Supervise the implementation process after soft verification.",
|
|
5533
5673
|
"First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
|
|
@@ -5663,6 +5803,53 @@ function buildDecisionNode(sources) {
|
|
|
5663
5803
|
decisionGate: { enabled: true, schemaVersion: 1, mode: "record-only" },
|
|
5664
5804
|
};
|
|
5665
5805
|
}
|
|
5806
|
+
/**
|
|
5807
|
+
* Supervised convergence chain node ids. Extends the controller default
|
|
5808
|
+
* (process-supervisor → process-gate → repair → hard-verify) with the review
|
|
5809
|
+
* chain so that a legitimate review `request-revision` re-enters the same
|
|
5810
|
+
* bounded repair-reverify-review loop instead of only blocking closeout.
|
|
5811
|
+
*/
|
|
5812
|
+
export const SUPERVISED_CONVERGENCE_CHAIN_NODE_IDS = [
|
|
5813
|
+
"process-supervisor-pi",
|
|
5814
|
+
"process-gate-shell",
|
|
5815
|
+
"repair-pi",
|
|
5816
|
+
"hard-verify-shell",
|
|
5817
|
+
"review-pi",
|
|
5818
|
+
"review-verdict-recovery-pi",
|
|
5819
|
+
"review-gate-shell",
|
|
5820
|
+
];
|
|
5821
|
+
/**
|
|
5822
|
+
* Derive the supervised DAG convergence spec from `maxFixLoops`. Unlike the
|
|
5823
|
+
* task-level `convergence` config (which stays opt-in / disabled by default
|
|
5824
|
+
* until smoke evidence is stronger), the supervised path uses `maxFixLoops`
|
|
5825
|
+
* as the authoritative bounded-repair budget:
|
|
5826
|
+
* - `maxFixLoops > 0` ⇒ convergence enabled, `maxPasses = maxFixLoops + 1`
|
|
5827
|
+
* (initial execution + repair budget), with the full supervised chain.
|
|
5828
|
+
* - `maxFixLoops === 0` ⇒ convergence disabled (no automatic repair).
|
|
5829
|
+
* An explicit user `convergence.enabled: true` does not override `maxFixLoops`;
|
|
5830
|
+
* the budget still wins so total pass count stays consistent (AC1/AC2).
|
|
5831
|
+
*/
|
|
5832
|
+
function resolveSupervisedConvergence(taskConfig) {
|
|
5833
|
+
const maxFixLoops = taskConfig.maxFixLoops;
|
|
5834
|
+
if (maxFixLoops <= 0) {
|
|
5835
|
+
return {
|
|
5836
|
+
enabled: false,
|
|
5837
|
+
maxPasses: 1,
|
|
5838
|
+
stopOnVerdictPass: true,
|
|
5839
|
+
stopOnHardVerifyPass: true,
|
|
5840
|
+
pauseOnRegression: true,
|
|
5841
|
+
chainNodeIds: [...SUPERVISED_CONVERGENCE_CHAIN_NODE_IDS],
|
|
5842
|
+
};
|
|
5843
|
+
}
|
|
5844
|
+
return {
|
|
5845
|
+
enabled: true,
|
|
5846
|
+
maxPasses: maxFixLoops + 1,
|
|
5847
|
+
stopOnVerdictPass: true,
|
|
5848
|
+
stopOnHardVerifyPass: true,
|
|
5849
|
+
pauseOnRegression: taskConfig.convergence?.pauseOnRegression ?? true,
|
|
5850
|
+
chainNodeIds: [...SUPERVISED_CONVERGENCE_CHAIN_NODE_IDS],
|
|
5851
|
+
};
|
|
5852
|
+
}
|
|
5666
5853
|
function buildSupervisedHybridDag(standard, sources) {
|
|
5667
5854
|
const contract = getTaskOrThrow(standard, "contract-pi");
|
|
5668
5855
|
const scoutSrc = getTaskOrThrow(standard, "scout-src");
|
|
@@ -5687,7 +5874,7 @@ function buildSupervisedHybridDag(standard, sources) {
|
|
|
5687
5874
|
title: `Supervised ${standard.title}`,
|
|
5688
5875
|
objective: `${standard.objective ?? ""}\n\nRoute: supervised implementation DAG selected by workflowPolicy/governanceProfile or explicit CLI profile.`.trim(),
|
|
5689
5876
|
globalConstraints: supervisedConstraints,
|
|
5690
|
-
convergence: sources.taskConfig
|
|
5877
|
+
convergence: resolveSupervisedConvergence(sources.taskConfig),
|
|
5691
5878
|
verifyStrategy: resolveDagVerifyStrategy(sources.taskConfig, "1"),
|
|
5692
5879
|
tasks: [
|
|
5693
5880
|
cloneTask(contract),
|
|
@@ -5732,7 +5919,10 @@ function buildSupervisedHybridDag(standard, sources) {
|
|
|
5732
5919
|
buildReviewVerdictRecoveryNode(sources),
|
|
5733
5920
|
buildReviewGateNode(sources),
|
|
5734
5921
|
buildDecisionNode(sources),
|
|
5735
|
-
cloneTask(closeout, {
|
|
5922
|
+
cloneTask(closeout, {
|
|
5923
|
+
depends_on: ["decision-pi"],
|
|
5924
|
+
failureAwareDependsOn: ["decision-pi"],
|
|
5925
|
+
}),
|
|
5736
5926
|
],
|
|
5737
5927
|
};
|
|
5738
5928
|
applySddEmbeddedEnhancements(spec, sources.sddEmbeddedSkills ?? new Set());
|