884 lines
39 KiB
Markdown
884 lines
39 KiB
Markdown
# 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.`
|