# Unified Project AI Handoff 成功案例验收包 ## 目的 这份文档把 `统一 Project Operating Context + 三路 handoff` 的 12 条黄金场景收成可直接过目的“成功案例包”。 本文聚焦三件事: 1. 每条 case 的最小输入 2. 从哪里入站、经过哪些主链、按什么步骤验 3. 成功时应该看到的结构化输出与最终用户可见输出 ## 使用边界 - 本文是“成功目标样例 + 验收步骤”文档,不等于 12 条都已经做完人工业务走读。 - 文中的结构化输出与最终输出,表示该 case 验收成功时应看到的目标结果。 - 自动化证据以 [2026-04-17-feishu-problem-inventory.md](/Users/yuuuu/Documents/AIProjectManger/.worktrees/codex/unified-project-ai-handoff/docs/superpowers/specs/2026-04-17-feishu-problem-inventory.md) 为真源。 - 飞书前台验收默认先看“后端最终出站”: - 非交互 QA 看 `qa_direct` 文本 - 强交互 requirement / draft 看 action card payload - 所有 Feishu 场景都必须经过 `feishu_reply_outbox`;如果没有看到 outbox,就不能算飞书链路验收成功。 ## 通用验收步骤 每条 case 都按同一顺序验: 1. 准备前置上下文 2. 用真实入站面发送最小输入 3. 先看结构化输出是否命中预期主链 4. 再看最终用户可见输出是否符合合同 5. 记录 pass / fail 与证据 每条 case 至少记录这 6 项: - 输入 - 入站面 - 主链 - 结构化输出 - 最终输出 - 结果判定 ## Feishu 场景统一 Transport 合同 凡是本文中真实入站面包含 Feishu 的 case,除了看业务主链,还必须额外确认 transport 层。 ### 非交互 QA 文本 必经链路: ```text message.receive_v1 -> handle_message_event() -> _handle_message_receive() -> capture_from_feishu() -> _route_scene_reply() -> _build_feishu_qa_direct_reply_text() -> _safe_reply_text() -> FeishuReplyOutboxMixin._enqueue_reply_outbox() -> feishu_reply_outbox ``` 必验点: - `reply_route=qa_direct` - `feishu_reply_outbox.message_text` 是纯文本 - 文本里包含成功案例要求的正文 / 当前项目 / 外部判断 / 下一步 ### 交互卡片 必经链路: ```text message.receive_v1 或 card.action.trigger -> handle_message_event() -> _handle_message_receive() / _handle_card_action_trigger() -> _route_scene_reply() -> _build_action_card_from_capture_result() -> _safe_reply_card() -> FeishuReplyOutboxMixin._enqueue_reply_outbox() -> feishu_reply_outbox ``` 必验点: - `reply_route=qa_action_card` 或对应 card action 路由 - `feishu_reply_outbox.message_text` 以 `__pmflow_feishu_card__:` 开头 - 解码后的 card payload 保留正确 `action` 和 `work_item_id` --- ## Case 01 ### 场景 纯内部系统问题 ### 前置条件 - 当前在项目页或带 `project_id=17` 的 Web 问答入口 - 无 active requirement-plan / draft confirm 干扰 ### 输入 - 用户原话:`当前 PMFlow 里 MCP 模板有几个?` - 真实入站面:Web `POST /rag/ask` 示例请求: ```json { "question": "当前 PMFlow 里 MCP 模板有几个?", "asked_via": "pmflow_manual", "project_id": 17, "action_mode": "read" } ``` ### 详细步骤 1. 从项目页问答入口发起问题。 2. 查看 `/rag/ask` 返回 payload 中的 `route_decision`。 3. 确认 `route_mode=internal`,且没有被转成 external。 4. 查看最终 `answer` 是否以 `内部知识库查询:` 开头。 ### 成功结构化输出 ```json { "route_decision": { "route_mode": "internal", "effective_source_policy": "internal" } } ``` ### 成功最终输出 ```text 内部知识库查询:当前 PMFlow 里共有 2 个 MCP 模板。 ``` ### 成功判定 - 命中 internal 主链 - 不混入外部来源摘要 - 不是泛化聊天式说明 --- ## Case 02 ### 场景 纯外部事实问题 ### 前置条件 - 普通 QA 线程 - 无 active requirement-plan ### 输入 - 用户原话:`OpenAI Docs 最近有没有新的 Responses API 最佳实践?` - 真实入站面:Web `POST /rag/ask` 示例请求: ```json { "question": "OpenAI Docs 最近有没有新的 Responses API 最佳实践?", "asked_via": "pmflow_manual", "action_mode": "read" } ``` ### 详细步骤 1. 从 Web QA 入口提交问题。 2. 检查 `route_decision.route_mode` 是否为 `external`。 3. 检查 external 证据是否经过 fail-closed 过滤。 4. 查看最终回答是否保持 `外部实时查询:` 合同。 ### 成功结构化输出 ```json { "route_decision": { "route_mode": "external", "effective_source_policy": "external" } } ``` ### 成功最终输出 ```text 外部实时查询:OpenAI Docs 最近补充了新的 Responses API 最佳实践说明。 ``` ### 成功判定 - 只能走 external 主链 - internal 来源不得混进 external 正文 --- ## Case 03 ### 场景 先判断,再给 plan 入口 ### 前置条件 - 当前项目明确 - repo 摘要可用 - 允许 external lane 构建 `fit_brief` ### 输入 - 用户原话:`OpenAI Docs 这种能力适不适合我们当前项目?` - 真实入站面: - Web `POST /rag/ask` - Feishu `message.receive_v1` Web 示例请求: ```json { "question": "OpenAI Docs 这种能力适不适合我们当前项目?", "asked_via": "pmflow_manual", "project_id": 12, "source_chat_id": "chat-12", "action_mode": "read" } ``` Feishu 最小入站: ```json { "message_type": "text", "chat_id": "chat-12", "sender_open_id": "ou-alpha", "message_id": "om-case-03", "content": "OpenAI Docs 这种能力适不适合我们当前项目?" } ``` ### 详细步骤 1. 从 Web 或 Feishu 发问题。 2. 检查 `project_operating_context` 是否带上当前项目。 3. 检查 `execution_recipe.plan_handoff_mode` 是否为 `offer_after_answer`。 4. 检查 `handoff_mode / can_enter_plan / recommended_next_step`。 5. Feishu 侧检查 `_route_scene_reply()` 是否走到 `qa_direct`。 6. Feishu 侧检查 `feishu_reply_outbox.message_text` 是否为最终出站文本。 7. Feishu 侧再看最终 `qa_direct` 文本是否已经变成围绕当前项目实体的自然分析回复,而不是标签摘要。 ### 成功结构化输出 ```json { "project_operating_context": { "current_project_id": 12, "fit_scope": "current_project_and_repo", "decision_strategy": "answer_then_offer_plan" }, "execution_recipe": { "primary_lane": "external", "fit_brief_required": true, "external_evidence_required": true, "plan_handoff_mode": "offer_after_answer" }, "handoff_mode": "offer_after_answer", "can_enter_plan": true, "recommended_next_step": "如果你要,我可以基于当前结论进入 plan,继续把需求范围和下一步收清楚。" } ``` ### 成功最终输出 Web: ```text 外部实时查询:目前更像背景相关,建议先验证它是否能映射到当前项目正式对象。 ``` Feishu: ```text 外部实时查询:目前更像背景相关,建议先验证它是否能映射到当前项目正式对象。 结合当前项目看,Alpha 是一个 AI 项目管理工作台,当前仓库负责统一项目上下文与 AI handoff。就这次判断来说,这条外部信息目前更像背景相关,还不能直接映射到当前正式对象,所以现在更适合作为参考,而不是直接当成已经命中当前项目的正式变化。 如果你要,我可以基于当前结论进入 plan,继续把需求范围和下一步收清楚。 ``` ### 成功判定 - 先给判断 - 再给 plan 入口 - 不得首轮直接切到 plan 主链 --- ## Case 04 ### 场景 显式强锚点直接进 plan ### 前置条件 - 上一轮已经给出结论 - 当前消息显式 reply 到上一轮结论 ### 输入 - 用户原话:`那按这个推进` - 真实入站面:Feishu `message.receive_v1` reply/thread 示例入站: ```json { "message_id": "om-case-04", "reply_to_message_id": "om-case-03-bot", "chat_id": "chat-12", "content": "那按这个推进" } ``` ### 详细步骤 1. 用 reply/thread 回复上一条结论消息。 2. 检查 canonical thread context 是否恢复成功。 3. 检查 `execution_recipe.plan_handoff_mode` 是否命中 `explicit_plan_now`,或直接回到 active requirement context。 4. 检查 `_route_scene_reply()` 后是否进入 card 或 follow-up 对应出站路径。 5. 检查最终 Feishu 回包是否通过 `feishu_reply_outbox` 出站。 6. 检查输出是否继续 requirement-plan,而不是普通 QA。 ### 成功结构化输出 ```json { "execution_recipe": { "plan_handoff_mode": "explicit_plan_now" } } ``` ### 成功最终输出 ```text AI PMO 需求收敛:我先按这条结论继续收敛需求范围。 ``` ### 成功判定 - 直接 handoff 到 plan - 不是重新解释 internal / external 模式 --- ## Case 05 ### 场景 reply/thread 明确续问 ### 前置条件 - 机器人上一条已给出解释性回复 - 当前消息显式 reply 到该条回复 ### 输入 - 用户原话:`你刚才这条说的“模型路由”具体指哪层?` - 真实入站面:Feishu reply/thread 或 QA session follow-up ### 详细步骤 1. 对上一条机器人解释消息做显式回复。 2. 检查 follow-up anchor 是否优先命中 reply anchor。 3. 检查问题是否被重写到上一条回复对象。 4. 确认没有被旧 `last_subject` 抢走。 ### 成功结构化输出 ```json { "anchor_mode": "reply_anchor", "resolved_anchor": { "source_message_id": "om-prev-answer" } } ``` ### 成功最终输出 ```text 内部知识库查询:这里说的“模型路由”指的是 PMFlow 本地路由层里的 tier / provider / response contract 决策层。 ``` ### 成功判定 - 续问对象来自 reply anchor - 不是把它当新问题重开 --- ## Case 06 ### 场景 同群同人但切新话题 ### 前置条件 - 同一群聊、同一用户 - 上一轮刚结束 requirement-plan ### 输入 - 用户原话:`另外,现在 PMFlow 里 signal source onboarding 做到哪了?` - 真实入站面:Feishu `message.receive_v1` ### 详细步骤 1. 在同一群里直接发一条新的事实问题。 2. 检查 session inheritance guard 是否拦住旧 requirement-plan 续写。 3. 检查显式 signal-source 请求是否避开 draft/plan 劫持。 4. 检查最终回答是否回到 internal query。 ### 成功结构化输出 ```json { "route_decision": { "route_mode": "internal" } } ``` ### 成功最终输出 ```text 内部知识库查询:当前 PMFlow 的 signal source onboarding 已支持受控接入与运行时探测。 ``` ### 成功判定 - 不得继续沿 requirement-plan 解释 - 必须识别为新的 internal query --- ## Case 07 ### 场景 旧 active context 劫持新问题 ### 前置条件 - 上一轮挂着 `pending_clarification(kind=query_route_selection)` - 当前用户显式改题 ### 输入 - 用户原话:`不是说那个,我现在问 external gateway。` - 真实入站面:Feishu `message.receive_v1` 或 Web QA session ### 详细步骤 1. 先制造一个 query-route 澄清状态。 2. 再发送这条显式改题消息。 3. 检查 ingress 是否调用 `WorkflowSemanticPlanner` 看 `topic_switch=new`。 4. 检查旧 `pending_clarification` 是否被立即清空。 5. 确认没有继续调用旧 session 的澄清协议消费链。 ### 成功结构化输出 ```json { "pending_clarification": null, "next_route": "query.answer" } ``` ### 成功最终输出 ```text 外部实时查询:external gateway 当前负责统一外部查询计划、provider 分发和结果校验。 ``` ### 成功判定 - 旧 `pending_clarification` 必须失效 - 当前消息必须作为新 query 进入主链 --- ## Case 08 ### 场景 明明该继续 plan,却掉回 query.answer ### 前置条件 - active requirement-plan 正在进行 - 上一轮刚发 A/B/C 选项 ### 输入 - 用户原话:`我选 C` - 真实入站面:Feishu `message.receive_v1` ### 详细步骤 1. 先进入 requirement-plan 并发出 A/B/C。 2. 再发送 `我选 C`。 3. 检查 active requirement context 是否命中当前 option map。 4. 确认协议解析直接 bypass 普通 QA semantic gate。 5. 查看最终输出是否继续 requirement-plan follow-up。 ### 成功结构化输出 ```json { "reply_template_key": "requirement_plan_followup", "parsed_choice": "C" } ``` ### 成功最终输出 ```text C. 混合增强路径 ⭐ 当前我更推荐:C ``` ### 成功判定 - 不得掉回普通 QA - 必须继续当前 requirement-plan 主链 --- ## Case 09 ### 场景 明明只是问结论,却被直接拉进 plan ### 前置条件 - 无 active plan - 无显式 reply/thread 强锚点 ### 输入 - 用户原话:`这个方案适不适合我们现在这套 PMFlow?` - 真实入站面:Web `POST /rag/ask` 或 Feishu `message.receive_v1` ### 详细步骤 1. 直接发一条适配性判断问题。 2. 检查 `execution_recipe.plan_handoff_mode`。 3. 确认首轮还是 `offer_after_answer`,不是 `explicit_plan_now`。 4. 确认最终输出先给结论,再给下一步。 ### 成功结构化输出 ```json { "execution_recipe": { "plan_handoff_mode": "offer_after_answer" }, "recommended_next_step": "如果你要,我可以基于当前结论进入 plan。" } ``` ### 成功最终输出 ```text 外部实时查询:目前适合作为背景参考,但还需要验证它与当前 PMFlow 正式对象的映射关系。 ``` ### 成功判定 - 首轮不能直接进入 plan - 必须保留“先答结论,再给下一步” --- ## Case 10 ### 场景 卡片动作和纯文本回复不一致 ### 前置条件 - 同一 requirement thread 同时支持按钮和文本协议 - 当前 active `work_item_id` 已存在 ### 输入 - 文本输入:`选 B` - 卡片输入:点击 `B` 按钮 - 真实入站面: - 文本:Feishu `message.receive_v1` - 卡片:Feishu `card.action.trigger` ### 详细步骤 1. 在同一 requirement 轮次里保留当前 action card。 2. 用文本发 `选 B`。 3. 再用卡片点击 `B`。 4. 检查两条路径是否都命中同一 `work_item_id` 和 canonical thread context。 5. 检查文本和卡片两条路径都写入 `feishu_reply_outbox`。 6. 检查最终都继续 requirement-plan。 ### 成功结构化输出 ```json { "work_item_id": 88, "action": "plan_choice", "choice": "B" } ``` ### 成功最终输出 ```json { "header": { "title": { "content": "AI PMO 需求收敛" } }, "elements": [ { "tag": "action", "actions": [ { "value": { "action": "plan_choice", "choice": "B", "work_item_id": 88 } } ] } ] } ``` ### 成功判定 - 文本和按钮都命中同一 requirement thread - 不能出现“一条续上、一条掉链” --- ## Case 11 ### 场景 Web 与 Feishu 对同一句话不同链路 ### 前置条件 - 当前项目一致 - Web 与 Feishu 都从同一项目语境发问 ### 输入 - 用户原话:`这个适合吗` - 真实入站面: - Web `POST /rag/ask` - Feishu `message.receive_v1` ### 详细步骤 1. 用同一句话分别从 Web 和 Feishu 发起问题。 2. 对比两边的 `project_operating_context`。 3. 对比两边的 `execution_recipe` 和 `handoff_mode`。 4. Web 看结构化 meta。 5. Feishu 检查 `_route_scene_reply()` 是否进入 `qa_direct`,并写入 `feishu_reply_outbox`。 6. Feishu 再看最终 `qa_direct` 文本。 ### 成功结构化输出 ```json { "execution_recipe": { "plan_handoff_mode": "offer_after_answer" }, "handoff_mode": "offer_after_answer", "can_enter_plan": true } ``` ### 成功最终输出 Web: ```text 外部实时查询:目前更像背景相关,还不能直接映射到当前正式对象。 ``` Feishu: ```text 外部实时查询:目前更像背景相关,还不能直接映射到当前正式对象。 结合当前项目看,Alpha 是一个 AI 项目管理工作台,当前仓库负责统一项目上下文与 AI handoff。就这次判断来说,这条外部信息目前更像背景相关,还不能直接映射到当前正式对象,所以现在更适合作为参考,而不是直接当成已经命中当前项目的正式变化。 如果你要,我可以基于当前结论进入 plan,继续把需求范围和下一步收清楚。 ``` ### 成功判定 - 两边都必须是 `offer_after_answer` - 不能一边进 plan、一边留在普通 QA --- ## Case 12 ### 场景 多候选 meta follow-up 需要澄清 ### 前置条件 - 最近 5 分钟内有 2 条候选回复 - 当前问题无法唯一锚定某一条 ### 输入 - 第一轮:`那你刚才那条是哪个模型回复的?` - 第二轮:`第二条` - 真实入站面:Feishu / QA session follow-up ### 详细步骤 1. 先制造两个最近候选回复。 2. 发送第一轮 meta follow-up。 3. 检查是否返回 `ambiguous_recent_reply` 和编号候选。 4. 再发送 `第二条`。 5. 检查是否恢复 `original_question` 并绑定正确 `selected_qa_id`。 ### 成功结构化输出 第一轮: ```json { "clarification_code": "ambiguous_recent_reply", "clarification_candidates": [ {"index": 1}, {"index": 2} ] } ``` 第二轮: ```json { "selected_qa_id": 102, "effective_question": "我问你这个是哪个模型执行回复的?" } ``` ### 成功最终输出 第一轮: ```text 我找到两条可能对应的回复: 1. ... 2. ... 请告诉我是第几条。 ``` 第二轮: ```text 内部知识库查询:你选的是第二条,那条回复由当前 query.answer 主链执行。 ``` ### 成功判定 - 第一轮必须先澄清 - 第二轮必须支持中文编号恢复原问题 --- ## 建议过目顺序 如果你是要先审文档,再决定人工走读顺序,建议按这 4 条先看: 1. Case 03:先判断,再给 plan 入口 2. Case 07:旧澄清失效 3. Case 08:A/B/C 继续 requirement-plan 4. Case 11:Web / Feishu 同句一致 这 4 条最能看出 unified handoff 现在是不是真的站住了。