PMFlow/docs/superpowers/plans/2026-04-17-phase2-explicit-...

18 KiB
Raw Blame History

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_effective
    • reply_contract.mode=clarify 或 effective_source_policy=uncertain
    • feedback_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 跳转,确保页面级强锚点入口真正可达。

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。
  • Create: backend/app/api/rag_api/routes_route_governance.py
    • 提供 manager-only 的 /rag/query-route-governance/* API。
  • 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。
  • 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/board and /projects/:id/charter/:charterId -> projectId

  • query project_id -> projectId

  • query source_chat_id -> sourceChatId

  • no strong anchor -> anchorType="none"

  • Step 2: Wire FloatingQAChat to 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_effective
  • feedback_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:

  • QueryRouteGovernanceSummaryResponse
  • QueryRouteGovernanceListItem
  • QueryRouteGovernanceDetailResponse

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/summary
  • GET /rag/query-route-governance/items
  • GET /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_id from 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:

  • /docs returns 200
  • pmflow-backend returns to healthy

Acceptance Checklist

  • 页面级全局浮窗在项目页、Inbox project_id 过滤页、AI Steward project_id query 下都会把锚点显式带进 /rag/ask
  • 项目页进入 AI 的入口不再裸跳 /ai-steward
  • QAConversationStatusBar 能让用户看见当前页面锚点
  • manager-only Route 治理看板 已可查看 summary/list/detail
  • 治理页能直接筛出 mismatch / clarify / negative feedback 样本
  • 文档、测试、build、backend restart 已完成