18 KiB
Phase 2 Explicit Anchor And Route Governance 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: 把网页问答入口真正绑定到当前页面业务对象,并补一页 manager-only 的 route/context 治理看板,让 PMFlow 能同时做到“前台少串对象、后台能看误路由样本”。
Architecture: Phase 2A 不改 AdaptiveRAG 的语义边界,只把现有 project_id / source_chat_id 从稳定网页入口显式送进 /rag/ask,并在全局浮窗展示“当前页面锚点”。Phase 2B 在不引入在线自动调参的前提下,围绕 query_route_memories 新增只读 summary/list/detail API 和一页治理 UI,优先解决“看不见 misroute / clarify-heavy / negative feedback 样本”的问题。
Tech Stack: FastAPI, Pydantic, SQLAlchemy 2.0, AdaptiveRAG, React 18, TypeScript, React Query, Vitest, pytest
Scope And Guardrails
- v1 显式锚点优先做稳定强锚点:
- 项目看板 / 宪章页路径里的
project_id Inbox/AI Steward页面 URL query 中已存在的project_id- URL query 中已显式存在的
source_chat_id
- 项目看板 / 宪章页路径里的
- v1 不在网页里新增“猜测型 source_chat_id 推断器”;只有真实入口已携带时才透传,避免把锚点系统重新做成隐式猜测。
- v1 治理页只做只读审计与人工排查支持:
route_mode_predicted != route_mode_effectivereply_contract.mode=clarify或effective_source_policy=uncertainfeedback_outcome.rating=negative- detail drawer 中暴露
probe_snapshot / query_profile / feedback_outcome
- v1 不做在线阈值自调、不自动改路由、不引入新的 route memory 打分逻辑。
Baseline File Map
Phase 2A: Explicit Web Anchor
- Modify:
backend/tests/test_rag_route_contract.py- 锁
/rag/ask在 request 里显式传入project_id / source_chat_id时必须原样进入 ingress contract。
- 锁
- Create:
frontend/src/components/ai-steward/qaPageAnchor.ts- 统一从
pathname + searchParams解析全局网页问答锚点,输出projectId / sourceChatId / anchorType / anchorLabel / origin。
- 统一从
- Create:
frontend/src/components/ai-steward/qaPageAnchor.test.ts- 锁不同页面路径和 query param 的锚点解析规则。
- Modify:
frontend/src/components/ai-steward/FloatingQAChat.tsx- 接入页面锚点解析,把
project_id / source_chat_id真正带进askRag()请求,并在状态条展示“当前页面锚点”。
- 接入页面锚点解析,把
- Modify:
frontend/src/components/ai-steward/QAConversationStatusBar.tsx- 增加页面锚点文案和“当前页面已锁定项目/会话”的显式状态。
- Modify:
frontend/src/components/ai-steward/FloatingQAChat.test.tsx- 锁全局浮窗在项目页 / inbox query 下会带锚点发问。
- Modify:
frontend/src/pages/project-board/ProjectBoardPage.tsx- 项目页“打开 AI”入口改成带
project_id跳转,确保页面级强锚点入口真正可达。
- 项目页“打开 AI”入口改成带
Phase 2B: Route And Context Governance Page
- Create:
backend/app/schemas/query_route_governance.py- 定义 summary/list/detail 的 response schema。
- Create:
backend/app/services/adaptive_rag_service/mixin_query_route_governance.py- 新增
summary / list / detail只读查询能力,封装query_route_memories的聚合、筛选和详情序列化。
- 新增
- Modify:
backend/app/services/adaptive_rag_service/service.py- 把治理 mixin 接入
AdaptiveRAGService。
- 把治理 mixin 接入
- Create:
backend/app/api/rag_api/routes_route_governance.py- 提供 manager-only 的
/rag/query-route-governance/*API。
- 提供 manager-only 的
- Modify:
backend/app/api/rag_api/__init__.py- 挂载新路由。
- Create:
backend/tests/test_query_route_governance_api.py- 锁 summary/list/detail 合同和权限边界。
- Create:
frontend/src/api/queryRouteGovernance.ts- 前端 API 封装和类型定义。
- Create:
frontend/src/pages/query-route-governance/QueryRouteGovernancePage.tsx- 治理页编排容器,沿用 manager-only 子页模式。
- Create:
frontend/src/pages/query-route-governance/components/QueryRouteGovernanceOverviewCards.tsx- 顶部 KPI 卡。
- Create:
frontend/src/pages/query-route-governance/components/QueryRouteGovernanceTable.tsx- 样本列表与筛选器。
- Create:
frontend/src/pages/query-route-governance/components/QueryRouteGovernanceDetailDialog.tsx- detail drawer,展示
reply_contract / probe_snapshot / query_profile / feedback_outcome。
- detail drawer,展示
- Create:
frontend/src/pages/QueryRouteGovernancePage.tsx- 页面 barrel export。
- Create:
frontend/src/pages/query-route-governance/QueryRouteGovernancePage.test.tsx- 锁 manager-only UI、KPI、表格和 detail dialog。
- Modify:
frontend/src/router/index.tsx- 挂载
/ai-config/query-route-governance,并放进 manager-only guard。
- 挂载
- Modify:
frontend/src/pages/ai-config/constants.ts- 把治理页挂到 AI Config 治理工具入口。
Docs And Inventory
- Modify:
CLAUDE.md - Modify:
docs/architecture/ARCHITECTURE_INVENTORY.md - Modify:
docs/architecture/CODE_REFERENCE_INDEX.md - Modify:
docs/architecture/RUNTIME_GUARDRAILS.md
Task 1: Lock Explicit Anchor Contract Before UI Changes
Files:
-
Modify:
backend/tests/test_rag_route_contract.py -
Create:
frontend/src/components/ai-steward/qaPageAnchor.test.ts -
Step 1: Add the failing backend contract test for explicit anchor forwarding
Add a route-contract test that proves:
request = RAGAskRequest(
question="这个项目怎么样",
project_id=17,
source_chat_id="pmflow-project-17",
)
kwargs = rag_ask_contract.build_kernel_ingress_kwargs(
request=request,
current_user=SimpleNamespace(id=1, tenant_id=1),
)
assert kwargs["project_hint_id"] == 17
assert kwargs["channel_ref"] == "pmflow-project-17"
assert kwargs["ingress_options"]["rag_request_context"]["project_id"] == 17
assert kwargs["ingress_options"]["rag_request_context"]["source_chat_id"] == "pmflow-project-17"
- Step 2: Add the failing front-end page anchor parser tests
Cover at least these cases:
expect(resolveQAPageAnchor("/projects/17/board", "")).toMatchObject({
projectId: 17,
anchorType: "project",
});
expect(resolveQAPageAnchor("/inbox", "?project_id=19")).toMatchObject({
projectId: 19,
origin: "search_param",
});
expect(resolveQAPageAnchor("/ai-steward", "?tab=qa&project_id=23&source_chat_id=chat-23")).toMatchObject({
projectId: 23,
sourceChatId: "chat-23",
});
- Step 3: Run the focused tests and confirm they fail
Run:
cd /Users/yuuuu/Documents/AIProjectManger && PYTHONPATH=backend backend/venv/bin/python -m pytest backend/tests/test_rag_route_contract.py -k "project_id or source_chat_id" -v
cd /Users/yuuuu/Documents/AIProjectManger/frontend && npx vitest run src/components/ai-steward/qaPageAnchor.test.ts
Expected: FAIL because the parser file does not exist yet and the forwarding assertions are not locked.
Task 2: Implement Stable Web Anchor Resolution In The Global Floating Chat
Files:
-
Create:
frontend/src/components/ai-steward/qaPageAnchor.ts -
Modify:
frontend/src/components/ai-steward/FloatingQAChat.tsx -
Modify:
frontend/src/components/ai-steward/QAConversationStatusBar.tsx -
Modify:
frontend/src/components/ai-steward/FloatingQAChat.test.tsx -
Test:
frontend/src/components/ai-steward/qaPageAnchor.test.ts -
Test:
frontend/src/components/ai-steward/FloatingQAChat.test.tsx -
Step 1: Implement the minimal page-anchor resolver
Add a helper that only consumes stable web hints:
export interface QAPageAnchor {
projectId: number | null;
sourceChatId: string | null;
anchorType: "project" | "project_filter" | "chat_scope" | "none";
anchorLabel: string | null;
origin: "pathname" | "search_param" | "none";
}
Parsing rules:
-
/projects/:id/boardand/projects/:id/charter/:charterId->projectId -
query
project_id->projectId -
query
source_chat_id->sourceChatId -
no strong anchor ->
anchorType="none" -
Step 2: Wire
FloatingQAChatto send explicit anchors
When submitting a question:
const pageAnchor = resolveQAPageAnchor(location.pathname, location.search);
await aiStewardApi.askRag({
...body,
project_id: pageAnchor.projectId ?? undefined,
source_chat_id: pageAnchor.sourceChatId ?? undefined,
});
Also keep the current session-state logic intact; page anchor only supplements request scope and UI display.
- Step 3: Extend the status bar with explicit page-anchor state
Render a compact line such as:
当前页面锚点:项目 #17
Only show it when pageAnchor.anchorType !== "none".
- Step 4: Update floating-chat tests to assert anchor forwarding
Add a case that mounts FloatingQAChat under a project-board route and asserts:
expect(aiStewardApiMock.askRag).toHaveBeenCalledWith(
expect.objectContaining({ project_id: 17 })
);
- Step 5: Re-run the focused front-end tests
Run:
cd /Users/yuuuu/Documents/AIProjectManger/frontend && npx vitest run src/components/ai-steward/qaPageAnchor.test.ts src/components/ai-steward/FloatingQAChat.test.tsx
Expected: PASS
Task 3: Route Existing Project Entry Points Into Anchor-Bearing AI URLs
Files:
-
Modify:
frontend/src/pages/project-board/ProjectBoardPage.tsx -
Modify:
frontend/src/components/ai-steward/FloatingQAChat.test.tsx -
Step 1: Write the failing project-entry test
Lock that project-board “打开 AI” jumps to an anchored URL:
expect(navigate).toHaveBeenCalledWith("/ai-steward?tab=qa&project_id=17");
- Step 2: Run the focused test to confirm it fails
Run:
cd /Users/yuuuu/Documents/AIProjectManger/frontend && npx vitest run src/components/ai-steward/FloatingQAChat.test.tsx
Expected: FAIL because project-board still routes to bare /ai-steward.
- Step 3: Update project-board AI entrypoints
Change the existing project-board entry from:
navigate("/ai-steward")
to:
navigate(`/ai-steward?tab=qa&project_id=${id}`)
- Step 4: Re-run the affected test
Run:
cd /Users/yuuuu/Documents/AIProjectManger/frontend && npx vitest run src/components/ai-steward/FloatingQAChat.test.tsx
Expected: PASS
Task 4: Lock Route Governance API Contract
Files:
-
Create:
backend/tests/test_query_route_governance_api.py -
Step 1: Write the failing summary/list/detail API tests
Seed query_route_memories rows that cover:
route_mode_predicted != route_mode_effectivefeedback_outcome.rating == "negative"reply_contract.mode == "clarify"
Assert summary returns:
assert payload["data"]["kpis"]["sample_total"] == 3
assert payload["data"]["kpis"]["route_mismatch_total"] == 1
assert payload["data"]["kpis"]["negative_feedback_total"] == 1
assert payload["data"]["kpis"]["clarify_total"] == 1
Assert list returns rows with:
assert item["route_mode_predicted"] == "internal"
assert item["route_mode_effective"] == "external"
assert item["feedback_rating"] == "negative"
assert item["is_clarify"] is True
Assert detail returns:
assert detail["probe_snapshot"]["effective_source_policy"] == "external"
assert detail["query_profile"]["source_policy"] == "internal"
assert detail["reply_contract"]["mode"] == "clarify"
- Step 2: Add the failing manager-only permission test
Assert non-manager gets 403, manager gets 200.
- Step 3: Run the backend test and confirm it fails
Run:
cd /Users/yuuuu/Documents/AIProjectManger && PYTHONPATH=backend backend/venv/bin/python -m pytest backend/tests/test_query_route_governance_api.py -v
Expected: FAIL because the API and service methods do not exist yet.
Task 5: Implement Read-Only Route Governance Backend
Files:
-
Create:
backend/app/schemas/query_route_governance.py -
Create:
backend/app/services/adaptive_rag_service/mixin_query_route_governance.py -
Modify:
backend/app/services/adaptive_rag_service/service.py -
Create:
backend/app/api/rag_api/routes_route_governance.py -
Modify:
backend/app/api/rag_api/__init__.py -
Test:
backend/tests/test_query_route_governance_api.py -
Step 1: Add the minimal governance schemas
Define Pydantic models for:
QueryRouteGovernanceSummaryResponseQueryRouteGovernanceListItemQueryRouteGovernanceDetailResponse
Keep the frontend-safe shape small; do not expose question_embedding.
- Step 2: Implement summary/list/detail service methods
Add read-only methods with project filter and status filters:
async def get_query_route_governance_summary(...)
async def list_query_route_governance_items(...)
async def get_query_route_governance_detail(...)
Derived fields:
-
is_route_mismatch = route_mode_predicted != route_mode_effective -
feedback_rating = feedback_outcome["rating"] -
is_clarify = reply_contract["mode"] == "clarify" -
Step 3: Expose manager-only API routes
Add:
GET /rag/query-route-governance/summaryGET /rag/query-route-governance/itemsGET /rag/query-route-governance/items/{route_memory_id}
Use require_roles("ADMIN", "BOSS").
- Step 4: Re-run the governance API test
Run:
cd /Users/yuuuu/Documents/AIProjectManger && PYTHONPATH=backend backend/venv/bin/python -m pytest backend/tests/test_query_route_governance_api.py -v
Expected: PASS
Task 6: Build The Manager-Only Route Governance Page
Files:
-
Create:
frontend/src/api/queryRouteGovernance.ts -
Create:
frontend/src/pages/query-route-governance/QueryRouteGovernancePage.tsx -
Create:
frontend/src/pages/query-route-governance/components/QueryRouteGovernanceOverviewCards.tsx -
Create:
frontend/src/pages/query-route-governance/components/QueryRouteGovernanceTable.tsx -
Create:
frontend/src/pages/query-route-governance/components/QueryRouteGovernanceDetailDialog.tsx -
Create:
frontend/src/pages/QueryRouteGovernancePage.tsx -
Create:
frontend/src/pages/query-route-governance/QueryRouteGovernancePage.test.tsx -
Modify:
frontend/src/router/index.tsx -
Modify:
frontend/src/pages/ai-config/constants.ts -
Step 1: Write the failing page test
Cover these cases:
it("renders manager-only guard copy for non-manager users", ...)
it("renders KPI cards and table rows from governance summary/list data", ...)
it("opens detail dialog and shows probe snapshot / feedback outcome", ...)
- Step 2: Run the front-end test to confirm it fails
Run:
cd /Users/yuuuu/Documents/AIProjectManger/frontend && npx vitest run src/pages/query-route-governance/QueryRouteGovernancePage.test.tsx
Expected: FAIL because the page and API wrapper do not exist yet.
- Step 3: Add the front-end API wrapper
Expose:
getSummary(params?)
listItems(params?)
getDetail(routeMemoryId)
- Step 4: Build the governance page using the semantic-facts board pattern
Page layout:
- top header + feature guide
- KPI cards
- filterable table
- detail drawer
Suggested table columns:
-
question text
-
predicted/effective route
-
decision origin
-
feedback rating
-
flags (
mismatch,clarify,negative) -
created_at
-
Step 5: Add manager-only route and AI Config entry
Router:
{ path: "ai-config/query-route-governance", element: <ManagerOnlyGuard><QueryRouteGovernancePage /></ManagerOnlyGuard> }
Governance tools card:
{
id: "query-route-governance",
name: "Route 治理看板",
to: "/ai-config/query-route-governance",
}
- Step 6: Re-run the front-end page test
Run:
cd /Users/yuuuu/Documents/AIProjectManger/frontend && npx vitest run src/pages/query-route-governance/QueryRouteGovernancePage.test.tsx
Expected: PASS
Task 7: Docs Sync And Final Verification
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: Sync docs with new page, route, and anchor rules
Document:
-
explicit web-anchor baseline (
project_id/source_chat_idfrom stable page contexts) -
manager-only route governance page and API
-
“治理页只读、不自动改路由”的 guardrail
-
Step 2: Run the focused backend verification
Run:
cd /Users/yuuuu/Documents/AIProjectManger && backend/venv/bin/python -m py_compile \
backend/app/schemas/query_route_governance.py \
backend/app/services/adaptive_rag_service/mixin_query_route_governance.py \
backend/app/api/rag_api/routes_route_governance.py
cd /Users/yuuuu/Documents/AIProjectManger && PYTHONPATH=backend backend/venv/bin/python -m pytest \
backend/tests/test_rag_route_contract.py -k "project_id or source_chat_id" -v
cd /Users/yuuuu/Documents/AIProjectManger && PYTHONPATH=backend backend/venv/bin/python -m pytest \
backend/tests/test_query_route_governance_api.py -v
Expected: PASS
- Step 3: Run the focused front-end verification
Run:
cd /Users/yuuuu/Documents/AIProjectManger/frontend && npx vitest run \
src/components/ai-steward/qaPageAnchor.test.ts \
src/components/ai-steward/FloatingQAChat.test.tsx \
src/pages/query-route-governance/QueryRouteGovernancePage.test.tsx
cd /Users/yuuuu/Documents/AIProjectManger/frontend && npx tsc --noEmit
cd /Users/yuuuu/Documents/AIProjectManger/frontend && npm run build
Expected: PASS
- Step 4: Restart backend and confirm runtime recovery
Run:
cd /Users/yuuuu/Documents/AIProjectManger && docker compose restart backend
cd /Users/yuuuu/Documents/AIProjectManger && curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8000/docs
cd /Users/yuuuu/Documents/AIProjectManger && docker compose ps backend
Expected:
/docsreturns200pmflow-backendreturns tohealthy
Acceptance Checklist
- 页面级全局浮窗在项目页、Inbox
project_id过滤页、AI Stewardproject_idquery 下都会把锚点显式带进/rag/ask - 项目页进入 AI 的入口不再裸跳
/ai-steward QAConversationStatusBar能让用户看见当前页面锚点- manager-only
Route 治理看板已可查看 summary/list/detail - 治理页能直接筛出
mismatch / clarify / negative feedback样本 - 文档、测试、build、backend restart 已完成