@tea-agent/loop-agent 0.20.0 → 0.20.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/application/dag/args.js +29 -0
  3. package/dist/application/dag/run-dag.js +3 -1
  4. package/dist/cli/command-definitions.js +15 -1
  5. package/dist/cli/program.js +11 -1
  6. package/dist/commands/dag-rerun-task.js +19 -0
  7. package/dist/commands/dag-rerun.js +111 -0
  8. package/dist/executors/shell-executor.js +1 -0
  9. package/dist/shared/operator/capabilities.js +54 -0
  10. package/dist/worker/console/index.js +1 -1
  11. package/dist/worker/console/inspect-split.js +82 -0
  12. package/dist/worker/console/operation-runner.js +3 -1
  13. package/dist/worker/console/operation-store.js +1 -0
  14. package/dist/worker/console/operator-actions.js +153 -2
  15. package/dist/worker/console/operator-user-error.js +10 -0
  16. package/dist/worker/console/pi-readiness.js +4 -0
  17. package/dist/worker/console/recovery-cta.js +116 -5
  18. package/dist/worker/console/recovery-selection.js +107 -0
  19. package/dist/worker/console/resolve-dag-run-for-task.js +50 -11
  20. package/dist/worker/console/routes.js +20 -0
  21. package/dist/worker/console/sibling-controller.js +12 -7
  22. package/dist/worker/console/static/assets/index-CUDke82y.js +18 -0
  23. package/dist/worker/console/static/assets/index-wSEksVSO.css +1 -0
  24. package/dist/worker/console/static/index.html +2 -2
  25. package/dist/worker/observability/read-model.js +60 -0
  26. package/dist/worker/observe/spec-evidence.js +33 -0
  27. package/dist/worker/observe/static/index.html +1 -1
  28. package/dist/worker/observe/static/views/dag-inspector.js +67 -4
  29. package/dist/worker/run-task/run-task.js +7 -0
  30. package/dist/workflows/dag/frontend-prewrite-gate.js +97 -2
  31. package/dist/workflows/dag/frontend-project-capability.js +6 -2
  32. package/dist/workflows/dag/frontend-test-result-contract.js +64 -0
  33. package/dist/workflows/dag/init-hybrid.js +91 -48
  34. package/dist/workflows/dag/node-execution.js +40 -6
  35. package/dist/workflows/dag/output-protocol.js +76 -0
  36. package/dist/workflows/dag/rerun-plan.js +611 -0
  37. package/dist/workflows/dag/rerun-run.js +497 -0
  38. package/dist/workflows/dag/rerun-task.js +284 -0
  39. package/dist/workflows/dag/retry-policy.js +20 -1
  40. package/dist/workflows/dag/runner.js +50 -0
  41. package/dist/workflows/dag/skill-snapshot.js +22 -3
  42. package/dist/workflows/dag/types.js +10 -0
  43. package/dist/workflows/dag/validate.js +11 -0
  44. package/dist/workflows/dag/workspace-checkpoint.js +163 -0
  45. package/docs/README.md +1 -0
  46. package/docs/templates/agent-dag.schema.json +17 -2
  47. package/docs/templates/frontend-test-case-checklist.md +16 -1
  48. package/docs/templates/frontend-test-dag.generate-cases.prompt.md +26 -2
  49. package/docs/templates/frontend-test-dag.json +65 -6
  50. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +4 -1
  51. package/package.json +1 -1
  52. package/skills/frontend-design-review/SKILL.md +5 -3
  53. package/skills/frontend-design-review/references/review-checklist.md +3 -2
  54. package/skills/frontend-implementation/references/design-spec.md +16 -8
  55. package/skills/frontend-review/SKILL.md +7 -1
  56. package/skills/frontend-review/references/review-findings.md +5 -1
  57. package/skills/frontend-verification/SKILL.md +5 -3
  58. package/skills/frontend-verification/references/verification-checklist.md +3 -2
  59. package/skills/loop-agent/references/command-reference.md +3 -0
  60. package/skills/playwright-cli-case-generator/SKILL.md +35 -7
  61. package/dist/worker/console/static/assets/index-3vsjZJHq.js +0 -16
  62. 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
@@ -58,6 +58,7 @@
58
58
  - `design/agent-worker-fullstack-workflow-integration.md` — workflow routing、Task Outcome、artifact-aware Ready、`fullstack-v1` 与 Verification Bundle
59
59
  - `design/fullstack-end-to-end-delivery-optimization-roadmap.md` — 全栈端到端优化收敛路线图(release train / Delivery / Final Verification)
60
60
  - `design/2026-07-22-console-observe-unified-operator-surface.md` — Observe 深度融合进 Console(Wave 1–3 已实现,目标 0.18.0)
61
+ - `design/2026-07-23-operator-task-rerun-and-node-retry.md` — Operator 自动节点重试、从节点重跑、standalone 完整任务重跑与 Worker Task 重排队(**Wave 1–2 已落地**;Wave 3–4 仍分波;优先解决 LLM/provider 不稳定;Inspect 只读 / Operate mutation)
61
62
  - `design/local-operator-console-from-pi-web.md` — Operator Console 设计输入;MVP 已随 `0.17.0`–`0.17.2` 发布;统一 surface 见上条
62
63
  - `design/taskspec-to-loop-agent-mapping.md` — TaskSpec → loop-agent task 兼容契约(文档镜像;runtime 真源在代码)
63
64
 
@@ -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 and is not part of the default retry set."
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 },
@@ -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`. Do not generate pytest or Playwright source code.
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
- Each case is independently executable and includes AC mapping, 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.
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": "HIGH",
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": "generate-frontend-functional-cases-pi",
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": "materialize-frontend-case-manifest-shell",
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: [...] }; shell command echo is permitted only as the prefix before exactly one final JSON line.",
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": [
@@ -10,5 +10,8 @@ Resolve a single absolute browser base URL and record it explicitly in `context.
10
10
  2. If no usable absolute `http://` / `https://` URL is found in `config.md` (or equivalent source facts), default to `http://localhost:5173`.
11
11
  3. Never use production hosts. Prefer local / isolated non-production URLs.
12
12
  4. Also record `baseUrlSource: config.md|<path>` or `baseUrlSource: default-localhost-5173` so later nodes can audit the choice.
13
- 5. Include the exact browser start prefix that generators must copy:
13
+ 5. Write `environmentProbe: pending`. The environment preflight shell will replace this with `reachable`, `unreachable`, or `curl-unavailable` plus a structured `blockedReason` (for example `frontend-base-url-unreachable`).
14
+ 6. Include the exact browser start prefix that generators must copy:
14
15
  `playwright-cli open --browser=chrome --headed <resolved-base-url>`.
16
+
17
+ Do not claim the environment is reachable until preflight completes. Preflight does not start the application.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.20.0",
3
+ "version": "0.20.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -10,9 +10,11 @@ references:
10
10
  # Frontend Design Review
11
11
 
12
12
  For frontend design review nodes. Read the checklist; audit contract, scout, Mock
13
- strategy, effective plan, task bounds, and traceable design evidence. The knowledge-
14
- base connector is TODO: never invent results. If unavailable or unmatched, require
15
- `<repoRoot>/openspec/**` search/read evidence before repository conventions.
13
+ strategy, effective plan, task bounds, and traceable design evidence.
14
+ Knowledge base and `openspec/` are parallel specification sources. Query the
15
+ knowledge-base connector when available; regardless of result, also search and
16
+ read `<repoRoot>/openspec/**` before accepting repository conventions. The
17
+ connector format is TODO: never invent results.
16
18
 
17
19
  ## Verdict Contract
18
20
 
@@ -9,8 +9,9 @@
9
9
  ## Project Fit
10
10
 
11
11
  - Reuse components, hooks, API helpers, mocks, schemas, router patterns, tokens, and theme rules.
12
- - Cite knowledge-base or `openspec/`; failed/empty knowledge queries must search `<repoRoot>/openspec/**`.
13
- - Record source status, query terms, paths/headings, conflicts, authorized deps, and allowed paths.
12
+ - Cite knowledge base and `openspec/` as parallel sources; failed or empty
13
+ knowledge queries must still search `<repoRoot>/openspec/**`.
14
+ - Record source status, query terms, paths/headings, conflicts, authorized deps, and allowed paths for both.
14
15
 
15
16
  ## Interaction / Quality
16
17
 
@@ -2,14 +2,22 @@
2
2
 
3
3
  ## Required Source Sequence
4
4
 
5
- 1. Attempt the configured component/design knowledge-base query first.
6
- 2. If unavailable, failed, timed out, or unmatched, recursively search the project root's exact `openspec/` directory.
7
- 3. Treat relevant matches as the current project's specification for this run.
8
- 4. Only then use component source, tokens, stories, tests, and pages as non-normative repository fallback.
9
-
10
- Never skip `openspec/` directly to neighboring-code conventions. Report source
11
- conflicts instead of combining them. Explicit task requirements remain the contract;
12
- flag conflicts with knowledge-base or `openspec/` rules.
5
+ Knowledge base and `openspec/` are parallel specification sources:
6
+
7
+ 1. Attempt the configured component/design knowledge-base query first when a
8
+ connector is available in the execution environment.
9
+ 2. Regardless of knowledge-base success, failure, timeout, no match, or no
10
+ configuration, also recursively search the project root's exact `openspec/`
11
+ directory for index files and task-relevant specification content.
12
+ 3. Treat relevant matches from both sources as the current project's
13
+ specification for this run.
14
+ 4. Only then use component source, tokens, stories, tests, and pages as
15
+ non-normative repository fallback.
16
+
17
+ Never skip `openspec/` directly to neighboring-code conventions, even when a
18
+ knowledge-base query returned results. Report source conflicts instead of
19
+ combining them. Explicit task requirements remain the contract; flag conflicts
20
+ with knowledge-base or `openspec/` rules.
13
21
 
14
22
  ## Knowledge Base Connection — TODO
15
23
 
@@ -34,7 +34,13 @@ required check, forbidden write, or unmet acceptance criterion forces revision.
34
34
  and no false real-integration claim. `not-needed` needs applicable real/no-remote
35
35
  evidence, or an explicit default-auto skipped-Mock rationale with the Real
36
36
  Integration Gap preserved when no project Mock capability is confirmed.
37
- - Component/design claims require traceable knowledge-base evidence or, after connection/query failure or no match, relevant `<repoRoot>/openspec/**` evidence. The connector format is TODO; never claim a query or fallback search without evidence. Execute explicit `grep`/`find` to locate spec files and `read` to load them before referencing their rules. Only successful `read` tool calls are observable as "已读取规范文件" in the spec-evidence inspector.
37
+ - Component/design claims require traceable evidence from two parallel sources:
38
+ knowledge base and `<repoRoot>/openspec/**`. Query the knowledge-base connector
39
+ when available; regardless of result, also read `<repoRoot>/openspec/**`.
40
+ The connector format is TODO; never claim a query or fallback search without
41
+ evidence. Execute explicit `grep`/`find` to locate spec files and `read` to
42
+ load them before referencing their rules. Only successful `read` tool calls are
43
+ observable as "已读取规范文件" in the spec-evidence inspector.
38
44
  - Treat shell exit status as authoritative. Do not edit files.
39
45
 
40
46
  ## Evidence And Output
@@ -11,7 +11,11 @@
11
11
  - Cite tight file locations, exact commands/results, or named DAG artifacts.
12
12
  - Never invent evidence; name the missing check. An implementation summary is not the actual diff.
13
13
  - Failed required static/behavior verification is at least Important unless proven unrelated.
14
- - A knowledge-base claim records connector/query, source ID/version, and retrieval time. If absent, failed, or unmatched, review evidence must show `<repoRoot>/openspec/**` search terms and matched paths/headings; label `openspec fallback`, `repository fallback`, or `unavailable` accurately.
14
+ - Treat the knowledge base and `openspec/` as parallel sources. Record
15
+ connector/query, source ID/version, and retrieval time for knowledge-base
16
+ claims. Regardless of that result, evidence must show
17
+ `<repoRoot>/openspec/**` search terms and matched paths/headings; label
18
+ `openspec`, `repository fallback`, or `unavailable` accurately.
15
19
 
16
20
  ## Review Sequence
17
21
 
@@ -24,9 +24,11 @@ verdict/findings, and required browser, visual, manual, or knowledge evidence.
24
24
  - Mock-backed behavior proves frontend rendering and state transitions only. It never
25
25
  proves backend readiness, transport compatibility, or real API integration.
26
26
  - Unavailable commands remain gaps.
27
- - Resolve design evidence via knowledge base, then `<repoRoot>/openspec/**` after
28
- failure/no match. Its connector format remains TODO; never invent it. An applied
29
- `openspec fallback` is available project evidence.
27
+ - Resolve design evidence from two parallel sources: query the execution environment's
28
+ knowledge base connector when available; regardless of result, also read
29
+ `<repoRoot>/openspec/**` for index and task-relevant specification content.
30
+ The connector format remains TODO; never invent it. Applied `openspec` rules
31
+ from successful reads are available project evidence.
30
32
  - Separate Mock service/handler checks from page consumption and record the
31
33
  dev/test-only boundary; handler tests alone do not prove page use.
32
34
 
@@ -12,8 +12,9 @@
12
12
 
13
13
  ## Design And Component Evidence
14
14
 
15
- - Claims cite knowledge-base retrieval or `<repoRoot>/openspec/**` fallback.
16
- - Evidence records query/source/time or fallback search terms, paths, headings, and applied rules.
15
+ - Claims cite two parallel sources: knowledge-base retrieval and
16
+ `<repoRoot>/openspec/**`.
17
+ - Evidence records query/source/time for knowledge base plus openspec search terms, paths, headings, and applied rules for both.
17
18
  - Relevant `openspec/` matches satisfy source availability; missing both sources blocks explicit compliance or required design decisions.
18
19
 
19
20
  ## Status
@@ -303,6 +303,9 @@ loop-agent dag report [--run-id <run-id>] [--lifecycle active|paused|completed|a
303
303
  loop-agent dag reconcile-run --run-id <run-id> # 只读检查 effectiveStatus 与恢复/收口资格
304
304
  loop-agent dag reconcile-run --run-id <run-id> --action supersede --reason "..." # 显式保留证据并标记为任务已另行完成
305
305
  loop-agent dag reconcile-run --run-id <run-id> --action abandon --reason "..." # 显式保留证据并收口为已放弃
306
+ loop-agent dag rerun --run-id <run-id> --from-node <node-id> --plan [--json] # 从节点重跑资格预检(不执行)
307
+ loop-agent dag rerun --run-id <run-id> --from-node <node-id> --plan-hash <sha256> --request-id <key> --reason "..." [--json] # 安全子图 continuation
308
+ loop-agent dag rerun-task --run-id <run-id> --reason "..." --request-id <key> [--profile auto] [--task-id <id>] [--json] # standalone 完整任务重跑
306
309
  loop-agent dag reconcile-tasks --glob '<pattern>' # 仅报告的 task/run/artifact/verify drift audit
307
310
  loop-agent dag final-verification <task-id> # 生成 closeout DAG,closeout artifact 后再 final verify
308
311
  loop-agent dag decision inspect --run-id <run-id> [--node-id <node-id>] # dry-run envelope 重解析;除 run 缺失外 exit 0
@@ -20,10 +20,20 @@ Playwright/Pytest 源码,也不修改被测应用。
20
20
 
21
21
  ## 输出
22
22
 
23
- - 写 `index.md`、`manifest.json` 与独立 case 文件
23
+ - 写 `index.md`、`manifest.draft.json`(生成阶段;materialize 会写成
24
+ `manifest.json`)与独立 case 文件
24
25
  `FE-<FEATURE>-<NNN>-<dimension>.md`;`dimension` 仅为 `core`、`boundary`、
25
26
  `flow` 或 `backend`。
26
- - `manifest.json` 使用 `schemaVersion: 1`,每项只含 `caseId`、`casePath`、
27
+ - **ID 契约(高频失败点,禁止混用)**:
28
+ - `caseId` / 文件名 = **用例 ID**,形态 `FE-<FEATURE>-<NNN>-<dimension>`
29
+ (例:`FE-LOGIN-001-core`)。**禁止**把验收标准写成 caseId
30
+ (错误:`AC-FE-001.md` / `caseId: "AC-FE-001"`)。
31
+ - `acIds` = **验收标准 ID 列表**,形态 `AC-FE-*` / `AC-*`
32
+ (例:`["AC-FE-001"]`)。**禁止**把用例 ID 放进 acIds
33
+ (错误:`acIds: ["FE-LOGIN-001-core"]`)。
34
+ - `casePath` 必须等于 `testcase/frontend/cases/<caseId>.md`;
35
+ `evidenceDir` 必须等于 `testcase/frontend/evidence/<caseId>/`。
36
+ - `manifest` 使用 `schemaVersion: 1`,每项只含 `caseId`、`casePath`、
27
37
  `dimension`、`acIds`、`evidenceDir`。所有 ID、路径和 evidenceDir 必须唯一,
28
38
  并位于 `testcase/frontend/` 内。
29
39
  - `index.md` 按功能点列出 case、维度、AC、数据依赖、API 映射和预期执行状态。
@@ -31,9 +41,9 @@ Playwright/Pytest 源码,也不修改被测应用。
31
41
  每个 case 必须包含:
32
42
 
33
43
  1. 元信息:功能、CRUD 分类、维度、关联 AC、RAG 来源、API 映射状态与数据策略。
34
- 2. 前置条件:独立 `-s=<case-id>` session、登录状态、fixture/存量数据、清理责任。
35
- 3. 可独立执行的命令序列:使用 RAG `context.md` 中已解析的绝对 `baseUrl`(优先来自任务源 `config.md`;缺失时默认 `http://localhost:5173`),写成
36
- `open --browser=chrome --headed <resolved-base-url>`,禁止保留 `<base-url>` 占位符;再按需登录/数据准备、
44
+ 2. 前置条件:默认浏览器 session 中的登录状态、fixture/存量数据、清理责任;不得用 `-s=<case-id>` 建立 named session
45
+ 3. 可独立执行的命令序列:使用 RAG `context.md` 中已解析的绝对 `baseUrl`(优先来自任务源 `config.md`;缺失时默认 `http://localhost:5173`),必须以
46
+ `playwright-cli open --browser=chrome --headed <resolved-base-url>` 开始,禁止保留 `<base-url>` 占位符,也不得使用 `-s=<case-id>` 或其他 named session;再按需登录/数据准备、
37
47
  `snapshot` 后优先使用元素引用、操作、UI 断言、可选 API 断言、cleanup、`close`。
38
48
  4. 明确的 UI/API 预期与数据清理结果;无法满足的环境或数据依赖必须写为 `blocked`。
39
49
 
@@ -42,6 +52,14 @@ Playwright/Pytest 源码,也不修改被测应用。
42
52
  `playwright-cli` skill 已声明的接口;不要生成 `requests --clear`、
43
53
  `request-body` 或 `response-body`。
44
54
 
55
+ ### playwright-cli-only(硬约束)
56
+
57
+ - 用例步骤只能使用 `playwright-cli` skill 已声明的命令语法。
58
+ - **禁止**裸 `playwright`、`npx playwright`、`playwright test`、`@playwright/test`、
59
+ Node Playwright API 或生成 Playwright/Pytest 源码。
60
+ - `playwright-cli` 不可用时不得降级到原生 Playwright;应写 blocked evidence,
61
+ `blockedReason: playwright-cli-unavailable`。
62
+
45
63
  ## 覆盖矩阵
46
64
 
47
65
  | CRUD 类型 | 必选 | 条件 |
@@ -63,8 +81,18 @@ Playwright/Pytest 源码,也不修改被测应用。
63
81
 
64
82
  - C-新增优先使用需求中给出的测试数据;仅在已授权 API 映射存在时才描述临时构造
65
83
  与清理。
66
- - R/U/D 优先使用知识包登记的 fixture 或 test-data;不足时只能使用已授权的
67
- 测试环境 API 注入并清理。
84
+ - R 查询优先使用知识包登记的 fixture 或当前用户可见数据。
85
+ - **U/D 修改删除归属顺序(硬约束)**:
86
+ 1. 仅操作可由**当前登录用户身份**与 UI/API 可观测归属字段共同证明的数据;
87
+ 禁止只凭名称、猜测 ID 或列表顺序认定归属。
88
+ 2. 当前用户无可用数据时,优先在当前用户上下文创建带 run/case 可追踪标记、
89
+ 可清理的数据,再执行 U/D,并在 cleanup 中验证清理。
90
+ 3. 无法安全创建时,仅可使用任务源/RAG 已确认且受路径/环境约束的 Mock,
91
+ 并明确标注为 Mock(不得声称真实后端验证)。
92
+ 4. 既无法证明归属、也无法安全创建或 Mock 时,写 `blocked` evidence,
93
+ `blockedReason` 使用:`current-user-data-unavailable` |
94
+ `data-ownership-unverifiable` | `safe-test-data-setup-unavailable`,不执行 U/D。
95
+ 5. 禁止修改/删除其他用户数据、共享 fixture、生产数据或无法确认可清理的数据。
68
96
  - 禁止使用生产 URL、真实用户凭据或不可清理的数据写入。无法证明隔离与清理时,
69
97
  case 必须为 `blocked`。
70
98