19 Commits

Author SHA1 Message Date
pangtong-fujunshi d25eea4e82 Merge PR #115: [moz] docs(§21): v1.2 §15b Comment @assignee 行为约束
Deploy / ci (push) Failing after 14s
Deploy / deploy (push) Has been skipped
Deploy / notify-deploy-failure (push) Successful in 2s
Deploy / notify-deploy-success (push) Successful in 0s
2026-06-21 15:30:10 +00:00
cfdaily 38c676c627 [moz] docs(§21): v1.2 §15b Comment @assignee 行为约束
CI / lint (pull_request) Successful in 31s
CI / test (pull_request) Successful in 53s
CI / frontend (pull_request) Successful in 27s
CI / notify-on-failure (pull_request) Successful in 0s
问题:agent 在 Issue/PR 上写 comment 不 @ 人时,assignee 收不到通知。
方案:prompt 约束(不走代码自动通知),让 agent 自主判断何时 @assignee。
不做:代码路径 3(自动通知 assignee,噪音问题)、subscribe 机制。

Closes #115
2026-06-21 23:27:10 +08:00
pangtong-fujunshi 0c1acbd073 Merge PR #113
Deploy / ci (push) Failing after 18s
Deploy / deploy (push) Has been skipped
Deploy / notify-deploy-failure (push) Successful in 2s
Deploy / notify-deploy-success (push) Successful in 0s
2026-06-21 15:04:35 +00:00
cfdaily b921b094ca [moz] impl(§21): §11b Issue opened 无 assignee → discussion task delegate 庞统
CI / lint (pull_request) Successful in 33s
CI / test (pull_request) Successful in 47s
CI / frontend (pull_request) Successful in 29s
CI / notify-on-failure (pull_request) Successful in 0s
设计文档 PR #112 已合并。本 PR 实现代码改动:

1. _send_toolchain_task to_agent 允许 None(None 时跳过 AGENT_IDS 校验)
2. _handle_issues opened 分支:无 assignee + 有 type/* label → 创建 issue_discussion task(assignee=None)
3. router 对 assignee=None → delegate 庞统(FALLBACK_AGENT,现有机制)
4. toolchain_handler verify: issue_discussion auto-pass
5. _ACTION_HINTS + EVENT_LABELS_ZH 加 issue_closed/issue_discussion

司马懿 Review 建议项处理:
- S1: router FALLBACK_AGENT=pangtong-fujunshi(router.py:71),assignee=None 必走 delegate
- S2: _send_toolchain_task 校验已改为 to_agent is not None and ...(line 246)

Closes #113
2026-06-21 21:18:29 +08:00
pangtong-fujunshi 7bb7637d24 Merge PR #112
Deploy / ci (push) Failing after 17s
Deploy / deploy (push) Has been skipped
Deploy / notify-deploy-failure (push) Successful in 2s
Deploy / notify-deploy-success (push) Successful in 1s
2026-06-21 12:57:01 +00:00
cfdaily 02883750f5 [moz] docs(§21): v1.1 §11b Issue opened 无 assignee 处理 + 修正 §13.1 触发路径
CI / lint (pull_request) Successful in 42s
CI / test (pull_request) Successful in 43s
CI / frontend (pull_request) Successful in 21s
CI / notify-on-failure (pull_request) Successful in 1s
- 新增 §11b: 无 assignee 的 Issue opened → toolchain task(assigee=None) → delegate 庞统 discussion
- 修正 §13.1: 原设计写 issues/assigned,实际 Gitea 无 assignee 的 Issue 发的是 issues/opened
- §11b.3: _send_toolchain_task to_agent 允许 None
- §11b.6: 为什么用 delegate 而非 broadcast 的设计决策

Closes #112
2026-06-21 20:54:16 +08:00
zhangfei-dev 60925c3395 Merge PR #110: [moz] fix(test): 修复 integration/e2e 测试 sys.path 污染导致 CI 失败
Deploy / ci (push) Failing after 8s
Deploy / deploy (push) Has been skipped
Deploy / notify-deploy-failure (push) Successful in 1s
Deploy / notify-deploy-success (push) Successful in 0s
2026-06-20 23:30:46 +00:00
cfdaily 7335cafa90 [moz] fix(test): 补回 test_e2e_v27.py 的 pytest.mark.e2e 标记 (Review G1)
CI / lint (pull_request) Successful in 25s
CI / test (pull_request) Successful in 1m1s
CI / frontend (pull_request) Successful in 16s
CI / notify-on-failure (pull_request) Successful in 1s
2026-06-21 07:27:47 +08:00
cfdaily 1362cc5d64 [moz] fix(test): 修复 integration/e2e 测试 sys.path 污染导致 CI 失败
CI / lint (pull_request) Successful in 19s
CI / test (pull_request) Successful in 43s
CI / frontend (pull_request) Successful in 16s
CI / notify-on-failure (pull_request) Successful in 0s
根因:4 个测试文件在模块级别执行 sys.path.insert(0, DEPLOY_DIR),
即使测试被 deselect/conftest 跳过,模块仍被 import 触发 sys.path 污染。
后续测试 import src 时从安装目录加载(缺少新增模块如 algorithms/)。

修复:在 sys.path 操作前加 allow_module_level skip guard,
未设置 RUN_INTEGRATION 时跳过整个模块,不执行任何模块级代码。

影响文件:
- tests/integration/test_e2e_api_s1_s8.py
- tests/e2e/test_e2e_stress.py
- tests/e2e/test_e2e_scenarios.py
- tests/e2e/test_e2e_v27.py
2026-06-21 07:23:41 +08:00
zhangfei-dev 6df7563070 Merge PR #109: [moz] feat(algorithms): 排序算法实现最大值查找
Deploy / ci (push) Failing after 7s
Deploy / deploy (push) Has been skipped
Deploy / notify-deploy-failure (push) Successful in 1s
Deploy / notify-deploy-success (push) Successful in 0s
2026-06-20 23:15:02 +00:00
cfdaily da137ba193 [moz] feat: 排序算法实现最大值查找
CI / lint (pull_request) Successful in 14s
CI / test (pull_request) Failing after 34s
CI / frontend (pull_request) Successful in 15s
CI / notify-on-failure (pull_request) Successful in 0s
2026-06-21 07:03:51 +08:00
pangtong-fujunshi ac4e28a57c Merge PR #107
Deploy / ci (push) Failing after 11m50s
Deploy / deploy (push) Has been skipped
Deploy / notify-deploy-failure (push) Successful in 0s
Deploy / notify-deploy-success (push) Successful in 0s
2026-06-20 15:46:20 +00:00
cfdaily 41d60cca2c [moz] fix: M1 label type/feature→type/feat 对齐仓库实际 label(姜维 Review)
CI / lint (pull_request) Successful in 45s
CI / test (pull_request) Successful in 1m0s
CI / frontend (pull_request) Successful in 27s
CI / notify-on-failure (pull_request) Successful in 1s
- impl.yml: type/feature → type/feat
- toolchain_routes.py: business_type feature→feat(匹配 Gitea label)
- toolchain-templates.yaml: feature→feat
2026-06-20 23:40:15 +08:00
cfdaily bae3244e24 [moz] fix: S2 _steps_cache None 崩溃修复(司马懿 Review 反馈)
clear_cache() 中 _steps_cache.clear() 改为 _steps_cache = None,
避免在 get_steps() 之前调用时 AttributeError。
2026-06-20 23:40:15 +08:00
cfdaily e238eaec31 [moz] fix: CI lint E303 + F401 修复
- toolchain_handler.py: 删除多余空行 (E303)
- toolchain_templates.py: 删除 unused json import (F401)
2026-06-20 23:40:15 +08:00
cfdaily b0bca0df5c [moz] fix(§21): GAP-1 issue_assigned 6路分流 + GAP-2 issue_closed auto-pass + GAP-3 Discussion Prompt v3
第一轮背靠背一致性检查发现的 3 个 GAP:

GAP-1: §21 §5 issue_assigned 按 type label 分流
- 从 labels 解析 business_type (feature/impl/bug/docs/refactor/test)
- 从 toolchain-templates.yaml get_steps() 获取对应 steps
- fallback 到硬编码 steps(向后兼容)

GAP-2: §21 §11 Issue closed 事件处理
- toolchain_handler verify 加 issue_closed auto-pass
- toolchain_routes 加 issue closed webhook → 纯通知 task

GAP-3: §21 §13.2 Discussion Prompt v3
- 新增 你是谁 段(agent_identity 注入)
- 新增 你必须做什么 4 维度(定位/建议/认领/风险)
- 新增 Comment 格式(角色名开头)
- Boids 行为准则不变
- 黑板 API 不变(§21 范围仅 toolchain)
2026-06-20 23:39:56 +08:00
pangtong-fujunshi 6d1d906551 Merge PR #106
Deploy / ci (push) Failing after 12m13s
Deploy / deploy (push) Has been skipped
Deploy / notify-deploy-failure (push) Successful in 0s
Deploy / notify-deploy-success (push) Successful in 0s
2026-06-20 15:35:47 +00:00
cfdaily 37b8115485 [moz] fix(lint): remove unused json import in toolchain_templates.py
CI / lint (pull_request) Successful in 34s
CI / test (pull_request) Successful in 1m45s
CI / frontend (pull_request) Successful in 20s
CI / notify-on-failure (pull_request) Successful in 0s
2026-06-20 23:29:00 +08:00
cfdaily 7c11c6b9aa [moz] impl(§21): toolchain-templates.yaml + Issue 模板补全 + G1 修复 + §20 superseded
CI / lint (pull_request) Failing after 1m7s
CI / test (pull_request) Has been skipped
CI / frontend (pull_request) Has been skipped
CI / notify-on-failure (pull_request) Successful in 2s
- config/toolchain-templates.yaml: §4 steps 模板化(7 种 business_type + ci_failure + review_result)
- src/daemon/toolchain_templates.py: 加 get_steps()/get_output_template() YAML 加载
- .gitea/ISSUE_TEMPLATE/: 补 impl.yml + docs.yml + refactor.yml
- src/daemon/toolchain_handler.py: G1 弯引号修复
- docs/design/20-issue-centric-orchestration.md: status 改为 superseded by §21

Closes #106
2026-06-20 23:10:00 +08:00
17 changed files with 724 additions and 40 deletions
+27
View File
@@ -0,0 +1,27 @@
name: 文档任务
about: 编写或更新文档
title: "[moz] docs: "
labels:
- type/docs
body:
- type: textarea
id: description
attributes:
label: 文档目标
description: 要写什么文档,解决什么问题
validations:
required: true
- type: textarea
id: path
attributes:
label: 文档路径
description: 目标文件路径(如 docs/design/xxx.md
validations:
required: true
- type: textarea
id: outline
attributes:
label: 大纲
description: 文档结构和要点
validations:
required: true
+33
View File
@@ -0,0 +1,33 @@
name: 实现任务
about: 按设计文档实现功能
title: "[moz] impl: "
labels:
- type/feat
body:
- type: textarea
id: description
attributes:
label: 实现目标
description: 清晰描述要实现什么
validations:
required: true
- type: textarea
id: design_doc
attributes:
label: 设计文档
description: 关联的设计文档路径(如 docs/design/xxx.md
validations:
required: true
- type: textarea
id: scope
attributes:
label: 实现范围
description: 涉及哪些文件/模块
validations:
required: true
- type: textarea
id: acceptance
attributes:
label: 验收标准
validations:
required: true
+27
View File
@@ -0,0 +1,27 @@
name: 重构任务
about: 重构现有代码
title: "[moz] refactor: "
labels:
- type/refactor
body:
- type: textarea
id: description
attributes:
label: 重构目标
description: 为什么重构,期望改善什么
validations:
required: true
- type: textarea
id: scope
attributes:
label: 影响范围
description: 涉及哪些文件/模块,是否有 breaking change
validations:
required: true
- type: textarea
id: tests
attributes:
label: 测试保障
description: 重构前哪些测试必须通过
validations:
required: true
+171
View File
@@ -0,0 +1,171 @@
# toolchain-templates.yaml
# §21 §4 steps 模板化 — 按 action_type + business_type 查找
# 占位符 {issue_number}/{brief}/{title}/{pr_number} 等运行时渲染
# ---------------------------------------------------------------------------
# issue_assigned(按 Issue label 分 6 路 + infrastructure
# ---------------------------------------------------------------------------
issue_assigned:
feat:
steps:
- "理解需求(Issue body)→ 如有不明确在 Issue comment 追问"
- "git checkout main && git pull origin main"
- "git checkout -b feat/{issue_number}-{brief}"
- "编码实现 + 写 UT"
- "文档同步:如果本次改动涉及设计变更或接口变更,在同一分支更新 docs/design/ 对应文档。如无需更新,在 action report 中说明「文档无需更新」"
- "git add -A && git commit -m '[moz] feat: {title}' && git push origin feat/{issue_number}-{brief}"
- "CI 通过后创建 PRbody 含 Closes #{issue_number}"
- "等 Review"
output_template: |
[Action Report]
**分支**feat/{issue_number}-{brief}
**PR**#{pr_number}
**改动文件**{files}
**CI**{ci_status}
**文档同步**{doc_status}
impl:
steps:
- "读设计文档(Issue body 中的路径)→ 理解实现范围"
- "git checkout main && git pull origin main"
- "git checkout -b impl/{issue_number}-{brief}"
- "按设计编码实现 + 写 UT"
- "文档同步:如果本次改动涉及设计变更或接口变更,在同一分支更新 docs/design/ 对应文档。如无需更新,在 action report 中说明「文档无需更新」"
- "git add -A && git commit -m '[moz] impl: {title}' && git push origin impl/{issue_number}-{brief}"
- "CI 通过后创建 PRbody 含 Closes #{issue_number} + 设计文档路径)"
- "等 Review"
output_template: |
[Action Report]
**设计文档**{design_doc}
**分支**impl/{issue_number}-{brief}
**PR**#{pr_number}
**改动文件**{files}
**CI**{ci_status}
**文档同步**{doc_status}
bug:
steps:
- "读 Bug 描述 + 复现步骤(Issue body"
- "定位根因(读代码/日志,不要猜测)"
- "git checkout main && git pull origin main"
- "git checkout -b fix/{issue_number}-{brief}"
- "修复 + 写回归测试"
- "文档同步:如修复涉及设计/接口变更,同步更新 docs/design/ 对应文档"
- "git add -A && git commit -m '[moz] fix: {title}' && git push origin fix/{issue_number}-{brief}"
- "CI 通过后创建 PRbody 含 Closes #{issue_number} + 根因和修复方式)"
- "等 Review"
output_template: |
[Action Report]
**根因**{root_cause}
**修复方式**{fix_approach}
**分支**fix/{issue_number}-{brief}
**PR**#{pr_number}
**CI**{ci_status}
**文档同步**{doc_status}
docs:
steps:
- "读文档目标(Issue body"
- "git checkout main && git pull origin main"
- "git checkout -b docs/{issue_number}-{brief}"
- "编写文档到 docs/ 对应目录"
- "git add -A && git commit -m '[moz] docs: {title}' && git push origin docs/{issue_number}-{brief}"
- "创建 PRbody 含 Closes #{issue_number}"
- "等 Review"
output_template: |
[Action Report]
**文档路径**{doc_path}
**分支**docs/{issue_number}-{brief}
**PR**#{pr_number}
refactor:
steps:
- "读重构目标 + 影响范围(Issue body"
- "git checkout main && git pull origin main"
- "git checkout -b refactor/{issue_number}-{brief}"
- "重构 + 确保现有测试不 breakpython3 -m pytest tests/unit/ -q"
- "文档同步:如重构涉及架构变更,同步更新 docs/design/ 对应文档"
- "git add -A && git commit -m '[moz] refactor: {title}' && git push origin refactor/{issue_number}-{brief}"
- "CI 通过后创建 PRbody 含 Closes #{issue_number} + 重构内容和影响范围)"
- "等 Review"
output_template: |
[Action Report]
**重构范围**{scope}
**测试结果**{test_result} passed
**分支**refactor/{issue_number}-{brief}
**PR**#{pr_number}
**CI**{ci_status}
**文档同步**{doc_status}
test:
steps:
- "读测试目标(Issue body"
- "git checkout main && git pull origin main"
- "git checkout -b test/{issue_number}-{brief}"
- "编写测试脚本到 tests/ 对应目录"
- "运行测试验证(python3 -m pytest {test_file} -v"
- "git add -A && git commit -m '[moz] test: {title}' && git push origin test/{issue_number}-{brief}"
- "创建 PRbody 含 Closes #{issue_number}"
- "等 Review"
output_template: |
[Action Report]
**测试文件**{test_file}
**测试结果**{test_result}
**分支**test/{issue_number}-{brief}
**PR**#{pr_number}
infrastructure:
steps:
- "根据 Issue body 中的错误来源和日志片段排查问题"
- "修复基础设施问题(CI runner/网络/Gitea/磁盘等)"
- "修复后在 Issue 上 comment 说明修复方式和结果"
- "提交 action reportPOST http://localhost:8083/api/projects/_toolchain/tasks/<task_id>/commentscomment_type=action_report"
output_template: |
[Action Report]
**问题**{problem}
**根因**{root_cause}
**修复方式**{fix}
**验证**{verification}
# ---------------------------------------------------------------------------
# ci_failure
# ---------------------------------------------------------------------------
ci_failure:
steps:
- "查看完整 CI 日志(PR 页面或 Gitea Actions 页面)"
- "根据 CI 日志判断失败原因类型:\n a. 代码问题(lint/test 失败)→ 修复失败的测试 → push 到原分支 → CI 自动重跑\n b. 基础设施问题(runner 环境/Python/venv/Gitea/网络故障)→ 在该仓库创建 Issue 指派 jiangwei-infralabel 必须包含 type/infrastructure"
- "文档同步:如修复涉及设计/接口变更,同步更新 docs/design/ 对应文档"
- "提交 action reportPOST http://localhost:8083/api/projects/_toolchain/tasks/<task_id>/commentscomment_type=action_report)— 报告中说明判断的原因类型和执行的操作,以及文档是否需要更新"
output_template: |
[Action Report]
**原因类型**{cause_type}
**操作**{action}
**CI 重跑**{ci_status}
**文档同步**{doc_status}
# ---------------------------------------------------------------------------
# review_result — APPROVED
# ---------------------------------------------------------------------------
review_result_approved:
steps:
- "合并 PRGitea API: POST /repos/{repo}/pulls/{pr_number}/merge"
- "提交 action reportPOST http://localhost:8083/api/projects/_toolchain/tasks/<task_id>/commentscomment_type=action_report"
output_template: |
[Action Report]
**PR #{pr_number}**merged
# ---------------------------------------------------------------------------
# review_result — REQUEST_CHANGES
# ---------------------------------------------------------------------------
review_result_request_changes:
steps:
- "按审查意见逐条修改代码"
- "文档同步:如审查涉及设计/接口变更,同步更新 docs/design/ 对应文档"
- "push 到原分支 → CI 自动跑"
- "CI 通过后等重新 Review"
- "提交 action reportPOST http://localhost:8083/api/projects/_toolchain/tasks/<task_id>/commentscomment_type=action_report)— 报告中必须说明文档是否需要更新及处理结果"
output_template: |
[Action Report]
**修改内容**{changes}
**CI**{ci_status}
**文档同步**{doc_status}
@@ -2,7 +2,7 @@
title: "Issue-Centric Orchestration — Gitea Issue 替代黑板 DB 协作面" title: "Issue-Centric Orchestration — Gitea Issue 替代黑板 DB 协作面"
created: 2026-06-19 created: 2026-06-19
version: v2.1 draft version: v2.1 draft
status: draft status: superseded by §21 (toolchain 部分)
changelog: v2.1 修正 M1dispatcher 直接 SQL 声明)+ M2Phase 格式)+ S1/S2TaskAdapter 残留清理) changelog: v2.1 修正 M1dispatcher 直接 SQL 声明)+ M2Phase 格式)+ S1/S2TaskAdapter 残留清理)
v2.0 纳入姜维+司马懿 Review 反馈 + 庞统 Repository 模式修正 v2.0 纳入姜维+司马懿 Review 反馈 + 庞统 Repository 模式修正
v1.1 纳入姜维 Review 反馈 v1.1 纳入姜维 Review 反馈
+185 -6
View File
@@ -1,9 +1,11 @@
--- ---
title: "Unified Toolchain Design — 统一工具链工作流设计" title: "Unified Toolchain Design — 统一工具链工作流设计"
created: 2026-06-20 created: 2026-06-20
version: v1.0 draft version: v1.2 draft
status: draft status: draft
changelog: v1.0 初版 changelog: v1.2 补充 §15b Comment @assignee 行为约束
v1.1 补充 §11b Issue opened 无 assignee 处理 + 修正 §13.1 触发路径
v1.0 初版
--- ---
# Unified Toolchain Design # Unified Toolchain Design
@@ -569,6 +571,125 @@ issue_closed 走 auto-pass(和 review_merged 一样),纯通知不需要 ag
--- ---
## §11b. Issue opened 无 assignee 处理
### 11b.1 问题
`_handle_issues``action == "opened"` 分支只处理"部署失败"关键词和 @mention。无 assignee 的普通 Issue(如庞统创建的 parent Issue)被静默忽略,无法触发 discussion 流程。
### 11b.2 设计
`_handle_issues``opened` 分支新增无 assignee 处理路径:
```
Gitea Issue 创建(无 assignee, 有 type/* label
→ webhook: issues/opened
→ _handle_issues:
action == "opened"
→ 非"部署失败"
→ 无 assignee + 有 type/* label
→ 创建 toolchain taskassignee=None, action_type=issue_discussion
→ ticker 扫到 pending task
→ router.route: assignee=None → 不走快速路径 4 → delegate 庞统
→ 庞统收到 discussion promptspawner._build_discussion_prompt
→ 庞统在 Issue 上 comment 发起讨论,引导其他 agent 参与
```
### 11b.3 `_send_toolchain_task` 改动
`to_agent` 参数允许 `None`
```python
def _send_toolchain_task(
to_agent: str | None, # None = 无指派,待路由
...
) -> str:
# None 不校验 AGENT_IDS(非 None 时仍然校验)
if to_agent is not None and to_agent not in AGENT_IDS:
logger.warning("Unknown agent: %s, skipping toolchain task", to_agent)
return ""
task = Task(
...
assignee=to_agent, # None → 待路由
...
)
```
### 11b.4 `_handle_issues` opened 分支改动
```python
elif action == "opened":
if "部署失败" in issue_title:
# 部署失败处理(不变)
...
elif not (issue.get("assignees") or issue.get("assignee")):
# §11b: 无 assignee 的普通 Issue
labels_list = [lbl.get("name", "")
for lbl in (issue.get("labels") or [])]
has_type_label = any(lbl.lower().startswith("type/") for lbl in labels_list)
if not has_type_label:
return # 无 type label 不处理(避免噪音)
title = f"Issue 讨论: {issue_title} ({repo}#{issue_number})"
_send_toolchain_task(
to_agent=None, # 无指派 → router delegate 庞统
title=title,
description=f"## Issue 需要讨论\n\n{issue.get('body', '(无描述)')}",
event_type="issue_discussion",
action_type="issue_discussion",
steps=[], # discussion 不需要结构化步骤
context_data={
"issue_number": issue_number,
"repo": repo,
"issue_title": issue_title,
"issue_body": issue.get("body", ""),
},
)
return
# @mention 检查(不变)
...
```
### 11b.5 ToolchainHandler verify 改动
`issue_discussion` 走 auto-pass(纯触发,不需要 agent 执行步骤):
```python
if meta.get("action_type") == "issue_discussion":
return VerifyResult(True, "discussion_passthrough",
"issue_discussion auto-pass")
```
### 11b.6 为什么用 delegate 而非 broadcast
现有 ticker 有两种调度路径:
- **deterministic**:有 assignee → 直接 spawn 给该 agent
- **broadcast**:无 assignee → 所有空闲 agent 收到 claim prompt
无 assignee 的 Issue 如果走 broadcast,每个 agent 收到的是 claim prompt(认领 task),不是 discussion prompt(讨论 Issue)。而 §13.2 设计的 discussion prompt 才是正确行为——agent 应该讨论 Issue 内容,自主决定是否参与,而不是竞争认领一个 task。
**delegate 庞统**是更合理的路径:
- 庞统收到 task → 读取 Issue body → 按副军师职责发起讨论
- 庞统在 Issue 上 comment 引导其他 agent 参与
- 其他 agent 看到 @mention 后自主创建 sub Issue
如果未来需要多 agent 并行讨论,可以在 ticker 中新增 discussion broadcast 路径(spawn_type=discussion)。但 MVP 阶段 delegate 庞统已足够。
### 11b.7 涉及改动
| 文件 | 改动 |
|------|------|
| `src/api/toolchain_routes.py` `_send_toolchain_task` | `to_agent` 允许 None |
| `src/api/toolchain_routes.py` `_handle_issues` | opened 分支加无 assignee 路径 |
| `src/daemon/toolchain_handler.py` `verify_completion` | issue_discussion auto-pass |
| `src/daemon/toolchain_handler.py` `_ACTION_HINTS` | 新增 issue_discussion |
| `src/daemon/toolchain_handler.py` `EVENT_LABELS_ZH` | 新增 issue_discussion |
| `tests/` | 新增 issue_discussion 测试 |
---
## §12. AI Native 能力完整性(v2 补充) ## §12. AI Native 能力完整性(v2 补充)
> 本节确保 Gitea 替代黑板后,PRD-v3.0 的 AI native 能力不降级。 > 本节确保 Gitea 替代黑板后,PRD-v3.0 的 AI native 能力不降级。
@@ -606,12 +727,17 @@ issue_closed 走 auto-pass(和 review_merged 一样),纯通知不需要 ag
庞统创建 parent Issue(无 assignee)后,触发 discussion 庞统创建 parent Issue(无 assignee)后,触发 discussion
``` ```
庞统创建 parent Issue → webhook: issues/assigned(或 ticker 发现 pending 无 assignee 庞统创建 parent Issue无 assignee
daemon 检测:无 assignee = 广播讨论 Gitea webhook: issues/opened(注意:不是 assigned,无 assignee 的 Issue 只触发 opened
ticker 广播 spawn 所有 agentspawn_type=discussion _handle_issues opened 分支检测:无 assignee + 有 type/* label
每个 agent 收到 DISCUSSION_PROMPT_TEMPLATE 创建 toolchain taskassignee=None, action_type=issue_discussion
→ ticker 扫到 pending task → router delegate 庞统
→ 庞统收到 discussion prompt → 在 Issue 上 comment 发起讨论
→ 其他 agent 看到讨论 → 自主创建 sub Issue 认领
``` ```
**关键修正(v1.1**:原设计写 "webhook: issues/assigned",实际 Gitea 对无 assignee 的 Issue 只发 `issues/opened` 事件。`_handle_issues``opened` 分支需要新增无 assignee 处理路径(见 §11b)。
### 13.2 Discussion Prompt 设计(v3 重构) ### 13.2 Discussion Prompt 设计(v3 重构)
**设计参考**:Edict(角色驱动主动发言)+ APM(自包含 Task Prompt+ PAV 循环(输入/输出/验证)。 **设计参考**:Edict(角色驱动主动发言)+ APM(自包含 Task Prompt+ PAV 循环(输入/输出/验证)。
@@ -954,6 +1080,59 @@ agent 写 Issue/PR comment → webhook: issue_comment/created → daemon 解析
--- ---
## §15b. Comment @assignee 行为约束
### 15b.1 问题
`_handle_issue_comment` 只有两条通知路径(CI 关键词 + @mention)。当 agent 在 Issue/PR 上写了 comment 但没有 @ 任何人时,Issue 的 assignee/创建者收不到通知。
例:姜维在 Issue #114 上写了排查结论,没有 @ 庞统。庞统不知道排查已完成。
### 15b.2 设计决策:prompt 约束 vs 代码路径
| 方案 | 优点 | 缺点 |
|------|------|------|
| 代码路径 3(自动通知 assignee) | 不依赖 agent 行为 | 噪音——每条 comment 都通知,review 来回几轮就炸 |
| prompt 约束(agent @assignee | agent 自主判断,语义精确 | 依赖 agent 遵守 |
**选择 prompt 约束**。理由:
1. 与 §21 设计哲学一致(agent 自主决策,不过度自动化)
2. 噪音问题是真实的——不是每条 comment 都需要通知 assignee
3. agent 可以根据场景判断:纯确认不 @,有结论/需关注才 @
4. 兜底机制:round review + 庞统人工检查
### 15b.3 约束内容
在 ToolchainConstraintsSection §5「所有协作通过 Gitea 完成」中新增一条:
```
### 5. 所有协作通过 Gitea 完成
...(现有内容不变)
- ⚠️ 在 Issue/PR 上写 comment 时,如果内容需要 Issue 的 assignee 或创建者知晓,必须在 comment 中 @对方。纯确认性回复(如"收到")不需要 @。
```
同时在 `_ACTION_HINTS` 中 review_comment 的 hint 补充提示:
```
"review_comment": "你收到一个 Review 评论,这是一个需要你查看并响应的事件。回复时 @评论者。",
```
### 15b.4 不做的事
- 不在 `_handle_issue_comment` 中加代码路径 3(自动通知 assignee)
- 不做 subscribe/unsubscribe 机制
- 不通知 Issue 创建者(只通知 assigneeassignee 是责任人)
### 15b.5 涉及改动
| 文件 | 改动 |
|------|------|
| `src/daemon/toolchain_handler.py` ToolchainConstraintsSection §5 | 加 @assignee 约束 |
| `src/daemon/toolchain_handler.py` `_ACTION_HINTS` | review_comment hint 补充 @提示 |
约 +5 行,1 文件。
---
## §16. Round Review 迁移 ## §16. Round Review 迁移
### 16.1 当前实现 ### 16.1 当前实现
+1
View File
@@ -0,0 +1 @@
# algorithms package
+14
View File
@@ -0,0 +1,14 @@
"""find_max — 从数字列表中查找最大值"""
from __future__ import annotations
def find_max(nums: list[int | float]) -> int | float | None:
"""返回列表中的最大值,空列表返回 None。"""
if not nums:
return None
result = nums[0]
for num in nums[1:]:
if num > result:
result = num
return result
+104 -12
View File
@@ -219,7 +219,7 @@ def _toolchain_db_path() -> Path:
def _send_toolchain_task( def _send_toolchain_task(
to_agent: str, to_agent: str | None,
title: str, title: str,
description: str, description: str,
event_type: str, event_type: str,
@@ -231,7 +231,7 @@ def _send_toolchain_task(
"""创建 Toolchain Task 并写入 _toolchain DB。 """创建 Toolchain Task 并写入 _toolchain DB。
Args: Args:
to_agent: 收件人 Agent ID to_agent: 收件人 Agent IDNone 表示无指派待路由/delegate
title: 任务标题 title: 任务标题
description: 任务描述模板渲染后的事件信息 description: 任务描述模板渲染后的事件信息
event_type: 事件类型review_result / ci_failure / ... event_type: 事件类型review_result / ci_failure / ...
@@ -243,7 +243,7 @@ def _send_toolchain_task(
Returns: Returns:
创建的 Task ID 创建的 Task ID
""" """
if to_agent not in AGENT_IDS: if to_agent is not None and to_agent not in AGENT_IDS:
logger.warning("Unknown agent: %s, skipping toolchain task", to_agent) logger.warning("Unknown agent: %s, skipping toolchain task", to_agent)
return "" return ""
@@ -1025,14 +1025,38 @@ async def _handle_issues(payload: Dict[str, Any]) -> None:
}, },
) )
else: else:
title = f"Issue 指派: {issue_title} ({repo}#{issue_number})" # §21 §5 按 type/* label 解析 business_type
_send_toolchain_task( business_type = "feat" # default
to_agent=assignee, for lbl in labels_list:
title=title, lbl_lower = lbl.lower()
description=text, if "bug" in lbl_lower:
event_type="issue_assigned", business_type = "bug"
action_type="issue_assigned", elif "impl" in lbl_lower:
steps=[ business_type = "impl"
elif "feat" in lbl_lower:
business_type = "feat"
elif "docs" in lbl_lower or "documentation" in lbl_lower:
business_type = "docs"
elif "refactor" in lbl_lower:
business_type = "refactor"
elif "test" in lbl_lower:
business_type = "test"
# §21 §4 从 YAML 模板获取 stepsfallback 到硬编码)
from src.daemon.toolchain_templates import get_steps
yaml_steps = get_steps("issue_assigned", business_type)
if yaml_steps:
# 渲染占位符
rendered_steps = []
for s in yaml_steps:
s = s.replace("{issue_number}", str(issue_number))
s = s.replace("{brief}", brief)
s = s.replace("{title}", issue_title[:30])
rendered_steps.append(s)
steps_to_use = rendered_steps
else:
# fallback: 硬编码 steps(向后兼容)
steps_to_use = [
f"在开发目录执行 git 操作:\n a. git checkout main && git pull origin main\n b. git checkout -b fix/{issue_number}-{brief}", f"在开发目录执行 git 操作:\n a. git checkout main && git pull origin main\n b. git checkout -b fix/{issue_number}-{brief}",
"编码 + 写 UT", "编码 + 写 UT",
"文档同步:如果本次改动涉及设计变更或接口变更,在同一分支更新 docs/design/ 对应文档。如无需更新,在 action report 中说明「文档无需更新」", "文档同步:如果本次改动涉及设计变更或接口变更,在同一分支更新 docs/design/ 对应文档。如无需更新,在 action report 中说明「文档无需更新」",
@@ -1040,7 +1064,16 @@ async def _handle_issues(payload: Dict[str, Any]) -> None:
f"CI 通过后创建 PRGitea API: POST /repos/{repo}/pullshead: fix/{issue_number}-{brief}, base: main)— PR body 必须含 Closes #{issue_number}", f"CI 通过后创建 PRGitea API: POST /repos/{repo}/pullshead: fix/{issue_number}-{brief}, base: main)— PR body 必须含 Closes #{issue_number}",
"等 Review", "等 Review",
"提交 action reportPOST http://localhost:8083/api/projects/_toolchain/tasks/<task_id>/commentscomment_type=action_report)— 报告中必须说明文档是否需要更新及处理结果", "提交 action reportPOST http://localhost:8083/api/projects/_toolchain/tasks/<task_id>/commentscomment_type=action_report)— 报告中必须说明文档是否需要更新及处理结果",
], ]
title = f"Issue 指派: {issue_title} ({repo}#{issue_number})"
_send_toolchain_task(
to_agent=assignee,
title=title,
description=text,
event_type="issue_assigned",
action_type="issue_assigned",
steps=steps_to_use,
context_data={ context_data={
"issue_number": issue_number, "issue_number": issue_number,
"repo": repo, "repo": repo,
@@ -1048,10 +1081,69 @@ async def _handle_issues(payload: Dict[str, Any]) -> None:
"labels": labels, "labels": labels,
"issue_body": issue_body or "(无描述)", "issue_body": issue_body or "(无描述)",
"brief": brief, "brief": brief,
"business_type": business_type,
}, },
) )
elif action == "closed":
# §21 §11 Issue closed 纯通知(auto-pass
assignee_login = ""
issue_assignees = issue.get("assignees") or []
if issue_assignees:
assignee_login = issue_assignees[-1].get("login", "")
if not assignee_login or assignee_login not in AGENT_IDS:
logger.debug("Issue closed but no valid assignee, skipping")
return
title = f"Issue 已关闭: {issue_title} ({repo}#{issue_number})"
_send_toolchain_task(
to_agent=assignee_login,
title=title,
description=f"## Issue 已关闭\n\n**{repo}#{issue_number}**: {issue_title}\n\nIssue 已被关闭。",
event_type="issue_closed",
action_type="issue_closed",
steps=[], # 纯通知,无步骤
context_data={"repo": repo, "issue_number": issue_number},
)
elif action == "opened": elif action == "opened":
# §11b: 无 assignee 的普通 Issue → discussion task
assignees = issue.get("assignees") or []
single_assignee = issue.get("assignee")
if single_assignee and isinstance(single_assignee, dict) and single_assignee not in assignees:
assignees = list(assignees) + [single_assignee]
if not assignees and not ("部署失败" in issue_title):
# 无 assignee + 非部署失败 → 检查是否有 type/* label
labels_list_opened = [
lbl.get("name", "") for lbl in (issue.get("labels") or [])
]
has_type_label = any(
lbl.lower().startswith("type/") for lbl in labels_list_opened
)
if has_type_label:
issue_body = issue.get("body", "") or "(无描述)"
title_discussion = f"Issue 讨论: {issue_title} ({repo}#{issue_number})"
_send_toolchain_task(
to_agent=None, # 无指派 → router delegate 庞统
title=title_discussion,
description=f"## Issue 需要讨论\n\n"
f"**{repo}#{issue_number}**: {issue_title}\n\n"
f"{issue_body}",
event_type="issue_discussion",
action_type="issue_discussion",
steps=[], # discussion 不需要结构化步骤
context_data={
"issue_number": issue_number,
"repo": repo,
"issue_title": issue_title,
"issue_body": issue_body,
},
)
logger.info(
"Issue #%s: no assignee + type label → discussion task",
issue_number)
# discussion task 创建后继续检查 @mention(不 return
if "部署失败" in issue_title: if "部署失败" in issue_title:
# 从 Issue body 提取 commit hashGitea deploy workflow 格式) # 从 Issue body 提取 commit hashGitea deploy workflow 格式)
sha_match = re.search(r'[0-9a-f]{40}', issue.get("body", "")) sha_match = re.search(r'[0-9a-f]{40}', issue.get("body", ""))
+32 -6
View File
@@ -103,9 +103,9 @@ SPAWN_PROMPT_TEMPLATE = """{identity_section}
""" """
DISCUSSION_PROMPT_TEMPLATE = """你被 spawn 来参与黑板讨论。这是一个 v2.9 四相循环的讨论环节。 DISCUSSION_PROMPT_TEMPLATE = """你被 spawn 来参与讨论。这是一个四相循环的讨论环节。
## 你的任务 ## 讨论主题
{goal_snapshot} {goal_snapshot}
@@ -113,14 +113,38 @@ DISCUSSION_PROMPT_TEMPLATE = """你被 spawn 来参与黑板讨论。这是一
{constraints} {constraints}
## 你是谁
{agent_identity}
## 你必须做什么
读完需求后在黑板 comment 回应必须不是可选
1.定位这个需求和你有什么关系你的专业能力能贡献什么
2.建议你对实现方案有什么建议技术选型数据来源实现路径
3.认领如果你需要参与创建 sub task 并在 parent task comment 注册
- 创建 sub task: POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks
body: {{"title": "...", "description": "...", "task_type": "...", "parent_task": "{task_id}", "must_haves": "{{\"capability\": \"...\"}}"}}
- 创建后在 parent task comment: "[你的角色名] 我创建了 sub: 任务名,我负责 简述"
4.风险如果你发现风险不合理的假设或遗漏的环节直接提出
每个 agent 必须 comment即使你认为和自己无关也要说明原因这证明你读过并思考过了
## Comment 格式
你的 comment 必须以角色名开头让其他人知道你是谁
[角色名] 你的观点
[张飞] 我来负责策略编码 vnpy CtaTemplate 实现
[关羽] 这个策略需要风控连亏 3 天应暂停
[赵云] 数据已就绪2024-08 缺失已补齐
## 黑板 API ## 黑板 API
你可以随时: - 读黑板:GET http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}?expand=all
- 读黑板:GET http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}?expand=all( commentsoutputs)
- comment:POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}/comments - comment:POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}/comments
body: {{"author": "{agent_id}", "body": "内容(@agent-id 自动路由)"}} body: {{"author": "{agent_id}", "body": "内容"}}
- 创建 sub task:POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks - 创建 sub task:POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks
body: {{"title": "...", "description": "...", "task_type": "...", "parent_task": "{task_id}", "must_haves": "{{\"capability\": \"...\"}}"}}
- 认领任务:POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{{sub_task_id}}/claim - 认领任务:POST http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{{sub_task_id}}/claim
## 行为准则 ## 行为准则
@@ -387,12 +411,14 @@ curl -X POST http://{self.api_host}:{self.api_port}/api/projects/{project_id}/ta
goal_snapshot = description or title goal_snapshot = description or title
constraints = must_haves or "(无特殊约束)" constraints = must_haves or "(无特殊约束)"
agent_identity = self._inject_agent_identity(agent_id)
return DISCUSSION_PROMPT_TEMPLATE.format( return DISCUSSION_PROMPT_TEMPLATE.format(
goal_snapshot=goal_snapshot, goal_snapshot=goal_snapshot,
constraints=constraints, constraints=constraints,
project_id=project_id, project_id=project_id,
task_id=task_id, task_id=task_id,
agent_id=agent_id, agent_id=agent_id,
agent_identity=agent_identity,
api_host=self.api_host, api_host=self.api_host,
api_port=self.api_port, api_port=self.api_port,
) )
+13 -1
View File
@@ -34,6 +34,8 @@ _ACTION_HINTS: Dict[str, str] = {
"review_comment": "你收到一个 Review 评论,这是一个需要你查看并响应的事件。", "review_comment": "你收到一个 Review 评论,这是一个需要你查看并响应的事件。",
"ci_failure": "你收到一个 CI 失败通知,这是一个需要你修复失败测试的事件。", "ci_failure": "你收到一个 CI 失败通知,这是一个需要你修复失败测试的事件。",
"issue_assigned": "你收到一个 Issue 指派,这是一个需要你编码实现的事件。", "issue_assigned": "你收到一个 Issue 指派,这是一个需要你编码实现的事件。",
"issue_closed": "你收到一个 Issue 关闭通知。这是一条纯通知,阅读即可。",
"issue_discussion": "你收到一个需要讨论的 Issue。请阅读 Issue 内容,发起讨论并引导其他 agent 参与。",
"deploy_failure": "你收到一个部署失败通知,这是一个需要你排查并修复的事件。", "deploy_failure": "你收到一个部署失败通知,这是一个需要你排查并修复的事件。",
"mention": "你收到一个 @mention 通知,这是一个需要你按指引响应的事件。", "mention": "你收到一个 @mention 通知,这是一个需要你按指引响应的事件。",
"review_merged": "你收到一个 PR 合并通知。这是一条纯通知,阅读即可。", "review_merged": "你收到一个 PR 合并通知。这是一条纯通知,阅读即可。",
@@ -264,7 +266,7 @@ class ToolchainConstraintsSection:
'| “步骤太多了,选几个做就行” | ❌ 错!必须逐条执行,不可跳过 |', '| “步骤太多了,选几个做就行” | ❌ 错!必须逐条执行,不可跳过 |',
'| “这个步骤不适用于当前情况” | ❌ 如果确实不适用,在 action report 中说明原因,但其他步骤必须执行 |', '| “这个步骤不适用于当前情况” | ❌ 如果确实不适用,在 action report 中说明原因,但其他步骤必须执行 |',
'| “CI/部署失败不是我代码的问题,我什么也不用做” | ❌ 错!即使是基础设施问题,你也必须创建 Issue 指派 jiangwei-infrabody 含错误来源链接 + 日志 + 判断依据),并在 action report 中说明。不能只报告“不是我的问题”就完事 |', '| “CI/部署失败不是我代码的问题,我什么也不用做” | ❌ 错!即使是基础设施问题,你也必须创建 Issue 指派 jiangwei-infrabody 含错误来源链接 + 日志 + 判断依据),并在 action report 中说明。不能只报告“不是我的问题”就完事 |',
'| "文档以后再说" | ❌ 错!文档同步和代码改动在同一 PR 中完成,action report 中必须说明文档处理情况 |', '| 文档以后再说 | ❌ 错!文档同步和代码改动在同一 PR 中完成,action report 中必须说明文档处理情况 |',
"", "",
] ]
return "\n".join(lines) return "\n".join(lines)
@@ -331,6 +333,16 @@ class ToolchainHandler(BaseTaskHandler):
return VerifyResult(True, "merged_passthrough", return VerifyResult(True, "merged_passthrough",
"review_merged auto-pass") "review_merged auto-pass")
# 特殊处理:issue_closed 始终通过(纯通知, §21 §11)
if meta.get("action_type") == "issue_closed":
return VerifyResult(True, "issue_closed_passthrough",
"issue_closed auto-pass")
# 特殊处理:issue_discussion 始终通过(触发 delegate 庞统 §21 §11b)
if meta.get("action_type") == "issue_discussion":
return VerifyResult(True, "discussion_passthrough",
"issue_discussion auto-pass")
# 1. 优先检查 action_report comment # 1. 优先检查 action_report comment
report_row = conn.execute( report_row = conn.execute(
"SELECT id FROM comments WHERE task_id=? " "SELECT id FROM comments WHERE task_id=? "
+63 -1
View File
@@ -8,7 +8,7 @@ from __future__ import annotations
import logging import logging
from collections import defaultdict from collections import defaultdict
from pathlib import Path from pathlib import Path
from typing import Dict from typing import Dict, List, Optional
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -87,3 +87,65 @@ def render_template(name: str, variables: Dict[str, str]) -> str:
def clear_cache() -> None: def clear_cache() -> None:
"""清空模板缓存(用于测试或热更新)""" """清空模板缓存(用于测试或热更新)"""
_template_cache.clear() _template_cache.clear()
global _steps_cache
_steps_cache = None # 重置为 None,强制下次 reload
# ---------------------------------------------------------------------------
# §21 §4.2 YAML steps 模板加载
# ---------------------------------------------------------------------------
STEPS_YAML_PATH = Path(__file__).parent.parent.parent / "config" / "toolchain-templates.yaml"
_steps_cache: Optional[dict] = None
def _load_steps_yaml() -> dict:
"""加载并缓存 toolchain-templates.yaml。"""
global _steps_cache
if _steps_cache is not None:
return _steps_cache
try:
import yaml
with open(STEPS_YAML_PATH, encoding="utf-8") as f:
_steps_cache = yaml.safe_load(f) or {}
logger.debug("Loaded steps YAML: %d action_types", len(_steps_cache))
except FileNotFoundError:
logger.warning("Steps YAML not found: %s", STEPS_YAML_PATH)
_steps_cache = {}
except Exception as e:
logger.error("Failed to load steps YAML: %s", e)
_steps_cache = {}
return _steps_cache
def get_steps(action_type: str, business_type: str = "") -> List[str]:
"""从 YAML 模板配置获取 steps。
Args:
action_type: 动作类型issue_assigned / ci_failure / ...
business_type: 业务子类型feature/impl/bug/docs/refactor/test/infrastructure
Returns:
steps 列表找不到返回空列表
"""
templates = _load_steps_yaml()
section = templates.get(action_type, {})
if isinstance(section, dict) and business_type:
subsection = section.get(business_type, {})
return subsection.get("steps", [])
if isinstance(section, dict):
return section.get("steps", [])
return []
def get_output_template(action_type: str, business_type: str = "") -> str:
"""从 YAML 模板配置获取 output_template。"""
templates = _load_steps_yaml()
section = templates.get(action_type, {})
if isinstance(section, dict) and business_type:
subsection = section.get(business_type, {})
return subsection.get("output_template", "")
if isinstance(section, dict):
return section.get("output_template", "")
return ""
+7 -2
View File
@@ -6,8 +6,14 @@
需要 RUN_INTEGRATION=1 + 生产 daemon 运行 需要 RUN_INTEGRATION=1 + 生产 daemon 运行
""" """
import json
import os import os
import pytest
if not os.environ.get("RUN_INTEGRATION"):
pytest.skip("E2E tests require RUN_INTEGRATION=1", allow_module_level=True)
import json
import re import re
import sqlite3 import sqlite3
import sys import sys
@@ -17,7 +23,6 @@ from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
from typing import Any, Dict from typing import Any, Dict
import pytest
import requests as http_requests import requests as http_requests
# 指向部署目录 # 指向部署目录
+7 -2
View File
@@ -4,15 +4,20 @@
需要 RUN_INTEGRATION=1 + 生产 daemon 运行 需要 RUN_INTEGRATION=1 + 生产 daemon 运行
""" """
import json
import os import os
import pytest
if not os.environ.get("RUN_INTEGRATION"):
pytest.skip("E2E tests require RUN_INTEGRATION=1", allow_module_level=True)
import json
import sys import sys
import time import time
import uuid import uuid
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List from typing import Any, Dict, List
import pytest
import requests as http_requests import requests as http_requests
# 指向部署目录 # 指向部署目录
+4 -7
View File
@@ -1,11 +1,10 @@
import pytest import pytest
import os
skip_no_integration = pytest.mark.skipif( if not os.environ.get("RUN_INTEGRATION"):
not __import__("os").environ.get("RUN_INTEGRATION"), pytest.skip("E2E tests require RUN_INTEGRATION=1", allow_module_level=True)
reason="Set RUN_INTEGRATION=1 to run E2E tests against real daemon",
)
pytestmark = [pytest.mark.e2e, skip_no_integration] pytestmark = pytest.mark.e2e
"""v2.7 端到端测试 — 全链路真实环境 """v2.7 端到端测试 — 全链路真实环境
@@ -14,7 +13,6 @@ pytestmark = [pytest.mark.e2e, skip_no_integration]
import asyncio import asyncio
import json import json
import os
import sys import sys
import time import time
import uuid import uuid
@@ -22,7 +20,6 @@ from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
import pytest
from unittest.mock import MagicMock from unittest.mock import MagicMock
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
+7 -2
View File
@@ -6,14 +6,19 @@
覆盖项目管理 Task CRUD SubTask Stage 进度 父状态聚合 依赖链 超时 Mail 覆盖项目管理 Task CRUD SubTask Stage 进度 父状态聚合 依赖链 超时 Mail
""" """
import json
import os import os
import pytest
if not os.environ.get("RUN_INTEGRATION"):
pytest.skip("Integration tests require RUN_INTEGRATION=1", allow_module_level=True)
import json
import sys import sys
import uuid import uuid
from pathlib import Path from pathlib import Path
from typing import Any, Dict from typing import Any, Dict
import pytest
from unittest.mock import MagicMock from unittest.mock import MagicMock
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
+28
View File
@@ -0,0 +1,28 @@
"""find_max 单元测试"""
import pytest
from src.algorithms.find_max import find_max
class TestFindMax:
def test_normal_list(self):
assert find_max([3, 1, 4, 1, 5, 9, 2, 6]) == 9
def test_empty_list(self):
assert find_max([]) is None
def test_single_element(self):
assert find_max([42]) == 42
def test_negative_numbers(self):
assert find_max([-5, -1, -10, -3]) == -1
def test_floats(self):
assert find_max([1.5, 2.7, 0.3, 3.14]) == 3.14
def test_mixed_int_float(self):
assert find_max([1, 2.5, 3, 0.1]) == 3
def test_duplicate_max(self):
assert find_max([7, 7, 7]) == 7