@tea-agent/loop-agent 0.24.4 → 0.24.5
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 +32 -0
- package/README.md +5 -2
- package/dist/application/dag/generate-task-dag.js +5 -8
- package/dist/commands/init.js +19 -5
- package/dist/task/task-demand-routing.js +27 -14
- package/dist/worker/cli.js +59 -14
- package/dist/workflows/dag/backend-test-markdown-workflow.js +75 -9
- package/dist/workflows/dag/init-hybrid.js +9 -8
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +3 -2
- package/docs/templates/backend-test-dag.json +7 -7
- package/docs/templates/backend-test-dag.review-cases.prompt.md +1 -1
- package/docs/templates/evaluation/agents-map-slim-v1.md +1 -1
- package/docs/templates/evaluation/agents-map-verbose-v0.md +2 -2
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/agent-worker/SKILL.md +1 -1
- package/skills/loop-agent/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +3 -2
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
package/AGENTS.md
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
4. 涉及测试纪律/验证声明/调试时继续读:`docs/governance/harness-methodology-*.md`。
|
|
38
38
|
5. 查看最近提交、相关 plan/progress/report;`git status --short --branch`;跑最小基线验证。
|
|
39
39
|
6. 后端/接口/pytest → `taskKind: "backend-test"`(不是 `--profile`);知识回写 `knowledge-sync`;图谱开荒 `knowledge-graph-bootstrap`。`--profile` 仅 `auto|minimal|standard|reviewed|supervised`。
|
|
40
|
-
7. 看板/observe → `agent-worker console serve --repo . --port 8790
|
|
40
|
+
7. 看板/observe → `agent-worker console`(默认 repo=当前目录、port=8790;兼容入口 `agent-worker console serve --repo . --port 8790`)(`/inspect/` 只读);`observe serve` 仅为兼容入口。
|
|
41
41
|
8. 分支合并 → 先读 `docs/operations/branch-merge-guideline.md`。
|
|
42
42
|
|
|
43
43
|
## 会话协议
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.24.5] - 2026-07-28
|
|
6
|
+
|
|
7
|
+
### 重点更新
|
|
8
|
+
|
|
9
|
+
- 后端测试 Markdown 模块与 pytest 脚本实现一对一精准映射,提升测试执行的确定性
|
|
10
|
+
- 修复后端测试 HTML 报告中请求参数显示为空的问题,正确展示请求与响应日志
|
|
11
|
+
- 初始化项目支持将「帮我完成/实现/修复」等通用需求表达直接路由进 Agent DAG
|
|
12
|
+
- 新增裸 agent-worker console 入口,简化本地看板启动流程
|
|
13
|
+
|
|
14
|
+
### 新增
|
|
15
|
+
|
|
16
|
+
- 初始化后的目标项目支持将「帮我完成/实现/修复/开发」等通用需求表达确定性地路由进 Agent DAG(new-task → dag run-task → dag validate → run-dag),无需追加额外的 cli 关键词
|
|
17
|
+
- 新增裸 agent-worker console 入口,直接启动 Loop Operator Console,repo 默认当前目录、port 默认 8790
|
|
18
|
+
- check-skill-entry.sh 及初始化投影脚本支持解析 YAML 折叠块,对通用需求触发词建立机器门禁以防漂移
|
|
19
|
+
|
|
20
|
+
### 改进
|
|
21
|
+
|
|
22
|
+
- 后端测试收敛模块映射,生成与评审 prompt 强制模块文件一对一映射,在映射缺失时支持确定性恢复
|
|
23
|
+
- 后端测试 execute/report 节点文案与 pytest-html 真源对齐,移除残留的 JUnit 表述
|
|
24
|
+
- 后端测试 HTML 报告对齐 R04 模板样式,采用全 inline style 设计(渐变 banner、可折叠用例卡片、状态胶囊及请求/响应分块展示)
|
|
25
|
+
- 本地看板启动更简,端口被占用时不再自动更换,改为明确提示使用 --port 显式指定其它端口
|
|
26
|
+
- 旧项目经 init check-update 与 apply-safe 即可安全补回新的「通用需求实现」路由行,并保留用户自定义前言
|
|
27
|
+
|
|
28
|
+
### 修复
|
|
29
|
+
|
|
30
|
+
- 修复后端测试 HTML 报告中请求参数显示为空的问题:渲染器现已兼容 payload= 键或 JSON request_record 格式的请求摘要
|
|
31
|
+
- 修复后端测试报告无法正确获取请求/响应日志的问题:兼容 HTTP_REQUEST 与 HTTP REQUEST 两种日志前缀,并在解析前正确解码 HTML 实体
|
|
32
|
+
- 修复 shell-executor 在 Windows 环境下因反斜杠路径导致 runDir 损坏的问题,确保路径注入稳定
|
|
33
|
+
- 修复前端 lint baseline 在 Windows 下因路径解析逻辑缺陷,拒绝相对路径输出导致评估误报为不可用的问题
|
|
34
|
+
- 修复多个测试固件在 Windows 下因嵌套 Shell 引号剥离导致命令执行输出为空或报错的问题(涉及 composite-shell 与 init-command)
|
|
35
|
+
|
|
5
36
|
## [0.24.4] - 2026-07-28
|
|
6
37
|
|
|
7
38
|
### 重点更新
|
|
@@ -26,6 +57,7 @@
|
|
|
26
57
|
### 重点更新
|
|
27
58
|
|
|
28
59
|
- 发布工作流改为发布已通过完整门禁并经 SHA-256 校验的 npm tarball,避免 `npm publish` 再次触发全量测试、类型检查与构建。
|
|
60
|
+
- 默认 `standard` 实现任务在 React、Next.js 或 Vue 项目中直接选择 `frontend-implementation` DAG,不再依赖需求关键词;明确后端、文档/测试范围或前端否定仍保留对应通用流程。
|
|
29
61
|
|
|
30
62
|
## [0.24.2] - 2026-07-28
|
|
31
63
|
|
package/README.md
CHANGED
|
@@ -52,6 +52,8 @@ loop-agent init reconcile --repo-root .
|
|
|
52
52
|
|
|
53
53
|
### 运行第一个任务
|
|
54
54
|
|
|
55
|
+
初始化后,在已初始化的目标项目中你可以直接对宿主 agent 说「**loop-agent 帮我完成 XXX 需求**」(或「帮我实现 / 帮我修复 / 帮我开发 XXX」「使用 loop-agent 完成 XXX」「按 loop-agent 流程处理 XXX」),初始化写入的 `AGENTS.md` 与 `.agents/skills/loop-agent/` 会把这类通用需求表达确定性地路由进 Agent DAG,**无需**追加额外的 `cli` 关键词。主会话只负责编排与验证,业务实现由受治理 DAG writer 完成。
|
|
56
|
+
|
|
55
57
|
```bash
|
|
56
58
|
loop-agent new-task example-task "实现一个有明确验收标准的小功能"
|
|
57
59
|
```
|
|
@@ -90,12 +92,13 @@ loop-agent dag report --run-id <run-id>
|
|
|
90
92
|
| 入口 | 命令 | 角色 |
|
|
91
93
|
| --- | --- | --- |
|
|
92
94
|
| 受治理 CLI | `loop-agent …` / `agent-worker …` | 权威写入与诊断 |
|
|
93
|
-
| Loop Operator Console | `agent-worker console
|
|
95
|
+
| Loop Operator Console | `agent-worker console` | Official 本地控制面(默认 repo=当前目录、`127.0.0.1:8790`,含 Operate 与 Inspect) |
|
|
94
96
|
| Observe | `agent-worker observe serve --repo . --port 8787` | 兼容期独立只读入口;不再作为推荐路径 |
|
|
95
97
|
|
|
96
98
|
```bash
|
|
97
99
|
# 终端 A:Console(任务/运行操作面;canonical mutation 只经 sibling loop-agent)
|
|
98
|
-
agent-worker console
|
|
100
|
+
agent-worker console # 默认 repo=当前目录、port=8790
|
|
101
|
+
agent-worker console serve --repo . # 兼容入口,等价于裸入口
|
|
99
102
|
agent-worker console doctor --repo .
|
|
100
103
|
|
|
101
104
|
```
|
|
@@ -239,10 +239,8 @@ export async function generateTaskDagUseCase(input) {
|
|
|
239
239
|
reasons: ["dag run-task validate did not report governanceProfile"],
|
|
240
240
|
});
|
|
241
241
|
const hasExplicitSpecializedTaskKind = candidateResult.templateSelection.source === "taskKind";
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
profileRouting.selectedTemplate !== "supervised-implementation";
|
|
245
|
-
if (hasExplicitSpecializedTaskKind || hasSafeAutomaticTaskSourceRoute) {
|
|
242
|
+
const hasAutomaticTaskSourceRoute = candidateResult.templateSelection.source === "task-source";
|
|
243
|
+
if (hasExplicitSpecializedTaskKind || hasAutomaticTaskSourceRoute) {
|
|
246
244
|
profileRouting.selectedTemplate = candidateResult.template;
|
|
247
245
|
profileRouting.source = hasExplicitSpecializedTaskKind
|
|
248
246
|
? "taskKind"
|
|
@@ -256,10 +254,9 @@ export async function generateTaskDagUseCase(input) {
|
|
|
256
254
|
profileRouting.selectedByProfile = parsed.profile;
|
|
257
255
|
}
|
|
258
256
|
}
|
|
259
|
-
// Persist
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
if (hasSafeAutomaticTaskSourceRoute && candidateResult.template === "frontend-implementation") {
|
|
257
|
+
// Persist the automatic business workflow route while preserving the
|
|
258
|
+
// governance profile as a separate routing dimension.
|
|
259
|
+
if (hasAutomaticTaskSourceRoute && candidateResult.template === "frontend-implementation") {
|
|
263
260
|
const taskConfig = await loadTaskConfig(repoRoot, parsed.taskId);
|
|
264
261
|
if (!taskConfig.taskKind || taskConfig.taskKind === "standard") {
|
|
265
262
|
await writeTaskConfig(repoRoot, parsed.taskId, {
|
package/dist/commands/init.js
CHANGED
|
@@ -415,6 +415,18 @@ check_skill "loop-agent" \\
|
|
|
415
415
|
"references/command-reference.md"
|
|
416
416
|
check_skill "agent-worker" "references/agent-worker-operator.md"
|
|
417
417
|
|
|
418
|
+
# loop-agent description is a YAML folded block (>-); grep ^description: only
|
|
419
|
+
# captures the indicator line. Validate the general-demand trigger terms
|
|
420
|
+
# against the whole skill file so host auto-discovery phrasing cannot drift.
|
|
421
|
+
loop_agent_skill=".agents/skills/loop-agent/SKILL.md"
|
|
422
|
+
loop_agent_text="$(tr '[:upper:]' '[:lower:]' < "\${loop_agent_skill}")"
|
|
423
|
+
for term in "loop-agent 帮我完成" "帮我实现" "帮我修复" "帮我开发" "agent dag" "new-task" "dag run-task" "dag validate" "run-dag"; do
|
|
424
|
+
if [[ "\${loop_agent_text}" != *"\${term}"* ]]; then
|
|
425
|
+
echo "skill entry 检查失败(loop-agent):description 缺少触发/路由词 \${term}" >&2
|
|
426
|
+
exit 1
|
|
427
|
+
fi
|
|
428
|
+
done
|
|
429
|
+
|
|
418
430
|
description_line="$(grep -E '^description:' .agents/skills/agent-worker/SKILL.md | head -n 1 | tr '[:upper:]' '[:lower:]')"
|
|
419
431
|
for term in "agent-worker" "feature packet" "taskspec" "task pool" "self-host" "candidate" "loop-agent"; do
|
|
420
432
|
if [[ "\${description_line}" != *"\${term}"* ]]; then
|
|
@@ -802,6 +814,7 @@ function buildManagedAgentsBlock(input) {
|
|
|
802
814
|
"| 初始化更新校验 / 检查初始化更新 / loop-agent 初始化更新校验 / loop agent初始化更新校验 | 更新校验 | 只读报告,不写入 |",
|
|
803
815
|
"| 初始化对齐 / 升级后对齐 / reconcile 初始化 / loop-agent 初始化对齐 | 升级对齐 | 自动应用确定性安全动作;活跃 DAG/Worker 或人工决策时零写入 |",
|
|
804
816
|
"| 初始化安全更新 / 应用初始化更新 / loop-agent 初始化安全更新 / loop agent初始化安全更新 | 安全更新 | 先 check-update,再只执行确定性安全动作 |",
|
|
817
|
+
"| loop-agent 帮我完成 / 帮我实现 / 帮我修复 / 帮我开发 <需求>;使用 loop-agent 完成 <X>;按 loop-agent 流程处理 <X> | 通用需求实现 | 先建任务源与 `task.json.allowedPaths` / `forbiddenPaths`,再 `new-task` → `dag run-task` → `dag validate` → `run-dag`;主会话不得直接修改业务实现 |",
|
|
805
818
|
"",
|
|
806
819
|
"**更新校验(只读)**:只读执行下面命令;**不得自动**执行 `apply-safe` 或模型合并。",
|
|
807
820
|
"",
|
|
@@ -861,10 +874,11 @@ function buildManagedAgentsBlock(input) {
|
|
|
861
874
|
"### 运行看板(只读)",
|
|
862
875
|
"",
|
|
863
876
|
"```bash",
|
|
864
|
-
"agent-worker console
|
|
877
|
+
"agent-worker console # 默认 repo=当前目录,port=8790",
|
|
878
|
+
"agent-worker console serve --repo . --port 8790 # 兼容入口,等价于上面裸入口",
|
|
865
879
|
"```",
|
|
866
880
|
"",
|
|
867
|
-
"浏览器打开 `http://127.0.0.1:8790/`;检视面为 `http://127.0.0.1:8790/inspect/`。默认绑定本机 `127.0.0.1
|
|
881
|
+
"浏览器打开 `http://127.0.0.1:8790/`;检视面为 `http://127.0.0.1:8790/inspect/`。默认绑定本机 `127.0.0.1`;不要直接暴露到公开网络。端口被占用时不会自动更换,请用 `--port <port>` 显式指定。`agent-worker observe serve` 仅为兼容入口。",
|
|
868
882
|
"",
|
|
869
883
|
"### DAG 诊断与收口",
|
|
870
884
|
"",
|
|
@@ -2049,11 +2063,11 @@ function buildTargetFeatureWorkflow(input) {
|
|
|
2049
2063
|
"",
|
|
2050
2064
|
"## Specialized Task Kinds",
|
|
2051
2065
|
"",
|
|
2052
|
-
"- standard tasks
|
|
2053
|
-
"-
|
|
2066
|
+
"- standard tasks first use the structured task type in `source/需求.md`, then combine `allowedPaths` with strong React/Next/Vue project evidence for deterministic routing.",
|
|
2067
|
+
"- A frontend project defaults eligible implementation work to the frontend DAG without requirement keyword matching. Explicit backend, mixed, frontend-negated, and documentation/test-only scopes keep the template selected by the normal governance profile.",
|
|
2054
2068
|
"- `frontend-mock-assess-pi` reads Mock/API/schema evidence and selects `native|browser-intercept|request-adapter|not-needed|blocked` before frontend planning; its deterministic gate rejects blocked or malformed output, while the existing frontend implementer remains the only writer.",
|
|
2055
2069
|
"- Mock-backed verification never proves real API integration. When the backend was not exercised, closeout must retain the gap and name `<task-id>-real-api-integration-verify`; that follow-up is explicitly created/run after backend readiness, never automatic.",
|
|
2056
|
-
"- Automatic frontend classification
|
|
2070
|
+
"- Automatic frontend classification selects the frontend implementation workflow and persists taskKind; explicit profiles, workflowPolicy, and supervised quality gates record governance strength without switching the business workflow back to a generic DAG.",
|
|
2057
2071
|
"- A backend implementation does not select the backend test DAG. `backend-test` remains an explicit test-engineering workflow.",
|
|
2058
2072
|
"- Explicit specialized `taskKind` values remain compatible and take precedence over task-source classification.",
|
|
2059
2073
|
"",
|
|
@@ -298,6 +298,11 @@ export function classifyTaskDemand(input) {
|
|
|
298
298
|
message: "allowedPaths include a backend-specific source path",
|
|
299
299
|
});
|
|
300
300
|
}
|
|
301
|
+
const allowedPathsOnlyCoverNonProductArtifacts = input.allowedPaths.length > 0 &&
|
|
302
|
+
input.allowedPaths.every((rawPath) => {
|
|
303
|
+
const allowedPath = normalizedAllowedPath(rawPath);
|
|
304
|
+
return matchesAny(allowedPath, NON_PRODUCT_ALLOWED_PATH_PATTERNS);
|
|
305
|
+
});
|
|
301
306
|
const pathSupportedFrontendDelivery = frontendPath &&
|
|
302
307
|
(titleSignals.pathSupportedFrontendDelivery ||
|
|
303
308
|
requirementSignals.pathSupportedFrontendDelivery ||
|
|
@@ -328,20 +333,27 @@ export function classifyTaskDemand(input) {
|
|
|
328
333
|
message: "task text contains user-visible interaction/state delivery and the project has strong frontend framework evidence",
|
|
329
334
|
});
|
|
330
335
|
}
|
|
336
|
+
const backendDelivery = titleSignals.backendDelivery || requirementSignals.backendDelivery;
|
|
337
|
+
const frontendProjectDefaultImplementation = hasStrongFrontendProjectEvidence &&
|
|
338
|
+
!backendDelivery &&
|
|
339
|
+
!frontendNegated &&
|
|
340
|
+
!allowedPathsOnlyCoverNonProductArtifacts;
|
|
341
|
+
if (frontendProjectDefaultImplementation) {
|
|
342
|
+
addSignal(signals, {
|
|
343
|
+
id: "frontend-project-default-implementation",
|
|
344
|
+
source: "project",
|
|
345
|
+
message: "frontend project evidence selects the frontend implementation workflow by default",
|
|
346
|
+
});
|
|
347
|
+
}
|
|
331
348
|
const frontendDelivery = titleSignals.frontendDelivery ||
|
|
332
349
|
requirementSignals.frontendDelivery ||
|
|
333
350
|
pathSupportedFrontendDelivery ||
|
|
334
|
-
projectSupportedFrontendDelivery
|
|
335
|
-
|
|
336
|
-
input.allowedPaths.every((rawPath) => {
|
|
337
|
-
const allowedPath = normalizedAllowedPath(rawPath);
|
|
338
|
-
return matchesAny(allowedPath, NON_PRODUCT_ALLOWED_PATH_PATTERNS);
|
|
339
|
-
});
|
|
351
|
+
projectSupportedFrontendDelivery ||
|
|
352
|
+
frontendProjectDefaultImplementation;
|
|
340
353
|
if (allowedPathsOnlyCoverNonProductArtifacts && frontendDelivery) {
|
|
341
354
|
blockers.push("non-product-allowed-paths");
|
|
342
355
|
}
|
|
343
356
|
const eligibleFrontendDelivery = frontendDelivery && !allowedPathsOnlyCoverNonProductArtifacts;
|
|
344
|
-
const backendDelivery = titleSignals.backendDelivery || requirementSignals.backendDelivery;
|
|
345
357
|
const taskTypeConflict = (hasFrontendTaskType && (backendDelivery || frontendNegated)) ||
|
|
346
358
|
(hasBackendTaskType && eligibleFrontendDelivery);
|
|
347
359
|
if (taskTypeConflict)
|
|
@@ -402,13 +414,6 @@ export function resolveTaskDagTemplateSelection(input) {
|
|
|
402
414
|
reasons: [],
|
|
403
415
|
};
|
|
404
416
|
}
|
|
405
|
-
if (requestedTemplate !== "standard-dag") {
|
|
406
|
-
return {
|
|
407
|
-
template: requestedTemplate,
|
|
408
|
-
source: "requested-template",
|
|
409
|
-
reasons: [],
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
417
|
const classification = classifyTaskDemand(input);
|
|
413
418
|
if (classification.kind === "frontend") {
|
|
414
419
|
const frontendSignals = classification.signals
|
|
@@ -423,6 +428,14 @@ export function resolveTaskDagTemplateSelection(input) {
|
|
|
423
428
|
classification,
|
|
424
429
|
};
|
|
425
430
|
}
|
|
431
|
+
if (requestedTemplate !== "standard-dag") {
|
|
432
|
+
return {
|
|
433
|
+
template: requestedTemplate,
|
|
434
|
+
source: "requested-template",
|
|
435
|
+
reasons: [],
|
|
436
|
+
classification,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
426
439
|
return {
|
|
427
440
|
template: requestedTemplate,
|
|
428
441
|
source: "requested-template",
|
package/dist/worker/cli.js
CHANGED
|
@@ -41,7 +41,8 @@ export function buildAgentWorkerProgram() {
|
|
|
41
41
|
.description("Local product-line worker utilities for TaskSpec validation")
|
|
42
42
|
.version(readPackageVersion(path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url))))) ?? "0.0.0", "-V, --version", "display version")
|
|
43
43
|
.showHelpAfterError()
|
|
44
|
-
.showSuggestionAfterError()
|
|
44
|
+
.showSuggestionAfterError()
|
|
45
|
+
.enablePositionalOptions();
|
|
45
46
|
const task = program.command("task").description("TaskSpec utilities");
|
|
46
47
|
const batch = program
|
|
47
48
|
.command("batch")
|
|
@@ -593,18 +594,17 @@ export function buildAgentWorkerProgram() {
|
|
|
593
594
|
process.stdout.write(`${JSON.stringify(snapshot)}\n`);
|
|
594
595
|
});
|
|
595
596
|
consoleCmd
|
|
596
|
-
.
|
|
597
|
-
.
|
|
597
|
+
.description("Loop Operator Console (local); bare `agent-worker console` starts it directly (repo defaults to current working directory, port defaults to 8790)")
|
|
598
|
+
.enablePositionalOptions()
|
|
599
|
+
.passThroughOptions()
|
|
600
|
+
.option("--repo <repo-root>", "Target repo root (default: current working directory)", process.cwd())
|
|
598
601
|
.option("--port <port>", "HTTP port", "8790")
|
|
599
602
|
.option("--host <host>", "Bind host (default 127.0.0.1; use 0.0.0.0 for LAN, no network auth)", "127.0.0.1")
|
|
600
603
|
.option("--debug", "Log request source/method/path/status to stderr")
|
|
601
604
|
.option("--app-data <path>", "Override Console application-data root (Draft/Operation/confirmation)")
|
|
602
|
-
.description("Start Loop Operator Console HTTP server (Operate + Inspect; static SPA + Operator API)")
|
|
603
605
|
.action(async (options) => {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
const server = await createConsoleServer({
|
|
607
|
-
repoRoot,
|
|
606
|
+
await runConsoleServe({
|
|
607
|
+
repoRoot: path.resolve(options.repo),
|
|
608
608
|
host: options.host,
|
|
609
609
|
port: Number.parseInt(options.port, 10),
|
|
610
610
|
debug: options.debug === true,
|
|
@@ -612,13 +612,25 @@ export function buildAgentWorkerProgram() {
|
|
|
612
612
|
? path.resolve(options.appData)
|
|
613
613
|
: undefined,
|
|
614
614
|
});
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
615
|
+
});
|
|
616
|
+
consoleCmd
|
|
617
|
+
.command("serve")
|
|
618
|
+
.option("--repo <repo-root>", "Target repo root (default: current working directory)", process.cwd())
|
|
619
|
+
.option("--port <port>", "HTTP port", "8790")
|
|
620
|
+
.option("--host <host>", "Bind host (default 127.0.0.1; use 0.0.0.0 for LAN, no network auth)", "127.0.0.1")
|
|
621
|
+
.option("--debug", "Log request source/method/path/status to stderr")
|
|
622
|
+
.option("--app-data <path>", "Override Console application-data root (Draft/Operation/confirmation)")
|
|
623
|
+
.description("Start Loop Operator Console HTTP server (compat entry; equivalent to bare `agent-worker console`)")
|
|
624
|
+
.action(async (options) => {
|
|
625
|
+
await runConsoleServe({
|
|
626
|
+
repoRoot: path.resolve(options.repo),
|
|
627
|
+
host: options.host,
|
|
628
|
+
port: Number.parseInt(options.port, 10),
|
|
629
|
+
debug: options.debug === true,
|
|
630
|
+
appDataRoot: options.appData
|
|
631
|
+
? path.resolve(options.appData)
|
|
632
|
+
: undefined,
|
|
620
633
|
});
|
|
621
|
-
await server.close();
|
|
622
634
|
});
|
|
623
635
|
consoleCmd
|
|
624
636
|
.command("doctor")
|
|
@@ -693,6 +705,39 @@ export async function main(argv = process.argv) {
|
|
|
693
705
|
async function readTaskYaml(taskSpecPath) {
|
|
694
706
|
return YAML.parse(await readFile(taskSpecPath, "utf-8"));
|
|
695
707
|
}
|
|
708
|
+
/** Shared console serve entry used by both bare `console` and `console serve`. */
|
|
709
|
+
async function runConsoleServe(options) {
|
|
710
|
+
const { createConsoleServer } = await import("./console/server.js");
|
|
711
|
+
try {
|
|
712
|
+
const server = await createConsoleServer({
|
|
713
|
+
repoRoot: options.repoRoot,
|
|
714
|
+
host: options.host,
|
|
715
|
+
port: options.port,
|
|
716
|
+
debug: options.debug === true,
|
|
717
|
+
appDataRoot: options.appDataRoot,
|
|
718
|
+
});
|
|
719
|
+
process.stdout.write(`${server.url}\n`);
|
|
720
|
+
await new Promise((resolve) => {
|
|
721
|
+
const shutdown = () => resolve();
|
|
722
|
+
process.once("SIGINT", shutdown);
|
|
723
|
+
process.once("SIGTERM", shutdown);
|
|
724
|
+
});
|
|
725
|
+
await server.close();
|
|
726
|
+
}
|
|
727
|
+
catch (error) {
|
|
728
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
729
|
+
const code = error?.code;
|
|
730
|
+
const isPortInUse = code === "EADDRINUSE" ||
|
|
731
|
+
/listen EADDRINUSE/i.test(detail) ||
|
|
732
|
+
/address already in use/i.test(detail);
|
|
733
|
+
if (isPortInUse) {
|
|
734
|
+
process.stderr.write(`端口 ${options.port} 已被占用,请使用 --port <port> 指定其它端口后重试。\n`);
|
|
735
|
+
process.exit(1);
|
|
736
|
+
}
|
|
737
|
+
process.stderr.write(`${detail}\n`);
|
|
738
|
+
process.exit(1);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
696
741
|
function handlePoolRecoveryCliError(error) {
|
|
697
742
|
const detail = error instanceof Error ? error.message : String(error);
|
|
698
743
|
process.stderr.write(`${detail}\n`);
|
|
@@ -413,6 +413,18 @@ export async function validateBackendMarkdownCases(input) {
|
|
|
413
413
|
if (!hasAssertableExpectedResult(expected)) {
|
|
414
414
|
findings.push(`${testCase.id} has no structured expected result`);
|
|
415
415
|
}
|
|
416
|
+
const expectedScript = expectedBackendTestPytestScriptForMarkdownModule(relativeFile);
|
|
417
|
+
const mappedScripts = extractMappedPytestScripts(testCase.body);
|
|
418
|
+
if (mappedScripts.length === 0) {
|
|
419
|
+
findings.push(`${testCase.id} has no mapped pytest script in Automation Notes/自动化映射 (expected ${expectedScript} from module ${path.basename(relativeFile)})`);
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
for (const script of mappedScripts) {
|
|
423
|
+
if (script !== expectedScript) {
|
|
424
|
+
findings.push(`${testCase.id} automation mapping ${script} must equal module one-to-one path ${expectedScript} (from ${path.basename(relativeFile)})`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
416
428
|
}
|
|
417
429
|
}
|
|
418
430
|
const missing = input.requiredRequirementIds.filter((id) => !coveredAc.has(id));
|
|
@@ -454,6 +466,27 @@ export async function validateBackendMarkdownCases(input) {
|
|
|
454
466
|
function collectMarkdownCaseIds(markdown) {
|
|
455
467
|
return splitCases(markdown).map((testCase) => testCase.id);
|
|
456
468
|
}
|
|
469
|
+
/**
|
|
470
|
+
* Derive the stable module stem used for Markdown ↔ pytest one-to-one mapping.
|
|
471
|
+
* `testcase/md/<module>.md` → stem → `testcase/test_<stem>.py`.
|
|
472
|
+
* Example: `BE-HEALTH.md` / `be-health.md` / `order-api.md` → `be_health` / `order_api`.
|
|
473
|
+
*/
|
|
474
|
+
export function normalizeBackendTestModuleStem(moduleFileName) {
|
|
475
|
+
const base = path.basename(moduleFileName).replace(/\.md$/i, "");
|
|
476
|
+
const stem = base
|
|
477
|
+
.toLowerCase()
|
|
478
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
479
|
+
.replace(/^_+|_+$/g, "")
|
|
480
|
+
.replace(/_+/g, "_");
|
|
481
|
+
if (!stem) {
|
|
482
|
+
throw new Error(`cannot derive backend-test module stem from: ${moduleFileName}`);
|
|
483
|
+
}
|
|
484
|
+
return stem;
|
|
485
|
+
}
|
|
486
|
+
/** Expected pytest script path for a Markdown module file (basename or relative path). */
|
|
487
|
+
export function expectedBackendTestPytestScriptForMarkdownModule(moduleFileName) {
|
|
488
|
+
return `testcase/test_${normalizeBackendTestModuleStem(moduleFileName)}.py`;
|
|
489
|
+
}
|
|
457
490
|
function extractMappedPytestScripts(testCaseBody) {
|
|
458
491
|
const automation = sectionBody(testCaseBody, CASE_SECTION_ALIASES.automationNotes);
|
|
459
492
|
if (!automation.trim())
|
|
@@ -462,6 +495,15 @@ function extractMappedPytestScripts(testCaseBody) {
|
|
|
462
495
|
.map((match) => match[1].replaceAll("\\", "/"))
|
|
463
496
|
.filter((value) => !value.includes("..")));
|
|
464
497
|
}
|
|
498
|
+
function isSafeBackendPytestScript(script, workspaceRoot) {
|
|
499
|
+
const absolute = path.resolve(workspaceRoot, script);
|
|
500
|
+
const relative = path.relative(workspaceRoot, absolute).replaceAll(path.sep, "/");
|
|
501
|
+
return (!relative.startsWith("..") &&
|
|
502
|
+
!path.isAbsolute(relative) &&
|
|
503
|
+
script.startsWith("testcase/") &&
|
|
504
|
+
!script.includes("..") &&
|
|
505
|
+
/^test_.*\.py$/i.test(path.basename(script)));
|
|
506
|
+
}
|
|
465
507
|
function testFunctionRegion(input) {
|
|
466
508
|
const functionLineStart = input.source.lastIndexOf("\n", input.functionIndex - 1) + 1;
|
|
467
509
|
const functionLine = input.source.slice(functionLineStart, input.functionHeaderEnd);
|
|
@@ -610,26 +652,50 @@ export async function collectBackendTestMappedPytestScripts(workspaceRoot) {
|
|
|
610
652
|
for (const file of files) {
|
|
611
653
|
if (path.basename(file).toLowerCase() === "readme.md")
|
|
612
654
|
continue;
|
|
655
|
+
const relativeFile = path.relative(workspaceRoot, file).replaceAll(path.sep, "/");
|
|
656
|
+
const expectedScript = expectedBackendTestPytestScriptForMarkdownModule(relativeFile);
|
|
613
657
|
const markdown = await readFile(file, "utf8");
|
|
658
|
+
const mappedFromModule = new Set();
|
|
614
659
|
for (const testCase of splitCases(markdown)) {
|
|
615
|
-
for (const script of extractMappedPytestScripts(testCase.body))
|
|
660
|
+
for (const script of extractMappedPytestScripts(testCase.body)) {
|
|
661
|
+
mappedFromModule.add(script);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
// Prefer explicit mappings, but recover to the deterministic module stem path
|
|
665
|
+
// when the model wrote the correct one-to-one file while Markdown still names
|
|
666
|
+
// a drifted script (common: health.md maps test_health.py but writer emitted
|
|
667
|
+
// test_be_health.py for BE-HEALTH.md, or the reverse).
|
|
668
|
+
if (mappedFromModule.size === 0) {
|
|
669
|
+
if (await exists(path.resolve(workspaceRoot, expectedScript))) {
|
|
670
|
+
scripts.add(expectedScript);
|
|
671
|
+
}
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
for (const script of mappedFromModule) {
|
|
675
|
+
if (!isSafeBackendPytestScript(script, workspaceRoot)) {
|
|
676
|
+
throw new Error(`unsafe mapped pytest script: ${script}`);
|
|
677
|
+
}
|
|
678
|
+
if (await exists(path.resolve(workspaceRoot, script))) {
|
|
616
679
|
scripts.add(script);
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
if (isSafeBackendPytestScript(expectedScript, workspaceRoot) &&
|
|
683
|
+
(await exists(path.resolve(workspaceRoot, expectedScript)))) {
|
|
684
|
+
scripts.add(expectedScript);
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
throw new Error(`mapped pytest script is missing: ${script} (module one-to-one path ${expectedScript} also missing)`);
|
|
617
688
|
}
|
|
618
689
|
}
|
|
619
690
|
if (scripts.size === 0) {
|
|
620
691
|
throw new Error("no pytest scripts are mapped by final Markdown cases");
|
|
621
692
|
}
|
|
622
693
|
const safeScripts = [];
|
|
623
|
-
for (const script of unique(scripts)) {
|
|
624
|
-
|
|
625
|
-
const relative = path.relative(workspaceRoot, absolute);
|
|
626
|
-
if (relative.startsWith("..") ||
|
|
627
|
-
path.isAbsolute(relative) ||
|
|
628
|
-
!script.startsWith("testcase/") ||
|
|
629
|
-
!/^test_.*\.py$/i.test(path.basename(script))) {
|
|
694
|
+
for (const script of unique([...scripts]).sort()) {
|
|
695
|
+
if (!isSafeBackendPytestScript(script, workspaceRoot)) {
|
|
630
696
|
throw new Error(`unsafe mapped pytest script: ${script}`);
|
|
631
697
|
}
|
|
632
|
-
if (!(await exists(
|
|
698
|
+
if (!(await exists(path.resolve(workspaceRoot, script)))) {
|
|
633
699
|
throw new Error(`mapped pytest script is missing: ${script}`);
|
|
634
700
|
}
|
|
635
701
|
safeScripts.push(script);
|
|
@@ -3302,8 +3302,9 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3302
3302
|
"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.",
|
|
3303
3303
|
"Create testcase/md/README.md as the concise entry page: test objective, target/environment, isolation/cleanup, module summary and a linked case index table with Case ID, Chinese case name, scenario type, endpoint and expected status/result. Avoid repeating every case body in README.",
|
|
3304
3304
|
"Write 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.",
|
|
3305
|
+
"Name each module file with a stable lowercase business stem such as `testcase/md/health.md` or `testcase/md/resource_notes.md`. Do not use Case-ID-like module filenames such as `BE-HEALTH.md` or `BE-NOTES.md`. For every automatable case, `自动化映射` must name exactly `testcase/test_<module>.py`, where <module> is that Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `testcase/md/health.md` → `testcase/test_health.py`; `testcase/md/resource_notes.md` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.",
|
|
3305
3306
|
"Place 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 ‘符合预期’.",
|
|
3306
|
-
"In `自动化映射`, 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.",
|
|
3307
|
+
"In `自动化映射`, record the planned script path and pytest function name when known, and keep the script path identical to the module one-to-one path above. 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.",
|
|
3307
3308
|
intake.boundedSourceContext, "## Authoritative reference index", JSON.stringify(intake.referenceIndex, null, 2),
|
|
3308
3309
|
"For each index entry, use `readPath` for Pi read-tool calls and copy `path` exactly into Markdown Source References. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.",
|
|
3309
3310
|
"Read only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text.",
|
|
@@ -3316,8 +3317,8 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3316
3317
|
outputContract: "Review source fidelity and directly revise only testcase/md/**; return concise Markdown, never JSON.",
|
|
3317
3318
|
subtask_prompt: [
|
|
3318
3319
|
"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.",
|
|
3319
|
-
"Check 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.",
|
|
3320
|
-
"Correct testcase/md/** directly: add documented omissions, remove unsupported cases, fix mappings/
|
|
3320
|
+
"Check 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 ‘符合预期’, Case-ID-like module filenames (for example `BE-HEALTH.md`), and missing or drifted script/function mapping where it can be derived.",
|
|
3321
|
+
"Correct testcase/md/** directly: add documented omissions, remove unsupported cases, rename module files to stable lowercase stems when needed, fix automation mappings so each case points at `testcase/test_<module>.py` derived from that module filename, 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.",
|
|
3321
3322
|
"Read only precise referenced source paths plus requirement sections needed for uncovered ACs. Do not scan the repository, modify source/**, generate pytest, execute tests, or emit JSON.",
|
|
3322
3323
|
intake.boundedSourceContext, "## Authoritative reference index", JSON.stringify(intake.referenceIndex, null, 2),
|
|
3323
3324
|
"For each index entry, use `readPath` for Pi read-tool calls and keep `path` as the exact Markdown Source References citation. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.",
|
|
@@ -3333,7 +3334,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3333
3334
|
subtask_prompt: [
|
|
3334
3335
|
"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.",
|
|
3335
3336
|
"Ensure 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.",
|
|
3336
|
-
"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`, and `testcase/md/order-api.md` maps to `testcase/test_order_api.py`. 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.",
|
|
3337
|
+
"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.",
|
|
3337
3338
|
"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.",
|
|
3338
3339
|
"Compare timestamps and other semantically equivalent protocol values by parsed meaning, not byte-for-byte serialization. In particular, normalize valid ISO-8601 instants before equality/order assertions so differences such as omitted trailing fractional seconds do not create TestBug failures; preserve exact-string assertions only when the Markdown explicitly requires representation equality.",
|
|
3339
3340
|
"Before logging, recursively redact sensitive keys and header values including authorization, proxy-authorization, cookie, set-cookie, token, password, secret, api key and credentials. Never print full Authorization/Cookie values. Apply bounded truncation to serialized request and response bodies (with an explicit truncation marker) so large payloads cannot flood pytest or report artifacts.",
|
|
@@ -3345,7 +3346,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3345
3346
|
'mkdir -p "${HARNESS_DAG_RUN_DIR}/reports"',
|
|
3346
3347
|
'echo "pytest targets are resolved at runtime from final Markdown 自动化映射"',
|
|
3347
3348
|
].join("; ");
|
|
3348
|
-
const execute = shellNode("execute-backend-pytest-and-html-report-shell", [traceability.id], "markdown-execute-html", "Resolve the final Markdown Automation Notes/自动化映射 to a unique, safe set of testcase/**/test_*.py targets and execute only those scripts exactly once.
|
|
3349
|
+
const execute = shellNode("execute-backend-pytest-and-html-report-shell", [traceability.id], "markdown-execute-html", "Resolve the final Markdown Automation Notes/自动化映射 to a unique, safe set of testcase/**/test_*.py targets and execute only those scripts exactly once. Prefer the deterministic module one-to-one path when a mapped script is missing but the module stem file exists. Generate a native pytest-html self-contained report, then render the primary self-contained Chinese HTML report from the same pytest-html plus final Markdown case metadata without rerun. Keep 测试结论, 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.", "One scoped pytest execution over Markdown-mapped scripts producing a valid pytest-html report with per-case captured output, self-contained reports/backend-test.html and reports/backend-test.md, plus internal reports/backend-test-facts.md evidence; exit 0/1 with valid evidence continues.", [pytestCommand], 300000);
|
|
3349
3350
|
if (execute.shell) {
|
|
3350
3351
|
execute.shell.envAllowlist = collectBackendTestShellEnvAllowlist(sources);
|
|
3351
3352
|
}
|
|
@@ -3358,11 +3359,11 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3358
3359
|
forbiddenPaths: forbidden,
|
|
3359
3360
|
outputContract: canWriteReport ? "Final Markdown report and L-5 conclusion under docs/test-reports/**; no JSON." : "Final Markdown report and L-5 conclusion in assistant output; no JSON or writes.",
|
|
3360
3361
|
subtask_prompt: [
|
|
3361
|
-
"Generate the final Markdown report from upstream facts and run-owned environment, advisory case-validation, advisory traceability,
|
|
3362
|
+
"Generate the final Markdown report from upstream facts and run-owned environment, advisory case-validation, advisory traceability, pytest-html and HTML evidence. Do not emit JSON.",
|
|
3362
3363
|
"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.",
|
|
3363
3364
|
"Always 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.",
|
|
3364
3365
|
"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.",
|
|
3365
|
-
"Never override Shell/
|
|
3366
|
+
"Never override Shell/pytest-html facts. L-5 requires pass=100%, AC=100%, automation>=90%, line>=80%, branch>=70%, skipped=0 and no blocking Critical risk.",
|
|
3366
3367
|
canWriteReport ? "Write only under docs/test-reports/**." : "Keep the full report in assistant output.",
|
|
3367
3368
|
].join("\n\n"),
|
|
3368
3369
|
};
|
|
@@ -3376,7 +3377,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3376
3377
|
...taskConfig.hardConstraints, ...STANDARD_GLOBAL_CONSTRAINTS,
|
|
3377
3378
|
"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.",
|
|
3378
3379
|
"Model nodes produce Markdown and pytest assets, never backend-test business JSON envelopes.",
|
|
3379
|
-
"Environment, advisory Markdown validation, advisory traceability,
|
|
3380
|
+
"Environment, advisory Markdown validation, advisory traceability, pytest-html, HTML and execution facts are deterministic evidence. Nodes 4 and 6 record findings without blocking nodes 5, 7 or 8.",
|
|
3380
3381
|
"Only Markdown case generation/review may read source facts; pytest generation must not read source/**.",
|
|
3381
3382
|
"Functional case IDs use BE-<MODULE>-<NNN>; production code/config, skip/xfail, repair and rerun are forbidden.",
|
|
3382
3383
|
],
|
|
@@ -22,11 +22,12 @@ class TestOrderApi:
|
|
|
22
22
|
- 断言只来自 `### 预期结果` / `### Expected Results`;setup 只来自必选 `### 前置条件`,以及存在时的 `### 测试数据`、`### 自动化映射` 或对应历史英文分节。
|
|
23
23
|
- 每条可自动化 Case 应在 `自动化映射` / `Automation Notes` 明确写出目标 pytest 脚本;第 6 节点只扫描这些脚本,不递归扫描无关历史 `test_*.py`。
|
|
24
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/
|
|
25
|
+
- 日志输出前必须递归脱敏 `authorization`、`proxy-authorization`、`cookie`、`set-cookie`、`token`、`password`、`secret`、`api key`、`credential` 等 key/header;禁止打印完整 Authorization/Cookie。序列化后的 request/response body 必须有明确长度上限和截断标识,避免大对象淹没 pytest/pytest-html/报告证据。
|
|
26
26
|
- 禁止 `skip` / `xfail`、吞断言、宽异常静默通过、mock 替代真实目标、删除用例或弱化断言。
|
|
27
27
|
- best-effort 清理只能捕获所选 HTTP client 实际抛出的窄 transport exception,例如 `requests.RequestException` 或 `urllib.error.URLError`;禁止 `except:`、`except Exception`、`except BaseException` 后 `pass`。
|
|
28
28
|
- 同一 Case ID 可以由多个 pytest 函数覆盖;额外映射会进入 traceability 报告,但不能伪造未在 Markdown 中定义的业务场景。
|
|
29
|
-
-
|
|
29
|
+
- 生成 pytest 文件名必须与 Markdown 模块一一对应:`testcase/md/<module>.md` → `testcase/test_<module>.py`(`<module>` 为文件名去 `.md` 后小写、非字母数字转 `_`)。即使 Markdown `自动化映射` 写了别的路径,也必须写模块 stem 路径,不得额外发明 `test_be_*` 前缀。
|
|
30
|
+
- 测试失败必须诚实保留,后续节点只执行一次 pytest,并从同一 pytest-html 报告生成 HTML/facts。
|
|
30
31
|
|
|
31
32
|
## 推荐输出
|
|
32
33
|
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"Replace REPLACE/WITH/NARROW/IMPLEMENT/PATHS/** with concrete paths before executing the implementation writer",
|
|
31
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, advisory Markdown validation, advisory traceability,
|
|
33
|
+
"Environment, advisory Markdown validation, advisory traceability, pytest-html, HTML and execution facts are deterministic evidence. Nodes 4 and 6 record findings without blocking nodes 5, 7 or 8.",
|
|
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
|
],
|
|
@@ -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>|<中文用例名称>`. 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."
|
|
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\nName each module file with a stable lowercase business stem such as `testcase/md/health.md` or `testcase/md/resource_notes.md`. Do not use Case-ID-like module filenames such as `BE-HEALTH.md` or `BE-NOTES.md`. For every automatable case, `自动化映射` must name exactly `testcase/test_<module>.py`, where <module> is that Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `testcase/md/health.md` → `testcase/test_health.py`; `testcase/md/resource_notes.md` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.\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, and keep the script path identical to the module one-to-one path above. 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 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/
|
|
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 ‘符合预期’, Case-ID-like module filenames (for example `BE-HEALTH.md`), and missing or drifted script/function mapping where it can be derived.\n\nCorrect testcase/md/** directly: add documented omissions, remove unsupported cases, rename module files to stable lowercase stems when needed, fix automation mappings so each case points at `testcase/test_<module>.py` derived from that module filename, 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",
|
|
@@ -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. 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.",
|
|
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\nName 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`, and `testcase/md/order-api.md` maps to `testcase/test_order_api.py`. 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.\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`."
|
|
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\nName 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.\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",
|
|
@@ -250,8 +250,8 @@
|
|
|
250
250
|
".harness/dag-runs/**",
|
|
251
251
|
"artifacts/**"
|
|
252
252
|
],
|
|
253
|
-
"outputContract": "One scoped pytest execution over Markdown-mapped scripts producing valid
|
|
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.
|
|
253
|
+
"outputContract": "One scoped pytest execution over Markdown-mapped scripts producing a valid pytest-html report with per-case captured output, self-contained reports/backend-test.html and reports/backend-test.md, plus internal reports/backend-test-facts.md evidence; 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. Prefer the deterministic module one-to-one path when a mapped script is missing but the module stem file exists. Generate a native pytest-html self-contained report, then render the primary self-contained Chinese HTML report from the same pytest-html plus final Markdown case metadata without rerun. Keep 测试结论, 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
257
|
"mkdir -p \"${HARNESS_DAG_RUN_DIR}/reports\"; echo \"pytest targets are resolved at runtime from final Markdown 自动化映射\""
|
|
@@ -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, advisory case-validation, advisory traceability,
|
|
287
|
+
"subtask_prompt": "Generate the final Markdown report from upstream facts and run-owned environment, advisory case-validation, advisory traceability, pytest-html 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 availability, and L-5 READY/NOT READY.\n\nNever override Shell/pytest-html facts. L-5 requires pass=100%, AC=100%, automation>=90%, line>=80%, branch>=70%, skipped=0 and no blocking Critical risk.\n\nWrite only under docs/test-reports/**."
|
|
288
288
|
}
|
|
289
289
|
],
|
|
290
290
|
"sourceBinding": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
- 对照每条 `需求依据` 和环境报告检查 AC、接口、字段/响应形状、状态码、错误语义、状态转换、正向/异常/边界场景。
|
|
23
23
|
- 删除无依据场景、合并重复用例、补齐有依据的遗漏;无法确认的内容写入中文证据缺口,不猜测行为或凭据。
|
|
24
24
|
- 拒绝“符合预期”“正常工作”等模糊结果,以及无意义的中英双写和大段重复 boilerplate。
|
|
25
|
-
- 若能确定脚本与函数命名,在 `自动化映射` 中写明计划脚本路径与 pytest
|
|
25
|
+
- 若能确定脚本与函数命名,在 `自动化映射` 中写明计划脚本路径与 pytest 函数/方法名;脚本路径必须等于模块 one-to-one 路径 `testcase/test_<module>.py`,`<module>` 来自当前 Markdown 文件名 stem,不得使用 Case-ID 式模块名(如 `BE-HEALTH.md`)或与模块 stem 不一致的映射。
|
|
26
26
|
|
|
27
27
|
## 推荐输出
|
|
28
28
|
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
4. 涉及测试纪律/验证声明/调试时继续读:`docs/harness-methodology-*.md`。
|
|
38
38
|
5. 查看最近提交、相关 plan/progress/report;`git status --short --branch`;跑最小基线验证。
|
|
39
39
|
6. 后端/接口/pytest → `taskKind: "backend-test"`(不是 `--profile`);知识回写 `knowledge-sync`;图谱开荒 `knowledge-graph-bootstrap`。`--profile` 仅 `auto|minimal|standard|reviewed|supervised`。
|
|
40
|
-
7. 看板/observe → `agent-worker console serve --repo . --port 8790
|
|
40
|
+
7. 看板/observe → `agent-worker console`(默认 repo=当前目录、port=8790;兼容 `agent-worker console serve --repo . --port 8790`)(`/inspect/` 只读);`observe serve` 仅为兼容入口。
|
|
41
41
|
8. 分支合并 → 先读 `docs/operations/branch-merge-guideline.md`。
|
|
42
42
|
|
|
43
43
|
## 会话协议
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
10. 检查 `git status --short --branch`。
|
|
55
55
|
11. 运行本次任务相关的最小基线验证。
|
|
56
56
|
12. 如果用户提到"后端测试"、"接口测试"、"pytest"、"自动化测试",在任务 `task.json` 中设置 `taskKind: "backend-test"` 再 `dag run-task`;不要用 `--profile backend-test`(CLI 不接受该值,专用模板只走 taskKind)。知识回写用 `taskKind: "knowledge-sync"`(须 `featureId`),图谱开荒用 `taskKind: "knowledge-graph-bootstrap"`。`--profile` 仅表示治理强度:`auto|minimal|standard|reviewed|supervised`。
|
|
57
|
-
13. 如果用户提到"看板"、"observe"、"监控面板"、"启动看板",使用 `agent-worker console serve --repo . --port 8790
|
|
57
|
+
13. 如果用户提到"看板"、"observe"、"监控面板"、"启动看板",使用 `agent-worker console` 启动统一 Operator Console(默认 repo=当前目录、port=8790;兼容入口 `agent-worker console serve --repo . --port 8790`);`/inspect/` 提供只读检视。`agent-worker observe serve --repo . --port 8787` 仅为兼容入口。
|
|
58
58
|
14. 如果用户要求“合并 `<source>` 到 `<target>`”或“合并 origin/main 到当前分支”,先阅读 `docs/operations/branch-merge-guideline.md`,按影响自动选择快速、标准或深度模式;始终冻结 source SHA、审查双方功能、运行 merge-tree、生成 source-SHA 合并报告,并在提交前再次 fetch 防止主干前进。
|
|
59
59
|
|
|
60
60
|
## 会话协议
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
- 长期决策写入 `docs/`,不要只留在聊天里。
|
|
90
90
|
- 分支合并遵循 `docs/operations/branch-merge-guideline.md`;快速模式只用于可证明的低风险/no-op 合并,涉及冲突、init/package/runtime/release/public API 时必须升级为标准或深度模式。
|
|
91
91
|
- 后端测试、接口/API 测试、pytest 或明确的后端自动化测试,必须把 `.harness/tasks/<task-id>/task.json` 的 `taskKind` 设置为 `"backend-test"`,不得保留默认 `standard`。`backend-test` 是 `taskKind`,不是 `--profile` 的可选值;`dag run-task` 继续使用 `--profile auto` 选择治理等级。仅说“自动化测试”且前后端不明时,先根据任务源和项目技术栈判断,禁止无条件路由。
|
|
92
|
-
- 本地 Operator Console:`agent-worker console serve --repo . --port 8790
|
|
92
|
+
- 本地 Operator Console:`agent-worker console`(默认 repo=当前目录、port=8790;兼容 `agent-worker console serve --repo . --port 8790`),访问 `http://127.0.0.1:8790/`;其中 `/inspect/` 为只读运行检视。默认绑定本机 `127.0.0.1`;可用 `--host 0.0.0.0` / `--debug`,不要直接暴露到公开网络。端口被占用时不会自动更换,请用 `--port <port>` 显式指定。
|
|
93
93
|
- 面向使用者的新增、修改、删除或修复,应同步更新根目录 `CHANGELOG.md`;保持版本级摘要即可,不写过细技术细节。
|
|
94
94
|
- 面向用户的中文更新日志、README 和说明文档应使用自然、结果导向的表达:先说明用户能获得什么或问题如何改善,保留必要的命令和产品术语,避免逐字翻译、内部实现细节和无意义的中英混杂。
|
|
95
95
|
- 涉及 `loop-agent init` 或目标项目投影的改动,必须同步考虑目标项目生成物:`AGENTS.md`、`README.md`、`harness.json`、`ai_workspace/loop-agent/`、`scripts/`、`.agents/skills/`、`.harness/prompts`、`.gitignore`(loop-agent runtime managed block)和 npm 包内置 assets;目标项目根 `docs/` 和根 `skills/` 的旧投影需要由 `init update --apply-safe` 安全迁移或退役。
|
package/harness.json
CHANGED
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@ references:
|
|
|
16
16
|
- **允许**:`agent-worker` / `loop-agent` CLI;只读 `pool doctor`、`observe`、status/report;冻结 controller identity;选择 Ready 工作与 recovery 命令。
|
|
17
17
|
- **禁止**:绕过 CLI 直接 Edit 业务实现;Worker/DAG 失败后主会话「救火改文件」。
|
|
18
18
|
- **失败时只允许**:保留 evidence → `task retry` / `task reconcile` / `pool mark-failed` / human gate → 再经 CLI 重跑;实现写入仍只经 published `loop-agent` DAG。
|
|
19
|
-
- **Official vs Compatibility**:`agent-worker console
|
|
19
|
+
- **Official vs Compatibility**:`agent-worker console` 是 Official 本地控制面(裸入口直接启动;默认 repo=当前目录、port=8790;兼容入口 `agent-worker console serve --repo . --port 8790` 等价)。同进程提供 Operate + Inspect,Inspect 路径 `/inspect/#/...`;openCode 等主会话仍是 Compatibility Assist,二者**不是**同等保证。原 `observe serve` 为兼容期只读入口(启动时输出 `OBSERVE_SERVE_DEPRECATED`,stdout 仍只输出 URL),功能等价于 Console 的 Inspect 面。
|
|
20
20
|
|
|
21
21
|
## Route the Work
|
|
22
22
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: loop-agent
|
|
3
3
|
description: >-
|
|
4
|
-
Use when implementing features, processing PRDs or requirements, running structured loop-agent workflows, creating harness tasks, using Agent DAG, run-dag, pi-prompt planning/review, cursor-prompt one-shot sidecar intervention, initializing a target project with loop-agent, checking init update status, applying a safe init update, or converging website/governance docs after user-visible changes in loop-agent. Triggers: loop-agent, workflow, structured development, harness task, Agent DAG, docs converge, Converge Docs, 文档收敛, 结构化开发, 工作流, 需求实现, PRD 实现, 初始化 loop-agent, loop agent 初始化, loop agent初始化, loop-agent 初始化, 初始化更新校验, loop agent初始化更新校验, 检查初始化更新, 初始化安全更新, loop agent初始化安全更新, 应用初始化更新.
|
|
4
|
+
Use when implementing features, processing PRDs or requirements, running structured loop-agent workflows, creating harness tasks, using Agent DAG, run-dag, pi-prompt planning/review, cursor-prompt one-shot sidecar intervention, initializing a target project with loop-agent, checking init update status, applying a safe init update, or converging website/governance docs after user-visible changes in loop-agent. Triggers: loop-agent, workflow, structured development, harness task, Agent DAG, docs converge, Converge Docs, 文档收敛, 结构化开发, 工作流, 需求实现, PRD 实现, 初始化 loop-agent, loop agent 初始化, loop agent初始化, loop-agent 初始化, 初始化更新校验, loop agent初始化更新校验, 检查初始化更新, 初始化安全更新, loop agent初始化安全更新, 应用初始化更新. 强路由:loop-agent 帮我完成需求, 帮我实现, 帮我修复, 帮我开发, 使用 loop-agent 完成, 按 loop-agent 流程处理, 通用需求实现;这些表达确定性地进入 Agent DAG/CLI(new-task, dag run-task, dag validate, run-dag),主会话编排而不直接写业务实现.
|
|
5
5
|
references:
|
|
6
6
|
- path: references/harness-policy.md
|
|
7
7
|
required: true
|
|
@@ -117,6 +117,7 @@ SDK 回归或 SDK 可选依赖不可用时用 `cli-only` 诊断。CLI fallback
|
|
|
117
117
|
| 初始化更新校验 / loop agent初始化更新校验 / 检查初始化更新 | `loop-agent init check-update --repo-root . --markdown`(只读,不得隐含 `apply-safe`) |
|
|
118
118
|
| 初始化安全更新 / loop agent初始化安全更新 / 应用初始化更新 | 先 `loop-agent init check-update --repo-root . --markdown`,再 `loop-agent init update --repo-root . --apply-safe`(surface 缺失时先 `--bootstrap-surface`;human decisions 存在时停下等用户) |
|
|
119
119
|
| 初始化对齐 / 升级后对齐 / init reconcile / 控制器升级后对齐目标项目 | `loop-agent init reconcile --repo-root .`(统一入口:surface 缺失返回 `needs-baseline` 零写入;human decisions 返回 `needs-human-decision` 零写入;活跃 DAG/Worker 或 Worker 状态无法确认时返回 `blocked-active-runtime` 零写入;其余执行 safe actions 后复查) |
|
|
120
|
+
| loop-agent 帮我完成 / 帮我实现 / 帮我修复 / 帮我开发 <需求>;使用 loop-agent 完成 <X>;按 loop-agent 流程处理 <X> | 先 `loop-agent new-task <task-id> "任务标题"`,写 `source/需求.md` 与 `source/执行约束.md`,同步 `task.json.allowedPaths` / `forbiddenPaths`,再 `loop-agent dag run-task <task-id> --profile auto --strict-models`、`dag validate`、`run-dag`;主会话编排而不直接写业务实现 |
|
|
120
121
|
|
|
121
122
|
```bash
|
|
122
123
|
loop-agent init instructions --repo-root <target-repo>
|
|
@@ -336,8 +337,8 @@ loop-agent dag resume --run-id <run-id> # approve 后继续
|
|
|
336
337
|
- 从 run facts dry-run envelope 解析用 `dag decision inspect|validate`;`validate` 在无效 envelope 时 exit 1;永不自动 resume/retry。
|
|
337
338
|
- Decision Gate prompt 可用 `buildDagDecisionGateEvidence()`(`src/workflows/dag/decision-evidence.ts`)做与 `dag report --json`、`ai_workspace/loop-agent/templates/agent-dag-report.schema.json` 对齐的只读摘要;不 mutate run state,不执行 retry/resume。
|
|
338
339
|
- 仅当有意在 `.harness/dag-runs/active/` 下要 active run snapshot 时用 `run-dag --dry-run`。
|
|
339
|
-
- task source 应从 `harness.json.workflowPolicy.dag.profileRouting` 与确定性 candidate `governanceProfile`
|
|
340
|
-
- 默认 `standard`
|
|
340
|
+
- task source 应从 `harness.json.workflowPolicy.dag.profileRouting` 与确定性 candidate `governanceProfile` 选择治理强度时用 `dag run-task --profile auto`。无 `--profile` 仅用于旧 standard-compatible 输出;`--profile minimal|standard|reviewed|supervised` 记录治理强度,不替换已识别的专用业务 workflow。
|
|
341
|
+
- 默认 `standard` 任务先读取 `source/需求.md` 中的结构化任务类型,再结合 `allowedPaths` 与 React/Next/Vue 项目能力做确定性分类。确认是前端项目且任务不是明确后端、混合、排除前端或仅文档/测试范围时,自动选择 `frontend-implementation`,不依赖需求关键词;普通后端实现绝不自动进入 `backend-test`。
|
|
341
342
|
- 新生成 DAG 会冻结任务源路径、SHA-256 和显式 `REQ/BR/AC` 到 `sourceBinding`。前端计划漏号时 `frontend-requirement-coverage-shell` 在 writer 前阻断。中断恢复应修复 task source 后重新运行 `dag run-task`,不要生成只携带上游摘要的 impl-only DAG;strict governance 会拒绝无来源绑定且无只读 planner 上游的 v3 孤立 writer。
|
|
342
343
|
- 显式专用 `taskKind` 保持兼容并优先于任务源分类,也不扩充 governance profile:`frontend-implementation` 可有意覆盖为带 Mock 规范评估、contract gate、design gate 和验证链的前端模板;可选 `frontendMock` 配置 auto/required/disabled、既有服务目录和专项验证命令,required 合同不完整时不会生成 writer。`backend-test` 显式选择需求分析 → 功能用例 → 评审 → pytest 生成/执行 → 复盘的后端测试工程模板。
|
|
343
344
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
此 policy 驱动 `dag run-task --profile auto`:CLI 仍要求显式 `dag run-task`、`dag validate`、`run-dag`,但 `--profile auto` 在确定性 candidate `governanceProfile` 推断后应用 `workflowPolicy.dag.profileRouting`。生成器还会把 `outputLanguage` 写入 DagSpec,runner 在每个 Pi/Cursor 节点 prompt 中注入语言规则;代码、命令、路径、JSON 字段与 gate token 保持原样。`humanGatePolicy` 是默认人机边界声明;真实暂停仍由 DAG 节点的 `decisionGate.mode: "pause-on-human"` 与 decision envelope 触发。无 profile 的 `dag run-task <task-id>` 仍为 standard-compatible,供 legacy/review workflow。
|
|
16
16
|
|
|
17
|
-
对于默认 `standard`
|
|
17
|
+
对于默认 `standard` 任务,生成器先读取 `source/需求.md` 中的结构化任务类型,再结合 `allowedPaths` 与 React/Next/Vue 强工程证据做确定性分类。确认是前端项目且任务不是明确后端、前后端混合、排除前端或仅文档/测试范围时,默认选择 `frontend-implementation` DAG,不依赖需求关键词。分类不会把普通后端实现路由到 `backend-test`;显式 profile、`workflowPolicy` 或 supervised quality gate 只记录治理强度,不把已识别的前端业务 workflow 换回通用模板。
|
|
18
18
|
|
|
19
19
|
前端专用链保留独立 contract/scout;plan 同时选择 Mock/API 策略并输出结构化 implementation contract。design initial pass 直接使用原计划,只有 request-revision 才运行 revision/final review;small-risk 只执行一次 design review。`frontend-prewrite-gate-shell` 合并生效 verdict、REQ/BR/AC 覆盖、Mock policy 和 contract 物化,是唯一写入授权。实现后 `frontend-verify-assess-shell` 合并 Mock/static/behavior/trace/assessment;只有 `eligible=true` 才运行同 writeSet 的 repair 和 `frontend-reverify-shell`。`frontend-review-context-shell` 绑定真实 diff 与有效验证证据后再 review/closeout。standard/high-risk 为 15 个顶层节点,small-risk 为 13;绿色路径执行 11 个节点、7 次 Pi。生成期 blocked Mock 只生成一个确定性阻塞节点且没有 writer。
|
|
20
20
|
|
|
@@ -67,7 +67,7 @@ loop-agent run-dag --dag <temp-dir>/hybrid-dag.json --init-only --canvas-path <t
|
|
|
67
67
|
**运维 warning**:
|
|
68
68
|
|
|
69
69
|
- **常规 validation**:`dag validate --dag <path>` 做 schema/topology/ranks。JSON 输出含 `governanceProfile`(确定性 `minimal|standard|reviewed|supervised` 推断,含 `process` / `delivery` / `codeChange` signal 与 `reasons`),及 model-matrix drift、governance lint(如 read-only artifact-boundary drift 或 DAG 内 `check-repo.sh` shell env drift)的 warnings。手写临时 DAG spec 执行前用 `dag validate --dag <path> --strict-models`;governance warning 应 fail fast 时加 `--strict-governance`。含 `executor: "cursor"` 的旧 DAG 会在 schema 校验失败;默认生成 DAG 使用 `pi` read-only / Pi write profile / shell。仅当有意在 `.harness/dag-runs/active/` 要 active run snapshot 时用 `run-dag --dry-run`。
|
|
70
|
-
- **Governance profile 推断与 routing(code vs skill 分工)**:`./src/workflows/dag/governance-profile.ts` 从 DAG 结构与 write scope 做 **硬确定性推断**。JSON 输出 **报告** `process` / `delivery` / `codeChange` signal 与人类可读 `reasons`;`profile` tier(`minimal|standard|reviewed|supervised`)仅由该模块 code rule 选择(如多个 exclusive writer、repair node、review-gate topology、`loop-agent-runtime-paths`、`scripts-ci-harness-paths`、weak post-implementation shell verification、supervised topology)。baseline `forbiddenPaths`(`.harness/**`、`.harness/dag-runs/**`、`artifacts/**`)是默认 governance,**本身不是** process-risk signal。skill prompt 与本 reference **解释** tier 并摘要 profile 选择原因;不替代 code 推断。`dag run-task` 转发 embedded validate step 的同一 candidate `governanceProfile`。`dag run-task --profile auto` 先将 candidate profile 经 `harness.json.workflowPolicy.dag.profileRouting` 映射,再在 candidate delivery signal 含 `loop-agent-runtime-paths`、`scripts-ci-harness-paths` 或 `public-contract-paths` 时应用 M4 `supervised-quality-gate` promotion;`profileRouting.routingReasons` 记录确定性 reason。无 profile `dag run-task <task-id>` 仍为 standard-compatible;显式 `--profile minimal|standard|reviewed|supervised`
|
|
70
|
+
- **Governance profile 推断与 routing(code vs skill 分工)**:`./src/workflows/dag/governance-profile.ts` 从 DAG 结构与 write scope 做 **硬确定性推断**。JSON 输出 **报告** `process` / `delivery` / `codeChange` signal 与人类可读 `reasons`;`profile` tier(`minimal|standard|reviewed|supervised`)仅由该模块 code rule 选择(如多个 exclusive writer、repair node、review-gate topology、`loop-agent-runtime-paths`、`scripts-ci-harness-paths`、weak post-implementation shell verification、supervised topology)。baseline `forbiddenPaths`(`.harness/**`、`.harness/dag-runs/**`、`artifacts/**`)是默认 governance,**本身不是** process-risk signal。skill prompt 与本 reference **解释** tier 并摘要 profile 选择原因;不替代 code 推断。`dag run-task` 转发 embedded validate step 的同一 candidate `governanceProfile`。`dag run-task --profile auto` 先将 candidate profile 经 `harness.json.workflowPolicy.dag.profileRouting` 映射,再在 candidate delivery signal 含 `loop-agent-runtime-paths`、`scripts-ci-harness-paths` 或 `public-contract-paths` 时应用 M4 `supervised-quality-gate` promotion;`profileRouting.routingReasons` 记录确定性 reason。无 profile `dag run-task <task-id>` 仍为 standard-compatible;显式 `--profile minimal|standard|reviewed|supervised` 与自动 promotion 记录治理强度,已识别的前端业务 workflow 仍使用前端专用模板。高风险 task 应用 `--profile auto` 或显式 `--profile supervised`,而非显式 `--profile reviewed`。
|
|
71
71
|
- **Executor model routing**:DAG spec 选 `executor` 与 `complexity`,可通过 `executorModels.pi` 覆盖 model 名;不选 provider。默认 routing:Pi LOW=`gpt-5.3-codex-spark`、MED=`glm-5.2`、HIGH=`gpt-5.5`。`shell` 不用 model,忽略 `executorModels`。
|
|
72
72
|
- **Active visibility**:真实 `run-dag` execution 在 run/node 转换时写 active `state.json`,归档前 core runner 暴露 isolated `DagRunObserver` hook 供 derived view。`.harness/dag-runs/completed/<run-id>/` / `paused/<run-id>/` 仍是 source of truth;observer 输出非 canonical。
|
|
73
73
|
- **可选 Canvas**:传 `--canvas-path <abs-path>` 或 `--canvas <name>` 输出 derived `.canvas.tsx` live view。省略 flag 行为不变。`--init-only` + Canvas 无需 `CURSOR_API_KEY`。
|