545 lines
18 KiB
Markdown
545 lines
18 KiB
Markdown
# 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:
|
||
|
||
```python
|
||
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:
|
||
|
||
```ts
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```ts
|
||
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:
|
||
|
||
```ts
|
||
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:
|
||
|
||
```tsx
|
||
当前页面锚点:项目 #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:
|
||
|
||
```ts
|
||
expect(aiStewardApiMock.askRag).toHaveBeenCalledWith(
|
||
expect.objectContaining({ project_id: 17 })
|
||
);
|
||
```
|
||
|
||
- [ ] **Step 5: Re-run the focused front-end tests**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```ts
|
||
expect(navigate).toHaveBeenCalledWith("/ai-steward?tab=qa&project_id=17");
|
||
```
|
||
|
||
- [ ] **Step 2: Run the focused test to confirm it fails**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```ts
|
||
navigate("/ai-steward")
|
||
```
|
||
|
||
to:
|
||
|
||
```ts
|
||
navigate(`/ai-steward?tab=qa&project_id=${id}`)
|
||
```
|
||
|
||
- [ ] **Step 4: Re-run the affected test**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```python
|
||
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:
|
||
|
||
```python
|
||
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:
|
||
|
||
```python
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```python
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```tsx
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```ts
|
||
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:
|
||
|
||
```tsx
|
||
{ path: "ai-config/query-route-governance", element: <ManagerOnlyGuard><QueryRouteGovernancePage /></ManagerOnlyGuard> }
|
||
```
|
||
|
||
Governance tools card:
|
||
|
||
```ts
|
||
{
|
||
id: "query-route-governance",
|
||
name: "Route 治理看板",
|
||
to: "/ai-config/query-route-governance",
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 6: Re-run the front-end page test**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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 已完成
|