PMFlow/docs/superpowers/specs/2026-04-17-feishu-problem-i...

37 KiB
Raw Permalink Blame History

Feishu 问题清单与黄金对话样例

目的

这份清单用于给 统一 Project Operating Context + 三路 handoff 提供验收基线。

本文件不负责定义实现方案,只负责收口三件事:

  1. 当前 Feishu / Web / query.answer / requirement-plan 的已知失败类型
  2. 用户可感知的黄金对话样例
  3. 能落到回归测试里的 before snapshot

若要直接过目 12 条成功案例的完整输入/输出/步骤,请看:

若要看这轮 analysis_packet family 单独收口出来的高标准新案例,请看:

Baseline Snapshot

  • 记录时间:2026-04-17
  • 基线分支:master
  • 基线提交:3f94cf7 (Add explicit QA anchors and route governance pages)
  • 执行命令:
cd /Users/yuuuu/Documents/AIProjectManger && PYTHONPATH=backend backend/venv/bin/python -m pytest \
  backend/tests/test_feishu_pipeline_queue_contract.py \
  backend/tests/test_ai_pmo_qa_http_mainline_e2e.py \
  backend/tests/test_adaptive_rag_external_followup_contract.py -v
  • 基线结果:
    • 73 个用例里 68 通过,5 失败
    • 其中 3 个失败属于测试契约漂移,不直接代表客户可见 runtime 回归:
      • _should_process_feishu_high_accuracy() 已改为返回 (should_sync, reason, semantic_bundle),但测试仍按二元组解包
    • 其中 2 个失败属于真实 follow-up 退化,直接影响 Feishu / QA 追问体验:
      • 多候选 meta follow-up 没进入澄清分支
      • 第二条 这类中文编号选择无法恢复原问题

飞书展示验收基线

飞书侧的客户可见验收,不以内部 rag_result / fit_brief / external_recommendation payload 直接为准,而以“后端最终准备发给飞书的内容”为准。

  • 非交互 QA:
    • 以 qa_direct 文本为基线
    • 真正的格式化出口是 FeishuReceiveRoutingMixin._build_feishu_qa_direct_reply_text()
    • 验收时要看最终文本是否已经包含:
      • 结论正文
      • 围绕当前项目实体的分析段
      • 外部判断对应的分析结果
      • 必要时才出现的下一步建议
  • 强交互 requirement / draft / pending-manual:
    • 以 action card payload 为基线
    • 真正的格式化出口是 FeishuCardActionMixin._build_action_card_from_capture_result()
    • 验收时要看最终 card 结构、按钮动作码与 work_item_id,而不是只看内部语义字段
  • 两类 Feishu 输出都必须经过 FeishuReplyOutboxMixin._enqueue_reply_outbox() 写入 feishu_reply_outbox
  • 因此飞书验收必须分开看两类最终输出:
    • qa_direct 文本
    • draft_pending_confirm / requirement_plan_followup / pending_manual card

最小样例口径:

外部实时查询:<结论正文>

结合当前项目看,<项目/仓库摘要>。就这次判断来说,<external_recommendation 的客户可见分析>。

<recommended_next_step>
{
  "header": {"title": {"content": "AI PMO 需求收敛"}},
  "elements": [
    {"tag": "div", "text": {"content": "A/B/C 选项正文"}},
    {"tag": "action", "actions": [{"value": {"action": "plan_choice", "work_item_id": 77}}]}
  ]
}

失败类型清单

1. reply/thread 锚点丢失或绑错

  • 表现:明明在回复某条机器人消息,runtime 却新开普通 query 话题
  • 风险:requirement-plan / draft confirm / follow-up 全部容易串线
  • 重点证据:
    • backend/tests/test_feishu_pipeline_queue_contract.py
    • docs/architecture/RUNTIME_GUARDRAILS.md

2. internal / external / planmode 误分流

  • 表现:同一句话在不同入口被解释成不同主链
  • 风险:回答与动作边界漂移,用户不知道当前系统在“答问题”还是“要执行”

3. 外部问答后的短 follow-up 错掉进 internal 或 plan

  • 表现:用户只回一句“继续核实”“那你刚才那条是哪个模型回的”,系统不留在 query.answer
  • 风险:外部 freshness 问答续链失败,或被错误拉进 plan
  • 重点证据:
    • backend/tests/test_adaptive_rag_external_followup_contract.py::test_ambiguous_recent_reply_returns_numbered_candidates
    • backend/tests/test_adaptive_rag_external_followup_contract.py::test_pending_clarification_selection_restores_original_question

4. “适不适合 / 推荐什么 / 怎么推进” 这类复合问题被拆错

  • 表现:系统只做内部事实问答,或只做外部搜索,或直接进 plan
  • 风险:丢掉“先判断,再给 plan 入口”的默认策略

5. 同群同人 scope 串线

  • 表现:同一聊天里换了新话题,旧 requirement / draft / QA subject 继续抢主链
  • 风险:用户需要反复声明“我现在说的是另一个东西”

6. 旧 active context 劫持新问题

  • 表现:明明已是新问题,runtime 还沿旧 pending_clarification / draft / active requirement 继续解释
  • 风险:回复看起来像“接着上轮说”,但对象完全不对

7. 明明该继续 plan,却掉回 query.answer

  • 表现:已经在 active requirement / explicit reply 锚点里,用户继续 A/B/C 或“那按这个推进”,却被当普通 QA
  • 风险:需求收敛链断掉

8. 明明只是问结论,却被直接拉进 plan

  • 表现:用户只是在问适配性或建议,系统马上切成 requirement-plan
  • 风险:用户没有先拿到结论判断,产品体验像“被抢操作权”

9. 卡片动作和纯文本回复行为不一致

  • 表现:点按钮能续上,文本协议续不上;或者反过来
  • 风险:同一 requirement thread 出现双轨行为

10. Web 与 Feishu 对同一句话给出不同链路

  • 表现:Web 浮窗保留在问答,Feishu 同句掉进计划链,或反过来
  • 风险:统一项目型 AI 的前台体验不一致

黄金对话样例

Case 01: 纯内部系统问题

  • 场景名:内部配置只读问答
  • 初始上下文:项目页内,project_id=17
  • 用户原话:当前 PMFlow 里 MCP 模板有几个?
  • 当前错误行为:被当成外部搜索或泛化说明题
  • 目标行为:走 internal,只回答当前系统内对象事实
  • 应命中的 lane / handoff:internal
  • 验证方式:/rag/ask 的 route_mode=internal,回答不混外部来源

Case 02: 纯外部事实问题

  • 场景名:外部官方信息问答
  • 初始上下文:普通 QA 线程,无 active plan
  • 用户原话:OpenAI Docs 最近有没有新的 Responses API 最佳实践?
  • 当前错误行为:被内化成系统配置题或泛化聊天
  • 目标行为:走 external,只基于外部证据回答
  • 应命中的 lane / handoff:external
  • 验证方式:external-only 来源合同生效,内部来源不进入正文

Case 03: 先判断,再给 plan 入口

  • 场景名:项目适配性判断
  • 初始上下文:当前项目明确,repo 摘要可用
  • 用户原话:OpenAI Docs 这种能力适不适合我们当前项目?
  • 当前错误行为:直接进 plan,或者只给功能介绍
  • 目标行为:先给项目判断,再给一个明确 plan 入口
  • 应命中的 lane / handoff:external + fit brief + offer_after_answer
  • 验证方式:回答里先有结论,再有 recommended_next_step / can_enter_plan

Case 04: 显式强锚点直接进 plan

  • 场景名:结论后直接推进
  • 初始上下文:上一轮已给适配性判断,当前消息显式 reply 到该轮
  • 用户原话:那按这个推进
  • 当前错误行为:回到普通 query.answer
  • 目标行为:直接 handoff 给 plan 主链
  • 应命中的 lane / handoff:explicit_plan_now
  • 验证方式:命中 active requirement / draft 主链,不再重问模式

Case 05: reply/thread 明确续问

  • 场景名:对上一条 QA 解释继续追问
  • 初始上下文:显式 reply 到机器人上一条解释性回复
  • 用户原话:你刚才这条说的“模型路由”具体指哪层?
  • 当前错误行为:按 session 旧主题或新问题重开
  • 目标行为:优先用 reply/thread 锚点改写问题
  • 应命中的 lane / handoff:query.answer follow-up
  • 验证方式:锚点优先于 last_subject

Case 06: 同群同人但切新话题

  • 场景名:scope 内新问题
  • 初始上下文:同一群、同一用户,上一轮刚结束 requirement-plan
  • 用户原话:另外,现在 PMFlow 里 signal source onboarding 做到哪了?
  • 当前错误行为:继续沿 requirement-plan 解析
  • 目标行为:显式识别为新 query 话题
  • 应命中的 lane / handoff:internal
  • 验证方式:旧 active context 不得抢走新问题

Case 07: 旧 active context 劫持新问题

  • 场景名:pending clarification 清理不彻底
  • 初始上下文:上一轮存在 follow-up 澄清候选
  • 用户原话:不是说那个,我现在问 external gateway。
  • 当前错误行为:继续消费旧澄清状态
  • 目标行为:新话题应显式跳出旧 clarification
  • 应命中的 lane / handoff:query.answer 新话题
  • 验证方式:pending clarification 被清理或失效

Case 08: 明明该继续 plan,却掉回 query.answer

  • 场景名:A/B/C 协议回复掉链
  • 初始上下文:active requirement-plan,上一轮刚发 A/B/C
  • 用户原话:我选 C
  • 当前错误行为:普通 QA 回复或 mixed reply
  • 目标行为:继续 requirement-plan.collect
  • 应命中的 lane / handoff:plan
  • 验证方式:协议解析命中当前 option map

Case 09: 明明只是问结论,却被直接拉进 plan

  • 场景名:建议题被抢执行权
  • 初始上下文:无 active plan,无强锚点
  • 用户原话:这个方案适不适合我们现在这套 PMFlow?
  • 当前错误行为:直接开始 requirement-plan 追问
  • 目标行为:先答结论,再给可选下一步
  • 应命中的 lane / handoff:offer_after_answer
  • 验证方式:首轮不得直接进入 plan 主链

Case 10: 卡片动作和纯文本回复不一致

  • 场景名:同一 requirement thread 的双轨行为
  • 初始上下文:同一轮次同时支持卡片按钮与文本协议
  • 用户原话:选 B
  • 当前错误行为:按钮能续上,文本不行
  • 目标行为:按钮与文本都复用 canonical thread context
  • 应命中的 lane / handoff:plan
  • 验证方式:按钮 / 文本都命中同一 active topic

Case 11: Web 与 Feishu 对同一句话不同链路

  • 场景名:跨前台不一致
  • 初始上下文:项目页 Web 与 Feishu 同时可问答
  • 用户原话:这个适合吗
  • 当前错误行为:Web 走判断,Feishu 直接进 plan 或掉成 internal
  • 目标行为:统一遵守“先判断,再给 plan 入口”
  • 应命中的 lane / handoff:external + offer_after_answer
  • 验证方式:Web / Feishu 结构化结果一致

Case 12: 多候选 meta follow-up 需要澄清

  • 场景名:上一轮候选不唯一
  • 初始上下文:最近 5 分钟内有 2 条候选回复
  • 用户原话:那你刚才那条是哪个模型回复的?
  • 当前错误行为:直接 no-anchor,或者答错对象
  • 目标行为:返回编号候选,等待用户选条目
  • 应命中的 lane / handoff:query.answer follow-up clarify
  • 验证方式:返回 ambiguous_recent_reply,并能消费 第二条

当前测试映射

  • Feishu pipeline:
    • backend/tests/test_feishu_pipeline_queue_contract.py
    • 当前已覆盖:reply/thread canonicalization、requirement-plan sync gate、显式协议续问
  • Feishu 最终展示:
    • backend/tests/test_feishu_card_action_contract.py
    • 当前已覆盖:qa_direct 富文本摘要、requirement_plan_followup action card、外部状态文案分流
  • HTTP mainline:
    • backend/tests/test_ai_pmo_qa_http_mainline_e2e.py
    • 当前已覆盖:/rag/ask 与 /ai-pmo/process-text 的 project_operating_context 前门 contract
  • external follow-up:
    • backend/tests/test_adaptive_rag_external_followup_contract.py
    • 当前已覆盖:显式 reply 锚点、recent reply candidate、meta follow-up clarification

严格验收矩阵

下面这张矩阵才是本轮统一 handoff 的正式验收标准。

  • 每个 case 都必须先给出最小输入
  • 每个 case 都必须指明真实入站面和函数入口
  • 每个 case 都必须说明“必须经过哪些主链”
  • 每个 case 都必须区分:
    • 结构化判定输出
    • 最终用户可见输出
  • 若当前没有完整自动化覆盖,必须显式标成 partial 或 gap
  • 本表最后一列只表示“自动化覆盖状态”,不等于“业务验收完成”
  • 若未单独注明,默认这些 case 仍未经过完整的人工业务验收 / 真实用户场景验收
  • 当前真正做过的人工级 smoke 只包括:
    • 后端模拟 qa_direct 最终出站文本
    • 后端模拟 requirement_plan_followup 最终 action card 出站
  • 这些 smoke 只能证明“后端最终输出层可工作”,不能替代完整业务验收
Case 最小输入 真实入站面 必经主链 必验结构化输出 必验最终输出 当前自动化证据 自动化覆盖状态
01 纯内部系统问题 当前 PMFlow 里 MCP 模板有几个? Web POST /rag/ask
backend/app/api/rag_api/routes_query.py::rag_ask
rag_ask -> rag_ask_contract.build_kernel_ingress_kwargs() -> AIPMOKernelService.process_ingress() -> AdaptiveRAGQueryRouteGatewayMixin._run_query_route_gateway() -> internal query path -> _render_answer_with_route_contract() route_mode=internal
effective_source_policy=internal
reply_contract.head=内部知识库查询:
HTTP answer 必须以 内部知识库查询: 开头;不得混入 external-only source backend/tests/test_adaptive_rag_mainline_contract.py::test_query_route_gateway_uses_upstream_internal_contract_without_runtime_probes
backend/tests/test_adaptive_rag_mainline_contract.py::test_render_answer_with_route_contract_adds_explicit_source_prefix_and_summary
covered
02 纯外部事实问题 OpenAI Docs 最近有没有新的 Responses API 最佳实践? Web POST /rag/ask rag_ask -> build_kernel_ingress_kwargs() -> process_ingress() -> AdaptiveRAGQueryRouteGatewayMixin._run_query_route_gateway() -> external_query_service/* -> ExternalAnswerPresenter.present() route_mode=external
effective_source_policy=external
external source filter 必须 fail-closed
最终回答只能来自 external route;internal source 进入 external route 时必须被过滤掉 backend/tests/test_adaptive_rag_mainline_contract.py::test_deep_guard_answer_external_route_uses_external_presenter_without_internal_context
backend/tests/test_adaptive_rag_mainline_contract.py::test_external_route_source_filter_is_fail_closed
covered
03 先判断,再给 plan 入口 OpenAI Docs 这种能力适不适合我们当前项目? Web POST /rag/ask;Feishu message.receive_v1 Web:rag_ask -> build_kernel_ingress_kwargs() -> process_ingress() -> ProjectOperatingContextBuilder.build_from_ingress() -> _run_query_route_gateway() -> _build_response_payload()
Feishu:handle_message_event() -> _handle_message_receive() -> _capture_and_persist_from_ingress() -> _finalize_receive_result() -> _route_scene_reply() -> _render_feishu_qa_direct_reply_text() -> _safe_reply_text() -> feishu_reply_outbox
project_operating_context.current_project_id 必须存在
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 非空
Feishu qa_direct 文本必须先经过受控 analysis plan,再输出最终文本;最终回复只允许围绕 1-2 个入选项目实体解释判断,不得把项目里所有任务/信号源摊开讲。若没有足够依据,不得硬塞下一步建议,也不得退回 当前项目:/外部判断:/下一步: 标签摘要 backend/tests/test_ai_pmo_qa_http_mainline_e2e.py::test_http_rag_ask_should_attach_project_operating_context_for_current_project
backend/tests/test_adaptive_rag_mainline_contract.py::test_query_route_gateway_builds_external_execution_recipe_for_current_project
backend/tests/test_adaptive_rag_mainline_contract.py::test_build_response_payload_exposes_execution_recipe_and_plan_handoff
backend/tests/test_feishu_card_action_contract.py::test_route_scene_reply_sends_rich_qa_direct_text_for_handoff_metadata
backend/tests/test_ai_pmo_ingress_mainline.py::test_route_scene_reply_enqueues_rich_qa_direct_text_into_feishu_outbox
covered
04 显式强锚点直接进 plan 那按这个推进 Feishu message.receive_v1 reply/thread;Web active thread follow-up handle_message_event() -> _handle_message_receive() -> capture_from_feishu() -> canonical thread context -> active requirement / follow-up resolution -> requirement-plan mainline execution_recipe.plan_handoff_mode=explicit_plan_now 或 requirement mainline 直接命中 active context;不得退回普通 query.answer 输出必须继续 plan 主链;不得重新询问 internal / external / plan 是哪种模式 backend/tests/test_adaptive_rag_mainline_contract.py::test_query_route_gateway_keeps_explicit_requirement_plan_recipe
backend/tests/test_feishu_pipeline_queue_contract.py::test_capture_from_feishu_explicit_reply_choice_protocol_recovers_active_requirement_thread_context
covered
05 reply/thread 明确续问 你刚才这条说的“模型路由”具体指哪层? Feishu reply/thread;QA session follow-up handle_message_event() -> _handle_message_receive() -> capture_from_feishu() -> AdaptiveRAGFollowupAnchorMixin._resolve_followup_anchor() -> answer mainline 必须先命中 reply anchor / resolved anchor reply;不得优先吃 last_subject 或旧 session lexical hint 有效问题必须被重写到上一条回复对象;不能当新题重开 backend/tests/test_adaptive_rag_external_followup_contract.py::test_explicit_reply_to_message_id_resolves_anchor_without_session_history
backend/tests/test_adaptive_rag_external_followup_contract.py::test_followup_anchor_prefers_structured_semantics_without_keyword_hints
covered
06 同群同人但切新话题 另外,现在 PMFlow 里 signal source onboarding 做到哪了? Feishu message.receive_v1 handle_message_event() -> _handle_message_receive() -> capture_from_feishu() -> kernel intent/session inheritance guard 旧 requirement / draft session 不得被继承;_should_inherit_recent_feishu_session_context() 必须返回 false;显式 signal-source 请求不得被 draft/plan 劫持 必须回到新的 internal query;不能继续 requirement-plan / draft-confirm backend/tests/test_ai_pmo_ingress_mainline.py::test_should_inherit_recent_feishu_session_context_skips_explicit_new_requirement_topic
backend/tests/test_ai_pmo_ingress_mainline.py::test_should_inherit_recent_feishu_session_context_skips_draft_pending_confirm_session
backend/tests/test_ai_pmo_ingress_mainline.py::test_should_inherit_recent_feishu_session_context_is_disabled_for_query_session
backend/tests/test_ai_pmo_ingress_mainline.py::test_draft_confirmation_route_does_not_hijack_explicit_signal_source_request
covered
07 旧 active context 劫持新问题 不是说那个,我现在问 external gateway。 Feishu message.receive_v1 或 Web QA session process_ingress() -> pending clarification / active draft guard -> new query path 旧 pending_clarification 必须被识别为不再适用,或在新题下显式失效;不得强制消费旧 option map / draft route 必须进入新 query topic;不能继续用旧 clarification 或旧 draft 解释 backend/tests/test_ai_pmo_ingress_mainline.py::test_process_ingress_short_circuits_query_route_pending_clarification_followup
backend/tests/test_ai_pmo_ingress_mainline.py::test_query_pending_clarification_followup_skips_explicit_new_topic_and_clears_stale_state
covered
08 明明该继续 plan,却掉回 query.answer 我选 C Feishu message.receive_v1;kernel active context follow-up handle_message_event() -> _handle_message_receive() -> capture_from_feishu() -> active requirement workspace lookup -> requirement choice protocol parser -> requirement-plan direct result 必须命中当前 option map / active requirement context;不得走普通 QA semantic gate 输出必须继续 requirement_plan_followup;不得出现普通 QA 解释文案 backend/tests/test_feishu_pipeline_queue_contract.py::test_capture_from_feishu_allows_group_choice_protocol_without_repeat_mention_when_requirement_plan_active
backend/tests/test_ai_pmo_ingress_mainline.py::test_active_requirement_choice_protocol_followup_bypasses_semantic_decision_gate
backend/tests/test_ai_pmo_ingress_mainline.py::test_execute_query_intent_requirement_plan_followup_persists_progress_and_asks_next_question
covered
09 明明只是问结论,却被直接拉进 plan 这个方案适不适合我们现在这套 PMFlow? Web POST /rag/ask;Feishu message.receive_v1 与 Case 03 相同,但验收重点是不能直接 handoff 到 plan 主链 execution_recipe.plan_handoff_mode=offer_after_answer
首轮不能直接 explicit_plan_now
首轮输出必须先给结论;recommended_next_step 只能作为可选下一步,不能直接切 plan scene backend/tests/test_adaptive_rag_mainline_contract.py::test_query_route_gateway_builds_external_execution_recipe_for_current_project
backend/tests/test_adaptive_rag_mainline_contract.py::test_build_response_payload_exposes_execution_recipe_and_plan_handoff
backend/tests/test_feishu_card_action_contract.py::test_route_scene_reply_sends_rich_qa_direct_text_for_handoff_metadata
covered
10 卡片动作和纯文本回复不一致 文本:选 B
卡片:点击 B 按钮
Feishu message.receive_v1;Feishu card.action.trigger 文本:handle_message_event() -> _handle_message_receive() -> capture_from_feishu() -> active requirement lookup -> requirement-plan mainline
卡片:handle_message_event() -> _handle_card_action_trigger() -> synthetic ingress -> process_ingress() -> _safe_reply_card()
文本与卡片都必须落到同一 work_item_id / canonical thread context;不得一条续上、一条掉链 文本续问与卡片动作都必须回到同一 requirement thread;输出都应继续 requirement-plan backend/tests/test_feishu_card_action_contract.py::test_handle_card_action_trigger_plan_choice_dispatches_synthetic_ingress
backend/tests/test_feishu_pipeline_queue_contract.py::test_capture_from_feishu_group_choice_protocol_prefers_active_requirement_over_older_pending_draft
backend/tests/test_feishu_card_action_contract.py::test_route_scene_reply_sends_requirement_plan_followup_as_card
covered
11 Web 与 Feishu 对同一句话不同链路 这个适合吗 Web POST /rag/ask;Feishu message.receive_v1 Web:rag_ask -> build_kernel_ingress_kwargs() -> process_ingress() -> AdaptiveRAG
Feishu:handle_message_event() -> _handle_message_receive() -> capture_from_feishu() -> _route_scene_reply()
Web 与 Feishu 必须共享同一 project_operating_context / execution_recipe / handoff_mode 语义;不得 Web 是 offer_after_answer 而 Feishu 直接 plan 或 internal Web 要显示同一 handoff meta;Feishu qa_direct 要落到同一“先判断,再给 plan 入口”文案结构 Web 侧:backend/tests/test_adaptive_rag_mainline_contract.py::test_build_response_payload_exposes_execution_recipe_and_plan_handoff
backend/tests/test_ai_pmo_qa_http_mainline_e2e.py::test_http_rag_ask_returns_offer_after_answer_handoff_metadata_for_current_project_fit_question
frontend/src/components/ai-steward/FloatingQAChat.test.tsx
Feishu 侧:backend/tests/test_feishu_card_action_contract.py::test_route_scene_reply_sends_rich_qa_direct_text_for_handoff_metadata
covered
12 多候选 meta follow-up 需要澄清 第一轮:那你刚才那条是哪个模型回复的?
第二轮:第二条
Feishu / QA session follow-up _resolve_followup_anchor() -> ambiguous candidates -> pending clarification -> _consume_pending_clarification_selection() 第一轮必须返回 ambiguous_candidates,带编号候选;第二轮必须恢复 original_question 并绑定 selected_qa_id 用户可见输出必须先列编号候选,再能消费中文编号 第二条 backend/tests/test_adaptive_rag_external_followup_contract.py::test_ambiguous_recent_reply_returns_numbered_candidates
backend/tests/test_adaptive_rag_external_followup_contract.py::test_pending_clarification_selection_restores_original_question
covered

严格判定规则

  • covered
    • 至少同时有:
      • 一个结构化主链测试
      • 一个最终输出测试
    • 或者该 case 本身只适合看结构化输出,且已由主链 contract 直接覆盖
  • partial
    • 已有相邻 contract,但还没有完全对上黄金输入或黄金输出
  • gap
    • 当前没有可直接证明该 case 的自动化证据

业务验收口径

  • “自动化覆盖” != “业务验收完成”
  • 业务验收至少要求:
    • 按黄金输入从真实入站面进入
    • 经过本文声明的主链
    • 结构化输出与最终用户可见输出都对上
    • 对用户有意义的业务结论和线程续接行为都正确
  • 按这个口径,当前还不能说“只剩 2 个业务 case 没测”
  • 当前能诚实宣称的只有:
    • 12/12 个 case 已有自动化证据
    • 但完整业务验收并未按 12 个黄金场景逐条跑完

本轮补齐结果

  • Case 07
    • 已补 backend/tests/test_ai_pmo_ingress_mainline.py::test_query_pending_clarification_followup_skips_explicit_new_topic_and_clears_stale_state
    • 当前要求是:显式新话题信号不能再被旧 pending_clarification 抢走,且旧澄清状态必须立即失效
  • Case 11
    • 已补 backend/tests/test_ai_pmo_qa_http_mainline_e2e.py::test_http_rag_ask_returns_offer_after_answer_handoff_metadata_for_current_project_fit_question
    • 当前要求是:同一句话在 Web 与 Feishu 两端都必须保留同一份 offer_after_answer handoff 语义

当前执行记录(2026-04-17)

本轮已实际执行一组覆盖 10 个黄金场景的关键切片,不再只停在“文档映射”。

执行命令:

cd /Users/yuuuu/Documents/AIProjectManger/.worktrees/codex/unified-project-ai-handoff
PYTHONPATH=backend /Users/yuuuu/Documents/AIProjectManger/backend/venv/bin/python -m pytest \
  backend/tests/test_adaptive_rag_mainline_contract.py::test_query_route_gateway_uses_upstream_internal_contract_without_runtime_probes \
  backend/tests/test_adaptive_rag_mainline_contract.py::test_render_answer_with_route_contract_adds_explicit_source_prefix_and_summary \
  backend/tests/test_adaptive_rag_mainline_contract.py::test_deep_guard_answer_external_route_uses_external_presenter_without_internal_context \
  backend/tests/test_adaptive_rag_mainline_contract.py::test_external_route_source_filter_is_fail_closed \
  backend/tests/test_adaptive_rag_mainline_contract.py::test_query_route_gateway_builds_external_execution_recipe_for_current_project \
  backend/tests/test_adaptive_rag_mainline_contract.py::test_query_route_gateway_keeps_explicit_requirement_plan_recipe \
  backend/tests/test_adaptive_rag_mainline_contract.py::test_build_response_payload_exposes_execution_recipe_and_plan_handoff \
  backend/tests/test_ai_pmo_qa_http_mainline_e2e.py::test_http_rag_ask_should_attach_project_operating_context_for_current_project \
  backend/tests/test_feishu_card_action_contract.py::test_route_scene_reply_sends_rich_qa_direct_text_for_handoff_metadata \
  backend/tests/test_ai_pmo_ingress_mainline.py::test_route_scene_reply_enqueues_rich_qa_direct_text_into_feishu_outbox \
  backend/tests/test_adaptive_rag_external_followup_contract.py::test_explicit_reply_to_message_id_resolves_anchor_without_session_history \
  backend/tests/test_ai_pmo_ingress_mainline.py::test_should_inherit_recent_feishu_session_context_is_disabled_for_query_session \
  backend/tests/test_ai_pmo_ingress_mainline.py::test_draft_confirmation_route_does_not_hijack_explicit_signal_source_request \
  backend/tests/test_feishu_pipeline_queue_contract.py::test_capture_from_feishu_allows_group_choice_protocol_without_repeat_mention_when_requirement_plan_active \
  backend/tests/test_ai_pmo_ingress_mainline.py::test_active_requirement_choice_protocol_followup_bypasses_semantic_decision_gate \
  backend/tests/test_feishu_card_action_contract.py::test_handle_card_action_trigger_plan_choice_dispatches_synthetic_ingress \
  backend/tests/test_feishu_pipeline_queue_contract.py::test_capture_from_feishu_group_choice_protocol_prefers_active_requirement_over_older_pending_draft \
  backend/tests/test_feishu_card_action_contract.py::test_route_scene_reply_sends_requirement_plan_followup_as_card \
  backend/tests/test_adaptive_rag_external_followup_contract.py::test_ambiguous_recent_reply_returns_numbered_candidates \
  backend/tests/test_adaptive_rag_external_followup_contract.py::test_pending_clarification_selection_restores_original_question \
  -v

执行结果:

  • 20 passed
  • 这组切片直接覆盖:
    • Case 01
    • Case 02
    • Case 03
    • Case 04
    • Case 05
    • Case 06
    • Case 08
    • Case 09
    • Case 10
    • Case 12
  • 未被这轮实跑切片覆盖的仍是:
    • Case 07
    • Case 11

补齐剩余 2 个 gap 的执行命令:

cd /Users/yuuuu/Documents/AIProjectManger/.worktrees/codex/unified-project-ai-handoff
PYTHONPATH=backend /Users/yuuuu/Documents/AIProjectManger/backend/venv/bin/python -m pytest \
  backend/tests/test_ai_pmo_ingress_mainline.py::test_query_pending_clarification_followup_skips_explicit_new_topic_and_clears_stale_state \
  backend/tests/test_ai_pmo_qa_http_mainline_e2e.py::test_http_rag_ask_returns_offer_after_answer_handoff_metadata_for_current_project_fit_question \
  -v

补齐结果:

  • 2 passed
  • 这轮补齐后,12 个黄金场景都已有对应自动化证据

最终合并验收命令:

cd /Users/yuuuu/Documents/AIProjectManger/.worktrees/codex/unified-project-ai-handoff
PYTHONPATH=backend /Users/yuuuu/Documents/AIProjectManger/backend/venv/bin/python -m pytest \
  backend/tests/test_adaptive_rag_mainline_contract.py::test_query_route_gateway_uses_upstream_internal_contract_without_runtime_probes \
  backend/tests/test_adaptive_rag_mainline_contract.py::test_render_answer_with_route_contract_adds_explicit_source_prefix_and_summary \
  backend/tests/test_adaptive_rag_mainline_contract.py::test_deep_guard_answer_external_route_uses_external_presenter_without_internal_context \
  backend/tests/test_adaptive_rag_mainline_contract.py::test_external_route_source_filter_is_fail_closed \
  backend/tests/test_adaptive_rag_mainline_contract.py::test_query_route_gateway_builds_external_execution_recipe_for_current_project \
  backend/tests/test_adaptive_rag_mainline_contract.py::test_query_route_gateway_keeps_explicit_requirement_plan_recipe \
  backend/tests/test_adaptive_rag_mainline_contract.py::test_build_response_payload_exposes_execution_recipe_and_plan_handoff \
  backend/tests/test_ai_pmo_qa_http_mainline_e2e.py::test_http_rag_ask_should_attach_project_operating_context_for_current_project \
  backend/tests/test_ai_pmo_qa_http_mainline_e2e.py::test_http_rag_ask_returns_offer_after_answer_handoff_metadata_for_current_project_fit_question \
  backend/tests/test_feishu_card_action_contract.py::test_route_scene_reply_sends_rich_qa_direct_text_for_handoff_metadata \
  backend/tests/test_ai_pmo_ingress_mainline.py::test_route_scene_reply_enqueues_rich_qa_direct_text_into_feishu_outbox \
  backend/tests/test_adaptive_rag_external_followup_contract.py::test_explicit_reply_to_message_id_resolves_anchor_without_session_history \
  backend/tests/test_ai_pmo_ingress_mainline.py::test_should_inherit_recent_feishu_session_context_is_disabled_for_query_session \
  backend/tests/test_ai_pmo_ingress_mainline.py::test_draft_confirmation_route_does_not_hijack_explicit_signal_source_request \
  backend/tests/test_feishu_pipeline_queue_contract.py::test_capture_from_feishu_allows_group_choice_protocol_without_repeat_mention_when_requirement_plan_active \
  backend/tests/test_ai_pmo_ingress_mainline.py::test_active_requirement_choice_protocol_followup_bypasses_semantic_decision_gate \
  backend/tests/test_feishu_card_action_contract.py::test_handle_card_action_trigger_plan_choice_dispatches_synthetic_ingress \
  backend/tests/test_feishu_pipeline_queue_contract.py::test_capture_from_feishu_group_choice_protocol_prefers_active_requirement_over_older_pending_draft \
  backend/tests/test_feishu_card_action_contract.py::test_route_scene_reply_sends_requirement_plan_followup_as_card \
  backend/tests/test_adaptive_rag_external_followup_contract.py::test_ambiguous_recent_reply_returns_numbered_candidates \
  backend/tests/test_adaptive_rag_external_followup_contract.py::test_pending_clarification_selection_restores_original_question \
  backend/tests/test_ai_pmo_ingress_mainline.py::test_query_pending_clarification_followup_skips_explicit_new_topic_and_clears_stale_state \
  -v

最终合并结果:

  • 22 passed
  • 这条命令是当前严格自动化验收基线;对应 12 个黄金场景的关键证据都已并入同一轮执行

代表性实跑输入输出

下面这些是当前真正拿得到的“实跑级”输入输出样本,用来证明本文不是只列测试名。

Sample A: Case 03 / Case 09 的 Feishu qa_direct

  • 输入:
{
  "scene": "qa",
  "message_content": "这个适合吗",
  "result": {
    "direct_reply_required": true,
    "direct_reply_text": "外部实时查询:目前证据不足,建议先明确要查融资还是招聘动态。",
    "rag_result": {
      "fit_brief": {
        "project_summary": "Alpha 是一个 AI 项目管理工作台。",
        "repo_summary": "当前仓库负责统一项目上下文与 AI handoff。"
      },
      "external_recommendation": {
        "retrieval_status": "evidence_found",
        "impact_status": "indirect_only",
        "external_impact": "目前更像背景相关,还不能直接映射到当前正式对象。"
      },
      "recommended_next_step": "如果你要,我可以基于当前结论进入 plan,继续把需求范围和下一步收清楚。"
    }
  }
}
  • 最终出站文本:
外部实时查询:目前证据不足,建议先明确要查融资还是招聘动态。

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

如果你要,我可以基于当前结论进入 plan,继续把需求范围和下一步收清楚。
  • 判定重点:
    • 先答结论
    • 再追加分析式项目映射和下一步建议
    • 不是直接切 plan

Sample B: Case 10 的 requirement_plan_followup action card

  • 输入:
{
  "scene": "qa",
  "message_content": "C",
  "result": {
    "reply_template_key": "requirement_plan_followup",
    "work_item_id": 88,
    "direct_reply_text": "C. 混合增强路径\n\n⭐ 当前我更推荐:C"
  }
}
  • 最终出站 card 关键结构:
{
  "header": {
    "title": {
      "content": "AI PMO 需求收敛"
    }
  },
  "elements": [
    {
      "tag": "div",
      "text": {
        "content": "C. 混合增强路径"
      }
    },
    {
      "tag": "div",
      "text": {
        "content": "⭐ 当前我更推荐:C"
      }
    },
    {
      "tag": "action",
      "actions": [
        { "value": { "action": "plan_choice", "choice": "A", "work_item_id": 88 } },
        { "value": { "action": "plan_choice", "choice": "B", "work_item_id": 88 } },
        { "value": { "action": "plan_choice", "choice": "C", "work_item_id": 88 } },
        { "value": { "action": "plan_choice", "choice": "D", "work_item_id": 88 } }
      ]
    }
  ]
}
  • 判定重点:
    • 最终输出不是普通文本
    • 必须是 requirement_plan_followup card
    • work_item_id 和 plan_choice 动作码必须保留

Sample C: Case 12 的编号澄清协议

  • 第一轮输入:
那你刚才那条是哪个模型回复的?
  • 第一轮结构化输出要求:
{
  "anchor_mode": "ambiguous_candidates",
  "candidate_replies": [
    { "index": 1 },
    { "index": 2 }
  ]
}
  • 第二轮输入:
第二条
  • 第二轮结构化输出要求:
{
  "selected_qa_id": 102,
  "effective_question": "我问你这个是哪个模型执行回复的?"
}
  • 判定重点:
    • 不得直接答错对象
    • 必须先给编号候选
    • 必须支持中文编号选择

Task 0 验收口径

  • 这轮 unified context / three-lane handoff 不以“架构看起来合理”为验收
  • 必须以本文件里的黄金样例 + 对应回归测试为准
  • 飞书前台展示必须额外以“后端最终格式化后的 qa_direct 文本 / action card payload”验收,不能只看内部 handoff payload
  • 若某个样例当前无法直接落测试,至少要在测试里以 xfail 或 coverage gap 形式显式占位
  • 任何修复不得回到关键词仓、正则路由或硬编码 semantic split helper