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 协作面"
created: 2026-06-19
version: v2.1 draft
status: draft
status: superseded by §21 (toolchain 部分)
changelog: v2.1 修正 M1dispatcher 直接 SQL 声明)+ M2Phase 格式)+ S1/S2TaskAdapter 残留清理)
v2.0 纳入姜维+司马懿 Review 反馈 + 庞统 Repository 模式修正
v1.1 纳入姜维 Review 反馈
+185 -6
View File
@@ -1,9 +1,11 @@
---
title: "Unified Toolchain Design — 统一工具链工作流设计"
created: 2026-06-20
version: v1.0 draft
version: v1.2 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
@@ -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 补充)
> 本节确保 Gitea 替代黑板后,PRD-v3.0 的 AI native 能力不降级。
@@ -606,12 +727,17 @@ issue_closed 走 auto-pass(和 review_merged 一样),纯通知不需要 ag
庞统创建 parent Issue(无 assignee)后,触发 discussion
```
庞统创建 parent Issue → webhook: issues/assigned(或 ticker 发现 pending 无 assignee
daemon 检测:无 assignee = 广播讨论
ticker 广播 spawn 所有 agentspawn_type=discussion
每个 agent 收到 DISCUSSION_PROMPT_TEMPLATE
庞统创建 parent Issue无 assignee
Gitea webhook: issues/opened(注意:不是 assigned,无 assignee 的 Issue 只触发 opened
_handle_issues opened 分支检测:无 assignee + 有 type/* label
创建 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 重构)
**设计参考**: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.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(
to_agent: str,
to_agent: str | None,
title: str,
description: str,
event_type: str,
@@ -231,7 +231,7 @@ def _send_toolchain_task(
"""创建 Toolchain Task 并写入 _toolchain DB。
Args:
to_agent: 收件人 Agent ID
to_agent: 收件人 Agent IDNone 表示无指派待路由/delegate
title: 任务标题
description: 任务描述模板渲染后的事件信息
event_type: 事件类型review_result / ci_failure / ...
@@ -243,7 +243,7 @@ def _send_toolchain_task(
Returns:
创建的 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)
return ""
@@ -1025,14 +1025,38 @@ async def _handle_issues(payload: Dict[str, Any]) -> None:
},
)
else:
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=[
# §21 §5 按 type/* label 解析 business_type
business_type = "feat" # default
for lbl in labels_list:
lbl_lower = lbl.lower()
if "bug" in lbl_lower:
business_type = "bug"
elif "impl" in lbl_lower:
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}",
"编码 + 写 UT",
"文档同步:如果本次改动涉及设计变更或接口变更,在同一分支更新 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}",
"等 Review",
"提交 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={
"issue_number": issue_number,
"repo": repo,
@@ -1048,10 +1081,69 @@ async def _handle_issues(payload: Dict[str, Any]) -> None:
"labels": labels,
"issue_body": issue_body or "(无描述)",
"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":
# §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:
# 从 Issue body 提取 commit hashGitea deploy workflow 格式)
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}
@@ -113,14 +113,38 @@ DISCUSSION_PROMPT_TEMPLATE = """你被 spawn 来参与黑板讨论。这是一
{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
你可以随时:
- 读黑板:GET http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}?expand=all( commentsoutputs)
- 读黑板:GET http://{api_host}:{api_port}/api/projects/{project_id}/tasks/{task_id}?expand=all
- 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
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
## 行为准则
@@ -387,12 +411,14 @@ curl -X POST http://{self.api_host}:{self.api_port}/api/projects/{project_id}/ta
goal_snapshot = description or title
constraints = must_haves or "(无特殊约束)"
agent_identity = self._inject_agent_identity(agent_id)
return DISCUSSION_PROMPT_TEMPLATE.format(
goal_snapshot=goal_snapshot,
constraints=constraints,
project_id=project_id,
task_id=task_id,
agent_id=agent_id,
agent_identity=agent_identity,
api_host=self.api_host,
api_port=self.api_port,
)
+13 -1
View File
@@ -34,6 +34,8 @@ _ACTION_HINTS: Dict[str, str] = {
"review_comment": "你收到一个 Review 评论,这是一个需要你查看并响应的事件。",
"ci_failure": "你收到一个 CI 失败通知,这是一个需要你修复失败测试的事件。",
"issue_assigned": "你收到一个 Issue 指派,这是一个需要你编码实现的事件。",
"issue_closed": "你收到一个 Issue 关闭通知。这是一条纯通知,阅读即可。",
"issue_discussion": "你收到一个需要讨论的 Issue。请阅读 Issue 内容,发起讨论并引导其他 agent 参与。",
"deploy_failure": "你收到一个部署失败通知,这是一个需要你排查并修复的事件。",
"mention": "你收到一个 @mention 通知,这是一个需要你按指引响应的事件。",
"review_merged": "你收到一个 PR 合并通知。这是一条纯通知,阅读即可。",
@@ -264,7 +266,7 @@ class ToolchainConstraintsSection:
'| “步骤太多了,选几个做就行” | ❌ 错!必须逐条执行,不可跳过 |',
'| “这个步骤不适用于当前情况” | ❌ 如果确实不适用,在 action report 中说明原因,但其他步骤必须执行 |',
'| “CI/部署失败不是我代码的问题,我什么也不用做” | ❌ 错!即使是基础设施问题,你也必须创建 Issue 指派 jiangwei-infrabody 含错误来源链接 + 日志 + 判断依据),并在 action report 中说明。不能只报告“不是我的问题”就完事 |',
'| "文档以后再说" | ❌ 错!文档同步和代码改动在同一 PR 中完成,action report 中必须说明文档处理情况 |',
'| 文档以后再说 | ❌ 错!文档同步和代码改动在同一 PR 中完成,action report 中必须说明文档处理情况 |',
"",
]
return "\n".join(lines)
@@ -331,6 +333,16 @@ class ToolchainHandler(BaseTaskHandler):
return VerifyResult(True, "merged_passthrough",
"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
report_row = conn.execute(
"SELECT id FROM comments WHERE task_id=? "
+63 -1
View File
@@ -8,7 +8,7 @@ from __future__ import annotations
import logging
from collections import defaultdict
from pathlib import Path
from typing import Dict
from typing import Dict, List, Optional
logger = logging.getLogger(__name__)
@@ -87,3 +87,65 @@ def render_template(name: str, variables: Dict[str, str]) -> str:
def clear_cache() -> None:
"""清空模板缓存(用于测试或热更新)"""
_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 运行
"""
import json
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 sqlite3
import sys
@@ -17,7 +23,6 @@ from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Dict
import pytest
import requests as http_requests
# 指向部署目录
+7 -2
View File
@@ -4,15 +4,20 @@
需要 RUN_INTEGRATION=1 + 生产 daemon 运行
"""
import json
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 time
import uuid
from pathlib import Path
from typing import Any, Dict, List
import pytest
import requests as http_requests
# 指向部署目录
+4 -7
View File
@@ -1,11 +1,10 @@
import pytest
import os
skip_no_integration = pytest.mark.skipif(
not __import__("os").environ.get("RUN_INTEGRATION"),
reason="Set RUN_INTEGRATION=1 to run E2E tests against real daemon",
)
if not os.environ.get("RUN_INTEGRATION"):
pytest.skip("E2E tests require RUN_INTEGRATION=1", allow_module_level=True)
pytestmark = [pytest.mark.e2e, skip_no_integration]
pytestmark = pytest.mark.e2e
"""v2.7 端到端测试 — 全链路真实环境
@@ -14,7 +13,6 @@ pytestmark = [pytest.mark.e2e, skip_no_integration]
import asyncio
import json
import os
import sys
import time
import uuid
@@ -22,7 +20,6 @@ from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional
import pytest
from unittest.mock import MagicMock
from fastapi.testclient import TestClient
+7 -2
View File
@@ -6,14 +6,19 @@
覆盖项目管理 Task CRUD SubTask Stage 进度 父状态聚合 依赖链 超时 Mail
"""
import json
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 uuid
from pathlib import Path
from typing import Any, Dict
import pytest
from unittest.mock import MagicMock
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