PMFlow/docs/superpowers/specs/2026-04-17-unified-project-...

18 KiB
Raw Permalink Blame History

Unified Project AI Handoff 成功案例验收包

目的

这份文档把 统一 Project Operating Context + 三路 handoff 的 12 条黄金场景收成可直接过目的“成功案例包”。

本文聚焦三件事:

  1. 每条 case 的最小输入
  2. 从哪里入站、经过哪些主链、按什么步骤验
  3. 成功时应该看到的结构化输出与最终用户可见输出

使用边界

  • 本文是“成功目标样例 + 验收步骤”文档,不等于 12 条都已经做完人工业务走读。
  • 文中的结构化输出与最终输出,表示该 case 验收成功时应看到的目标结果。
  • 自动化证据以 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 文本

必经链路:

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 是纯文本
  • 文本里包含成功案例要求的正文 / 当前项目 / 外部判断 / 下一步

交互卡片

必经链路:

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

示例请求:

{
  "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 是否以 内部知识库查询: 开头。

成功结构化输出

{
  "route_decision": {
    "route_mode": "internal",
    "effective_source_policy": "internal"
  }
}

成功最终输出

内部知识库查询:当前 PMFlow 里共有 2 个 MCP 模板。

成功判定

  • 命中 internal 主链
  • 不混入外部来源摘要
  • 不是泛化聊天式说明

Case 02

场景

纯外部事实问题

前置条件

  • 普通 QA 线程
  • 无 active requirement-plan

输入

  • 用户原话:OpenAI Docs 最近有没有新的 Responses API 最佳实践?
  • 真实入站面:Web POST /rag/ask

示例请求:

{
  "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. 查看最终回答是否保持 外部实时查询: 合同。

成功结构化输出

{
  "route_decision": {
    "route_mode": "external",
    "effective_source_policy": "external"
  }
}

成功最终输出

外部实时查询: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 示例请求:

{
  "question": "OpenAI Docs 这种能力适不适合我们当前项目?",
  "asked_via": "pmflow_manual",
  "project_id": 12,
  "source_chat_id": "chat-12",
  "action_mode": "read"
}

Feishu 最小入站:

{
  "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 文本是否已经变成围绕当前项目实体的自然分析回复,而不是标签摘要。

成功结构化输出

{
  "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:

外部实时查询:目前更像背景相关,建议先验证它是否能映射到当前项目正式对象。

Feishu:

外部实时查询:目前更像背景相关,建议先验证它是否能映射到当前项目正式对象。

结合当前项目看,Alpha 是一个 AI 项目管理工作台,当前仓库负责统一项目上下文与 AI handoff。就这次判断来说,这条外部信息目前更像背景相关,还不能直接映射到当前正式对象,所以现在更适合作为参考,而不是直接当成已经命中当前项目的正式变化。

如果你要,我可以基于当前结论进入 plan,继续把需求范围和下一步收清楚。

成功判定

  • 先给判断
  • 再给 plan 入口
  • 不得首轮直接切到 plan 主链

Case 04

场景

显式强锚点直接进 plan

前置条件

  • 上一轮已经给出结论
  • 当前消息显式 reply 到上一轮结论

输入

  • 用户原话:那按这个推进
  • 真实入站面:Feishu message.receive_v1 reply/thread

示例入站:

{
  "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。

成功结构化输出

{
  "execution_recipe": {
    "plan_handoff_mode": "explicit_plan_now"
  }
}

成功最终输出

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 抢走。

成功结构化输出

{
  "anchor_mode": "reply_anchor",
  "resolved_anchor": {
    "source_message_id": "om-prev-answer"
  }
}

成功最终输出

内部知识库查询:这里说的“模型路由”指的是 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。

成功结构化输出

{
  "route_decision": {
    "route_mode": "internal"
  }
}

成功最终输出

内部知识库查询:当前 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 的澄清协议消费链。

成功结构化输出

{
  "pending_clarification": null,
  "next_route": "query.answer"
}

成功最终输出

外部实时查询: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。

成功结构化输出

{
  "reply_template_key": "requirement_plan_followup",
  "parsed_choice": "C"
}

成功最终输出

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. 确认最终输出先给结论,再给下一步。

成功结构化输出

{
  "execution_recipe": {
    "plan_handoff_mode": "offer_after_answer"
  },
  "recommended_next_step": "如果你要,我可以基于当前结论进入 plan。"
}

成功最终输出

外部实时查询:目前适合作为背景参考,但还需要验证它与当前 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。

成功结构化输出

{
  "work_item_id": 88,
  "action": "plan_choice",
  "choice": "B"
}

成功最终输出

{
  "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 文本。

成功结构化输出

{
  "execution_recipe": {
    "plan_handoff_mode": "offer_after_answer"
  },
  "handoff_mode": "offer_after_answer",
  "can_enter_plan": true
}

成功最终输出

Web:

外部实时查询:目前更像背景相关,还不能直接映射到当前正式对象。

Feishu:

外部实时查询:目前更像背景相关,还不能直接映射到当前正式对象。

结合当前项目看,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。

成功结构化输出

第一轮:

{
  "clarification_code": "ambiguous_recent_reply",
  "clarification_candidates": [
    {"index": 1},
    {"index": 2}
  ]
}

第二轮:

{
  "selected_qa_id": 102,
  "effective_question": "我问你这个是哪个模型执行回复的?"
}

成功最终输出

第一轮:

我找到两条可能对应的回复:
1. ...
2. ...
请告诉我是第几条。

第二轮:

内部知识库查询:你选的是第二条,那条回复由当前 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 现在是不是真的站住了。