@tea-agent/loop-agent 0.28.13 → 0.29.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +73 -15
- package/dist/commands/client-recovery.js +56 -1
- package/dist/commands/init-upgrade.js +186 -21
- package/dist/commands/init.js +1 -1
- package/dist/executors/dag-pi-executor.js +52 -3
- package/dist/executors/pi-playwright-cli-tool.js +14 -8
- package/dist/executors/shell-executor.js +288 -0
- package/dist/task/config-types.js +21 -0
- package/dist/worker/console/app-data.js +132 -11
- package/dist/worker/console/chat/pi-runtime.js +24 -42
- package/dist/worker/console/chat/resource-loader.js +11 -20
- package/dist/worker/console/chat/routes.js +7 -8
- package/dist/worker/console/chat/runtime-context.js +1 -1
- package/dist/worker/console/chat/tools.js +67 -54
- package/dist/worker/console/operation-runner.js +15 -1
- package/dist/worker/console/operation-store.js +70 -49
- package/dist/worker/console/operator-actions.js +57 -1
- package/dist/worker/console/static/assets/index-Cwx-ZVEQ.js +29 -0
- package/dist/worker/console/static/favicon.svg +37 -0
- package/dist/worker/console/static/index.html +2 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +846 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +418 -0
- package/dist/workflows/dag/frontend-test-case-checklist.js +94 -15
- package/dist/workflows/dag/frontend-test-case-manifest.js +104 -0
- package/dist/workflows/dag/frontend-test-html-report.js +106 -24
- package/dist/workflows/dag/frontend-test-result-contract.js +3 -0
- package/dist/workflows/dag/init-hybrid.js +187 -108
- package/dist/workflows/dag/node-execution.js +31 -2
- package/dist/workflows/dag/retry-policy.js +55 -18
- package/dist/workflows/dag/types.js +41 -0
- package/dist/workflows/dag/validate.js +42 -4
- package/docs/operations/README.md +1 -1
- package/docs/templates/README.md +2 -1
- package/docs/templates/agent-dag.schema.json +9 -4
- package/docs/templates/backend-test-dag.json +36 -11
- package/docs/templates/frontend-test-case-checklist.md +1 -1
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +9 -1
- package/docs/templates/frontend-test-dag.json +125 -267
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +7 -1
- package/docs/templates/frontend-test-dag.review-cases.prompt.md +1 -1
- package/docs/templates/frontend-test-standard-scenarios.v1.json +114 -0
- package/docs/templates/init-managed-agents.md +1 -1
- package/harness.json +2 -2
- package/package.json +2 -1
- package/skills/playwright-cli/SKILL.md +1 -1
- package/skills/playwright-cli-case-generator/SKILL.md +1 -1
- package/dist/worker/console/static/assets/index-BfRgtLF4.js +0 -29
|
@@ -9,7 +9,7 @@ import { planMavenVerification, } from "../../verification/maven/index.js";
|
|
|
9
9
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
10
10
|
import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
|
|
11
11
|
import { buildDecisionEnvelopePromptContract } from "./decision-envelope.js";
|
|
12
|
-
import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY, STRUCTURED_REQUIRED_PI_RETRY_POLICY, isSafeReadOnlyPiRetryCandidate, } from "./retry-policy.js";
|
|
12
|
+
import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY, STRUCTURED_REQUIRED_PI_RETRY_POLICY, BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY, isSafeReadOnlyPiRetryCandidate, } from "./retry-policy.js";
|
|
13
13
|
import { REVIEW_JSON_VERDICT_OUTPUT_PROTOCOL, REVIEW_VERDICT_OUTPUT_PROTOCOL, } from "./output-protocol.js";
|
|
14
14
|
import { resolveAdapter } from "../../adapters/index.js";
|
|
15
15
|
import { loadHarnessManifest } from "../../governance/harness.js";
|
|
@@ -3716,9 +3716,11 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3716
3716
|
type: "implementation-outcome-v1",
|
|
3717
3717
|
requireChangedFiles: true,
|
|
3718
3718
|
},
|
|
3719
|
+
retryPolicy: BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY,
|
|
3719
3720
|
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.",
|
|
3720
3721
|
subtask_prompt: [
|
|
3721
3722
|
"This is a required file-generation node. After reading the bounded inputs, immediately use write/edit tools to create testcase/md/README.md and the module Markdown files. Do not end after analysis or planning, and do not return before a non-empty bounded diff exists.",
|
|
3723
|
+
"Output budget protocol (hard, max output <=16K per turn): Never paste full Matrix, case bodies, or source text into assistant chat. Each write/edit tool call touches at most one file. Order: README.md (Scope+Matrix+module index only) → one module file per turn → short IMPLEMENTATION_OUTCOME. Splitting modules preserves every in-scope rule/TP; it must not drop coverage. Compact tables/lists are required; omitting required sections or in-scope variants is forbidden. If a Completeness Gate / OUTPUT_LIMIT_RECOVERY retry is injected, continue only listed target paths.",
|
|
3722
3724
|
"The first non-empty response line must be exactly IMPLEMENTATION_OUTCOME: changed after the required files have been written, or IMPLEMENTATION_OUTCOME: blocked when precise missing evidence prevents safe generation. already-satisfied is not valid for this node.",
|
|
3723
3725
|
"Read the upstream environment report. Generate a Markdown-first backend test strategy and cases under testcase/md/**.",
|
|
3724
3726
|
"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.",
|
|
@@ -3754,6 +3756,8 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3754
3756
|
outputContract: "Review source fidelity and directly revise only testcase/md/**; return concise Markdown, never JSON.",
|
|
3755
3757
|
subtask_prompt: [
|
|
3756
3758
|
"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.",
|
|
3759
|
+
"Output budget protocol: default to local edit per file; never dump full Matrix/case bodies into assistant chat. Review order is README (Scope/Matrix) then one module file per turn. When adding omitted in-scope cases, write one file per tool call and keep every required section. Do not bulk-delete in-scope cases to save tokens.",
|
|
3760
|
+
"For every variant Test Point, ensure the Markdown scenario intent is machine-checkable: prefer an explicit line `场景意图: <empty|missing|null|min-1|min|max|max+1|pattern-invalid|enum-invalid|wrong-type|nominal|custom-literal:V>; field=<name>; bound=<n optional>; example=<optional>` near 测试数据/操作步骤, and keep pytest params later aligned to that intent.",
|
|
3757
3761
|
"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.",
|
|
3758
3762
|
"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.",
|
|
3759
3763
|
"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.",
|
|
@@ -3780,9 +3784,16 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3780
3784
|
],
|
|
3781
3785
|
allowedPaths: Array.from(new Set([...ro, "testcase/**"])),
|
|
3782
3786
|
forbiddenPaths: forbidden,
|
|
3787
|
+
retryPolicy: BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY,
|
|
3788
|
+
writerOutcomePolicy: {
|
|
3789
|
+
type: "implementation-outcome-v1",
|
|
3790
|
+
requireChangedFiles: true,
|
|
3791
|
+
},
|
|
3783
3792
|
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.",
|
|
3784
3793
|
subtask_prompt: [
|
|
3785
3794
|
"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.",
|
|
3795
|
+
"Output budget protocol (hard, max output <=16K per turn): Write helpers/factories first, then exactly one test_<module>.py per write/edit tool call following MD stems. Never paste full Python modules into assistant chat. Do not merge modules. Do not reduce params/assertions/skips to fit. If OUTPUT_LIMIT_RECOVERY is injected, continue only listed missing/broken scripts.",
|
|
3796
|
+
"Align every variant pytest.param payload with the Markdown scenario intent (empty/missing/null/length/pattern/enum/wrong-type/nominal). Prefer literal payloads over Faker for intent-critical fields so pre-execution scenario-param checks can verify them.",
|
|
3786
3797
|
'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 前置条件/测试数据/自动化映射.',
|
|
3787
3798
|
"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.",
|
|
3788
3799
|
"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.",
|
|
@@ -3822,6 +3833,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3822
3833
|
"Repair the generated backend pytest asset as one bounded program using the direct upstream collection assessment. This is the only repair attempt and happens before any business test body execution.",
|
|
3823
3834
|
"Fix only collection-proven generated testcase-local defects: create the exact safe missing mapped test_*.py paths listed by the initial facts, or repair syntax, module path, missing symbol, circular import, fixture-name, decorator or parameterization inconsistencies. Inspect all affected importers and providers so the repair is cross-file consistent; do not create unrelated pytest scripts.",
|
|
3824
3835
|
"Preserve final testcase/md/** semantics, every Case ID, Rule/Test Point binding, primary symbol, parameter ID, expected status/body/schema assertion, HTTP logging, redaction and truncation behavior.",
|
|
3836
|
+
"Use local edit only on assessment-listed paths; keep summaries short; never rewrite unrelated modules.",
|
|
3825
3837
|
"Do not read task source/** or reinterpret requirements. Do not modify Markdown, conftest, pytest config, production code or dependencies.",
|
|
3826
3838
|
"Do not add skip/skipif/xfail, remove tests, reduce collected items, loosen assertions, swallow exceptions, use try/except ImportError fallback, mutate sys.path/PYTHONPATH, or replace the real API with mocks.",
|
|
3827
3839
|
"Do not execute pytest; the deterministic effective collection gate owns the final collection attempt.",
|
|
@@ -3829,7 +3841,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3829
3841
|
};
|
|
3830
3842
|
const collectionEffective = shellNode("effective-backend-pytest-collection-gate-shell", [collectionAssess.id, repairPytest.id], "markdown-collection-effective", "If initial collection passed, verify unchanged asset hashes and reuse it without another collection. If the single repair ran, collect the final mapped scripts once and fail closed unless it passes. BLOCKED initial facts, repair failure, final collection failure or hash drift must prevent business pytest execution.", "Run-owned reports/backend-test-pytest-collection-effective.md and contracts/backend-test-pytest-collection-effective.json proving the exact final assets are collectable; initial PASS is reused, repair path records attempt=1.", [], 120000);
|
|
3831
3843
|
collectionEffective.dependsPolicy = "all-or-condition-skip";
|
|
3832
|
-
const traceability = shellNode("backend-test-traceability-gate-shell", [collectionEffective.id], "markdown-traceability", "Deterministically scan only Markdown-mapped pytest scripts after the effective hash-bound collection gate. 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.
|
|
3844
|
+
const traceability = shellNode("backend-test-traceability-gate-shell", [collectionEffective.id], "markdown-traceability", "Deterministically scan only Markdown-mapped pytest scripts after the effective hash-bound collection gate. 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. In the same shell, assess scenario-intent vs pytest.param literal features, apply at most one deterministic pre-execution scenario-param repair for repairable MISMATCH entries, reassess final consistency, and bind asset hashes for execute. Findings for correspondence remain advisory; residual scenario-param MISMATCH is advisory unless strictScenarioParamGate is enabled. Never block pytest solely on correspondence FAIL.", "Run-owned reports/backend-test-traceability.md, reports/backend-test-markdown-pytest-correspondence.md, contracts/backend-test-markdown-pytest-correspondence-facts.json, reports/backend-test-scenario-param-consistency.md and contracts/backend-test-scenario-param-consistency-facts.json (initial+final) with optional repair audit; PASS/FAIL/UNAVAILABLE correspondence facts bound after effective collection.");
|
|
3833
3845
|
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.");
|
|
3834
3846
|
const pytestCommand = [
|
|
3835
3847
|
'mkdir -p "${HARNESS_DAG_RUN_DIR}/reports"',
|
|
@@ -3859,8 +3871,9 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3859
3871
|
? "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."
|
|
3860
3872
|
: "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.",
|
|
3861
3873
|
subtask_prompt: [
|
|
3862
|
-
"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; nodes 6/8 backend-test-pytest-collection initial/effective reports and facts; node 9 backend-test-traceability.md
|
|
3863
|
-
"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.",
|
|
3874
|
+
"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; nodes 6/8 backend-test-pytest-collection initial/effective reports and facts; node 9 backend-test-traceability.md, backend-test-markdown-pytest-correspondence.md and backend-test-scenario-param-consistency.md; node 10 contracts/backend-test-case-manifest.json; and node 11 backend-test-result.json, backend-test-facts.md, backend-test-failure-analysis.md, pytest-html/HTML and L-5 dashboard. Do not use node 2/3/5 assistant prose as facts. Do not emit JSON.",
|
|
3875
|
+
"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. Prefer linking reports/backend-test-failure-analysis.md for structured failure analysis rather than inventing classifications.",
|
|
3876
|
+
"Output budget: list evidence paths first, then write a short fixed six-section report; never paste upstream full text into chat.",
|
|
3864
3877
|
"The L-5 metrics and visualization are produced deterministically by node 11 at reports/backend-test-l5-dashboard.html. Link to that dashboard as the authoritative L-5 view. Pytest execution facts come from node 11; coverage/correspondence numbers and materializationStatus come from node 10; collection authorization comes from nodes 6/8; detailed coverage findings come from node 4; detailed mapping findings come from node 9. Never recompute these values. If machine manifest and human reports disagree, report evidence inconsistency rather than silently choosing.",
|
|
3865
3878
|
"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 9 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.",
|
|
3866
3879
|
"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.",
|
|
@@ -3882,9 +3895,10 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3882
3895
|
...STANDARD_GLOBAL_CONSTRAINTS,
|
|
3883
3896
|
"backend-test-dag uses exactly 12 real top-level tasks. Pytest collection runs once on the green path and at most twice only when one bounded pre-execution repair is eligible; business pytest test bodies execute exactly once over safe scripts explicitly mapped by final Markdown cases.",
|
|
3884
3897
|
"Model nodes produce Markdown and pytest assets, never backend-test business JSON envelopes.",
|
|
3885
|
-
"Environment, advisory Markdown validation/coverage, collection initial/effective facts, advisory traceability/correspondence, canonical manifest, pytest-html, HTML and execution facts are deterministic evidence. Node 4 quality findings stay advisory; nodes 6/8 form the fail-closed collection authorization; node 9 traceability findings stay advisory; node 10 partial/unavailable manifest does not block node 11 when effective collection remains fresh.",
|
|
3898
|
+
"Environment, advisory Markdown validation/coverage, collection initial/effective facts, advisory traceability/correspondence, pre-execution scenario-param consistency (with at most one deterministic param repair), canonical manifest, pytest-html, HTML, failure-analysis and execution facts are deterministic evidence. Node 4 quality findings stay advisory; nodes 6/8 form the fail-closed collection authorization; node 9 traceability/scenario-param findings stay advisory by default; node 10 partial/unavailable manifest does not block node 11 when effective collection remains fresh.",
|
|
3886
3899
|
"Only Markdown case generation/review may read source facts; pytest generation must not read source/**.",
|
|
3887
|
-
"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, execution-result repair and business pytest rerun are forbidden;
|
|
3900
|
+
"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, execution-result repair and business pytest rerun are forbidden; pre-execution repairs are limited to one collection-proven generated-test asset repair and one scenario-param payload repair (deterministic preferred).",
|
|
3901
|
+
"Writer nodes must obey multi-file output-budget protocol under 16K max tokens: one file per write/edit, no chat dumps; Completeness Gate may trigger bounded incomplete-write-set recovery without lowering coverage quality.",
|
|
3888
3902
|
],
|
|
3889
3903
|
defaults: {
|
|
3890
3904
|
...BACKEND_TEST_DEFAULTS,
|
|
@@ -3963,12 +3977,25 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
3963
3977
|
maxTotalTokens: rawFrontendTest?.maxTotalTokens,
|
|
3964
3978
|
reviewMode: rawFrontendTest?.reviewMode ?? "off",
|
|
3965
3979
|
strictOutcomeGate: rawFrontendTest?.strictOutcomeGate === true,
|
|
3980
|
+
maxRerunAttempts: (() => {
|
|
3981
|
+
const raw = rawFrontendTest?.maxRerunAttempts;
|
|
3982
|
+
if (raw === undefined || raw === null || Number.isNaN(Number(raw)))
|
|
3983
|
+
return 2;
|
|
3984
|
+
return Math.min(4, Math.max(0, Math.trunc(Number(raw))));
|
|
3985
|
+
})(),
|
|
3986
|
+
reports: {
|
|
3987
|
+
retrospect: rawFrontendTest?.reports?.retrospect === true,
|
|
3988
|
+
l5: rawFrontendTest?.reports?.l5 !== false,
|
|
3989
|
+
},
|
|
3966
3990
|
};
|
|
3967
3991
|
const declaredRequirementIds = buildDagSourceBinding(sources).requirementIds;
|
|
3968
3992
|
const declaredAcIds = declaredRequirementIds.filter((id) => /^AC(?:-[A-Z0-9]+)+$/i.test(id));
|
|
3969
3993
|
const reviewMode = config.reviewMode;
|
|
3970
3994
|
const blockingReview = reviewMode === "blocking";
|
|
3971
3995
|
const strictOutcomeGate = config.strictOutcomeGate;
|
|
3996
|
+
const maxRerunAttempts = config.maxRerunAttempts ?? 2;
|
|
3997
|
+
const enableRetrospect = config.reports?.retrospect === true;
|
|
3998
|
+
const enableL5Report = config.reports?.l5 !== false;
|
|
3972
3999
|
const hasFrontendTestWriteScope = sources.taskConfig.allowedPaths.some((pattern) => pattern === "testcase/frontend/**" ||
|
|
3973
4000
|
pattern === "testcase/**" ||
|
|
3974
4001
|
pattern === "**");
|
|
@@ -3999,7 +4026,7 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
3999
4026
|
"if(!Array.isArray(manifest.cases)||manifest.cases.length===0)throw new Error('checklist: empty cases');",
|
|
4000
4027
|
`const declaredAc=new Set(${declaredAcIdsLiteral});`,
|
|
4001
4028
|
"const issues=[];",
|
|
4002
|
-
"const openRe=/playwright-cli\\s+open\\s+--browser=chrome\\s+--
|
|
4029
|
+
"const openRe=/playwright-cli\\s+open\\s+--browser=chrome\\s+--headless\\s+https?:\\/\\/\\S+/i;",
|
|
4003
4030
|
"const prodRe=/(?:^|\\/\\/)(?:www\\.)?[^\\s\\/]*(?:prod|production)/i;",
|
|
4004
4031
|
"const caseIdRe=/^FE-[A-Za-z0-9][A-Za-z0-9-]*$/;",
|
|
4005
4032
|
,
|
|
@@ -4012,9 +4039,9 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4012
4039
|
" if(!casePath||!fs.existsSync(casePath)){issues.push({ruleId:'case-file-missing',caseId:id,detail:String(casePath)});continue;}",
|
|
4013
4040
|
" 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'});",
|
|
4014
4041
|
" const body=fs.readFileSync(casePath,'utf8');",
|
|
4015
|
-
" if(!openRe.test(body))issues.push({ruleId:'open-prefix',caseId:id,detail:'missing playwright-cli open --browser=chrome --
|
|
4042
|
+
" if(!openRe.test(body))issues.push({ruleId:'open-prefix',caseId:id,detail:'missing playwright-cli open --browser=chrome --headless <absolute-url>; playwright-cli is strongly recommended for browser execution'});",
|
|
4016
4043
|
,
|
|
4017
|
-
" const m=body.match(/playwright-cli\\s+open\\s+--browser=chrome\\s+--
|
|
4044
|
+
" const m=body.match(/playwright-cli\\s+open\\s+--browser=chrome\\s+--headless\\s+(https?:\\/\\/\\S+)/i);",
|
|
4018
4045
|
" if(m){const url=m[1].replace(/[)\\]},.\"']+$/,''); if(prodRe.test(url))issues.push({ruleId:'production-url',caseId:id,detail:url});}",
|
|
4019
4046
|
" 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)'});",
|
|
4020
4047
|
" else {",
|
|
@@ -4111,13 +4138,34 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4111
4138
|
},
|
|
4112
4139
|
},
|
|
4113
4140
|
{
|
|
4114
|
-
id: "
|
|
4141
|
+
id: "prepare-frontend-test-package-shell",
|
|
4115
4142
|
depends_on: ["preflight-frontend-browser-tool-shell"],
|
|
4143
|
+
role: "verifier",
|
|
4144
|
+
executor: "shell",
|
|
4145
|
+
complexity: "LOW",
|
|
4146
|
+
writePolicy: "exclusive",
|
|
4147
|
+
writeSet: ragWriteSet,
|
|
4148
|
+
allowedPaths: [...ragWriteSet],
|
|
4149
|
+
forbiddenPaths: forbidden,
|
|
4150
|
+
outputContract: "Write testcase/frontend/rag/standard-scenarios.v1.json for generate-time standard scenario coverage.",
|
|
4151
|
+
subtask_prompt: "Prepare frontend-test package: materialize standard-scenarios.v1.json into the RAG package.",
|
|
4152
|
+
shell: {
|
|
4153
|
+
commands: [
|
|
4154
|
+
["node -e", JSON.stringify("const fs=require('fs'),path=require('path');const dest='testcase/frontend/rag/standard-scenarios.v1.json';const candidates=[path.join('docs','templates','frontend-test-standard-scenarios.v1.json')];let src=null;for(const c of candidates){if(fs.existsSync(c)){src=c;break;}}fs.mkdirSync(path.dirname(dest),{recursive:true});if(src){fs.copyFileSync(src,dest);process.stdout.write(JSON.stringify({status:'copied',from:src,to:dest}));}else{const minimal={schemaVersion:1,id:'frontend-test-standard-scenarios-v1',scenarios:[{id:'STD-FE-SMOKE-ENTRY',title:'入口可打开',category:'smoke',priority:'must',testPoints:['open'],minCases:1}]};fs.writeFileSync(dest,JSON.stringify(minimal,null,2)+'\n');process.stdout.write(JSON.stringify({status:'fallback',to:dest}));}")].join(" "),
|
|
4155
|
+
],
|
|
4156
|
+
cwd: ".",
|
|
4157
|
+
timeoutMs: 60_000,
|
|
4158
|
+
},
|
|
4159
|
+
},
|
|
4160
|
+
{
|
|
4161
|
+
id: "retrieve-frontend-test-context-pi",
|
|
4162
|
+
depends_on: ["prepare-frontend-test-package-shell"],
|
|
4116
4163
|
role: "planner",
|
|
4117
4164
|
executor: "pi",
|
|
4118
4165
|
toolProfile: "write",
|
|
4119
4166
|
complexity: "MED",
|
|
4120
4167
|
writePolicy: "exclusive",
|
|
4168
|
+
writeGuardPolicy: "tools-only",
|
|
4121
4169
|
writeSet: ragWriteSet,
|
|
4122
4170
|
allowedPaths: [...commonReadOnlyPaths(sources), ...ragWriteSet],
|
|
4123
4171
|
forbiddenPaths: forbidden,
|
|
@@ -4126,7 +4174,7 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4126
4174
|
"Build the frontend test RAG package (keep it short).",
|
|
4127
4175
|
"Read task source, routes/components/API or Mock facts, and execution contract. Write only testcase/frontend/rag/context.md and coverage-map.md.",
|
|
4128
4176
|
"Prefer fixed fields: baseUrl, baseUrlSource, environmentProbe, AC table, capability matrix (backend real/mock, pagination data, HTTP observation, error injection), risks, forbidden hosts. Do not paste large implementation dumps.",
|
|
4129
|
-
`Controller-frozen origin (required, not model-selectable): write exactly \`baseUrl: ${controllerFrontend.baseUrl}\` and \`baseUrlSource: ${controllerFrontend.baseUrlSource}\`. Do not derive, replace, or override the origin from model reasoning or other repository text. Write \`environmentProbe: pending\`. Include exact start prefix: playwright-cli open --browser=chrome --
|
|
4177
|
+
`Controller-frozen origin (required, not model-selectable): write exactly \`baseUrl: ${controllerFrontend.baseUrl}\` and \`baseUrlSource: ${controllerFrontend.baseUrlSource}\`. Do not derive, replace, or override the origin from model reasoning or other repository text. Write \`environmentProbe: pending\`. Include exact start prefix: playwright-cli open --browser=chrome --headless ${controllerFrontend.baseUrl}.`,
|
|
4130
4178
|
buildSourceContextBlock(sources),
|
|
4131
4179
|
].join("\n\n"),
|
|
4132
4180
|
},
|
|
@@ -4161,12 +4209,14 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4161
4209
|
toolProfile: "write",
|
|
4162
4210
|
complexity: "HIGH",
|
|
4163
4211
|
writePolicy: "exclusive",
|
|
4212
|
+
writeGuardPolicy: "tools-only",
|
|
4164
4213
|
writeSet: caseDraftWriteSet,
|
|
4165
4214
|
allowedPaths: [...ragWriteSet, ...caseDraftWriteSet],
|
|
4166
4215
|
forbiddenPaths: forbidden,
|
|
4167
4216
|
outputContract: "Write executable Markdown frontend cases, index.md, and manifest.draft.json schemaVersion 1 only; the exclusive shell materializer promotes the validated draft to manifest.json. Do not write manifest.json or test source code.",
|
|
4168
4217
|
subtask_prompt: [
|
|
4169
4218
|
"Use skill playwright-cli-case-generator.",
|
|
4219
|
+
"Read testcase/frontend/rag/standard-scenarios.v1.json and cover priority=must scenarios (or record GAP in coverage-map). Include ## 测试点 and ## 测试步骤 in each case.",
|
|
4170
4220
|
"Read only testcase/frontend/rag/context.md, testcase/frontend/rag/coverage-map.md, and the draft case paths testcase/frontend/cases/FE-*.md, testcase/frontend/cases/index.md, and testcase/frontend/cases/manifest.draft.json. Write only those same draft paths. Do not write testcase/frontend/cases/manifest.json.",
|
|
4171
4221
|
"Generate Markdown cases, index.md and manifest.draft.json (schemaVersion 1; cases[] with caseId, casePath, dimension, acIds, evidenceDir).",
|
|
4172
4222
|
"HARD ID CONTRACT (do not confuse these):",
|
|
@@ -4177,7 +4227,7 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4177
4227
|
"dimensions: core|boundary|flow|backend only.",
|
|
4178
4228
|
"Prefer a small smoke suite (default max roughly 4-8 cases unless task frontendTest.maxCasesPerBatch is higher). Never invent unavailable API fields or credentials. Do not create pytest or Playwright source.",
|
|
4179
4229
|
"HARD playwright-cli-only: every browser step must use repo skill playwright-cli declared commands only. Forbidden: bare `playwright`, `npx playwright`, `playwright test`, `@playwright/test`, Node Playwright API, or generating Playwright/Pytest source. No fallback when playwright-cli is unavailable - case must instruct blocked evidence playwright-cli-unavailable.",
|
|
4180
|
-
`Use only the controller-frozen baseUrl ${controllerFrontend.baseUrl} from ${controllerFrontend.baseUrlSource}; context.md may reference it but cannot establish or override it. Every browser start command must be exactly: playwright-cli open --browser=chrome --
|
|
4230
|
+
`Use only the controller-frozen baseUrl ${controllerFrontend.baseUrl} from ${controllerFrontend.baseUrlSource}; context.md may reference it but cannot establish or override it. Every browser start command must be exactly: playwright-cli open --browser=chrome --headless ${controllerFrontend.baseUrl}. Never leave a base-url placeholder. Use default browser session only; never write -s=<case-id>.`,
|
|
4181
4231
|
"HARD dynamic refs: executable playwright-cli lines must never contain an angle-bracket token such as <fresh-ref> or descriptive <...> placeholder. Use only shell-safe documentation placeholders `eX`, `eY`, ...; each means the real `eNN` ref parsed from the immediately preceding latest `snapshot`. Write a fresh snapshot before every element reference. eX/eY are never literal structured-tool arguments; a later snapshot invalidates prior refs, so never reuse stale refs.",
|
|
4182
4232
|
"HARD file-output argv: use canonical `--filename` only. Screenshot uses `playwright-cli screenshot --filename final.png` (a real ref may precede the flag); PDF uses `playwright-cli pdf --filename final.pdf`; snapshot without filename is response-only and a snapshot file uses `playwright-cli snapshot --filename snapshot.txt`. Never generate `playwright-cli screenshot <path>`, use `--path`, `--output`, or `--file`, or pass an output path as a positional target.",
|
|
4183
4233
|
"Each case must be independently reproducible with fixture/reset, UI reset, snapshot-before-ref, evidence write point under testcase/frontend/evidence/<case-id>/. If the isolated environment is unavailable, require writing blocked evidence before any browser command.",
|
|
@@ -4206,6 +4256,7 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4206
4256
|
toolProfile: "write",
|
|
4207
4257
|
complexity: "HIGH",
|
|
4208
4258
|
writePolicy: "exclusive",
|
|
4259
|
+
writeGuardPolicy: "tools-only",
|
|
4209
4260
|
writeSet: caseDraftWriteSet,
|
|
4210
4261
|
allowedPaths: [...ragWriteSet, ...caseDraftWriteSet],
|
|
4211
4262
|
forbiddenPaths: forbidden,
|
|
@@ -4256,38 +4307,28 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4256
4307
|
? ["final-frontend-case-review-gate-shell"]
|
|
4257
4308
|
: ["generate-frontend-functional-cases-pi"];
|
|
4258
4309
|
tasks.push({
|
|
4259
|
-
id: "
|
|
4310
|
+
id: "checklist-and-materialize-manifest-shell",
|
|
4260
4311
|
depends_on: checklistDependsOn,
|
|
4261
4312
|
role: "verifier",
|
|
4262
4313
|
executor: "shell",
|
|
4263
4314
|
complexity: "LOW",
|
|
4264
|
-
writePolicy: "
|
|
4315
|
+
writePolicy: "exclusive",
|
|
4316
|
+
writeSet: casesWriteSet,
|
|
4265
4317
|
allowedPaths: [...ragWriteSet, ...casesWriteSet],
|
|
4266
4318
|
forbiddenPaths: forbidden,
|
|
4267
|
-
outputContract: "Mechanical checklist
|
|
4268
|
-
subtask_prompt: "
|
|
4319
|
+
outputContract: "Mechanical checklist then atomic manifest.json materialization; stdout one final JSON line {cases}.",
|
|
4320
|
+
subtask_prompt: "Run deterministic checklist then materialize manifest.json from draft after optional blocking review.",
|
|
4269
4321
|
shell: {
|
|
4270
4322
|
commands: [],
|
|
4271
|
-
|
|
4323
|
+
frontendTestCaseManifest: {
|
|
4324
|
+
maxCases: config.maxCasesPerBatch,
|
|
4325
|
+
},
|
|
4272
4326
|
cwd: ".",
|
|
4273
4327
|
timeoutMs: 120000,
|
|
4274
4328
|
},
|
|
4275
|
-
}, {
|
|
4276
|
-
id: "materialize-frontend-case-manifest-shell",
|
|
4277
|
-
depends_on: ["frontend-case-checklist-shell"],
|
|
4278
|
-
role: "verifier",
|
|
4279
|
-
executor: "shell",
|
|
4280
|
-
complexity: "LOW",
|
|
4281
|
-
writePolicy: "exclusive",
|
|
4282
|
-
writeSet: casesWriteSet,
|
|
4283
|
-
allowedPaths: casesWriteSet,
|
|
4284
|
-
forbiddenPaths: forbidden,
|
|
4285
|
-
outputContract: "Validated frontend manifest payload { cases: [...] }; ruleId-tagged fail-closed validation; atomically materialize testcase/frontend/cases/manifest.json via temp+rename then delete draft; stdout is exactly one final JSON line {cases}.",
|
|
4286
|
-
subtask_prompt: "Validate manifest.draft.json and materialize manifest.json after the mechanical checklist (and optional blocking review) passes.",
|
|
4287
|
-
shell: { commands: [manifestValidation], cwd: ".", timeoutMs: 120000 },
|
|
4288
4329
|
}, {
|
|
4289
4330
|
id: "execute-frontend-cases-map",
|
|
4290
|
-
depends_on: ["
|
|
4331
|
+
depends_on: ["checklist-and-materialize-manifest-shell"],
|
|
4291
4332
|
role: "verifier",
|
|
4292
4333
|
executor: "static",
|
|
4293
4334
|
complexity: "LOW",
|
|
@@ -4300,7 +4341,7 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4300
4341
|
dynamicExpansion: {
|
|
4301
4342
|
type: "map_agent",
|
|
4302
4343
|
workflowNodeId: "execute-frontend-cases-map",
|
|
4303
|
-
itemsFrom: "$.nodes['
|
|
4344
|
+
itemsFrom: "$.nodes['checklist-and-materialize-manifest-shell'].output.cases",
|
|
4304
4345
|
itemName: "case",
|
|
4305
4346
|
maxItems: config.maxCasesPerBatch,
|
|
4306
4347
|
maxExpandedNodes: config.maxCasesPerBatch,
|
|
@@ -4322,6 +4363,7 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4322
4363
|
},
|
|
4323
4364
|
complexity: "MED",
|
|
4324
4365
|
writePolicy: "exclusive",
|
|
4366
|
+
writeGuardPolicy: "tools-only",
|
|
4325
4367
|
allowedPaths: [
|
|
4326
4368
|
"testcase/frontend/cases/{{case.caseId}}.md",
|
|
4327
4369
|
"testcase/frontend/rag/context.md",
|
|
@@ -4334,15 +4376,93 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4334
4376
|
subtaskPromptTemplate: [
|
|
4335
4377
|
"Primary job: EXECUTE case {{case.caseId}} from {{case.casePath}} with skill playwright-cli (fresh Pi session; do not use /new). playwright-cli-only: never bare Playwright CLI/API/test runner; no fallback.",
|
|
4336
4378
|
"Use the structured playwright_cli tool for every browser action. Do not request or search for bash. Do not execute raw shell commands. Translate each playwright-cli line in the case Markdown into one playwright_cli tool call ({command, args?, timeoutSeconds?}).",
|
|
4337
|
-
`1) The controller-frozen baseUrl is ${controllerFrontend.baseUrl} from ${controllerFrontend.baseUrlSource}; model-authored context/case text cannot establish or override it. 2) Start browser ONLY via playwright_cli command=open with args [--browser=chrome, --
|
|
4379
|
+
`1) The controller-frozen baseUrl is ${controllerFrontend.baseUrl} from ${controllerFrontend.baseUrlSource}; model-authored context/case text cannot establish or override it. 2) Start browser ONLY via playwright_cli command=open with args [--browser=chrome, --headless, ${controllerFrontend.baseUrl}] (default session only; no -s=). 3) Dynamic refs: eX/eY in case Markdown are documentation placeholders, never tool args. Immediately before every structured playwright_cli call that references an element, parse the current real eNN from the immediately preceding latest snapshot and pass only that real eNN; never send literal \`eX\`/\`eY\`. A new snapshot invalidates prior refs, so never reuse stale refs. File outputs are canonical: screenshot args [--filename, final.png] (or [e5, --filename, final.png] for a real target), PDF args [--filename, final.pdf], and snapshot writes a file only with [--filename, snapshot.txt]; snapshot without filename is response-only. Never use --path, --output, --file, or any output path as a positional target. Follow case steps with snapshot before element refs using only playwright_cli. A passed case requires this same child receipt order: successful open → successful find → controller post-execution cleanup. Only successful find is a meaningful assertion; snapshot, goto, screenshot, request/console, click/fill and other ordinary interactions cannot establish passed authority. 4) Only when preflight or playwright_cli tool explicitly fails may you write blocked evidence (blockedReason playwright-cli-unavailable | frontend-base-url-unreachable); never invent CLI-unavailable solely because bash is absent. 5) For U/D: enforce current-user ownership / create-or-mock-or-blocked; never mutate other users' data.`,
|
|
4338
4380
|
"Always write {{case.evidenceDir}}execution.md and {{case.evidenceDir}}case-result.json with caseId={{case.caseId}}, status passed|failed|blocked, evidencePaths (relative under evidenceDir). blocked needs non-empty blockedReason. After writing, self-check the same contract; if self-check fails, rewrite both files as status=blocked blockedReason=invalid-evidence-shape (never leave missing/malformed evidence).",
|
|
4339
4381
|
"Business failed/blocked is a recorded result, not a node failure. Close browser via playwright_cli command=close. Return compact JSON (<=1200 chars): {caseId,status,evidencePaths,errorSummary,tokens}.",
|
|
4340
4382
|
].join("\n\n"),
|
|
4341
4383
|
},
|
|
4342
4384
|
},
|
|
4343
|
-
}
|
|
4344
|
-
|
|
4345
|
-
|
|
4385
|
+
});
|
|
4386
|
+
if (maxRerunAttempts > 0) {
|
|
4387
|
+
tasks.push({
|
|
4388
|
+
id: "select-frontend-rerun-candidates-shell",
|
|
4389
|
+
depends_on: ["execute-frontend-cases-map"],
|
|
4390
|
+
role: "verifier",
|
|
4391
|
+
executor: "shell",
|
|
4392
|
+
complexity: "LOW",
|
|
4393
|
+
writePolicy: "exclusive",
|
|
4394
|
+
writeSet: ["testcase/frontend/evidence/**"],
|
|
4395
|
+
allowedPaths: [...casesWriteSet, "testcase/frontend/evidence/**"],
|
|
4396
|
+
forbiddenPaths: forbidden,
|
|
4397
|
+
outputContract: "Stdout JSON {cases} for blocked or missing-result-file cases with rerunAttempt < maxRerunAttempts.",
|
|
4398
|
+
subtask_prompt: "Select frontend-test cases eligible for bounded rerun.",
|
|
4399
|
+
shell: {
|
|
4400
|
+
commands: [
|
|
4401
|
+
["node -e", JSON.stringify("const fs=require('fs'),path=require('path');const manifestPath='testcase/frontend/cases/manifest.json';if(!fs.existsSync(manifestPath)){process.stdout.write(JSON.stringify({cases:[]}));process.exit(0);}const manifest=JSON.parse(fs.readFileSync(manifestPath,'utf8'));const cases=[];for(const c of (manifest.cases||[])){const evidenceDir=(c.evidenceDir||('testcase/frontend/evidence/'+c.caseId+'/')).replace(/\\/+$/,'')+'/';const resultPath=path.join(evidenceDir,'case-result.json');const execPath=path.join(evidenceDir,'execution.md');let reason=null;let attempt=0;let missing=false;if(!fs.existsSync(resultPath)){missing=true;reason='missing-result-files';}else{try{const r=JSON.parse(fs.readFileSync(resultPath,'utf8'));attempt=Number(r.rerunAttempt||0)||0;if(r.status==='blocked')reason='blocked';if(!r.status){missing=true;reason='missing-result-files';}}catch(e){missing=true;reason='missing-result-files';}}if(!fs.existsSync(execPath)&&reason!=='blocked'){missing=true;reason=reason||'missing-result-files';}const should=(reason==='blocked'||missing)&&attempt<" + maxRerunAttempts + ";if(should){cases.push({caseId:c.caseId,casePath:c.casePath||('testcase/frontend/cases/'+c.caseId+'.md'),evidenceDir,dimension:c.dimension||'core',acIds:c.acIds||[],rerunAttempt:attempt+1,reason:reason||'blocked'});}}fs.mkdirSync('testcase/frontend/evidence',{recursive:true});fs.writeFileSync('testcase/frontend/evidence/rerun-candidates.json',JSON.stringify({schemaVersion:1,cases},null,2)+'\\n');process.stdout.write(JSON.stringify({cases}));")].join(" "),
|
|
4402
|
+
],
|
|
4403
|
+
cwd: ".",
|
|
4404
|
+
timeoutMs: 120_000,
|
|
4405
|
+
},
|
|
4406
|
+
}, {
|
|
4407
|
+
id: "rerun-frontend-cases-map",
|
|
4408
|
+
depends_on: ["select-frontend-rerun-candidates-shell"],
|
|
4409
|
+
role: "verifier",
|
|
4410
|
+
executor: "static",
|
|
4411
|
+
complexity: "LOW",
|
|
4412
|
+
writePolicy: "none",
|
|
4413
|
+
allowedPaths: [],
|
|
4414
|
+
forbiddenPaths: forbidden,
|
|
4415
|
+
outputContract: "Serial rerun of blocked/missing-result frontend cases.",
|
|
4416
|
+
subtask_prompt: "Expand rerun candidates into serial browser case children.",
|
|
4417
|
+
static: { resultMarkdown: "Frontend case rerun map expansion barrier." },
|
|
4418
|
+
dynamicExpansion: {
|
|
4419
|
+
type: "map_agent",
|
|
4420
|
+
workflowNodeId: "rerun-frontend-cases-map",
|
|
4421
|
+
itemsFrom: "$.nodes['select-frontend-rerun-candidates-shell'].output.cases",
|
|
4422
|
+
itemName: "case",
|
|
4423
|
+
maxItems: config.maxCasesPerBatch,
|
|
4424
|
+
maxExpandedNodes: config.maxCasesPerBatch,
|
|
4425
|
+
childIdPrefix: "rerun-frontend-case",
|
|
4426
|
+
workspaceTemplate: "{{case.evidenceDir}}",
|
|
4427
|
+
tolerateChildFailures: true,
|
|
4428
|
+
tokenBudget: {
|
|
4429
|
+
maxTokensPerCase: config.maxTokensPerCase,
|
|
4430
|
+
maxTotalTokens: config.maxTotalTokens,
|
|
4431
|
+
},
|
|
4432
|
+
childTask: {
|
|
4433
|
+
executor: "pi",
|
|
4434
|
+
role: "implementer",
|
|
4435
|
+
skills: ["playwright-cli"],
|
|
4436
|
+
toolProfile: "write",
|
|
4437
|
+
commandPolicy: {
|
|
4438
|
+
mode: "capability-allowlist",
|
|
4439
|
+
capabilities: ["playwright-cli"],
|
|
4440
|
+
},
|
|
4441
|
+
complexity: "MED",
|
|
4442
|
+
writePolicy: "exclusive",
|
|
4443
|
+
writeGuardPolicy: "tools-only",
|
|
4444
|
+
allowedPaths: [
|
|
4445
|
+
"testcase/frontend/cases/{{case.caseId}}.md",
|
|
4446
|
+
"testcase/frontend/rag/context.md",
|
|
4447
|
+
"testcase/frontend/rag/coverage-map.md",
|
|
4448
|
+
`${evidenceRoot}/{{case.caseId}}/**`,
|
|
4449
|
+
],
|
|
4450
|
+
forbiddenPaths: forbidden,
|
|
4451
|
+
writeSet: [`${evidenceRoot}/{{case.caseId}}/**`],
|
|
4452
|
+
outputContract: "Compact JSON <=1200 characters with case status, evidence paths, error summary, tokens, and rerunAttempt.",
|
|
4453
|
+
subtaskPromptTemplate: [
|
|
4454
|
+
"RERUN attempt {{case.rerunAttempt}} for {{case.caseId}} (reason={{case.reason}}). Rewrite authoritative evidence; set rerunAttempt={{case.rerunAttempt}}.",
|
|
4455
|
+
"Primary job: EXECUTE case {{case.caseId}} from {{case.casePath}} with skill playwright-cli (fresh Pi session). Use structured playwright_cli only; headless open.",
|
|
4456
|
+
`Start via playwright_cli command=open with args [--browser=chrome, --headless, ${controllerFrontend.baseUrl}]. Passed requires open → find → cleanup receipts.`,
|
|
4457
|
+
"Always write {{case.evidenceDir}}execution.md and {{case.evidenceDir}}case-result.json with caseId, status, evidencePaths, rerunAttempt={{case.rerunAttempt}}.",
|
|
4458
|
+
].join("\n\n"),
|
|
4459
|
+
},
|
|
4460
|
+
},
|
|
4461
|
+
});
|
|
4462
|
+
}
|
|
4463
|
+
tasks.push({
|
|
4464
|
+
id: "finalize-frontend-test-result-shell",
|
|
4465
|
+
depends_on: [maxRerunAttempts > 0 ? "rerun-frontend-cases-map" : "execute-frontend-cases-map"],
|
|
4346
4466
|
role: "verifier",
|
|
4347
4467
|
executor: "shell",
|
|
4348
4468
|
complexity: "LOW",
|
|
@@ -4350,40 +4470,18 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4350
4470
|
writeSet: [`${evidenceRoot}/**`],
|
|
4351
4471
|
allowedPaths: ["testcase/frontend/cases/**", `${evidenceRoot}/**`],
|
|
4352
4472
|
forbiddenPaths: forbidden,
|
|
4353
|
-
outputContract: "
|
|
4354
|
-
subtask_prompt: "Validate
|
|
4473
|
+
outputContract: "Evidence validation (advisory missing/malformed does not fail the node; hard-fail only path escape) then hash-bound frontend-test-result-v1. Node success means result-v1 was written, not that all cases passed.",
|
|
4474
|
+
subtask_prompt: "Validate case evidence then materialize authoritative frontend-test-result-v1. Missing/malformed case evidence is advisory; only unsafe evidence paths hard-fail. Do not use Pi prose as input.",
|
|
4355
4475
|
shell: {
|
|
4356
4476
|
commands: [],
|
|
4357
|
-
|
|
4358
|
-
cwd: ".",
|
|
4359
|
-
timeoutMs: 120000,
|
|
4360
|
-
},
|
|
4361
|
-
}, {
|
|
4362
|
-
id: "materialize-frontend-test-result-shell",
|
|
4363
|
-
depends_on: ["validate-frontend-case-evidence-shell"],
|
|
4364
|
-
role: "verifier",
|
|
4365
|
-
executor: "shell",
|
|
4366
|
-
complexity: "LOW",
|
|
4367
|
-
writePolicy: "read-only",
|
|
4368
|
-
allowedPaths: ["testcase/frontend/cases/**", `${evidenceRoot}/**`],
|
|
4369
|
-
forbiddenPaths: forbidden,
|
|
4370
|
-
outputContract: "Run-owned hash-bound frontend-test-result-v1 derived only from the manifest and validated case evidence.",
|
|
4371
|
-
subtask_prompt: "Materialize the authoritative frontend-test-result-v1. Do not use Pi prose or retrospective output as input.",
|
|
4372
|
-
shell: {
|
|
4373
|
-
commands: [],
|
|
4374
|
-
jsonArtifactGate: {
|
|
4375
|
-
fromNodeId: "validate-frontend-case-evidence-shell",
|
|
4376
|
-
schemaId: "frontend-test-result-v1",
|
|
4377
|
-
artifactName: "frontend-test-result.json",
|
|
4378
|
-
outputDir: "contracts",
|
|
4379
|
-
},
|
|
4477
|
+
frontendTestResultFinalize: {},
|
|
4380
4478
|
cwd: ".",
|
|
4381
4479
|
timeoutMs: 120000,
|
|
4382
4480
|
},
|
|
4383
4481
|
});
|
|
4384
4482
|
tasks.push({
|
|
4385
|
-
id: "frontend-test-
|
|
4386
|
-
depends_on: ["
|
|
4483
|
+
id: "frontend-test-reports-shell",
|
|
4484
|
+
depends_on: ["finalize-frontend-test-result-shell"],
|
|
4387
4485
|
role: "verifier",
|
|
4388
4486
|
executor: "shell",
|
|
4389
4487
|
complexity: "LOW",
|
|
@@ -4391,11 +4489,11 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4391
4489
|
writeSet: ["testcase/frontend/reports/**"],
|
|
4392
4490
|
allowedPaths: ["testcase/frontend/**"],
|
|
4393
4491
|
forbiddenPaths: forbidden,
|
|
4394
|
-
outputContract: "Deterministic
|
|
4395
|
-
subtask_prompt: "Render
|
|
4492
|
+
outputContract: "Deterministic L-5 (optional) plus main frontend-test-report.md/html from frontend-test-result-v1. Pipeline acceptance = result-v1 + main HTML.",
|
|
4493
|
+
subtask_prompt: "Render operational reports from frontend-test-result-v1 only. Do not invent coverage. Main HTML is required; L-5 follows frontendTest.reports.l5.",
|
|
4396
4494
|
shell: {
|
|
4397
|
-
frontendTestL5Report: {},
|
|
4398
4495
|
commands: [],
|
|
4496
|
+
frontendTestReports: { l5: enableL5Report },
|
|
4399
4497
|
cwd: ".",
|
|
4400
4498
|
timeoutMs: 120000,
|
|
4401
4499
|
},
|
|
@@ -4404,8 +4502,8 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4404
4502
|
tasks.push({
|
|
4405
4503
|
id: "frontend-test-result-outcome-gate-shell",
|
|
4406
4504
|
depends_on: [
|
|
4407
|
-
"
|
|
4408
|
-
"frontend-test-
|
|
4505
|
+
"finalize-frontend-test-result-shell",
|
|
4506
|
+
"frontend-test-reports-shell",
|
|
4409
4507
|
],
|
|
4410
4508
|
role: "verifier",
|
|
4411
4509
|
executor: "shell",
|
|
@@ -4413,8 +4511,8 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4413
4511
|
writePolicy: "read-only",
|
|
4414
4512
|
allowedPaths: [],
|
|
4415
4513
|
forbiddenPaths: forbidden,
|
|
4416
|
-
outputContract: "Optional quality gate: pass only when frontend-test-result-v1 is outcome=passed and integrationMode=real with 0 failed/blocked and no missing AC. Does not gate
|
|
4417
|
-
subtask_prompt: "Opt-in Delivery/Worker quality gate (frontendTest.strictOutcomeGate=true).
|
|
4514
|
+
outputContract: "Optional quality gate: pass only when frontend-test-result-v1 is outcome=passed and integrationMode=real with 0 failed/blocked and no missing AC. Does not gate reports closeout.",
|
|
4515
|
+
subtask_prompt: "Opt-in Delivery/Worker quality gate (frontendTest.strictOutcomeGate=true). Main reports do not depend on this node.",
|
|
4418
4516
|
shell: {
|
|
4419
4517
|
commands: [frontendTestOutcomeGate],
|
|
4420
4518
|
cwd: ".",
|
|
@@ -4422,49 +4520,30 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4422
4520
|
},
|
|
4423
4521
|
});
|
|
4424
4522
|
}
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
"
|
|
4429
|
-
"
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
}
|
|
4442
|
-
tasks.push({
|
|
4443
|
-
id: "frontend-test-html-report-shell",
|
|
4444
|
-
depends_on: ["frontend-test-retrospect-pi"],
|
|
4445
|
-
role: "verifier",
|
|
4446
|
-
executor: "shell",
|
|
4447
|
-
complexity: "LOW",
|
|
4448
|
-
writePolicy: "exclusive",
|
|
4449
|
-
writeSet: ["testcase/frontend/reports/**"],
|
|
4450
|
-
allowedPaths: ["testcase/frontend/**"],
|
|
4451
|
-
forbiddenPaths: forbidden,
|
|
4452
|
-
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.",
|
|
4453
|
-
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.",
|
|
4454
|
-
shell: {
|
|
4455
|
-
commands: [],
|
|
4456
|
-
frontendTestHtmlReport: {},
|
|
4457
|
-
cwd: ".",
|
|
4458
|
-
timeoutMs: 120000,
|
|
4459
|
-
},
|
|
4460
|
-
});
|
|
4523
|
+
if (enableRetrospect) {
|
|
4524
|
+
tasks.push({
|
|
4525
|
+
id: "frontend-test-retrospect-pi",
|
|
4526
|
+
depends_on: ["finalize-frontend-test-result-shell", "frontend-test-reports-shell"],
|
|
4527
|
+
role: "closeout",
|
|
4528
|
+
executor: "pi",
|
|
4529
|
+
toolProfile: "write",
|
|
4530
|
+
complexity: "MED",
|
|
4531
|
+
writePolicy: "exclusive",
|
|
4532
|
+
writeGuardPolicy: "tools-only",
|
|
4533
|
+
writeSet: ["testcase/frontend/reports/**"],
|
|
4534
|
+
allowedPaths: ["testcase/frontend/**"],
|
|
4535
|
+
forbiddenPaths: forbidden,
|
|
4536
|
+
outputContract: "Optional retrospective under testcase/frontend/reports/frontend-test-retrospect-<date>.md. Not required for pipeline acceptance.",
|
|
4537
|
+
subtask_prompt: "Write optional frontend-test retrospective after result + reports. Do not recompute L-5. Pipeline success does not require this file.",
|
|
4538
|
+
});
|
|
4539
|
+
}
|
|
4461
4540
|
const globalConstraints = [
|
|
4462
4541
|
...sources.taskConfig.hardConstraints,
|
|
4463
4542
|
...STANDARD_GLOBAL_CONSTRAINTS,
|
|
4464
4543
|
"frontend-test-dag generates Markdown cases and browser evidence only; it must not generate pytest or Playwright test source code.",
|
|
4465
4544
|
"Each browser case runs serially in a fresh Pi execution boundary. Persist its evidence before starting the next case.",
|
|
4466
4545
|
"Use only the declared isolated test environment. Production URLs, real credentials, and unauthorized data are blocked.",
|
|
4467
|
-
`Browser startup for generated cases must be playwright-cli open --browser=chrome --
|
|
4546
|
+
`Browser startup for generated cases must be playwright-cli open --browser=chrome --headless ${controllerFrontend.baseUrl}; this origin is frozen by the controller from ${controllerFrontend.baseUrlSource}, and model-authored files cannot establish or override it; generated operations stay in the default browser session and must not use unverified named-session flags.`,
|
|
4468
4547
|
"Browser-tool preflight runs before any frontend-test Pi node; cli-only rollback, missing/incompatible Pi SDK custom-tool capability, missing verified playwright-cli launcher, or incompatible --help fails with zero Pi calls.",
|
|
4469
4548
|
"Browser case children use commandPolicy capability-allowlist playwright-cli and structured playwright_cli tool; ordinary writers remain without Bash.",
|
|
4470
4549
|
"Passed cases require same-child ordered controller receipts: successful open → successful find → successful post-execution cleanup. Pre-start cleanup, snapshot/goto/screenshot/request/console and ordinary interactions are insufficient; missing or unordered receipts convert to blocked (browser-command-evidence-missing).",
|
|
@@ -4472,7 +4551,7 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
4472
4551
|
"Environment preflight must curl-probe the frozen non-production baseUrl before generate; unreachable or curl-unavailable ends preflight as blocked (frontend-base-url-unreachable|curl-unavailable) so generate/map do not run.",
|
|
4473
4552
|
"U/D cases must prove current-user data ownership or create cleanable current-user data or authorized Mock; otherwise blocked (current-user-data-unavailable|data-ownership-unverifiable|safe-test-data-setup-unavailable) without cross-user mutation.",
|
|
4474
4553
|
"Token settings are post-case stop thresholds, never a hard provider token cap. Unstarted cases after a threshold are blocked: token-budget-exhausted.",
|
|
4475
|
-
"Pipeline acceptance for frontend-test is the
|
|
4554
|
+
"Pipeline acceptance for frontend-test is frontend-test-result-v1 plus the main report under testcase/frontend/reports/frontend-test-report.html; case pass rate and outcome=passed are quality signals; retrospect is opt-in (frontendTest.reports.retrospect).",
|
|
4476
4555
|
blockingReview
|
|
4477
4556
|
? "frontendTest.reviewMode=blocking: a frontend case review must emit VERDICT: pass before checklist/manifest materialization; request-revision blocks browser execution."
|
|
4478
4557
|
: "frontendTest.reviewMode is off|advisory by default: mechanical checklist-shell gates materialize/execute; LLM review is not a hard browser gate.",
|