@tea-agent/loop-agent 0.20.1-beta.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/dist/application/dag/args.js +29 -0
- package/dist/application/dag/run-dag.js +3 -1
- package/dist/cli/command-definitions.js +15 -1
- package/dist/cli/program.js +11 -1
- package/dist/commands/dag-rerun-task.js +19 -0
- package/dist/commands/dag-rerun.js +111 -0
- package/dist/commands/init.js +7 -0
- package/dist/executors/dag-pi-executor.js +24 -0
- package/dist/executors/pi-executor.js +111 -36
- package/dist/executors/pi-sdk-executor.js +105 -29
- package/dist/executors/shell-executor.js +54 -11
- package/dist/shared/operator/capabilities.js +54 -0
- package/dist/worker/console/index.js +1 -1
- package/dist/worker/console/inspect-split.js +82 -0
- package/dist/worker/console/operation-runner.js +3 -1
- package/dist/worker/console/operation-store.js +1 -0
- package/dist/worker/console/operator-actions.js +153 -2
- package/dist/worker/console/operator-user-error.js +10 -0
- package/dist/worker/console/pi-readiness.js +4 -0
- package/dist/worker/console/recovery-cta.js +116 -5
- package/dist/worker/console/recovery-selection.js +107 -0
- package/dist/worker/console/resolve-dag-run-for-task.js +50 -11
- package/dist/worker/console/routes.js +20 -0
- package/dist/worker/console/sibling-controller.js +12 -7
- package/dist/worker/console/static/assets/index-CUDke82y.js +18 -0
- package/dist/worker/console/static/assets/index-wSEksVSO.css +1 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/loop-agent/loop-agent-client.js +43 -9
- package/dist/worker/observability/read-model.js +67 -1
- package/dist/worker/observe/static/constants.js +5 -0
- package/dist/worker/observe/static/format-pool.js +22 -3
- package/dist/worker/observe/static/index.html +1 -1
- package/dist/worker/observe/static/styles.css +32 -3
- package/dist/worker/observe/static/views/dag-inspector.js +2 -2
- package/dist/worker/run-task/run-task.js +23 -6
- package/dist/workflows/dag/backend-test-markdown-workflow.js +291 -97
- package/dist/workflows/dag/backend-test-result-contract.js +10 -4
- package/dist/workflows/dag/frontend-test-result-contract.js +64 -0
- package/dist/workflows/dag/init-hybrid.js +117 -64
- package/dist/workflows/dag/lifecycle.js +60 -4
- package/dist/workflows/dag/liveness-policy.js +250 -0
- package/dist/workflows/dag/node-execution.js +89 -6
- package/dist/workflows/dag/output-protocol.js +76 -0
- package/dist/workflows/dag/rerun-plan.js +611 -0
- package/dist/workflows/dag/rerun-run.js +497 -0
- package/dist/workflows/dag/rerun-task.js +284 -0
- package/dist/workflows/dag/retry-policy.js +20 -1
- package/dist/workflows/dag/runner.js +71 -1
- package/dist/workflows/dag/skill-snapshot.js +22 -3
- package/dist/workflows/dag/types.js +12 -0
- package/dist/workflows/dag/validate.js +11 -0
- package/dist/workflows/dag/workspace-checkpoint.js +163 -0
- package/docs/README.md +5 -5
- package/docs/architecture/dag-execution.md +11 -0
- package/docs/architecture/facts-and-state.md +1 -0
- package/docs/architecture/worker-and-feature.md +10 -0
- package/docs/templates/agent-dag.schema.json +17 -2
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +5 -2
- package/docs/templates/backend-test-dag.json +15 -15
- package/docs/templates/frontend-test-case-checklist.md +16 -1
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +26 -2
- package/docs/templates/frontend-test-dag.json +65 -6
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +4 -1
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/loop-agent/references/command-reference.md +5 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/skills/playwright-cli-case-generator/SKILL.md +35 -7
- package/dist/worker/console/static/assets/index-3vsjZJHq.js +0 -16
- package/dist/worker/console/static/assets/index-i1wV4LrY.css +0 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { lstat, readFile, realpath } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
6
|
+
/**
|
|
7
|
+
* Workspace checkpoint for DAG continuation safety.
|
|
8
|
+
* Proves current repo content matches parent terminal state before importing facts.
|
|
9
|
+
*/
|
|
10
|
+
export const WORKSPACE_CHECKPOINT_START_REL = ".runtime/workspace-checkpoint-start.json";
|
|
11
|
+
export const WORKSPACE_CHECKPOINT_TERMINAL_REL = ".runtime/workspace-checkpoint-terminal.json";
|
|
12
|
+
function sha256Hex(input) {
|
|
13
|
+
return createHash("sha256").update(input).digest("hex");
|
|
14
|
+
}
|
|
15
|
+
async function runGit(cwd, args) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const child = spawn("git", args, {
|
|
18
|
+
cwd,
|
|
19
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
20
|
+
});
|
|
21
|
+
let stdout = "";
|
|
22
|
+
let stderr = "";
|
|
23
|
+
child.stdout.setEncoding("utf-8");
|
|
24
|
+
child.stderr.setEncoding("utf-8");
|
|
25
|
+
child.stdout.on("data", (chunk) => {
|
|
26
|
+
stdout += chunk;
|
|
27
|
+
});
|
|
28
|
+
child.stderr.on("data", (chunk) => {
|
|
29
|
+
stderr += chunk;
|
|
30
|
+
});
|
|
31
|
+
child.on("error", reject);
|
|
32
|
+
child.on("close", (code) => {
|
|
33
|
+
if (code === 0) {
|
|
34
|
+
resolve(stdout);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
reject(new Error(`git ${args.join(" ")} failed: ${stderr.trim() || stdout.trim()}`));
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function normalizeGitPath(value) {
|
|
42
|
+
const target = value.includes(" -> ")
|
|
43
|
+
? (value.split(" -> ").at(-1) ?? value)
|
|
44
|
+
: value;
|
|
45
|
+
return target.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
46
|
+
}
|
|
47
|
+
function classifyPorcelainState(x, y) {
|
|
48
|
+
if (x === "?" || y === "?")
|
|
49
|
+
return "untracked";
|
|
50
|
+
if (x === "R" || y === "R")
|
|
51
|
+
return "renamed";
|
|
52
|
+
if (x === "D" || y === "D")
|
|
53
|
+
return "deleted";
|
|
54
|
+
if (x === "A" || y === "A")
|
|
55
|
+
return "added";
|
|
56
|
+
if (x === "M" || y === "M")
|
|
57
|
+
return "modified";
|
|
58
|
+
return "modified";
|
|
59
|
+
}
|
|
60
|
+
async function hashWorkspacePath(repoRoot, relPath, state) {
|
|
61
|
+
if (state === "deleted") {
|
|
62
|
+
return { contentSha256: sha256Hex(`deleted:${relPath}`) };
|
|
63
|
+
}
|
|
64
|
+
const abs = path.join(repoRoot, relPath);
|
|
65
|
+
try {
|
|
66
|
+
const st = await lstat(abs);
|
|
67
|
+
const mode = (st.mode & 0o777777).toString(8);
|
|
68
|
+
if (st.isSymbolicLink()) {
|
|
69
|
+
const target = await realpath(abs).catch(async () => {
|
|
70
|
+
const { readlink } = await import("node:fs/promises");
|
|
71
|
+
return readlink(abs);
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
mode,
|
|
75
|
+
contentSha256: sha256Hex(`symlink:${relPath}->${target}`),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (st.isDirectory()) {
|
|
79
|
+
// Submodule or directory entry — fingerprint via git ls-files / HEAD if possible.
|
|
80
|
+
return {
|
|
81
|
+
mode,
|
|
82
|
+
contentSha256: sha256Hex(`dir:${relPath}`),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const bytes = await readFile(abs);
|
|
86
|
+
return { mode, contentSha256: sha256Hex(bytes) };
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return { contentSha256: sha256Hex(`missing:${relPath}`) };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function computeFingerprint(input) {
|
|
93
|
+
const payload = JSON.stringify({
|
|
94
|
+
gitHeadSha: input.gitHeadSha,
|
|
95
|
+
statusPorcelainSha256: input.statusPorcelainSha256,
|
|
96
|
+
changedPaths: input.changedPaths,
|
|
97
|
+
});
|
|
98
|
+
return sha256Hex(payload);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Capture a workspace checkpoint for the current working tree.
|
|
102
|
+
*/
|
|
103
|
+
export async function captureWorkspaceCheckpoint(repoRoot, options) {
|
|
104
|
+
const capturedAt = (options?.now ?? new Date()).toISOString();
|
|
105
|
+
const gitHeadSha = (await runGit(repoRoot, ["rev-parse", "HEAD"])).trim();
|
|
106
|
+
const porcelain = await runGit(repoRoot, [
|
|
107
|
+
"status",
|
|
108
|
+
"--porcelain=v1",
|
|
109
|
+
"--untracked-files=all",
|
|
110
|
+
]);
|
|
111
|
+
const statusPorcelainSha256 = sha256Hex(porcelain);
|
|
112
|
+
const changedPaths = [];
|
|
113
|
+
for (const line of porcelain.split("\n")) {
|
|
114
|
+
const trimmed = line.trimEnd();
|
|
115
|
+
if (!trimmed)
|
|
116
|
+
continue;
|
|
117
|
+
const x = trimmed[0] ?? " ";
|
|
118
|
+
const y = trimmed[1] ?? " ";
|
|
119
|
+
const rawPath = trimmed.slice(3);
|
|
120
|
+
const relPath = normalizeGitPath(rawPath);
|
|
121
|
+
const state = classifyPorcelainState(x, y);
|
|
122
|
+
const hashed = await hashWorkspacePath(repoRoot, relPath, state);
|
|
123
|
+
changedPaths.push({
|
|
124
|
+
path: relPath,
|
|
125
|
+
state,
|
|
126
|
+
...hashed,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
changedPaths.sort((a, b) => a.path.localeCompare(b.path));
|
|
130
|
+
const fingerprint = computeFingerprint({
|
|
131
|
+
gitHeadSha,
|
|
132
|
+
statusPorcelainSha256,
|
|
133
|
+
changedPaths,
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
schemaVersion: 1,
|
|
137
|
+
capturedAt,
|
|
138
|
+
gitHeadSha,
|
|
139
|
+
statusPorcelainSha256,
|
|
140
|
+
changedPaths,
|
|
141
|
+
fingerprint,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export async function writeWorkspaceCheckpoint(runDir, relPath, checkpoint) {
|
|
145
|
+
await writeJsonAtomic(path.join(runDir, relPath), checkpoint);
|
|
146
|
+
}
|
|
147
|
+
export async function readWorkspaceCheckpoint(runDir, relPath) {
|
|
148
|
+
try {
|
|
149
|
+
const raw = JSON.parse(await readFile(path.join(runDir, relPath), "utf8"));
|
|
150
|
+
if (raw?.schemaVersion !== 1 || typeof raw.fingerprint !== "string") {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
return raw;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export function workspaceFingerprintsMatch(left, right) {
|
|
160
|
+
if (!left?.fingerprint || !right?.fingerprint)
|
|
161
|
+
return false;
|
|
162
|
+
return left.fingerprint === right.fingerprint;
|
|
163
|
+
}
|
package/docs/README.md
CHANGED
|
@@ -52,16 +52,14 @@
|
|
|
52
52
|
完整索引与归档策略见 `design/README.md`。下列为当前高频活入口:
|
|
53
53
|
|
|
54
54
|
- `design/backend-test-workflow.md` — backend-test Markdown-first 8 节点流程、单次 pytest、run-owned artifacts 与 L-5
|
|
55
|
-
- `design/frontend-
|
|
56
|
-
- `design/frontend-implementation-workflow.md` — 前端实现 / 评审 / 验证工作流
|
|
55
|
+
- `design/frontend-implementation-workflow.md` — 前端实现 / 评审 / 验证工作流(含 Mock/API 策略、capability seed、verification bundle)
|
|
57
56
|
- `design/dag-source-binding-and-recovery.md` — 新生成 DAG 的任务源绑定与中断恢复
|
|
58
57
|
- `design/agent-worker-fullstack-workflow-integration.md` — workflow routing、Task Outcome、artifact-aware Ready、`fullstack-v1` 与 Verification Bundle
|
|
59
58
|
- `design/fullstack-end-to-end-delivery-optimization-roadmap.md` — 全栈端到端优化收敛路线图(release train / Delivery / Final Verification)
|
|
60
|
-
- `design/
|
|
61
|
-
- `design/local-operator-console-from-pi-web.md` — Operator Console 设计输入;MVP 已随 `0.17.0`–`0.17.2` 发布;统一 surface 见上条
|
|
59
|
+
- `design/local-operator-console-from-pi-web.md` — Operator Console 设计输入;MVP 已随 `0.17.0`–`0.17.2` 发布;统一 surface 设计已归档(见下)
|
|
62
60
|
- `design/taskspec-to-loop-agent-mapping.md` — TaskSpec → loop-agent task 兼容契约(文档镜像;runtime 真源在代码)
|
|
63
61
|
|
|
64
|
-
已实现且仅作历史说明的设计见 `design/archive/`(例如 `design/archive/2026-07-14-loop-agent-self-update-notifier.md`)。
|
|
62
|
+
已实现且仅作历史说明的设计见 `design/archive/`(例如 `design/archive/2026-07-14-loop-agent-self-update-notifier.md`、`design/archive/2026-07-22-console-observe-unified-operator-surface.md`、`design/archive/2026-07-23-operator-task-rerun-and-node-retry.md`、`design/archive/frontend-mock-data-workflow.md`)。
|
|
65
63
|
|
|
66
64
|
## 进行中 / 近期完成
|
|
67
65
|
|
|
@@ -75,6 +73,8 @@
|
|
|
75
73
|
|
|
76
74
|
完整 completed 列表与主题速览见 `exec-plans/completed/README.md`。近期高频归档:
|
|
77
75
|
|
|
76
|
+
- `exec-plans/completed/2026-07-24-backend-test-request-response-logs-report-format.md` — pytest 请求/响应脱敏日志与第 7/8 节点报告版式;真实 `my-webapp` Campaign R04 8/8、17 passed
|
|
77
|
+
- `exec-plans/completed/2026-07-23-backend-test-advisory-gates.md` — 第 4/6 节点改为非阻断 advisory:用例只强制前置条件/步骤/预期,第 6 节点只扫描 Markdown 映射脚本;真实 `my-webapp` Campaign R02 8/8、10 passed
|
|
78
78
|
- `exec-plans/completed/2026-07-23-backend-test-human-readable-artifacts.md` — 中文 README/用例卡片、class-based pytest traceability 与逐条 self-contained HTML 报告;真实 `my-webapp` Campaign Round 04 8/8、16 passed
|
|
79
79
|
- `exec-plans/completed/2026-07-22-backend-markdown-gate-fix.md` — backend-test Markdown gate、traceability、中文生成与真实 Campaign 收口
|
|
80
80
|
- `exec-plans/completed/2026-07-22-backend-test-markdown-first-8-node.md` — backend-test Markdown-first 8 节点
|
|
@@ -101,6 +101,17 @@ snapshot 与 controller identity 是两个不同冻结层,详见 `runtime-boun
|
|
|
101
101
|
|
|
102
102
|
decision envelope 中的 **model verdict**(`decision` / `riskLevel` 等解析自文本)是 `advisoryOnly: true` 派生视图,**不**是完成权威(`facts-and-state.md`)。
|
|
103
103
|
|
|
104
|
+
## 自适应 liveness(节点活动 vs runner lease)
|
|
105
|
+
|
|
106
|
+
实现见 `src/workflows/dag/liveness-policy.ts` 与设计文档 `docs/design/dag-adaptive-liveness-and-supervision.md`。
|
|
107
|
+
|
|
108
|
+
- **runner lease**(`runner.heartbeatAt`,约 15s)只证明 runner 进程事件循环;**不是** Pi meaningful progress。
|
|
109
|
+
- **真实活动**:SDK provider/tool event 与 CLI stdout|stderr 刷新 provider/tool/output;attempt-fenced,旧 attempt 晚到事件 no-op。SDK noisy delta 只续租 transport watchdog,不刷新 meaningful progress。
|
|
110
|
+
- **节点投影** `livenessStatus`:`active | quiet | suspected-stall | probing | needs-attention`。
|
|
111
|
+
- **absolute max** 默认 4h wall clock(不可被空心跳续期);历史 30 分钟不再是活跃 Pi 节点硬杀边界。
|
|
112
|
+
- `assessDagRunLiveness` 在 lease 新鲜但无 meaningful activity 时返回 `node-quiet` / `suspected-stall` / `needs-attention`;doctor codes 含 `node-activity-quiet`、`node-activity-suspected-stall`、`node-needs-attention`。
|
|
113
|
+
- Observe/read-model 仅为 derived/advisory。
|
|
114
|
+
|
|
104
115
|
## 生命周期:active / paused / completed
|
|
105
116
|
|
|
106
117
|
`src/workflows/dag/lifecycle.ts` 定义三个目录:
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
- **Task Pool 是 Worker 专用可选根**:只有使用 `agent-worker` 产品线时才存在;唯一根 `.harness/task-pool/`。
|
|
24
24
|
- **Task Pool state identity(ADR 0004)**:canonical 键为 `{ featureId, taskId }`(`TaskPoolTaskRef`),不是裸 `taskId`。
|
|
25
25
|
- **Observe snapshot 不是事实源**:`buildGlobalSnapshot` 投影失败返回安全错误摘要而非全零健康,不改变执行成败。
|
|
26
|
+
- **节点 liveness 投影是保守 canonical 字段**:`lastProviderActivityAt` / `lastToolActivityAt` / `lastOutputActivityAt` / `lastMeaningfulProgressAt` / `livenessStatus` 由 runner/executor 写入 active run facts;runner lease heartbeat **不得**刷新 meaningful progress。Observe badge(quiet/suspected-stall/needs-attention/needs-reconcile)仍是 derived。
|
|
26
27
|
|
|
27
28
|
### Task Pool state 布局与迁移
|
|
28
29
|
|
|
@@ -17,6 +17,16 @@
|
|
|
17
17
|
|
|
18
18
|
`shell: false` + 绝对 launch spec(见下)意味着 Worker 不走 PATH 重新解析,也不在当前进程内加载 CLI command 实现。
|
|
19
19
|
|
|
20
|
+
### `run-dag` kernel-owned supervision vs explicit hard timeout
|
|
21
|
+
|
|
22
|
+
`run-task` 启动 `run-dag` 时默认把外层 `timeoutMs` 设为 `0`,由 DAG kernel 负责节点 liveness,**不再**无条件把 30 分钟当作 hard kill:
|
|
23
|
+
|
|
24
|
+
- 省略 `worker.timeout_ms` → Worker 不设置 wall-clock deadline,并等待 `run-dag` 产生可确认终态。
|
|
25
|
+
- 显式 `worker.timeout_ms` → hard kill(可 capped),观察到 exit 后可报 `timedOut`。
|
|
26
|
+
- 客户端 `onHeartbeat` 仍是监管心跳,不等于 Pi meaningful progress。
|
|
27
|
+
|
|
28
|
+
设计真源:`docs/design/dag-adaptive-liveness-and-supervision.md`。
|
|
29
|
+
|
|
20
30
|
## controller identity(冻结的已发布 controller)
|
|
21
31
|
|
|
22
32
|
每次写入型 Feature/batch/Task/final verification 在任何目标仓库或 Task Pool 状态写入前,`LoopAgentClient` 解析并冻结 schemaVersion 1 identity(`ControllerIdentityV1`,定义在 `src/shared/package-metadata.ts`):
|
|
@@ -402,10 +402,10 @@
|
|
|
402
402
|
"retryCategories": {
|
|
403
403
|
"type": "array",
|
|
404
404
|
"items": {
|
|
405
|
-
"enum": ["timeout", "network", "rate-limit", "unavailable", "output-too-large"]
|
|
405
|
+
"enum": ["timeout", "network", "rate-limit", "unavailable", "output-too-large", "protocol-invalid"]
|
|
406
406
|
},
|
|
407
407
|
"default": ["timeout", "network", "rate-limit", "unavailable"],
|
|
408
|
-
"description": "Failure categories eligible for retry. quota is never eligible. output-too-large is reserved for explicit structured-required nodes
|
|
408
|
+
"description": "Failure categories eligible for retry. quota is never eligible. output-too-large is reserved for explicit structured-required nodes. protocol-invalid is for nodes that declare outputProtocol with retryOnInvalid."
|
|
409
409
|
}
|
|
410
410
|
}
|
|
411
411
|
},
|
|
@@ -496,6 +496,21 @@
|
|
|
496
496
|
"minLength": 1,
|
|
497
497
|
"description": "Optional protocol prefix whose first matching Pi assistant-output line is promoted to the canonical first line. Missing matches are not synthesized."
|
|
498
498
|
},
|
|
499
|
+
"outputProtocol": {
|
|
500
|
+
"type": "object",
|
|
501
|
+
"additionalProperties": false,
|
|
502
|
+
"required": ["type", "validLines"],
|
|
503
|
+
"description": "Machine-readable output protocol for safe read-only Pi nodes. Phase 1 supports first-line-enum only (e.g. VERDICT lines). Invalid output may auto-retry when retryOnInvalid is true.",
|
|
504
|
+
"properties": {
|
|
505
|
+
"type": { "const": "first-line-enum" },
|
|
506
|
+
"validLines": {
|
|
507
|
+
"type": "array",
|
|
508
|
+
"items": { "type": "string", "minLength": 1 },
|
|
509
|
+
"minItems": 1
|
|
510
|
+
},
|
|
511
|
+
"retryOnInvalid": { "type": "boolean", "default": true }
|
|
512
|
+
}
|
|
513
|
+
},
|
|
499
514
|
"allowedPaths": {
|
|
500
515
|
"type": "array",
|
|
501
516
|
"items": { "type": "string", "minLength": 1 },
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
供 `generate-backend-pytest-pi` 使用。该节点是 `executor: "pi"`、`role: "implementer"`、`toolProfile: "write"` 的受限 writer
|
|
5
|
+
供 `generate-backend-pytest-pi` 使用。该节点是 `executor: "pi"`、`role: "implementer"`、`toolProfile: "write"` 的受限 writer,只把经过独立 Review 和 advisory Markdown 校验的最终用例转换为 pytest 资产;第 4 节点报告为 FAIL 时仍继续,但不得据此发明缺失行为。
|
|
6
6
|
|
|
7
7
|
## 当前合同
|
|
8
8
|
|
|
@@ -19,7 +19,10 @@ class TestOrderApi:
|
|
|
19
19
|
...
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
- 断言只来自 `### 预期结果` / `### Expected Results`;setup
|
|
22
|
+
- 断言只来自 `### 预期结果` / `### Expected Results`;setup 只来自必选 `### 前置条件`,以及存在时的 `### 测试数据`、`### 自动化映射` 或对应历史英文分节。
|
|
23
|
+
- 每条可自动化 Case 应在 `自动化映射` / `Automation Notes` 明确写出目标 pytest 脚本;第 6 节点只扫描这些脚本,不递归扫描无关历史 `test_*.py`。
|
|
24
|
+
- 每次接口请求必须通过统一日志 helper 或等价 client wrapper 打印请求与响应诊断信息:请求日志至少包含 HTTP method、URL/path、query 与 JSON/body/payload 参数摘要;响应日志至少包含 status code 与 JSON/text/body 结果摘要。日志必须能出现在 pytest stdout/stderr,不能改变断言或把失败伪装成通过。
|
|
25
|
+
- 日志输出前必须递归脱敏 `authorization`、`proxy-authorization`、`cookie`、`set-cookie`、`token`、`password`、`secret`、`api key`、`credential` 等 key/header;禁止打印完整 Authorization/Cookie。序列化后的 request/response body 必须有明确长度上限和截断标识,避免大对象淹没 pytest/JUnit/报告证据。
|
|
23
26
|
- 禁止 `skip` / `xfail`、吞断言、宽异常静默通过、mock 替代真实目标、删除用例或弱化断言。
|
|
24
27
|
- best-effort 清理只能捕获所选 HTTP client 实际抛出的窄 transport exception,例如 `requests.RequestException` 或 `urllib.error.URLError`;禁止 `except:`、`except Exception`、`except BaseException` 后 `pass`。
|
|
25
28
|
- 同一 Case ID 可以由多个 pytest 函数覆盖;额外映射会进入 traceability 报告,但不能伪造未在 Markdown 中定义的业务场景。
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"Root artifacts/ is reserved for explicit exclusive write nodes, not read-only scout/reviewer output",
|
|
29
29
|
"exclusive implementer nodes must use narrow, concrete writeSet paths; never keep ** or repo root",
|
|
30
30
|
"Replace REPLACE/WITH/NARROW/IMPLEMENT/PATHS/** with concrete paths before executing the implementation writer",
|
|
31
|
-
"backend-test-dag uses exactly 8 real top-level tasks and executes pytest exactly once.",
|
|
31
|
+
"backend-test-dag uses exactly 8 real top-level tasks and executes pytest exactly once over only the safe scripts explicitly mapped by final Markdown cases.",
|
|
32
32
|
"Model nodes produce Markdown and pytest assets, never backend-test business JSON envelopes.",
|
|
33
|
-
"Environment, Markdown validation, traceability, JUnit, HTML and execution facts are deterministic
|
|
33
|
+
"Environment, advisory Markdown validation, advisory traceability, JUnit, HTML and execution facts are deterministic evidence. Nodes 4 and 6 record findings without blocking nodes 5, 7 or 8.",
|
|
34
34
|
"Only Markdown case generation/review may read source facts; pytest generation must not read source/**.",
|
|
35
35
|
"Functional case IDs use BE-<MODULE>-<NNN>; production code/config, skip/xfail, repair and rerun are forbidden."
|
|
36
36
|
],
|
|
@@ -68,8 +68,8 @@
|
|
|
68
68
|
"executorModels": {
|
|
69
69
|
"pi": {
|
|
70
70
|
"LOW": "gpt-5.3-codex-spark",
|
|
71
|
-
"MED": "
|
|
72
|
-
"HIGH": "gpt-5.
|
|
71
|
+
"MED": "glm-5.2",
|
|
72
|
+
"HIGH": "gpt-5.5"
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
"tasks": [
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
"artifacts/**"
|
|
126
126
|
],
|
|
127
127
|
"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.",
|
|
128
|
-
"subtask_prompt": "Read the upstream environment report. Generate a Markdown-first backend test strategy and cases under testcase/md/**.\n\nWrite 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.\n\nCreate 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.\n\nWrite each module as readable case cards. Every case starts with `## BE-<MODULE>-<NNN
|
|
128
|
+
"subtask_prompt": "Read the upstream environment report. Generate a Markdown-first backend test strategy and cases under testcase/md/**.\n\nWrite 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.\n\nCreate 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.\n\nWrite each module as readable case cards. Every case starts with `## BE-<MODULE>-<NNN>|<中文用例名称>`. The only sections required by the deterministic validator are `### 前置条件`, `### 操作步骤`, and `### 预期结果` (legacy English aliases remain accepted). Add `测试目的`, `验收标准`, `需求依据`, `测试数据`, and `自动化映射` when useful for human readability; every automatable case should explicitly name its target pytest script under `自动化映射` so traceability can scan only that script.\n\nPlace steps and their expected results in a compact readable table when that improves clarity; otherwise keep numbered executable steps and numbered/bulleted independently assertable results. Every result must name the observable HTTP status, response field/value, state transition or membership condition, never vague wording such as ‘符合预期’.\n\nIn `自动化映射`, record the planned script path and pytest function name when known. Put implementation-only restrictions in a concise `<details>` block rather than dominating the main case flow. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor 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.\n\nRead only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text."
|
|
129
129
|
},
|
|
130
130
|
{
|
|
131
131
|
"id": "review-and-revise-backend-md-cases-pi",
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
"artifacts/**"
|
|
150
150
|
],
|
|
151
151
|
"outputContract": "Review source fidelity and directly revise only testcase/md/**; return concise Markdown, never JSON.",
|
|
152
|
-
"subtask_prompt": "Independently review generated Markdown cases against
|
|
152
|
+
"subtask_prompt": "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.\n\nCheck AC completeness/meaning, endpoint, fields/shape, status/error codes, rules, states, documented boundaries/auth, positive/negative coverage, executable steps and assertable results. Reject avoidable English prose, duplicated bilingual wording, repeated boilerplate, oversized unstructured sections, vague results such as ‘符合预期’, and missing script/function mapping where it can be derived.\n\nCorrect testcase/md/** directly: add documented omissions, remove unsupported cases, fix mappings/expectations, merge duplicates, improve navigation/tables/Chinese wording, or record gaps in Chinese. Keep Case IDs, AC/REQ/BR IDs, HTTP methods, paths, fields, enum values, filenames, code symbols and source citations exact. The validator accepts Chinese and legacy English section aliases; retain or converge to the Chinese human-readable headings without losing structure.\n\nRead 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.\n\n## Derived task contract: 需求.md\n\n# Backend test\n- AC-001 proof\n\n## Authoritative reference index\n\n[]\n\nFor 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."
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
"id": "validate-backend-md-cases-shell",
|
|
@@ -169,8 +169,8 @@
|
|
|
169
169
|
".harness/dag-runs/**",
|
|
170
170
|
"artifacts/**"
|
|
171
171
|
],
|
|
172
|
-
"outputContract": "Run-owned reports/backend-md-case-validation.md
|
|
173
|
-
"subtask_prompt": "
|
|
172
|
+
"outputContract": "Run-owned reports/backend-md-case-validation.md with PASS/FAIL advisory findings; downstream execution continues.",
|
|
173
|
+
"subtask_prompt": "Record advisory findings for missing/duplicate IDs, missing core sections (preconditions, steps, expected results), AC coverage, executable steps, assertable results or placeholders. Do not validate source-reference existence. Keep quality findings advisory, but fail closed after writing the report when secret-shaped values are detected so downstream pytest/report nodes cannot consume them.",
|
|
174
174
|
"shell": {
|
|
175
175
|
"commands": [],
|
|
176
176
|
"backendTestPipeline": "markdown-cases",
|
|
@@ -203,7 +203,7 @@
|
|
|
203
203
|
"artifacts/**"
|
|
204
204
|
],
|
|
205
205
|
"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; no JSON and no pytest execution.",
|
|
206
|
-
"subtask_prompt": "Convert
|
|
206
|
+
"subtask_prompt": "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.\n\nEnsure every final Markdown Case ID appears in at least one real pytest test function or pytest test class method region, preferably as `test_BE_<MODULE>_<NNN>_<description>` and in that function/method docstring. Module-level functions and class-based pytest methods are both supported. Multiple test functions may cover one Case ID; assertions come only from 预期结果/Expected Results and setup comes only from 前置条件 plus any optional 测试数据/自动化映射 or their legacy English aliases.\n\nGenerate 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.\n\nCompare 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.\n\nBefore 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.\n\nDo 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`."
|
|
207
207
|
},
|
|
208
208
|
{
|
|
209
209
|
"id": "backend-test-traceability-gate-shell",
|
|
@@ -223,8 +223,8 @@
|
|
|
223
223
|
".harness/dag-runs/**",
|
|
224
224
|
"artifacts/**"
|
|
225
225
|
],
|
|
226
|
-
"outputContract": "Run-owned reports/backend-test-traceability.md
|
|
227
|
-
"subtask_prompt": "
|
|
226
|
+
"outputContract": "Run-owned reports/backend-test-traceability.md with PASS/FAIL advisory findings for Markdown Case to mapped pytest script/symbol coverage.",
|
|
227
|
+
"subtask_prompt": "Record advisory findings when a real Markdown case heading has no associated pytest test function or class method in the script explicitly mapped by that Markdown case, or when a mapped HTTP test script lacks request parameters logging, response result logging, recursive redaction or bounded truncation evidence. Accept exact Case IDs in the function/method name or its decorator/body/docstring region. Do not scan unrelated test_*.py files and do not block pytest execution.",
|
|
228
228
|
"shell": {
|
|
229
229
|
"commands": [],
|
|
230
230
|
"backendTestPipeline": "markdown-traceability",
|
|
@@ -250,11 +250,11 @@
|
|
|
250
250
|
".harness/dag-runs/**",
|
|
251
251
|
"artifacts/**"
|
|
252
252
|
],
|
|
253
|
-
"outputContract": "One pytest execution producing valid JUnit, self-contained HTML and reports/backend-test-facts.md; exit 0/1 with valid evidence continues.",
|
|
254
|
-
"subtask_prompt": "
|
|
253
|
+
"outputContract": "One scoped pytest execution over Markdown-mapped scripts producing valid JUnit with per-case captured output, self-contained HTML and reports/backend-test-facts.md; exit 0/1 with valid evidence continues.",
|
|
254
|
+
"subtask_prompt": "Resolve the final Markdown Automation Notes/自动化映射 to a unique, safe set of testcase/**/test_*.py targets and execute only those scripts exactly once. Validate JUnit, then render the primary self-contained Chinese HTML report from the same JUnit plus final Markdown case metadata without rerun. Keep 测试结论, quality status, failure overview, and a polished per-case result card with 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.",
|
|
255
255
|
"shell": {
|
|
256
256
|
"commands": [
|
|
257
|
-
"mkdir -p \"${HARNESS_DAG_RUN_DIR}/reports\";
|
|
257
|
+
"mkdir -p \"${HARNESS_DAG_RUN_DIR}/reports\"; echo \"pytest targets are resolved at runtime from final Markdown 自动化映射\""
|
|
258
258
|
],
|
|
259
259
|
"backendTestPipeline": "markdown-execute-html",
|
|
260
260
|
"cwd": ".",
|
|
@@ -284,7 +284,7 @@
|
|
|
284
284
|
"artifacts/**"
|
|
285
285
|
],
|
|
286
286
|
"outputContract": "Final Markdown report and L-5 conclusion under docs/test-reports/**; no JSON.",
|
|
287
|
-
"subtask_prompt": "Generate the final Markdown report from upstream facts and run-owned environment, case-validation, traceability, JUnit and HTML evidence. Do not emit JSON.\n\nInclude environment, case quality/review, automation mapping, exact pytest facts, failure classification/analysis, risks, regression recommendations, evidence paths/hashes, coverage/stability availability, and L-5 READY/NOT READY.\n\nNever override Shell/JUnit facts. One run cannot prove FlakyTest. Missing coverage/stability is Unavailable. L-5 requires pass=100%, AC=100%, automation>=90%, stability>=95% n>=5, line>=80%, branch>=70%, skipped=0 and no blocking Critical risk.\n\nWrite only under docs/test-reports/**."
|
|
287
|
+
"subtask_prompt": "Generate the final Markdown report from upstream facts and run-owned environment, advisory case-validation, advisory traceability, JUnit and HTML evidence. Do not emit JSON.\n\nUse 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.\n\nAlways state the exact PASS/FAIL status and findings from nodes 4 and 6. Their FAIL status does not block pytest, but it must remain visible as a quality/traceability risk and must never be rewritten as PASS.\n\nInclude environment, case quality/review, automation mapping, exact pytest facts, failure classification/analysis, risks, regression recommendations, evidence paths/hashes, coverage/stability availability, and L-5 READY/NOT READY.\n\nNever override Shell/JUnit facts. One run cannot prove FlakyTest. Missing coverage/stability is Unavailable. L-5 requires pass=100%, AC=100%, automation>=90%, stability>=95% n>=5, line>=80%, branch>=70%, skipped=0 and no blocking Critical risk.\n\nWrite only under docs/test-reports/**."
|
|
288
288
|
}
|
|
289
289
|
],
|
|
290
290
|
"sourceBinding": {
|
|
@@ -9,9 +9,24 @@ LLM review (when `frontendTest.reviewMode=blocking`) must not invent blocking ru
|
|
|
9
9
|
|---|---|
|
|
10
10
|
| `open-prefix` | Each case body includes `playwright-cli open --browser=chrome --headed <absolute-http(s)-url>` |
|
|
11
11
|
| `production-url` | Open URL must not look like a production host |
|
|
12
|
-
| `ac-mapping` | Manifest entry has non-empty `acIds` |
|
|
12
|
+
| `ac-mapping` | Manifest entry has non-empty `acIds` (**acceptance** ids, not case ids) |
|
|
13
|
+
| `ac-id-shape` | Each `acIds[]` entry matches `AC-*` / `AC-FE-*` |
|
|
14
|
+
| `ac-id-is-case` | `acIds` must not contain `FE-*` case ids |
|
|
15
|
+
| `unknown-ac` | When task `sourceBinding.requirementIds` lists ACs, every `acIds` entry must be in that set |
|
|
16
|
+
| `case-id-shape` | `caseId` matches `FE-<FEATURE>-<NNN>-...` (never `AC-FE-*`) |
|
|
17
|
+
| `case-id-is-ac` | Do not use acceptance id as `caseId` / filename |
|
|
18
|
+
| `case-path-mismatch` | `casePath === testcase/frontend/cases/{caseId}.md` |
|
|
13
19
|
| `case-file-missing` | `casePath` exists |
|
|
14
20
|
| `no-test-source` | Case text must not introduce pytest / Playwright test source (`pytest`, `playwright.test`, `@playwright/test`) |
|
|
21
|
+
| `playwright-cli-only` | Case steps may only use repo skill `playwright-cli` declared commands; bare `playwright` / `npx playwright` / `playwright test` / `@playwright/test` / Node Playwright API are forbidden |
|
|
22
|
+
|
|
23
|
+
### ID 对照(避免混用)
|
|
24
|
+
|
|
25
|
+
| 字段 | 正确示例 | 错误示例 |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `caseId` | `FE-LOGIN-001-core` | `AC-FE-001` |
|
|
28
|
+
| `acIds` | `["AC-FE-001"]` | `["FE-LOGIN-001-core"]` |
|
|
29
|
+
| 文件名 | `FE-LOGIN-001-core.md` | `AC-FE-001.md` |
|
|
15
30
|
|
|
16
31
|
## Non-blocking (notes only)
|
|
17
32
|
|
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
# Generate frontend functional cases
|
|
2
2
|
|
|
3
|
-
Use `playwright-cli-case-generator`. Read only `testcase/frontend/rag/context.md`, `coverage-map.md`, and existing `testcase/frontend/cases/`; write only that cases directory. Produce Markdown cases, `index.md`, and schema-version-1 `manifest.json
|
|
3
|
+
Use `playwright-cli-case-generator`. Read only `testcase/frontend/rag/context.md`, `coverage-map.md`, and existing `testcase/frontend/cases/`; write only that cases directory. Produce Markdown cases, `index.md`, and schema-version-1 `manifest.draft.json` (materialize promotes to `manifest.json`). Do not generate pytest or Playwright source code.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## HARD ID contract (frequent failure)
|
|
6
|
+
|
|
7
|
+
| Concept | Field | Shape | Example |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| Test case id | `caseId` + filename | `FE-<FEATURE>-<NNN>-<dimension>` | `FE-LOGIN-001-core` |
|
|
10
|
+
| Acceptance criteria | `acIds[]` | `AC-FE-*` / `AC-*` | `AC-FE-001` |
|
|
11
|
+
|
|
12
|
+
- **Never** set `caseId` to `AC-FE-001` or name the file `AC-FE-001.md`.
|
|
13
|
+
- **Never** put `FE-LOGIN-001-core` into `acIds`.
|
|
14
|
+
- `casePath` must be `testcase/frontend/cases/<caseId>.md`.
|
|
15
|
+
- `evidenceDir` must be `testcase/frontend/evidence/<caseId>/`.
|
|
16
|
+
|
|
17
|
+
Each case is independently executable and includes AC mapping (`acIds`), preconditions, cleanup, UI assertions, and its isolated evidence path. Use dimensions `core`, `boundary`, `flow`, or `backend`. Never guess API fields, constraints, SLA, credentials, or unrecorded data.
|
|
6
18
|
|
|
7
19
|
Every case must use the **resolved** absolute base URL from `testcase/frontend/rag/context.md` (field `baseUrl` / base URL line). Do not leave a `<base-url>` placeholder. Resolution policy (already applied by retrieve-context): prefer `config.md` frontend URL; else default `http://localhost:5173`.
|
|
8
20
|
|
|
@@ -14,6 +26,18 @@ playwright-cli open --browser=chrome --headed <resolved-base-url-from-context.md
|
|
|
14
26
|
|
|
15
27
|
Do not put a session flag before `open`. Every later Playwright CLI command must stay in that same default browser session: do **not** emit `-s=<case-id>`, `-s=...`, or assume an undocumented named-session binding.
|
|
16
28
|
|
|
29
|
+
## playwright-cli-only (hard)
|
|
30
|
+
|
|
31
|
+
Every browser step must use only commands declared by the repo-local `playwright-cli` skill. Forbidden with **no fallback**: bare `playwright`, `npx playwright`, `playwright test`, `@playwright/test`, Node Playwright API, or generating Playwright/Pytest source. If `playwright-cli` is unavailable, the case must require blocked evidence with `blockedReason: playwright-cli-unavailable` and must not open a browser.
|
|
32
|
+
|
|
33
|
+
## U/D data ownership (hard for modify/delete)
|
|
34
|
+
|
|
35
|
+
1. Only mutate data whose ownership is proven by the **current login identity** plus observable UI/API owner fields — never by name, guessed id, or list order alone.
|
|
36
|
+
2. If the current user has no suitable data, create tagged, cleanable data in the current-user context, then modify/delete, then cleanup and verify cleanup.
|
|
37
|
+
3. If safe create is impossible, only task-authorized Mock may construct data, and the case must label it as Mock (not real backend proof).
|
|
38
|
+
4. If ownership is unverifiable and create/Mock are unavailable: require blocked evidence with one of `current-user-data-unavailable`, `data-ownership-unverifiable`, `safe-test-data-setup-unavailable` — never risk cross-user data.
|
|
39
|
+
5. Never touch other users' data, shared fixtures, production data, or non-cleanable data.
|
|
40
|
+
|
|
17
41
|
For every executable sub-scenario, state the fixture/reset operation, UI reset operation, a fresh snapshot before using element references, and the exact evidence write point. If the isolated environment is unavailable, require writing blocked evidence before any browser command; do not open or connect to a browser.
|
|
18
42
|
|
|
19
43
|
Each case must require the executor to persist, even when blocked:
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
"Case children execute serially. Persist each case result, logs and browser evidence before the next child starts.",
|
|
16
16
|
"A token threshold is a post-case stop check, not a model hard token cap; unstarted cases must be recorded as blocked: token-budget-exhausted.",
|
|
17
17
|
"Default pipeline acceptance is the final retrospect report under testcase/frontend/reports/; case full green is optional quality (frontendTest.strictOutcomeGate).",
|
|
18
|
-
"Default frontendTest.reviewMode=off uses mechanical checklist-shell before materialize; set reviewMode=blocking for legacy dual LLM review gate."
|
|
18
|
+
"Default frontendTest.reviewMode=off uses mechanical checklist-shell before materialize; set reviewMode=blocking for legacy dual LLM review gate.",
|
|
19
|
+
"playwright-cli-only: generators and executors may call only skill-declared playwright-cli commands; bare playwright / npx playwright / @playwright/test / Playwright source are forbidden with no native Playwright fallback.",
|
|
20
|
+
"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.",
|
|
21
|
+
"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."
|
|
19
22
|
],
|
|
20
23
|
"tasks": [
|
|
21
24
|
{
|
|
@@ -24,7 +27,7 @@
|
|
|
24
27
|
"executor": "pi",
|
|
25
28
|
"role": "planner",
|
|
26
29
|
"toolProfile": "write",
|
|
27
|
-
"complexity": "
|
|
30
|
+
"complexity": "MED",
|
|
28
31
|
"writePolicy": "exclusive",
|
|
29
32
|
"writeSet": [
|
|
30
33
|
"testcase/frontend/rag/**"
|
|
@@ -37,14 +40,43 @@
|
|
|
37
40
|
".harness/**",
|
|
38
41
|
"artifacts/**"
|
|
39
42
|
],
|
|
40
|
-
"outputContract": "RAG context.md and coverage-map.md.",
|
|
43
|
+
"outputContract": "RAG context.md and coverage-map.md with baseUrl, baseUrlSource, environmentProbe=pending.",
|
|
41
44
|
"subtask_prompt_markdown": "./frontend-test-dag.retrieve-context.prompt.md"
|
|
42
45
|
},
|
|
43
46
|
{
|
|
44
|
-
"id": "
|
|
47
|
+
"id": "materialize-frontend-test-execution-shell",
|
|
45
48
|
"depends_on": [
|
|
46
49
|
"retrieve-frontend-test-context-pi"
|
|
47
50
|
],
|
|
51
|
+
"executor": "shell",
|
|
52
|
+
"role": "verifier",
|
|
53
|
+
"complexity": "LOW",
|
|
54
|
+
"writePolicy": "exclusive",
|
|
55
|
+
"writeSet": [
|
|
56
|
+
"testcase/frontend/rag/**"
|
|
57
|
+
],
|
|
58
|
+
"allowedPaths": [
|
|
59
|
+
"testcase/frontend/rag/**"
|
|
60
|
+
],
|
|
61
|
+
"forbiddenPaths": [
|
|
62
|
+
".harness/**",
|
|
63
|
+
"artifacts/**"
|
|
64
|
+
],
|
|
65
|
+
"outputContract": "Fail-closed environment preflight: absolute non-production baseUrl + curl HTTP reachability; writes environmentProbe facts; unreachable => blockedReason frontend-base-url-unreachable (node ERROR so generate/map do not run).",
|
|
66
|
+
"subtask_prompt": "Parse frozen baseUrl from context.md (config.md preferred, else http://localhost:5173). Reject production / non-http(s). Probe with curl (HEAD then GET fallback; connect/max-time; no auth/cookie). 2xx/3xx => reachable and continue. 4xx/5xx/DNS/timeout/connection refused/TLS => blockedReason frontend-base-url-unreachable. Missing curl => blockedReason curl-unavailable. Do not start the app. Runtime hybrid generator embeds the authoritative probe script.",
|
|
67
|
+
"shell": {
|
|
68
|
+
"commands": [
|
|
69
|
+
"node -e \"console.log('template placeholder: runtime hybrid DAG embeds curl preflight; do not use this static command as source of truth')\""
|
|
70
|
+
],
|
|
71
|
+
"cwd": ".",
|
|
72
|
+
"timeoutMs": 60000
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "generate-frontend-functional-cases-pi",
|
|
77
|
+
"depends_on": [
|
|
78
|
+
"materialize-frontend-test-execution-shell"
|
|
79
|
+
],
|
|
48
80
|
"executor": "pi",
|
|
49
81
|
"role": "implementer",
|
|
50
82
|
"toolProfile": "write",
|
|
@@ -202,7 +234,7 @@
|
|
|
202
234
|
"dependsPolicy": "all-or-condition-skip"
|
|
203
235
|
},
|
|
204
236
|
{
|
|
205
|
-
"id": "
|
|
237
|
+
"id": "frontend-case-checklist-shell",
|
|
206
238
|
"depends_on": [
|
|
207
239
|
"final-frontend-case-review-gate-shell"
|
|
208
240
|
],
|
|
@@ -210,6 +242,33 @@
|
|
|
210
242
|
"role": "verifier",
|
|
211
243
|
"complexity": "LOW",
|
|
212
244
|
"writePolicy": "read-only",
|
|
245
|
+
"allowedPaths": [
|
|
246
|
+
"testcase/frontend/rag/**",
|
|
247
|
+
"testcase/frontend/cases/**"
|
|
248
|
+
],
|
|
249
|
+
"forbiddenPaths": [
|
|
250
|
+
".harness/**",
|
|
251
|
+
"artifacts/**"
|
|
252
|
+
],
|
|
253
|
+
"outputContract": "Mechanical checklist: open-prefix, non-prod absolute URL, acIds, playwright-cli-only, no pytest/playwright test source; emit structured ruleId issues on failure.",
|
|
254
|
+
"subtask_prompt": "Scan generated cases/manifest against the shared blocking checklist. Runtime hybrid embeds the authoritative script.",
|
|
255
|
+
"shell": {
|
|
256
|
+
"commands": [
|
|
257
|
+
"node -e \"console.log('template placeholder: runtime hybrid embeds checklist')\""
|
|
258
|
+
],
|
|
259
|
+
"cwd": ".",
|
|
260
|
+
"timeoutMs": 120000
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"id": "materialize-frontend-case-manifest-shell",
|
|
265
|
+
"depends_on": [
|
|
266
|
+
"frontend-case-checklist-shell"
|
|
267
|
+
],
|
|
268
|
+
"executor": "shell",
|
|
269
|
+
"role": "verifier",
|
|
270
|
+
"complexity": "LOW",
|
|
271
|
+
"writePolicy": "read-only",
|
|
213
272
|
"allowedPaths": [
|
|
214
273
|
"testcase/frontend/cases/**"
|
|
215
274
|
],
|
|
@@ -217,7 +276,7 @@
|
|
|
217
276
|
".harness/**",
|
|
218
277
|
"artifacts/**"
|
|
219
278
|
],
|
|
220
|
-
"outputContract": "Validated frontend manifest payload { cases: [...] };
|
|
279
|
+
"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}.",
|
|
221
280
|
"subtask_prompt": "Validate and materialize the generated frontend case manifest.",
|
|
222
281
|
"shell": {
|
|
223
282
|
"commands": [
|