# Unified Project Operating Context And Three-Lane Handoff Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 先补一层统一的 `Project Operating Context`,再把 `internal / external / planmode` 收口成一个统一入口下的三条执行能力,让 PMFlow 变成“懂当前项目、能取外部资料、能继续推进计划”的项目型 AI,而不是三套互相打架的聊天模式。 **Architecture:** 本次不把三条链继续拆成三个彼此孤立的产品模式,而是在 ingress 先建立统一上下文层,再产出单次 `execution_recipe`。`internal` 负责内部对象事实与项目适配画像,`external` 负责外部可核验资料与推荐,`planmode` 只作为后续 handoff 能力接棒,默认策略固定为“先给判断结论,再明确给一个进入 plan 的下一步”。这里的 `planmode` 只是对话层简称;落到 repo 真名时,统一指 `plan_mode` 顶层 gate 以及其后的 `requirement_plan / draft_pending_confirm` 主链,不额外引入第四个 runtime route 名。外部推荐允许消费内部 `fit brief` 生成检索计划,但最终外部答案正文仍必须只由外部证据支撑。 **Tech Stack:** FastAPI, Pydantic, SQLAlchemy 2.0, AdaptiveRAG, AI PMO Kernel, External Query Gateway, React 18, TypeScript, React Query, Vitest, pytest --- ## Scope And Guardrails - 本计划在真正实现前,必须先补一份 `Feishu 问题清单 + 黄金对话样例`,把用户已知痛点收成可回归样本;没有这一步,不允许直接进入三路整合实现。 - 本计划的统一上下文默认范围固定为: - `当前项目` - `当前仓库摘要` - `当前线程 / 页面 / reply` 强锚点 - 这里的“当前仓库”按 repo 现状必须理解成“运行时可取得的 codebase snapshot / repo summary”,不是默认已经具备全 workspace、全语言、全目录的完整理解;当前 codebase overview 仍以 backend snapshot 为主,若后续扩展前端或全仓库画像,应作为显式增量实现。 - v1 不默认扩到“用户名下所有项目”或“整个租户 portfolio”,避免重新回到全局聊天串线。 - 默认多意图策略固定为: - `A`:先给基于项目的判断结论 - 再明确给一个“要不要基于这个进入 plan”的下一步 - 强锚点仍优先于旧 session 和记忆续写: - `reply/thread` - 页面 `project_id` - 显式 `work_item_id` - 已绑定 active requirement / draft context - 默认 `A` 只适用于未命中显式 `plan_mode` gate 的 recommendation / 判断题;一旦命中 active requirement context、draft confirm、显式 `reply/thread` 或 `work_item_id` 强锚点,必须继续留在 repo 既有的 `requirement_plan / draft_pending_confirm` 主链,不能被“先答再给 plan 入口”抢走。 - 严禁回到散落运行时语义拍板,这是一条硬约束: - 不准新增关键词仓 - 不准新增正则拍板 helper - 不准新增“看到某些词就切 internal / external / plan”的硬编码语义分流 - 不准在执行层靠“问题长相”重判 internal / external / plan - 不准把飞书问题修成一堆 bypass 规则 - repo 里其它路径仍可能残留历史 keyword / lookup 工具;这轮 unified ingress 和三路 handoff 不得复用它们来做来源裁决、plan 夺权或飞书短句分流。 - external 推荐允许使用 internal 侧生成的 `fit brief` 作为“检索约束输入”,但 final external answer 仍必须遵守 external-only 展示合同: - `fit brief` 只属于 pre-retrieval planning input,不放宽 repo 现有的单一 `source_policy` 合同,也不允许同一轮 query 混搜 internal + external 证据 - presenter 不重新注入 `system_snapshot / user_snapshot / project_context / internal context_blocks` - 客户可见正文不混写内部事实充当外部证据,且最终展示仍必须遵守 `customer_safe / admin_debug` 双视图边界 - external recommendation 必须显式区分外部证据状态: - `evidence_found` - `verifier_blocked` - `no_relevant_results` - `retrieval_failed` - 下面这些 `impact/action` 约束属于这份计划的 v1 product guardrail,不应被误读成 repo 现状已经完整落地的 runtime contract;实现时若与现有对象模型冲突,应先回到正式业务对象和 guardrail 真源校正。 - external recommendation 的“影响判断”必须独立于“检索状态”表达: - `direct_match` - `indirect_only` - `no_direct_impact` - 外部影响默认优先绑定正式项目对象;若当前项目存在正式 `signal source` / capability 锚点,则优先绑定这些对象。 - 若外部证据只能说明行业背景、技术可行性或泛市场趋势,而不能精确命中当前项目正式对象,只能返回 `indirect_only`,禁止伪装成“已命中项目推荐”。 - 外部动作只能落在: - 范围调整 - 规则/口径核验 - 接口兼容性验证 - 监控补点 - 数据映射补齐 - 外部动作禁止涉及: - 招聘 - 人员分配 - 负责人指派 - 组织调整 - 直接修改项目/信号源状态 - planmode 不再作为第一反应默认吞掉“适不适合 / 推荐什么 / 要不要接”这类问题;只有显式强动作锚点或统一 ingress recipe 判定为 `explicit_plan_now` 时才直接接管。 ## Non-Goals For This Plan - 不做 portfolio-wide 全局 AI 助手 - 不做“只要像建议题就自动进 plan”的激进策略 - 不做在线自动学习路由阈值 - 不在 v1 里引入新的自由工具调用或自由联网 agent - 不把 external answer presenter 改成混合 internal + external 的单 prompt - 不以关键词/正则/硬编码语义规则作为飞书问题的修复手段 ## Baseline File Map ### Context Layer - Create: `backend/app/schemas/project_operating_context.py` - 统一定义 `ProjectOperatingContext`、`ProjectFitBrief`、`ExecutionRecipe` 等结构化合同。 - Create: `backend/app/services/ai_pmo/project_operating_context_builder.py` - 从 `current_project_id / current repo snapshot / page anchor / thread anchor / world model` 组装统一上下文包。 - Modify: `backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py` - 在 ingress 入口建立并透传统一上下文,而不是先各走各的语义判断。 - Modify: `backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_builder.py` - 暴露 `resolved_project_id` 之外的项目运行态摘要,供统一上下文层使用。 - Modify: `backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/ai_pmo_kernel_mixins_world_model_session.py` - 持久化最小必要的 project operating context snapshot,避免 session 只记 `current_project_id` 太粗。 ### Unified Query Handoff - Modify: `backend/app/services/adaptive_rag_service/mixin_query_route_gateway.py` - 在保留 `route_mode / effective_source_policy / reply_contract / probe_snapshot` 合同的前提下,叠加更高层的 `execution_recipe` 输出。 - Modify: `backend/app/services/adaptive_rag_service/mixin_core_intent.py` - 让 query semantics 可消费统一上下文和 handoff hint,而不是只看问题文本。 - Modify: `backend/app/services/adaptive_rag_service/mixin_answer_mainline.py` - 按 `execution_recipe` 接棒 internal / external / plan-offer,而不是三路各自隐式争抢。 - Modify: `backend/app/services/adaptive_rag_service/mixin_followup_anchor.py` - follow-up 续接优先基于统一上下文,而不是仅靠 session state。 ### Internal Fit Brief - Create: `backend/app/services/adaptive_rag_service/mixin_project_fit_brief.py` - 负责从项目对象、功能目录、repo 概览、当前能力缺口生成 customer-safe `fit brief`。 - Modify: `backend/app/services/adaptive_rag_service/service.py` - 接入新的 fit-brief mixin。 - Modify: `backend/app/services/adaptive_rag_service/mixin_manifest_feature_p1.py` - 暴露 feature catalog 命中摘要给 fit brief 使用。 - Modify: `backend/app/services/project_knowledge_graph_service/mixin_codebase_overview.py` - 把 codebase overview 从单纯 backend Python 统计扩成可用于“当前仓库摘要”的最小基线。 - Modify: `backend/app/services/context_cache_service/mixin_user_snapshot.py` - 只补 customer-safe 的当前项目参与状态,不扩成 portfolio 大杂烩。 ### External Recommendation - Modify: `backend/app/services/external_query_service/schemas.py` - 给 external plan / execution 增加 `fit_brief_summary / recommendation_goal / project_scope_hint` 等检索约束字段。 - Modify: `backend/app/services/external_query_service/planner.py` - 让 planner 可以吃 `fit brief`,生成更贴项目的 query plan。 - Modify: `backend/app/services/external_query_service/gateway.py` - 保留 adapter/verifier 主链,同时把 `fit_brief` 作为 planner meta 一部分审计。 - Modify: `backend/app/services/external_query_service/presenter.py` - 明确拒绝消费 internal evidence;只消费 external packet,但支持输出“适配判断”模板位。 - Modify: `backend/app/services/adaptive_rag_service/mixin_answer_payload.py` - 把 `fit brief` 和 `external evidence` 拆成不同 payload 区块,而不是混成一段答案。 ### Plan Handoff And UX - Modify: `backend/app/services/adaptive_rag_service/mixin_answer_payload.py` - 在回答 payload 里增加 `recommended_next_step / can_enter_plan / handoff_mode`。 - Modify: `backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_default.py` - 飞书默认按 `A` 策略:先答,再给一个明确的 plan handoff 入口。 - Modify: `backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_a.py` - 把当前项目 / 当前线程 / 当前 repo 级别提示收进统一上下文,不在飞书旁路拼接脏 context。 - Modify: `frontend/src/components/ai-steward/FloatingQAChat.tsx` - 展示 unified context、fit brief、外部推荐结果和“进入 plan”下一步。 - Modify: `frontend/src/components/ai-steward/QAConversationStatusBar.tsx` - 展示当前 operating context,而不是只展示“内部还是外部”。 - Modify: `frontend/src/components/ai-steward/QAResponseMetaCard.tsx` - 新增“项目画像 / 外部证据 / 下一步”三段式元信息卡。 ### Tests - Create: `backend/tests/test_project_operating_context_contract.py` - Modify: `backend/tests/test_ai_pmo_ingress_mainline.py` - Modify: `backend/tests/test_rag_route_contract.py` - Modify: `backend/tests/test_adaptive_rag_mainline_contract.py` - Modify: `backend/tests/test_adaptive_rag_external_followup_contract.py` - Modify: `backend/tests/test_external_query_planner.py` - Modify: `backend/tests/test_internal_query_context_packet.py` - Modify: `backend/tests/test_ai_pmo_qa_http_mainline_e2e.py` - Modify: `backend/tests/test_feishu_pipeline_queue_contract.py` - Create: `frontend/src/components/ai-steward/QAResponseMetaCard.test.tsx` - Modify: `frontend/src/components/ai-steward/FloatingQAChat.test.tsx` ### Docs - Modify: `CLAUDE.md` - Modify: `docs/architecture/ARCHITECTURE_INVENTORY.md` - Modify: `docs/architecture/CODE_REFERENCE_INDEX.md` - Modify: `docs/architecture/RUNTIME_GUARDRAILS.md` - Create: `docs/superpowers/specs/2026-04-17-feishu-problem-inventory.md` --- ### Task 0: Build The Feishu Problem Inventory Before Any Runtime Changes **Files:** - Create: `docs/superpowers/specs/2026-04-17-feishu-problem-inventory.md` - Modify: `backend/tests/test_feishu_pipeline_queue_contract.py` - Modify: `backend/tests/test_ai_pmo_qa_http_mainline_e2e.py` - Modify: `backend/tests/test_adaptive_rag_external_followup_contract.py` - Modify: `docs/architecture/RUNTIME_GUARDRAILS.md` - [ ] **Step 1: Collect the current Feishu failure classes from existing code, tests, and guardrails** The inventory must explicitly classify at least these buckets: ```text 1. reply/thread 锚点丢失或绑错 2. internal / external / planmode 误分流 3. 外部问答后续一句话错误掉进 internal 或 plan 4. “适不适合 / 推荐什么 / 怎么推进” 这类复合问题被拆错 5. 同群同人 scope 串线 6. 旧 active context 劫持新问题 7. 明明该继续 plan,却掉回 query.answer 8. 明明只是问结论,却被直接拉进 plan 9. 卡片动作和纯文本回复行为不一致 10. Web 与 Feishu 对同一句话给出不同链路 ``` - [ ] **Step 2: Write 10-15 gold conversation cases in the inventory doc** Each case must include: ```markdown - 场景名 - 初始上下文 - 用户原话 - 当前错误行为 - 目标行为 - 应命中的 lane / handoff - 验证方式 ``` At least one case must cover each of: - 纯内部系统问题 - 纯外部事实问题 - “先判断,再给 plan 入口” - 显式强锚点直接进 plan - reply/thread 明确续问 - 同群同人但切新话题 - [ ] **Step 3: Convert the highest-value cases into failing regression tests** Required minimum: - 2 cases in `test_feishu_pipeline_queue_contract.py` - 2 cases in `test_ai_pmo_qa_http_mainline_e2e.py` - 1 case in `test_adaptive_rag_external_followup_contract.py` These tests should fail under the current runtime if the problem still exists, or be marked as expected coverage gaps if the exact behavior is not yet implemented. - [ ] **Step 4: Record explicit acceptance criteria in `RUNTIME_GUARDRAILS.md`** Add a short section saying the unified context / three-lane handoff work must be accepted against the Feishu problem inventory, not just architecture conformance. Also add an explicit line: ```text This work is not allowed to introduce new keyword routing, regex routing, or hard-coded semantic split helpers. ``` - [ ] **Step 5: Run the inventory-aligned tests and capture the baseline** Run: ```bash 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 ``` Expected: - either explicit FAIL on known bad cases - or explicit PASS with coverage notes in the inventory doc This baseline becomes the “before” snapshot for the rest of the plan. ### Task 1: Lock The Unified Project Operating Context Contract **Files:** - Create: `backend/tests/test_project_operating_context_contract.py` - Modify: `backend/tests/test_ai_pmo_ingress_mainline.py` - Modify: `backend/tests/test_rag_route_contract.py` - [ ] **Step 1: Write the failing contract tests for the new context packet** Add tests that lock a minimal packet such as: ```python context = ProjectOperatingContext( current_project_id=17, current_repo_scope="pmflow-main", page_anchor={"project_id": 17, "origin": "pathname"}, thread_anchor={"reply_to_message_id": "om_xxx"}, fit_scope="current_project_and_repo", decision_strategy="answer_then_offer_plan", ) assert context.current_project_id == 17 assert context.current_repo_scope == "pmflow-main" assert context.decision_strategy == "answer_then_offer_plan" ``` - [ ] **Step 2: Add ingress tests that require the packet to be attached before query execution** Lock that `AIPMOKernelService.process_ingress()` or its contract builder must produce: ```python assert ingress_options["project_operating_context"]["current_project_id"] == 17 assert ingress_options["project_operating_context"]["fit_scope"] == "current_project_and_repo" ``` - [ ] **Step 3: Lock route-contract tests so `/rag/ask` receives the unified packet** The HTTP-facing RAG contract should prove: ```python assert payload["project_operating_context"]["current_project_id"] == 17 assert payload["project_operating_context"]["decision_strategy"] == "answer_then_offer_plan" ``` - [ ] **Step 4: Run focused tests and confirm they fail before implementation** Run: ```bash cd /Users/yuuuu/Documents/AIProjectManger && PYTHONPATH=backend backend/venv/bin/python -m pytest \ backend/tests/test_project_operating_context_contract.py \ backend/tests/test_ai_pmo_ingress_mainline.py \ backend/tests/test_rag_route_contract.py -v ``` Expected: FAIL because the packet/schema/builder do not exist yet. ### Task 2: Build And Persist Project Operating Context At Ingress **Files:** - Create: `backend/app/schemas/project_operating_context.py` - Create: `backend/app/services/ai_pmo/project_operating_context_builder.py` - Modify: `backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py` - Modify: `backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_builder.py` - Modify: `backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/ai_pmo_kernel_mixins_world_model_session.py` - Test: `backend/tests/test_project_operating_context_contract.py` - Test: `backend/tests/test_ai_pmo_ingress_mainline.py` - [ ] **Step 1: Add the minimal Pydantic schema** Create a focused schema module: ```python class ProjectOperatingContext(BaseModel): context_version: str = "v1" current_project_id: int | None = None current_repo_scope: str | None = None page_anchor: dict[str, Any] = Field(default_factory=dict) thread_anchor: dict[str, Any] = Field(default_factory=dict) fit_scope: Literal["current_project_and_repo"] = "current_project_and_repo" decision_strategy: Literal["answer_then_offer_plan"] = "answer_then_offer_plan" ``` - [ ] **Step 2: Implement a small builder that composes current project, repo, and anchors** The builder should only consume stable inputs already present in runtime: - `resolved_project_id` - current repo / codebase scope - explicit page anchor - explicit thread anchor Do not infer portfolio scope or global user history here. - [ ] **Step 3: Attach the packet in kernel ingress before any query route or plan handoff logic** Make `mixin_entry.py` the single place that builds and stores the packet in `ingress_options`. - [ ] **Step 4: Persist only the minimal session snapshot** Store only: - `current_project_id` - `current_repo_scope` - `last_page_anchor` - `last_thread_anchor` Do not persist a giant free-form context blob. - [ ] **Step 5: Re-run focused tests and confirm they pass** Run: ```bash cd /Users/yuuuu/Documents/AIProjectManger && PYTHONPATH=backend backend/venv/bin/python -m pytest \ backend/tests/test_project_operating_context_contract.py \ backend/tests/test_ai_pmo_ingress_mainline.py -v ``` Expected: PASS - [ ] **Step 6: Commit** ```bash git add backend/app/schemas/project_operating_context.py \ backend/app/services/ai_pmo/project_operating_context_builder.py \ backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py \ backend/app/services/ai_pmo/ai_pmo_kernel_world_model_core/mixin_builder.py \ backend/app/services/ai_pmo/ai_pmo_kernel/mixins/world_model/ai_pmo_kernel_mixins_world_model_session.py \ backend/tests/test_project_operating_context_contract.py \ backend/tests/test_ai_pmo_ingress_mainline.py git commit -m "feat: add unified project operating context" ``` ### Task 3: Replace Hard Split Mode Guessing With A Unified Execution Recipe **Files:** - Modify: `backend/app/services/adaptive_rag_service/mixin_query_route_gateway.py` - Modify: `backend/app/services/adaptive_rag_service/mixin_core_intent.py` - Modify: `backend/app/services/adaptive_rag_service/mixin_answer_mainline.py` - Modify: `backend/app/services/adaptive_rag_service/mixin_followup_anchor.py` - Modify: `backend/tests/test_adaptive_rag_mainline_contract.py` - Modify: `backend/tests/test_rag_route_contract.py` - [ ] **Step 1: Write the failing tests for `execution_recipe`** Lock a composed decision like: ```python assert recipe == { "primary_lane": "external", "fit_brief_required": True, "external_evidence_required": True, "plan_handoff_mode": "offer_after_answer", } ``` Use a scenario like: - question asks “这个方案适不适合我们项目” - current project exists - no explicit draft / requirement strong anchor - [ ] **Step 2: Extend query route gateway output from `route_mode` to `execution_recipe`** Keep `route_mode / effective_source_policy / reply_contract` for compatibility, but add a higher-level recipe: ```python { "primary_lane": "internal" | "external" | "plan", "fit_brief_required": bool, "external_evidence_required": bool, "plan_handoff_mode": "none" | "offer_after_answer" | "explicit_plan_now", } ``` - [ ] **Step 3: Make answer mainline follow the recipe instead of local mode guesses** Required behavior: - explicit plan anchor -> `explicit_plan_now` - pure internal question -> `internal` - pure external fact question -> `external` - “适不适合当前项目 / 推荐什么 / 要不要接” -> `external + fit_brief + offer_after_answer` - [ ] **Step 4: Keep follow-up rewrite anchored to the unified packet** Follow-up should first look at: - explicit anchor - project operating context - prior handoff state Only then fall back to session subject. - [ ] **Step 5: Run the query mainline tests** Run: ```bash cd /Users/yuuuu/Documents/AIProjectManger && PYTHONPATH=backend backend/venv/bin/python -m pytest \ backend/tests/test_rag_route_contract.py \ backend/tests/test_adaptive_rag_mainline_contract.py -v ``` Expected: PASS - [ ] **Step 6: Commit** ```bash git add backend/app/services/adaptive_rag_service/mixin_query_route_gateway.py \ backend/app/services/adaptive_rag_service/mixin_core_intent.py \ backend/app/services/adaptive_rag_service/mixin_answer_mainline.py \ backend/app/services/adaptive_rag_service/mixin_followup_anchor.py \ backend/tests/test_adaptive_rag_mainline_contract.py \ backend/tests/test_rag_route_contract.py git commit -m "feat: add unified execution recipe for project ai" ``` ### Task 4: Add Project Fit Brief Without Breaking External-Only Answer Contracts **Files:** - Create: `backend/app/services/adaptive_rag_service/mixin_project_fit_brief.py` - Modify: `backend/app/services/adaptive_rag_service/service.py` - Modify: `backend/app/services/adaptive_rag_service/mixin_manifest_feature_p1.py` - Modify: `backend/app/services/project_knowledge_graph_service/mixin_codebase_overview.py` - Modify: `backend/app/services/context_cache_service/mixin_user_snapshot.py` - Modify: `backend/app/services/external_query_service/schemas.py` - Modify: `backend/app/services/external_query_service/planner.py` - Modify: `backend/app/services/external_query_service/gateway.py` - Modify: `backend/app/services/external_query_service/presenter.py` - Modify: `backend/app/services/adaptive_rag_service/mixin_answer_payload.py` - Modify: `backend/tests/test_external_query_planner.py` - Modify: `backend/tests/test_internal_query_context_packet.py` - Modify: `backend/tests/test_adaptive_rag_external_followup_contract.py` - [ ] **Step 1: Write failing tests for a customer-safe fit brief** Lock a brief such as: ```python brief = { "project_summary": "当前项目是 React + FastAPI 的项目管理系统", "repo_summary": "当前仓库同时包含 frontend/backend", "current_capabilities": ["feature_catalog", "feishu", "adaptive_rag"], "constraints": ["必须绑定正式业务对象", "不能做泛化聊天"], "recommendation_focus": ["适合当前项目", "接入成本", "对现有架构影响"], } ``` Also lock the external recommendation contract: ```python assert payload["external_recommendation"]["retrieval_status"] in { "evidence_found", "verifier_blocked", "no_relevant_results", "retrieval_failed", } assert payload["external_recommendation"]["impact_status"] in { "direct_match", "indirect_only", "no_direct_impact", } ``` - [ ] **Step 2: Implement the fit-brief builder from internal project and repo signals** Allowed inputs: - current project summary - feature catalog - codebase overview - customer-safe current user/project participation snapshot Not allowed: - raw internal source labels in final external answer prompt - giant free-form repo dump - silent fallback that collapses `verifier_blocked / no_relevant_results / retrieval_failed` into one generic “暂无外部变化” - [ ] **Step 3: Extend external planner to consume the brief as search guidance** The planner may use: - `fit_brief_summary` - `project_scope_hint` - `recommendation_goal` - formal project object anchors such as `signal_sources` / current capability names as query constraints But `presenter` must still only consume verified external evidence and plan summary. - [ ] **Step 3.5: Add object-binding and impact-tier rules before final external recommendation** Required behavior: - `retrieval_status` is system-owned and passed through to payload/presenter; the model must not guess it. - if `retrieval_status != evidence_found`, force: - `impact_status=no_direct_impact` - empty `external_summary` - empty `external_impact` - empty `affected_signal_sources` - empty `external_action` - if evidence is real but only loosely related to the project, force `impact_status=indirect_only` - only allow `impact_status=direct_match` when external evidence can be mapped to at least one formal current-project object, preferably a formal `signal source` - if current project has formal `signal_sources`, the recommendation layer should prefer binding to those sources before falling back to abstract capability labels - `external_action` must be a validation/mapping/monitoring action, never a staffing or ownership action - [ ] **Step 3.6: Lock presenter wording for failure and indirect-only states** Web/Feishu presentation must explicitly distinguish: - `verifier_blocked`: searched candidates existed but did not pass grounding / verifier checks - `no_relevant_results`: search completed but no sufficiently relevant external evidence was found - `retrieval_failed`: external retrieval failed and no usable external evidence was produced - `indirect_only`: evidence exists but only supports broad market/background relevance, not a direct project-object match - [ ] **Step 4: Emit fit brief as a separate payload block** The answer payload should look more like: ```json { "fit_brief": {...}, "external_recommendation": { "retrieval_status": "evidence_found | verifier_blocked | no_relevant_results | retrieval_failed", "impact_status": "direct_match | indirect_only | no_direct_impact", "affected_signal_sources": [...] }, "recommended_next_step": {...} } ``` Do not collapse all three into one answer string. Do not collapse retrieval state, impact state, and next-step CTA into one generic prose paragraph. - [ ] **Step 5: Run focused fit-brief and external planner tests** Run: ```bash cd /Users/yuuuu/Documents/AIProjectManger && PYTHONPATH=backend backend/venv/bin/python -m pytest \ backend/tests/test_external_query_planner.py \ backend/tests/test_internal_query_context_packet.py \ backend/tests/test_adaptive_rag_external_followup_contract.py -v ``` Expected: PASS - [ ] **Step 6: Commit** ```bash git add backend/app/services/adaptive_rag_service/mixin_project_fit_brief.py \ backend/app/services/adaptive_rag_service/service.py \ backend/app/services/adaptive_rag_service/mixin_manifest_feature_p1.py \ backend/app/services/project_knowledge_graph_service/mixin_codebase_overview.py \ backend/app/services/context_cache_service/mixin_user_snapshot.py \ backend/app/services/external_query_service/schemas.py \ backend/app/services/external_query_service/planner.py \ backend/app/services/external_query_service/gateway.py \ backend/app/services/external_query_service/presenter.py \ backend/app/services/adaptive_rag_service/mixin_answer_payload.py \ backend/tests/test_external_query_planner.py \ backend/tests/test_internal_query_context_packet.py \ backend/tests/test_adaptive_rag_external_followup_contract.py git commit -m "feat: add project fit brief for external recommendation" ``` ### Task 5: Implement Default A Handoff In Web And Feishu **Files:** - Modify: `backend/app/services/adaptive_rag_service/mixin_answer_payload.py` - Modify: `backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py` - Modify: `backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_default.py` - Modify: `backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_a.py` - Modify: `backend/tests/test_ai_pmo_qa_http_mainline_e2e.py` - Modify: `backend/tests/test_feishu_pipeline_queue_contract.py` - Modify: `frontend/src/components/ai-steward/FloatingQAChat.tsx` - Modify: `frontend/src/components/ai-steward/QAConversationStatusBar.tsx` - Modify: `frontend/src/components/ai-steward/QAResponseMetaCard.tsx` - Create: `frontend/src/components/ai-steward/QAResponseMetaCard.test.tsx` - Modify: `frontend/src/components/ai-steward/FloatingQAChat.test.tsx` - [ ] **Step 1: Write failing end-to-end tests for the default A behavior** Lock a case where the user asks: ```text 这个方案适不适合我们当前项目? ``` Expected response contract: - first returns a judgment - includes fit brief summary - includes external evidence summary - includes one clear “是否进入 plan” next step - does not directly jump into requirement-plan - if external recommendation is `verifier_blocked / no_relevant_results / retrieval_failed / indirect_only`, the UI copy must preserve that distinction instead of rendering a single generic empty-state sentence - [ ] **Step 2: Add payload fields for handoff and next-step CTA** Minimum fields: ```json { "recommended_next_step": { "mode": "offer_plan", "label": "基于这个结论进入计划", "allowed": true } } ``` - [ ] **Step 3: Render the same structure in web and Feishu** Web should show three regions: - 当前项目画像 - 外部推荐与证据 - 下一步 Feishu should keep a simpler rendering: - 先给结论 - 再给一行“如果你要,我可以基于这个结论继续帮你出计划” Feishu 验收时不要只看内部 handoff payload;必须以“后端最终准备发给飞书的格式”为准: - `qa_direct` 文本:看 `FeishuReceiveRoutingMixin._build_feishu_qa_direct_reply_text()` - `requirement_plan_followup / draft_pending_confirm / pending_manual`:看 `FeishuCardActionMixin._build_action_card_from_capture_result()` - [ ] **Step 3.5: Render explicit external state variants instead of a single empty fallback** Web must distinguish at least: - `evidence_found + direct_match` - `evidence_found + indirect_only` - `verifier_blocked` - `no_relevant_results` - `retrieval_failed` Feishu may be shorter, but must still avoid collapsing the above states into one generic “暂无外部变化/暂无推荐”. - [ ] **Step 4: Keep explicit plan anchors stronger than the default A fallback** If the message already contains a strong plan anchor: - skip the offer - enter plan immediately This prevents regression for active requirement / draft threads. - [ ] **Step 5: Run web and Feishu integration tests** Run: ```bash cd /Users/yuuuu/Documents/AIProjectManger && PYTHONPATH=backend backend/venv/bin/python -m pytest \ backend/tests/test_ai_pmo_qa_http_mainline_e2e.py \ backend/tests/test_feishu_pipeline_queue_contract.py -v cd /Users/yuuuu/Documents/AIProjectManger/frontend && npx vitest run \ src/components/ai-steward/FloatingQAChat.test.tsx \ src/components/ai-steward/QAResponseMetaCard.test.tsx ``` Expected: PASS - [ ] **Step 6: Commit** ```bash git add backend/app/services/adaptive_rag_service/mixin_answer_payload.py \ backend/app/services/ai_pmo/ai_pmo_kernel_ingress_flow/mixin_entry.py \ backend/app/services/integrations/feishu_signal_pipeline/mixin_process_message_default.py \ backend/app/services/integrations/feishu_signal_pipeline/mixin_context_prefill_a.py \ backend/tests/test_ai_pmo_qa_http_mainline_e2e.py \ backend/tests/test_feishu_pipeline_queue_contract.py \ frontend/src/components/ai-steward/FloatingQAChat.tsx \ frontend/src/components/ai-steward/QAConversationStatusBar.tsx \ frontend/src/components/ai-steward/QAResponseMetaCard.tsx \ frontend/src/components/ai-steward/QAResponseMetaCard.test.tsx \ frontend/src/components/ai-steward/FloatingQAChat.test.tsx git commit -m "feat: add unified plan handoff for project ai answers" ``` ### Task 6: Update Docs And Run Final Regression **Files:** - Modify: `CLAUDE.md` - Modify: `docs/architecture/ARCHITECTURE_INVENTORY.md` - Modify: `docs/architecture/CODE_REFERENCE_INDEX.md` - Modify: `docs/architecture/RUNTIME_GUARDRAILS.md` - [ ] **Step 1: Update runtime guardrails** Document these new stable rules: - unified `Project Operating Context` - default scope is `current project + current repo` - default multi-intent strategy is `answer_then_offer_plan` - external planner may consume `fit brief`, but external presenter remains external-only - [ ] **Step 2: Update inventory and code reference docs** Add: - new schema/module paths - ingress builder path - fit brief builder path - handoff payload path - external recommendation retrieval/impact state contract - explicit object-binding preference for formal `signal_sources` - [ ] **Step 3: Run backend compile, tests, and deployment restart** Run: ```bash cd /Users/yuuuu/Documents/AIProjectManger && backend/venv/bin/python -m py_compile \ backend/app/schemas/project_operating_context.py \ backend/app/services/ai_pmo/project_operating_context_builder.py \ backend/app/services/adaptive_rag_service/*.py \ backend/app/services/external_query_service/*.py cd /Users/yuuuu/Documents/AIProjectManger && PYTHONPATH=backend backend/venv/bin/python -m pytest \ backend/tests/test_project_operating_context_contract.py \ backend/tests/test_ai_pmo_ingress_mainline.py \ backend/tests/test_rag_route_contract.py \ backend/tests/test_adaptive_rag_mainline_contract.py \ backend/tests/test_adaptive_rag_external_followup_contract.py \ backend/tests/test_external_query_planner.py \ backend/tests/test_internal_query_context_packet.py \ backend/tests/test_ai_pmo_qa_http_mainline_e2e.py \ backend/tests/test_feishu_pipeline_queue_contract.py -v cd /Users/yuuuu/Documents/AIProjectManger/frontend && npx tsc --noEmit cd /Users/yuuuu/Documents/AIProjectManger/frontend && npm run build cd /Users/yuuuu/Documents/AIProjectManger && docker compose up -d --build backend ``` Expected: - `py_compile` success - targeted pytest all PASS - `tsc --noEmit` success - `npm run build` success - backend container returns `healthy` - [ ] **Step 3.5: Add runtime feature flags / rollback guards** At minimum, the implementation should support disabling independently: - unified execution recipe - fit-brief-driven external recommendation - default `answer_then_offer_plan` handoff rendering The rollback path must not require reverting unrelated ingress/context changes. - [ ] **Step 3.6: Run real-chain verification on at least three external recommendation states** Before final sign-off, capture real examples for: - `verifier_blocked` - `evidence_found + indirect_only` - `evidence_found + direct_match` For each example, confirm: - external payload state is correct - presenter wording matches the state - no generic empty fallback sentence hides the real reason - `direct_match` only appears when a formal project object is actually bound - [ ] **Step 4: Commit docs and verification updates** ```bash git add CLAUDE.md \ docs/architecture/ARCHITECTURE_INVENTORY.md \ docs/architecture/CODE_REFERENCE_INDEX.md \ docs/architecture/RUNTIME_GUARDRAILS.md git commit -m "docs: document unified project ai context and handoff" ``` ## Final Verification Checklist - `Project Operating Context` is present in ingress before any route or plan handoff - external recommendation can consume internal fit brief without mixing internal evidence into final external answer - external recommendation explicitly separates retrieval state from impact state - external recommendation prefers binding to formal current-project objects, especially `signal_sources` - `direct_match` is only used when external evidence truly maps to a formal project object - `indirect_only` is used for broad market/background relevance instead of pretending the recommendation is project-bound - external actions never include staffing/ownership changes or direct business-status mutation - multi-intent recommendation questions default to “answer first, then offer plan” - strong plan anchors still override the offer and enter plan directly - web and Feishu both display the same handoff logic - no new runtime keyword/regex route-takeover helpers were introduced - implementation diff does not reintroduce hard semantic routing under another name ## Execution Notes - Task order matters: - Task 1 and Task 2 first, otherwise later tasks will keep smuggling context through ad-hoc fields - Task 3 before Task 4, otherwise fit brief will bolt onto the old route split - Task 5 only after the recipe and fit-brief payload exist - Keep commits small and reviewable; do not batch all six tasks into one mega-commit. - If the implementation reveals that full repo understanding needs a separate ingestion pipeline beyond current `codebase snapshot`, split that work out as a follow-up plan instead of bloating this one. ## Final Architecture Baseline This plan must land on the following stable architecture baseline and should not drift after implementation starts. 1. PMFlow must end up with `one unified project AI ingress`, not three isolated user-facing modes. 2. The first runtime layer is always `Project Operating Context`. Default scope is fixed to: - current project - current repo summary when runtime snapshot is available - explicit thread / page / reply anchors 3. After the top-level `plan_mode` gate, non-plan recommendation / query flows should produce a single `execution_recipe`. That recipe layers on top of the existing `route_mode / effective_source_policy / reply_contract / probe_snapshot` contract and decides: - internal execution - external execution - post-answer handoff to `plan_mode` downstream capability 4. `internal` is responsible for: - internal object facts - project state - project fit brief - bounded compatibility / capability constraints 5. `external` is responsible for: - external evidence - grounded recommendations - impact assessment - explicit retrieval / verifier state - customer-safe presentation on top of external-only evidence 6. Here `planmode` means the repo's `plan_mode` gate plus `requirement_plan / draft_pending_confirm` mainline, not a separate free-floating route name. 7. `planmode` is a downstream capability for recommendation questions, not the default first response. Default strategy remains: - answer first - then offer plan handoff 8. External recommendation may consume internal `fit brief`, but the final external answer must remain external-only and single-source-policy compliant. 9. Strong anchors override the default answer-then-offer-plan strategy: - explicit reply/thread - explicit work item - active requirement/draft context 10. Feishu is a first-class acceptance surface. The work is not complete until the Feishu problem inventory and golden cases pass. 11. The implementation is forbidden from reintroducing hard semantic routing: - no keyword warehouse - no regex routing helpers - no “looks like this kind of question” split logic in execution code Target end-state in one sentence: `Understand the current PMFlow project first, then decide how internal evidence, external evidence, and plan handoff should cooperate under one ingress.`