merge main flow p8 review decision loop

This commit is contained in:
Albert 2026-04-26 22:19:38 +08:00
commit 61def5280b
16 changed files with 754 additions and 36 deletions

View File

@ -36,7 +36,7 @@ AIProjectManger/
- 本地预览端口现按职责拆分:`docker-compose.dev.yml` 的热更新开发前端固定占用 `5174`,`docker-compose.yml` 的静态生产预览前端默认改为 `5175`(可通过 `FRONTEND_PROD_PORT` 覆盖),避免再和 Vite live preview 抢端口。 - 本地预览端口现按职责拆分:`docker-compose.dev.yml` 的热更新开发前端固定占用 `5174`,`docker-compose.yml` 的静态生产预览前端默认改为 `5175`(可通过 `FRONTEND_PROD_PORT` 覆盖),避免再和 Vite live preview 抢端口。
- `docs/architecture/` 当前包含:`MASTER_CONTRACT.md`、`RUNTIME_GUARDRAILS.md`、`CODE_REFERENCE_INDEX.md`、`API_CONTRACTS.md`、`DATAFLOW.md`、`TASK_BRIEF_TEMPLATE.md`、`ARCHITECTURE_INVENTORY.md` - `docs/architecture/` 当前包含:`MASTER_CONTRACT.md`、`RUNTIME_GUARDRAILS.md`、`CODE_REFERENCE_INDEX.md`、`API_CONTRACTS.md`、`DATAFLOW.md`、`TASK_BRIEF_TEMPLATE.md`、`ARCHITECTURE_INVENTORY.md`
- `docs/PMFLOW_PRODUCT_BLUEPRINT.md` 是当前产品蓝图真源:定义 PMFlow 为“对话驱动的 AI 产品执行操作系统”,并锁定 `solo-first, team-ready`、项目真相层、待确认变更中心、执行桥、人机混编团队等产品方向。 - `docs/PMFLOW_PRODUCT_BLUEPRINT.md` 是当前产品蓝图真源:定义 PMFlow 为“对话驱动的 AI 产品执行操作系统”,并锁定 `solo-first, team-ready`、项目真相层、待确认变更中心、执行桥、人机混编团队等产品方向。
- `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md` 是 PMFlow 产品推进执行标准:记录当前推进状态、Phase A-F/P8 下一步、单次任务范围、Codex 并行推进协议、完成后进度回写规则和新线程接续标准;继续产品推进前必须先读。P8 当前推荐任务已重定为 Review Decision Loop,从 reviewer routing 只读模型推进到 review request、reviewer decision、approval audit 和 personal attention。 - `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md` 是 PMFlow 产品推进执行标准:记录当前推进状态、Phase A-F/P8 下一步、单次任务范围、Codex 并行推进协议、完成后进度回写规则和新线程接续标准;继续产品推进前必须先读。P8 Main Flow 已把 reviewer routing 只读模型推进到 review request、reviewer decision 和 approval audit,后续剩余推荐是 Space/Account 消费面与 integration 收口到 personal attention。
- `docs/PMFLOW_UI_CONTRACT.md` 是 PMFlow UI 契约真源:锁定 Logo、浅色白底 + `#2463EB`、移动端固定底部导航、用户动作/AI 操作/系统状态/待确认视觉语法、核心功能 canonical 契约和当前前端一致性审计;后续原型图和代码化原型不得绕过该文档。 - `docs/PMFLOW_UI_CONTRACT.md` 是 PMFlow UI 契约真源:锁定 Logo、浅色白底 + `#2463EB`、移动端固定底部导航、用户动作/AI 操作/系统状态/待确认视觉语法、核心功能 canonical 契约和当前前端一致性审计;后续原型图和代码化原型不得绕过该文档。
- `docs/superpowers/plans/` 保存实现计划,`docs/superpowers/specs/` 保存需求/方案规格,`docs/task_briefs/` 保存任务包产物与验收截图。 - `docs/superpowers/plans/` 保存实现计划,`docs/superpowers/specs/` 保存需求/方案规格,`docs/task_briefs/` 保存任务包产物与验收截图。
- 当前与“统一 AI handoff / 全量业务结构化”直接相关的设计规格包括:`docs/superpowers/specs/2026-04-17-unified-project-ai-handoff.md`、`docs/superpowers/specs/2026-04-19-interaction-boundary-four-level-contract-design.md`、`docs/superpowers/specs/2026-04-20-full-business-structuring-and-conversation-baseline-design.md` 与 `docs/superpowers/specs/2026-04-20-business-golden20-refresh.md`;它们分别对应统一入口与项目型 AI 方向、四挡 interaction boundary、全量业务结构化与 58 条广覆盖语义矩阵,以及面向当前真实项目池和 plan mode 主链的 20 条核心黄金案例。 - 当前与“统一 AI handoff / 全量业务结构化”直接相关的设计规格包括:`docs/superpowers/specs/2026-04-17-unified-project-ai-handoff.md`、`docs/superpowers/specs/2026-04-19-interaction-boundary-four-level-contract-design.md`、`docs/superpowers/specs/2026-04-20-full-business-structuring-and-conversation-baseline-design.md` 与 `docs/superpowers/specs/2026-04-20-business-golden20-refresh.md`;它们分别对应统一入口与项目型 AI 方向、四挡 interaction boundary、全量业务结构化与 58 条广覆盖语义矩阵,以及面向当前真实项目池和 plan mode 主链的 20 条核心黄金案例。
@ -113,8 +113,8 @@ frontend/
- `frontend/src/features/personal-space/`(个人空间首页;通过 `frontend/src/api/space.ts` 只读聚合项目摘要、PMFlow OS truth-state / execution-summary,承载继续推进、待确认、AI 正在推进、草案箱和最近项目入口) - `frontend/src/features/personal-space/`(个人空间首页;通过 `frontend/src/api/space.ts` 只读聚合项目摘要、PMFlow OS truth-state / execution-summary,承载继续推进、待确认、AI 正在推进、草案箱和最近项目入口)
- `frontend/src/features/project-launch/`(首次进入页 / 新项目启动台;默认调用真实 `frontend/src/api/pmflowLaunch.ts` draft/confirm API 创建项目,只有显式 `?demo=1` 或 `?dataSource=demo` 才启用本地 mock 演示状态机) - `frontend/src/features/project-launch/`(首次进入页 / 新项目启动台;默认调用真实 `frontend/src/api/pmflowLaunch.ts` draft/confirm API 创建项目,只有显式 `?demo=1` 或 `?dataSource=demo` 才启用本地 mock 演示状态机)
- `frontend/src/features/account-settings/`(`/me` 个人中心与设置;通过 `frontend/src/api/accountSettings.ts` 管理个人资料、默认语言、AI 回答深度、证据展示偏好和通知偏好,移动端使用分组列表与 bottom sheet,不进入 auth core) - `frontend/src/features/account-settings/`(`/me` 个人中心与设置;通过 `frontend/src/api/accountSettings.ts` 管理个人资料、默认语言、AI 回答深度、证据展示偏好和通知偏好,移动端使用分组列表与 bottom sheet,不进入 auth core)
- `frontend/src/features/pmflow-os/`(PMFlow OS 前端产品域;共享 truth store 已支持 `?projectId=<id>` 读取真实后端 projection,真实模式下 message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet `commit`、Team reviewer routing、单条 iteration recommendation -> ChangeSet draft 与同组 iteration recommendation 批量 -> ChangeSet draft 均调后端,显式 `?demo=1` 才启用本地演示状态机) - `frontend/src/features/pmflow-os/`(PMFlow OS 前端产品域;共享 truth store 已支持 `?projectId=<id>` 读取真实后端 projection,真实模式下 message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet `commit`、review decision、Team reviewer routing、单条 iteration recommendation -> ChangeSet draft 与同组 iteration recommendation 批量 -> ChangeSet draft 均调后端,显式 `?demo=1` 才启用本地演示状态机)
- `backend/app/api/pmflow_os_api/` + `backend/app/services/pmflow_os_projection_service.py` + `backend/app/services/pmflow_change_review_service.py` + `backend/app/schemas/pmflow_os.py` + `backend/app/models/pmflow_change_review.py`(PMFlow OS A3/C1/C2 真实路径;提供 `truth-state`、`execution-summary`、thread message、`proposed_items` extraction、ChangeSet detail、ChangeItem `accept/edit/defer`、ChangeSet `commit`、execution handoff/progress writeback、release validation run、release feedback ingest、Team reviewer routing、recommendation change-set draft 与 recommendation group change-set draft;复用 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync / FeedbackIngest / IterationRecommendation` 并投影 `pmflow_threads / pmflow_thread_messages / pmflow_change_sets / pmflow_change_items / pmflow_approvals / pmflow_evidence_links`;`truth-state.reviewerRouting` 是 `{status, warningCode, summary, items}` 只读合同,生成建议审查人/原因/来源/风险/跳转 URL,不写 approval、不自动 assign reviewer;C2 commit 默认 atomic,`ModelDecision / MetricSignal` 暂 fail-closed) - `backend/app/api/pmflow_os_api/` + `backend/app/services/pmflow_os_projection_service.py` + `backend/app/services/pmflow_change_review_service.py` + `backend/app/schemas/pmflow_os.py` + `backend/app/models/pmflow_change_review.py`(PMFlow OS A3/C1/C2/P8 真实路径;提供 `truth-state`、`execution-summary`、thread message、`proposed_items` extraction、ChangeSet detail、ChangeItem `accept/edit/defer`、ChangeSet `commit`、review decision、execution handoff/progress writeback、release validation run、release feedback ingest、Team reviewer routing、recommendation change-set draft 与 recommendation group change-set draft;复用 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync / FeedbackIngest / IterationRecommendation` 并投影 `pmflow_threads / pmflow_thread_messages / pmflow_change_sets / pmflow_change_items / pmflow_approvals / pmflow_evidence_links`;`truth-state.reviewerRouting` 是 `{status, warningCode, summary, items}` 合同,生成建议审查人/原因/来源/风险/跳转 URL,并投影 `reviewDecision / approvalState / requestedAt / decidedAt / decisionReason`;`POST /pmflow-os/change-sets/{change_set_id}/review-decisions` 支持 request_review/approve/request_changes/reject,复用 `PMFlowApproval` 审计,不自动 commit;C2 commit 默认 atomic,`ModelDecision / MetricSignal` 暂 fail-closed)
- `backend/app/api/pmflow_launch_api/` + `backend/app/services/pmflow_launch_service.py` + `backend/app/schemas/pmflow_launch.py` + `backend/app/models/pmflow_launch.py`(Project Launch Phase B 真实创建链;`drafts` 只写 `pmflow_launch_drafts`,`confirm` 在同一事务写真实 Project、启动线程、启动消息、初始 truth_nodes、tasks 与 semantic_facts seed) - `backend/app/api/pmflow_launch_api/` + `backend/app/services/pmflow_launch_service.py` + `backend/app/schemas/pmflow_launch.py` + `backend/app/models/pmflow_launch.py`(Project Launch Phase B 真实创建链;`drafts` 只写 `pmflow_launch_drafts`,`confirm` 在同一事务写真实 Project、启动线程、启动消息、初始 truth_nodes、tasks 与 semantic_facts seed)
- `docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md`(PMFlow OS 产品合同真源;真实后端接入前先读,锁定本地 truth store 对象与 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync` 的映射;Phase C1 review 表和审查 API 已实现,Phase C2 backend/frontend commit 已实现) - `docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md`(PMFlow OS 产品合同真源;真实后端接入前先读,锁定本地 truth store 对象与 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync` 的映射;Phase C1 review 表和审查 API 已实现,Phase C2 backend/frontend commit 已实现)
- `frontend/src/pages/inbox/`(消息台) - `frontend/src/pages/inbox/`(消息台)
@ -134,7 +134,7 @@ frontend/
- `frontend/src/features/project-launch/` 是新项目启动台独立产品域:`types.ts / useProjectLaunchState.ts` 承载状态合同和真实 API 编排,`mockData.ts` 只在显式 demo 模式下使用;默认路径调用 `POST /pmflow-launch/drafts` 生成可审查草案,确认后调用 `POST /pmflow-launch/drafts/{draft_id}/confirm` 创建真实项目并跳转 `/pmflow-os?projectId=<id>`。`LaunchShell` 桌面端复用 `frontend/src/features/personal-space/components/SpaceChrome.tsx` 的空间左侧栏和顶部条,主画布仍由 `IntentInputPanel / AIProgressCanvas / HumanReviewCanvas / DraftEditCanvas / FailureStatePanel / SuccessTransitionPanel` 承接,移动端仍由 `MobileLaunchLayout` 承接;`frontend/src/pages/ProjectLaunchPage.tsx` 仅做薄入口。 - `frontend/src/features/project-launch/` 是新项目启动台独立产品域:`types.ts / useProjectLaunchState.ts` 承载状态合同和真实 API 编排,`mockData.ts` 只在显式 demo 模式下使用;默认路径调用 `POST /pmflow-launch/drafts` 生成可审查草案,确认后调用 `POST /pmflow-launch/drafts/{draft_id}/confirm` 创建真实项目并跳转 `/pmflow-os?projectId=<id>`。`LaunchShell` 桌面端复用 `frontend/src/features/personal-space/components/SpaceChrome.tsx` 的空间左侧栏和顶部条,主画布仍由 `IntentInputPanel / AIProgressCanvas / HumanReviewCanvas / DraftEditCanvas / FailureStatePanel / SuccessTransitionPanel` 承接,移动端仍由 `MobileLaunchLayout` 承接;`frontend/src/pages/ProjectLaunchPage.tsx` 仅做薄入口。
- `frontend/src/features/personal-space/` 是空间层入口:`PersonalSpacePage.tsx` 承接桌面个人工作面与独立移动单手布局,`frontend/src/api/space.ts` 只读聚合 `projects` summary、PMFlow OS `truth-state`、`execution-summary` 并生成 `nextActionQueue` 我的下一步队列、继续推进、待确认、发布关注、发布后反馈关注、下一轮迭代草稿关注、AI 正在推进、草案箱、最近项目摘要;Space P6 的下一步队列按高风险待确认、发布阻塞/失败证据、handoff consumed 后缺少后续 progress、下一轮 draft、发布后反馈/收入/合规信号排序,点击只跳 `/change-review?projectId=<id>&changeSetId=<id>`、`/execution-bridge?projectId=<id>` 或 `/release-growth?projectId=<id>`;Space P3 的发布关注优先消费 `truth-state.releases` 的 release validation 投影,没有验证投影时才回落 `execution-summary.releaseGate`;Space P4/P5 的发布后反馈/下一轮草稿关注只读消费 `execution-summary.releaseSignals`;不在 Space 写 Project Memory、ProgressEvent、ReleaseValidationRun、FeedbackIngest、RevenueEvent、ComplianceCheckRun、ChangeSet 状态或发布执行。`components/SpaceChrome.tsx` 承接空间层左侧栏、顶部条与空间用量组件,并被 `/project-launch` 桌面首次新建壳复用;`frontend/src/pages/PersonalSpacePage.tsx` 仅做薄入口。 - `frontend/src/features/personal-space/` 是空间层入口:`PersonalSpacePage.tsx` 承接桌面个人工作面与独立移动单手布局,`frontend/src/api/space.ts` 只读聚合 `projects` summary、PMFlow OS `truth-state`、`execution-summary` 并生成 `nextActionQueue` 我的下一步队列、继续推进、待确认、发布关注、发布后反馈关注、下一轮迭代草稿关注、AI 正在推进、草案箱、最近项目摘要;Space P6 的下一步队列按高风险待确认、发布阻塞/失败证据、handoff consumed 后缺少后续 progress、下一轮 draft、发布后反馈/收入/合规信号排序,点击只跳 `/change-review?projectId=<id>&changeSetId=<id>`、`/execution-bridge?projectId=<id>` 或 `/release-growth?projectId=<id>`;Space P3 的发布关注优先消费 `truth-state.releases` 的 release validation 投影,没有验证投影时才回落 `execution-summary.releaseGate`;Space P4/P5 的发布后反馈/下一轮草稿关注只读消费 `execution-summary.releaseSignals`;不在 Space 写 Project Memory、ProgressEvent、ReleaseValidationRun、FeedbackIngest、RevenueEvent、ComplianceCheckRun、ChangeSet 状态或发布执行。`components/SpaceChrome.tsx` 承接空间层左侧栏、顶部条与空间用量组件,并被 `/project-launch` 桌面首次新建壳复用;`frontend/src/pages/PersonalSpacePage.tsx` 仅做薄入口。
- `frontend/src/features/account-settings/` 是 Account Settings 独立产品域:`AccountSettingsPanel.tsx` 承接 `/me` 桌面设置页、移动端分组列表与 bottom sheet,`frontend/src/api/accountSettings.ts` 定义个人资料、默认语言、AI 回答深度、证据展示偏好和四类通知偏好合同;当前已有账号同步 API fallback、本地 UI 通知消费模型、Release/Growth 偏好 helper、post-release / next change review 偏好 helper,以及 `getAccountReminderConsumption(settings, event)` 提醒强度消费合同,支持 `reminderIntensity=silent|normal|prominent` 与 `evidenceDisplay=compact|expanded`;`releaseGate / failureAlert / pendingConfirmation / executionWriteback` 只影响页面内提醒表达强度和证据展开方式,不代表真实邮件、桌面推送或系统通知中心已接入,也不得隐藏真实业务证据或审计状态,不改 OAuth/JWT/auth core;`frontend/src/features/pmflow-os/pages/AccountPage.tsx` 仅做薄入口并复用既有退出登录 bottom sheet。 - `frontend/src/features/account-settings/` 是 Account Settings 独立产品域:`AccountSettingsPanel.tsx` 承接 `/me` 桌面设置页、移动端分组列表与 bottom sheet,`frontend/src/api/accountSettings.ts` 定义个人资料、默认语言、AI 回答深度、证据展示偏好和四类通知偏好合同;当前已有账号同步 API fallback、本地 UI 通知消费模型、Release/Growth 偏好 helper、post-release / next change review 偏好 helper,以及 `getAccountReminderConsumption(settings, event)` 提醒强度消费合同,支持 `reminderIntensity=silent|normal|prominent` 与 `evidenceDisplay=compact|expanded`;`releaseGate / failureAlert / pendingConfirmation / executionWriteback` 只影响页面内提醒表达强度和证据展开方式,不代表真实邮件、桌面推送或系统通知中心已接入,也不得隐藏真实业务证据或审计状态,不改 OAuth/JWT/auth core;`frontend/src/features/pmflow-os/pages/AccountPage.tsx` 仅做薄入口并复用既有退出登录 bottom sheet。
- `frontend/src/features/pmflow-os/` 当前承载新的对话驱动前端产品域:`store.tsx` 提供共享 truth store;真实模式通过 `?projectId=<id>` 调用 `frontend/src/api/pmflowOs.ts` 读取后端 `ProjectTruthState` projection,并调用真实 message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet `commit`、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest、单条 iteration recommendation -> ChangeSet draft 与同组 iteration recommendation 批量 -> ChangeSet draft API,成功后 refetch truth-state / execution-summary;显式 `?demo=1` 才启用 `command_idle -> ai_extracting -> extraction_ready -> review_open -> writeback_success` 本地演示状态机,以及本地 `promoteAcceptedToGraph` 等演示动作。`pages/` 下包含 `CommandCenter / StructuredExtraction / ChangeReview / MemoryWritebackResult / WorkGraph / ExecutionBridge / ReleaseGrowth / TeamLayer / ProjectMemory / Account` 主页面;`TeamLayerPage` 真实模式读取 `state.reviewerRouting` 展示 ready/empty/warning、待审 ChangeSet、来源、风险等级、建议审查人或 fallback role、审查原因、待审 item 数和证据数,动作只跳转 `/change-review?projectId=<id>&changeSetId=<id>`,不自动分派 reviewer;`ExecutionBridgePage` 已支持创建 handoff、运行端领取确认和执行进度回写;`ReleaseGrowthPage` 真实模式展示 `execution-summary.releaseGate`、execution evidence、阻塞原因、下一步动作、`latestValidationRun` 和 `releaseSignals`,可创建 release validation run、摄取发布后 feedback、展示 recommendation quality grouping、从真实 recommendation 单条或同组批量生成下一轮待确认 ChangeSet draft,并消费 Account notification preference 静默强调型提醒,但不隐藏真实证据、审计状态或发布后信号;`ChangeReviewPage` 会展示来自 release feedback recommendation group 的组、反馈、风险、影响和审查优先级,帮助 reviewer 判断为什么生成这一组待确认变更;静态发布/增长原型仅保留在显式 demo 模式;后端只记录 progress/release gate/release validation/release feedback/recommendation draft 元数据,不执行本机命令、真实发布命令、外部支付、真实推送、自动 Project Memory commit 或正式任务创建;`ProjectMemoryPage` 在真实模式消费后端 `truth_nodes + semantic_facts` 的 `projectMemory` 投影,真实模式下 accepted/edited ChangeItem 只有 commit 成功后才进入 Project Memory。`components/` 下除了 `ProjectStageRail / ThreadComposer / ExtractionRail / ChangeGroupPanel / ObjectGraphSection / ContextPacketPanel / ExecutionTimeline / ReleaseGatePanel / TeamFeedPanel` 之外,新增 `ProjectStructuredLoop / PMFlowOsFrame / AccountControls / FeedbackPrimitives` 等行为组件,用来承接项目指挥台、结构化提取、待确认审查、项目记忆写入结果、项目内面包屑、项目切换器、账号退出登录交互,以及 `PageBanner / InlineNotice / HumanDecisionBar / ConfirmDialog / MobileBottomSheet` 这组契约化提示提醒组件;`components/MobileBottomNav.tsx` 是正式应用壳唯一移动底部导航,固定为 `空间 / 指挥台 / 待确认 / 动态 / 我的`。 - `frontend/src/features/pmflow-os/` 当前承载新的对话驱动前端产品域:`store.tsx` 提供共享 truth store;真实模式通过 `?projectId=<id>` 调用 `frontend/src/api/pmflowOs.ts` 读取后端 `ProjectTruthState` projection,并调用真实 message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet `commit`、review decision、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest、单条 iteration recommendation -> ChangeSet draft 与同组 iteration recommendation 批量 -> ChangeSet draft API,成功后 refetch truth-state / execution-summary;显式 `?demo=1` 才启用 `command_idle -> ai_extracting -> extraction_ready -> review_open -> writeback_success` 本地演示状态机,以及本地 `promoteAcceptedToGraph` 等演示动作。`pages/` 下包含 `CommandCenter / StructuredExtraction / ChangeReview / MemoryWritebackResult / WorkGraph / ExecutionBridge / ReleaseGrowth / TeamLayer / ProjectMemory / Account` 主页面;`ChangeReviewPage` 真实模式可请求审查、通过、要求修改或拒绝当前 ChangeSet,展示审查状态、建议 reviewer、审查意见和 high-risk approval gate,但不自动 commit;`TeamLayerPage` 真实模式读取 `state.reviewerRouting` 展示 ready/empty/warning、待审 ChangeSet、来源、风险等级、建议审查人或 fallback role、审查原因、待审 item 数、证据数、审查状态、审查意见和请求/决策时间,动作跳转 `/change-review?projectId=<id>&changeSetId=<id>`;`ExecutionBridgePage` 已支持创建 handoff、运行端领取确认和执行进度回写;`ReleaseGrowthPage` 真实模式展示 `execution-summary.releaseGate`、execution evidence、阻塞原因、下一步动作、`latestValidationRun` 和 `releaseSignals`,可创建 release validation run、摄取发布后 feedback、展示 recommendation quality grouping、从真实 recommendation 单条或同组批量生成下一轮待确认 ChangeSet draft,并消费 Account notification preference 静默强调型提醒,但不隐藏真实证据、审计状态或发布后信号;静态发布/增长原型仅保留在显式 demo 模式;后端只记录 review decision/progress/release gate/release validation/release feedback/recommendation draft 元数据,不执行本机命令、真实发布命令、外部支付、真实推送、自动 Project Memory commit 或正式任务创建;`ProjectMemoryPage` 在真实模式消费后端 `truth_nodes + semantic_facts` 的 `projectMemory` 投影,真实模式下 accepted/edited ChangeItem 只有 commit 成功后才进入 Project Memory。`components/` 下除了 `ProjectStageRail / ThreadComposer / ExtractionRail / ChangeGroupPanel / ObjectGraphSection / ContextPacketPanel / ExecutionTimeline / ReleaseGatePanel / TeamFeedPanel` 之外,新增 `ProjectStructuredLoop / PMFlowOsFrame / AccountControls / FeedbackPrimitives` 等行为组件,用来承接项目指挥台、结构化提取、待确认审查、项目记忆写入结果、项目内面包屑、项目切换器、账号退出登录交互,以及 `PageBanner / InlineNotice / HumanDecisionBar / ConfirmDialog / MobileBottomSheet` 这组契约化提示提醒组件;`components/MobileBottomNav.tsx` 是正式应用壳唯一移动底部导航,固定为 `空间 / 指挥台 / 待确认 / 动态 / 我的`。
- 旧登录实现 `frontend/src/components/ui/sign-in.tsx` 与 `frontend/src/components/ui/demo.tsx` 已移除,`frontend/src/components/login/` 是当前唯一登录真源。 - 旧登录实现 `frontend/src/components/ui/sign-in.tsx` 与 `frontend/src/components/ui/demo.tsx` 已移除,`frontend/src/components/login/` 是当前唯一登录真源。
### 5.1 `frontend/src/components/sstc/` 目录说明(模块化拆分后) ### 5.1 `frontend/src/components/sstc/` 目录说明(模块化拆分后)

View File

@ -21,6 +21,7 @@ from ...schemas.pmflow_os import (
PMFlowIterationRecommendationGroupChangeSetDraftRequest, PMFlowIterationRecommendationGroupChangeSetDraftRequest,
PMFlowReleaseFeedbackIngestRequest, PMFlowReleaseFeedbackIngestRequest,
PMFlowReleaseValidationRunCreateRequest, PMFlowReleaseValidationRunCreateRequest,
PMFlowReviewDecisionRequest,
PMFlowThreadMessageCreateRequest, PMFlowThreadMessageCreateRequest,
) )
from ...services.auth import has_any_role from ...services.auth import has_any_role
@ -231,6 +232,40 @@ async def commit_pmflow_os_change_set(
return success_response(payload) return success_response(payload)
@router.post("/change-sets/{change_set_id}/review-decisions")
async def create_pmflow_os_review_decision(
change_set_id: int,
body: PMFlowReviewDecisionRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
tenant_id = int(current_user.tenant_id)
review_service = PMFlowChangeReviewService(db, tenant_id=tenant_id)
project_id = await review_service.get_change_set_project_id(change_set_id=int(change_set_id))
if project_id is None:
return JSONResponse(status_code=404, content=error_response(404, "changeset_not_found"))
await _ensure_project_member_or_manager(db, tenant_id=tenant_id, project_id=project_id, current_user=current_user)
try:
review_payload = await review_service.record_review_decision(
change_set_id=int(change_set_id),
body=body,
user_id=int(current_user.id),
)
truth_state = await PMFlowOSProjectionService(db, tenant_id=tenant_id).get_truth_state(
project_id=int(project_id),
current_user_id=int(current_user.id),
)
except PMFlowChangeReviewError as exc:
return _review_error(exc)
return success_response(
{
"changeSetDetail": review_payload["changeSetDetail"],
"truthState": truth_state.model_dump(mode="json"),
"warnings": review_payload.get("warnings", []),
}
)
@router.get("/projects/{project_id}/execution-summary") @router.get("/projects/{project_id}/execution-summary")
async def get_pmflow_os_execution_summary( async def get_pmflow_os_execution_summary(
project_id: int, project_id: int,

View File

@ -38,6 +38,8 @@ PMFlowReviewerRoutingSourceType = Literal["manual", "ai_extraction", "iteration_
PMFlowReviewerRoutingPriority = Literal["normal", "elevated", "urgent"] PMFlowReviewerRoutingPriority = Literal["normal", "elevated", "urgent"]
PMFlowReviewerConfidence = Literal["low", "medium", "high"] PMFlowReviewerConfidence = Literal["low", "medium", "high"]
PMFlowFallbackReviewerRole = Literal["OWNER", "REVIEWER", "MEMBER"] PMFlowFallbackReviewerRole = Literal["OWNER", "REVIEWER", "MEMBER"]
PMFlowReviewDecisionAction = Literal["request_review", "approve", "request_changes", "reject"]
PMFlowApprovalState = Literal["not_requested", "requested", "approved", "changes_requested", "rejected"]
PMFlowObjectType = Literal[ PMFlowObjectType = Literal[
"Requirement", "Requirement",
"Decision", "Decision",
@ -160,6 +162,13 @@ class PMFlowIterationRecommendationGroupChangeSetDraftRequest(BaseModel):
note: Optional[str] = Field(default=None, max_length=4000) note: Optional[str] = Field(default=None, max_length=4000)
class PMFlowReviewDecisionRequest(BaseModel):
action: PMFlowReviewDecisionAction
reviewer_id: Optional[int] = Field(default=None, ge=1)
reason: Optional[str] = Field(default=None, max_length=4000)
item_ids: list[str] = Field(default_factory=list)
class PMFlowChangeSetDetailResponse(BaseModel): class PMFlowChangeSetDetailResponse(BaseModel):
changeSet: dict[str, Any] changeSet: dict[str, Any]
items: list[dict[str, Any]] = Field(default_factory=list) items: list[dict[str, Any]] = Field(default_factory=list)
@ -309,6 +318,11 @@ class PMFlowReviewerRoutingItem(BaseModel):
evidenceCount: int = 0 evidenceCount: int = 0
suggestedReviewer: Optional[PMFlowReviewerSuggestion] = None suggestedReviewer: Optional[PMFlowReviewerSuggestion] = None
fallbackReviewerRole: Optional[PMFlowFallbackReviewerRole] = None fallbackReviewerRole: Optional[PMFlowFallbackReviewerRole] = None
reviewDecision: Optional[PMFlowReviewDecisionAction] = None
approvalState: PMFlowApprovalState = "not_requested"
requestedAt: Optional[str] = None
decidedAt: Optional[str] = None
decisionReason: Optional[str] = None
targetUrl: str targetUrl: str
createdAt: Optional[str] = None createdAt: Optional[str] = None
updatedAt: Optional[str] = None updatedAt: Optional[str] = None
@ -319,6 +333,10 @@ class PMFlowReviewerRoutingSummary(BaseModel):
highRiskCount: int = 0 highRiskCount: int = 0
missingReviewerCount: int = 0 missingReviewerCount: int = 0
iterationDraftCount: int = 0 iterationDraftCount: int = 0
requestedReviewCount: int = 0
approvedCount: int = 0
changesRequestedCount: int = 0
rejectedCount: int = 0
class PMFlowReviewerRoutingProjection(BaseModel): class PMFlowReviewerRoutingProjection(BaseModel):

View File

@ -6,6 +6,7 @@ from typing import Any, Optional
from sqlalchemy import and_, func, select from sqlalchemy import and_, func, select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm.attributes import flag_modified
from ..models import ( from ..models import (
PMFlowApproval, PMFlowApproval,
@ -17,11 +18,14 @@ from ..models import (
SemanticFact, SemanticFact,
Task, Task,
TruthNode, TruthNode,
User,
UserProject,
) )
from ..schemas.pmflow_os import ( from ..schemas.pmflow_os import (
PMFlowChangeItemEditRequest, PMFlowChangeItemEditRequest,
PMFlowChangeSetCommitRequest, PMFlowChangeSetCommitRequest,
PMFlowExtractionCreateRequest, PMFlowExtractionCreateRequest,
PMFlowReviewDecisionRequest,
PMFlowThreadMessageCreateRequest, PMFlowThreadMessageCreateRequest,
) )
from ..utils.timezone import now_cn_naive from ..utils.timezone import now_cn_naive
@ -291,6 +295,117 @@ class PMFlowChangeReviewService:
await self.db.rollback() await self.db.rollback()
raise PMFlowChangeReviewError("commit_failed", "commit_failed", status_code=500) from exc raise PMFlowChangeReviewError("commit_failed", "commit_failed", status_code=500) from exc
async def record_review_decision(
self,
*,
change_set_id: int,
body: PMFlowReviewDecisionRequest,
user_id: int,
) -> dict[str, Any]:
try:
change_set = await self._load_change_set_for_review_decision(change_set_id=change_set_id)
items = await self._load_items(project_id=int(change_set.project_id), change_set_id=int(change_set.id))
selected_items = self._select_review_decision_items(items=items, raw_item_ids=body.item_ids)
warnings: list[str] = []
if body.action == "request_review":
reviewer = await self._load_project_reviewer(
project_id=int(change_set.project_id),
reviewer_id=body.reviewer_id,
)
if reviewer is None:
warnings.append("reviewer_missing")
else:
self._mark_change_set_review_meta(
change_set=change_set,
action="request_review",
approval_state="requested",
user_id=user_id,
reviewer_id=int(reviewer.id),
reason=body.reason,
selected_items=selected_items,
)
await self.db.commit()
return {
"changeSetDetail": await self.get_change_set_detail(
project_id=int(change_set.project_id),
change_set_id=int(change_set.id),
),
"warnings": warnings,
}
if not selected_items:
raise PMFlowChangeReviewError("review_decision_item_empty", "review_decision_item_empty", status_code=409)
before_status_by_id = {int(item.id): item.status for item in selected_items}
if body.action == "approve":
decision = "approved"
approval_state = "approved"
after_status = "accepted"
for item in selected_items:
item.status = "accepted"
item.reviewed_at = now_cn_naive()
elif body.action == "request_changes":
decision = "changes_requested"
approval_state = "changes_requested"
after_status = "pending_review"
elif body.action == "reject":
decision = "rejected"
approval_state = "rejected"
after_status = "deferred"
for item in selected_items:
item.status = "deferred"
item.reviewed_at = now_cn_naive()
else:
raise PMFlowChangeReviewError("invalid_review_decision", "invalid_review_decision", status_code=422)
actor_id = int(user_id)
reviewer_meta_id = int(body.reviewer_id or user_id)
for item in selected_items:
self.db.add(
PMFlowApproval(
tenant_id=self.tenant_id,
project_id=int(item.project_id),
change_set_id=int(item.change_set_id),
change_item_id=int(item.id),
actor_user_id=actor_id,
action=body.action,
decision=decision,
before_status=before_status_by_id[int(item.id)],
after_status=after_status if body.action != "approve" else item.status,
before_payload=dict(item.edited_payload or item.payload or {}),
after_payload={
**dict(item.edited_payload or item.payload or {}),
"reviewDecision": body.action,
"approvalState": approval_state,
},
reason=body.reason,
)
)
self._mark_change_set_review_meta(
change_set=change_set,
action=body.action,
approval_state=approval_state,
user_id=user_id,
reviewer_id=reviewer_meta_id,
reason=body.reason,
selected_items=selected_items,
)
if body.action in {"approve", "reject"}:
self._mark_change_set_reviewed_if_no_pending(change_set=change_set, items=items)
await self.db.commit()
return {
"changeSetDetail": await self.get_change_set_detail(
project_id=int(change_set.project_id),
change_set_id=int(change_set.id),
),
"warnings": warnings,
}
except PMFlowChangeReviewError:
await self.db.rollback()
raise
async def _resolve_or_create_thread(self, *, project_id: int, thread_id: str) -> PMFlowThread: async def _resolve_or_create_thread(self, *, project_id: int, thread_id: str) -> PMFlowThread:
cleaned = str(thread_id or "").strip() cleaned = str(thread_id or "").strip()
if cleaned.isdigit(): if cleaned.isdigit():
@ -380,6 +495,21 @@ class PMFlowChangeReviewService:
raise PMFlowChangeReviewError("changeset_not_found", "changeset_not_found", status_code=404) raise PMFlowChangeReviewError("changeset_not_found", "changeset_not_found", status_code=404)
return row return row
async def _load_change_set_for_review_decision(self, *, change_set_id: int) -> PMFlowChangeSet:
result = await self.db.execute(
select(PMFlowChangeSet)
.where(and_(PMFlowChangeSet.tenant_id == self.tenant_id, PMFlowChangeSet.id == int(change_set_id)))
.with_for_update()
)
row = result.scalar_one_or_none()
if row is None:
raise PMFlowChangeReviewError("changeset_not_found", "changeset_not_found", status_code=404)
if row.status == "committed":
raise PMFlowChangeReviewError("changeset_already_committed", "changeset_already_committed", status_code=409)
if row.status not in {"open", "reviewed"}:
raise PMFlowChangeReviewError("changeset_not_open", "changeset_not_open", status_code=409)
return row
async def _load_items(self, *, project_id: int, change_set_id: int) -> list[PMFlowChangeItem]: async def _load_items(self, *, project_id: int, change_set_id: int) -> list[PMFlowChangeItem]:
result = await self.db.execute( result = await self.db.execute(
select(PMFlowChangeItem) select(PMFlowChangeItem)
@ -434,6 +564,88 @@ class PMFlowChangeReviewService:
raise PMFlowChangeReviewError("change_item_not_found", "change_item_not_found", status_code=404) raise PMFlowChangeReviewError("change_item_not_found", "change_item_not_found", status_code=404)
return row[0], row[1] return row[0], row[1]
async def _load_project_reviewer(self, *, project_id: int, reviewer_id: Optional[int]) -> Optional[User]:
if reviewer_id is None:
return None
result = await self.db.execute(
select(User)
.join(UserProject, UserProject.user_id == User.id)
.where(
and_(
User.tenant_id == self.tenant_id,
User.id == int(reviewer_id),
UserProject.tenant_id == self.tenant_id,
UserProject.project_id == int(project_id),
UserProject.user_id == int(reviewer_id),
User.role.in_(["OWNER", "REVIEWER"]),
)
)
)
return result.scalar_one_or_none()
def _select_review_decision_items(
self,
*,
items: list[PMFlowChangeItem],
raw_item_ids: list[str],
) -> list[PMFlowChangeItem]:
selected_ids = self._parse_review_decision_item_ids(raw_item_ids)
if selected_ids:
item_ids = {int(item.id) for item in items}
if not selected_ids.issubset(item_ids):
raise PMFlowChangeReviewError("invalid_review_decision_item_ids", "invalid_review_decision_item_ids", status_code=422)
return [item for item in items if int(item.id) in selected_ids]
return [item for item in items if item.status == "pending_review"]
@staticmethod
def _parse_review_decision_item_ids(raw_item_ids: list[str]) -> set[int]:
parsed: set[int] = set()
for raw in raw_item_ids or []:
text = str(raw or "").strip()
if not text.isdigit():
raise PMFlowChangeReviewError("invalid_review_decision_item_ids", "invalid_review_decision_item_ids", status_code=422)
parsed.add(int(text))
return parsed
def _mark_change_set_review_meta(
self,
*,
change_set: PMFlowChangeSet,
action: str,
approval_state: str,
user_id: int,
reviewer_id: Optional[int],
reason: Optional[str],
selected_items: list[PMFlowChangeItem],
) -> None:
now = now_cn_naive()
routing_meta = dict(change_set.routing_meta or {})
review_meta = dict(routing_meta.get("reviewDecision") or {})
if action == "request_review":
review_meta["requestedAt"] = review_meta.get("requestedAt") or now.isoformat()
review_meta["requestedBy"] = str(user_id)
else:
review_meta["decidedAt"] = now.isoformat()
review_meta["decidedBy"] = str(user_id)
review_meta.update(
{
"action": action,
"approvalState": approval_state,
"reviewerId": None if reviewer_id is None else str(reviewer_id),
"decisionReason": reason,
"itemIds": [str(item.id) for item in selected_items],
}
)
routing_meta["reviewDecision"] = review_meta
change_set.routing_meta = routing_meta
flag_modified(change_set, "routing_meta")
@staticmethod
def _mark_change_set_reviewed_if_no_pending(*, change_set: PMFlowChangeSet, items: list[PMFlowChangeItem]) -> None:
if not any(item.status == "pending_review" for item in items):
change_set.status = "reviewed"
change_set.reviewed_at = now_cn_naive()
async def _review_item( async def _review_item(
self, self,
*, *,
@ -795,6 +1007,7 @@ class PMFlowChangeReviewService:
"sourceMessageId": str(row.source_message_id), "sourceMessageId": str(row.source_message_id),
"rawStatus": row.status, "rawStatus": row.status,
"committedAt": row.committed_at.isoformat() if row.committed_at else None, "committedAt": row.committed_at.isoformat() if row.committed_at else None,
"routingMeta": row.routing_meta or {},
} }
@staticmethod @staticmethod

View File

@ -1289,8 +1289,10 @@ class PMFlowOSProjectionService:
evidence: list[dict[str, Any]], evidence: list[dict[str, Any]],
members: list[User], members: list[User],
) -> tuple[dict[str, Any], list[str]]: ) -> tuple[dict[str, Any], list[str]]:
all_items_by_set: dict[str, list[dict[str, Any]]] = {}
items_by_set: dict[str, list[dict[str, Any]]] = {} items_by_set: dict[str, list[dict[str, Any]]] = {}
for item in change_items: for item in change_items:
all_items_by_set.setdefault(str(item.get("changeSetId")), []).append(item)
if item.get("status") != "pending_review": if item.get("status") != "pending_review":
continue continue
items_by_set.setdefault(str(item.get("changeSetId")), []).append(item) items_by_set.setdefault(str(item.get("changeSetId")), []).append(item)
@ -1303,14 +1305,20 @@ class PMFlowOSProjectionService:
warnings: list[str] = [] warnings: list[str] = []
for change_set in change_sets: for change_set in change_sets:
change_set_id = str(change_set.get("id") or "") change_set_id = str(change_set.get("id") or "")
review_meta = self._change_set_review_decision_meta(change_set)
review_action = self._review_decision_action(review_meta)
approval_state = self._approval_state(review_meta)
pending_items = items_by_set.get(change_set_id, []) pending_items = items_by_set.get(change_set_id, [])
if not change_set_id or not pending_items: all_items = all_items_by_set.get(change_set_id, [])
decision_visible = bool(review_action and approval_state != "not_requested")
if not change_set_id or (not pending_items and not decision_visible):
continue continue
change_set_evidence = evidence_by_set.get(change_set_id, []) change_set_evidence = evidence_by_set.get(change_set_id, [])
risk_levels = [str(item.get("riskLevel") or "medium") for item in pending_items] review_items = pending_items or all_items
object_types = sorted({str(item.get("objectType") or "Unknown") for item in pending_items}) risk_levels = [str(item.get("riskLevel") or "medium") for item in review_items]
risk_groups = sorted({str(item.get("group") or "unknown") for item in pending_items}) object_types = sorted({str(item.get("objectType") or "Unknown") for item in review_items})
high_risk_signal = any(str(item.get("objectType") or "").lower() == "risk" or str(item.get("group") or "").lower() == "risk" for item in pending_items) risk_groups = sorted({str(item.get("group") or "unknown") for item in review_items})
high_risk_signal = any(str(item.get("objectType") or "").lower() == "risk" or str(item.get("group") or "").lower() == "risk" for item in review_items)
highest_risk = "high" if high_risk_signal else self._highest_risk_level(risk_levels) highest_risk = "high" if high_risk_signal else self._highest_risk_level(risk_levels)
cross_domain = len(object_types) > 1 or len(risk_groups) > 1 cross_domain = len(object_types) > 1 or len(risk_groups) > 1
review_reasons = self._review_routing_reasons( review_reasons = self._review_routing_reasons(
@ -1319,6 +1327,8 @@ class PMFlowOSProjectionService:
object_types=object_types, object_types=object_types,
risk_groups=risk_groups, risk_groups=risk_groups,
cross_domain=cross_domain, cross_domain=cross_domain,
review_action=review_action,
approval_state=approval_state,
) )
suggested_reviewer = self._suggest_reviewer( suggested_reviewer = self._suggest_reviewer(
members=members, members=members,
@ -1335,6 +1345,9 @@ class PMFlowOSProjectionService:
fallback_role = "OWNER" fallback_role = "OWNER"
elif not change_set_evidence: elif not change_set_evidence:
warning = "evidence_missing" warning = "evidence_missing"
if decision_visible and not pending_items and approval_state in {"approved", "changes_requested", "rejected"}:
warning = None
fallback_role = None
if warning: if warning:
warnings.append(f"reviewer_routing_warning:{change_set_id}:{warning}") warnings.append(f"reviewer_routing_warning:{change_set_id}:{warning}")
source_type = self._reviewer_routing_source_type(change_set_evidence) source_type = self._reviewer_routing_source_type(change_set_evidence)
@ -1350,17 +1363,26 @@ class PMFlowOSProjectionService:
"riskLevel": highest_risk, "riskLevel": highest_risk,
"priority": priority, "priority": priority,
"changeTypes": object_types, "changeTypes": object_types,
"itemCount": len(change_set.get("itemIds") or pending_items), "itemCount": len(change_set.get("itemIds") or review_items),
"pendingItemCount": len(pending_items), "pendingItemCount": len(pending_items),
"evidenceCount": len(change_set_evidence), "evidenceCount": len(change_set_evidence),
"suggestedReviewer": suggested_reviewer, "suggestedReviewer": suggested_reviewer,
"fallbackReviewerRole": fallback_role, "fallbackReviewerRole": fallback_role,
"reviewDecision": review_action,
"approvalState": approval_state,
"requestedAt": review_meta.get("requestedAt"),
"decidedAt": review_meta.get("decidedAt"),
"decisionReason": review_meta.get("decisionReason"),
"targetUrl": f"/change-review?projectId={int(project_id)}&changeSetId={change_set_id}", "targetUrl": f"/change-review?projectId={int(project_id)}&changeSetId={change_set_id}",
"createdAt": change_set.get("createdAt"), "createdAt": change_set.get("createdAt"),
"updatedAt": change_set.get("updatedAt") or change_set.get("createdAt"), "updatedAt": change_set.get("updatedAt") or change_set.get("createdAt"),
} }
) )
missing_reviewer_count = sum(1 for item in routing_items if item.get("suggestedReviewer") is None) missing_reviewer_count = sum(
1
for item in routing_items
if self._reviewer_routing_requires_reviewer(item) and item.get("suggestedReviewer") is None
)
warning_code = self._reviewer_routing_warning_code(routing_items) warning_code = self._reviewer_routing_warning_code(routing_items)
status = "empty" if not routing_items else ("warning" if warning_code else "ready") status = "empty" if not routing_items else ("warning" if warning_code else "ready")
return ( return (
@ -1373,6 +1395,10 @@ class PMFlowOSProjectionService:
"highRiskCount": sum(1 for item in routing_items if item.get("riskLevel") == "high"), "highRiskCount": sum(1 for item in routing_items if item.get("riskLevel") == "high"),
"missingReviewerCount": missing_reviewer_count, "missingReviewerCount": missing_reviewer_count,
"iterationDraftCount": sum(1 for item in routing_items if item.get("sourceType") == "iteration_recommendation"), "iterationDraftCount": sum(1 for item in routing_items if item.get("sourceType") == "iteration_recommendation"),
"requestedReviewCount": sum(1 for item in routing_items if item.get("approvalState") == "requested"),
"approvedCount": sum(1 for item in routing_items if item.get("approvalState") == "approved"),
"changesRequestedCount": sum(1 for item in routing_items if item.get("approvalState") == "changes_requested"),
"rejectedCount": sum(1 for item in routing_items if item.get("approvalState") == "rejected"),
}, },
}, },
warnings, warnings,
@ -1396,8 +1422,18 @@ class PMFlowOSProjectionService:
object_types: list[str], object_types: list[str],
risk_groups: list[str], risk_groups: list[str],
cross_domain: bool, cross_domain: bool,
review_action: Optional[str] = None,
approval_state: str = "not_requested",
) -> list[str]: ) -> list[str]:
reasons = [f"{pending_count} 个变更项仍处于待审状态。"] reasons = [f"{pending_count} 个变更项仍处于待审状态。"] if pending_count else ["当前 ChangeSet 已形成审查决策,等待下一步处理。"]
if approval_state == "requested":
reasons.append("已请求 reviewer 审查,等待审查意见。")
elif approval_state == "approved":
reasons.append("reviewer 已通过,仍需人工决定是否 commit。")
elif approval_state == "changes_requested":
reasons.append("reviewer 要求修改,不能直接写入 Project Memory。")
elif approval_state == "rejected":
reasons.append("reviewer 已拒绝,本轮不得写入 Project Memory。")
if highest_risk == "high": if highest_risk == "high":
reasons.append("包含 high risk 变更,需要 REVIEWER 或 OWNER 复核。") reasons.append("包含 high risk 变更,需要 REVIEWER 或 OWNER 复核。")
if cross_domain: if cross_domain:
@ -1406,6 +1442,8 @@ class PMFlowOSProjectionService:
reasons.append("包含风险类变更,审查时需要确认缓解策略是否足够。") reasons.append("包含风险类变更,审查时需要确认缓解策略是否足够。")
if "decision" in {group.lower() for group in risk_groups}: if "decision" in {group.lower() for group in risk_groups}:
reasons.append("包含决策类变更,审查时需要确认拍板依据。") reasons.append("包含决策类变更,审查时需要确认拍板依据。")
if review_action == "request_changes":
reasons.append("下一步应回到 Change Review 修改内容后再重新请求审查。")
return reasons return reasons
@staticmethod @staticmethod
@ -1419,7 +1457,7 @@ class PMFlowOSProjectionService:
@staticmethod @staticmethod
def _reviewer_routing_source_type(evidence: list[dict[str, Any]]) -> str: def _reviewer_routing_source_type(evidence: list[dict[str, Any]]) -> str:
source_types = {str(row.get("sourceType") or "").strip() for row in evidence} source_types = {str(row.get("sourceType") or "").strip() for row in evidence}
if "iteration_recommendation" in source_types: if "iteration_recommendation_group" in source_types or "iteration_recommendation" in source_types:
return "iteration_recommendation" return "iteration_recommendation"
if "thread_message" in source_types: if "thread_message" in source_types:
return "ai_extraction" return "ai_extraction"
@ -1431,12 +1469,20 @@ class PMFlowOSProjectionService:
def _reviewer_routing_warning_code(items: list[dict[str, Any]]) -> Optional[str]: def _reviewer_routing_warning_code(items: list[dict[str, Any]]) -> Optional[str]:
if not items: if not items:
return None return None
if any(item.get("suggestedReviewer") is None for item in items): active_items = [item for item in items if PMFlowOSProjectionService._reviewer_routing_requires_reviewer(item)]
if any(item.get("suggestedReviewer") is None for item in active_items):
return "reviewer_missing" return "reviewer_missing"
if any(int(item.get("evidenceCount") or 0) == 0 for item in items): if any(int(item.get("evidenceCount") or 0) == 0 for item in active_items):
return "evidence_missing" return "evidence_missing"
return None return None
@staticmethod
def _reviewer_routing_requires_reviewer(item: dict[str, Any]) -> bool:
approval_state = str(item.get("approvalState") or "not_requested")
if approval_state in {"approved", "changes_requested", "rejected"}:
return int(item.get("pendingItemCount") or 0) > 0
return True
@staticmethod @staticmethod
def _suggest_reviewer( def _suggest_reviewer(
*, *,
@ -1467,6 +1513,26 @@ class PMFlowOSProjectionService:
"confidence": confidence, "confidence": confidence,
} }
@staticmethod
def _change_set_review_decision_meta(change_set: dict[str, Any]) -> dict[str, Any]:
routing_meta = change_set.get("routingMeta")
if not isinstance(routing_meta, dict):
return {}
review_meta = routing_meta.get("reviewDecision")
return dict(review_meta) if isinstance(review_meta, dict) else {}
@staticmethod
def _review_decision_action(review_meta: dict[str, Any]) -> Optional[str]:
action = str(review_meta.get("action") or "").strip()
return action if action in {"request_review", "approve", "request_changes", "reject"} else None
@staticmethod
def _approval_state(review_meta: dict[str, Any]) -> str:
state = str(review_meta.get("approvalState") or "").strip()
if state in {"requested", "approved", "changes_requested", "rejected"}:
return state
return "not_requested"
@staticmethod @staticmethod
def _active_change_set_id(change_sets: list[dict[str, Any]]) -> Optional[str]: def _active_change_set_id(change_sets: list[dict[str, Any]]) -> Optional[str]:
for row in change_sets: for row in change_sets:

View File

@ -20,6 +20,7 @@ from app.models import ( # noqa: E402
FeedbackIngest, FeedbackIngest,
FeishuItem, FeishuItem,
GitHubWebhookDelivery, GitHubWebhookDelivery,
IterationRecommendation,
ModelCatalogItem, ModelCatalogItem,
ModelEvalRun, ModelEvalRun,
ModelRecommendationSnapshot, ModelRecommendationSnapshot,
@ -76,6 +77,7 @@ TABLES = [
ProgressEvent.__table__, ProgressEvent.__table__,
ReleaseValidationRun.__table__, ReleaseValidationRun.__table__,
FeedbackIngest.__table__, FeedbackIngest.__table__,
IterationRecommendation.__table__,
RevenueEvent.__table__, RevenueEvent.__table__,
ROISnapshot.__table__, ROISnapshot.__table__,
GitHubWebhookDelivery.__table__, GitHubWebhookDelivery.__table__,
@ -331,6 +333,96 @@ async def test_change_item_accept_edit_defer_write_approvals_and_do_not_write_me
assert len(truth.json()["data"]["approvals"]) == 3 assert len(truth.json()["data"]["approvals"]) == 3
@pytest.mark.asyncio
async def test_review_decision_request_and_approve_high_risk_satisfies_commit_gate(db_env) -> None:
session = db_env
user, project = await _seed_user_project(session)
reviewer = User(
tenant_id=1,
username="reviewer",
email="reviewer@example.com",
password_hash="hashed",
name="审查负责人",
role="REVIEWER",
assigned_domains=[],
workload=0,
status="active",
is_active=True,
)
session.add(reviewer)
await session.flush()
session.add(UserProject(tenant_id=1, user_id=reviewer.id, project_id=project.id))
await session.commit()
app = _build_app(session, SimpleNamespace(id=user.id, tenant_id=1, role="MEMBER"))
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
_, detail = await _create_commit_extraction(client, project.id)
change_set_id = detail["changeSet"]["id"]
high_risk_id = next(item["id"] for item in detail["items"] if item["riskLevel"] == "high")
request_review = await client.post(
f"/pmflow-os/change-sets/{change_set_id}/review-decisions",
json={"action": "request_review", "reviewer_id": reviewer.id, "reason": "请 reviewer 复核高风险项。"},
)
approve = await client.post(
f"/pmflow-os/change-sets/{change_set_id}/review-decisions",
json={"action": "approve", "reviewer_id": reviewer.id, "reason": "高风险项通过。", "item_ids": [high_risk_id]},
)
commit = await client.post(
f"/pmflow-os/change-sets/{change_set_id}/commit",
json={"item_ids": [high_risk_id], "mode": "atomic"},
)
assert request_review.status_code == 200
request_payload = request_review.json()["data"]
assert request_payload["warnings"] == []
routing_item = request_payload["truthState"]["reviewerRouting"]["items"][0]
assert routing_item["reviewDecision"] == "request_review"
assert routing_item["approvalState"] == "requested"
assert routing_item["requestedAt"]
assert approve.status_code == 200
approve_payload = approve.json()["data"]
assert approve_payload["truthState"]["reviewerRouting"]["items"][0]["approvalState"] == "approved"
assert approve_payload["truthState"]["reviewerRouting"]["items"][0]["decisionReason"] == "高风险项通过。"
approval_rows = (await session.execute(select(PMFlowApproval).where(PMFlowApproval.change_set_id == int(change_set_id)))).scalars().all()
assert any(row.action == "approve" and row.decision == "approved" and row.change_item_id == int(high_risk_id) for row in approval_rows)
assert any(row.action == "approve" and row.actor_user_id == user.id for row in approval_rows)
high_risk = (await session.execute(select(PMFlowChangeItem).where(PMFlowChangeItem.id == int(high_risk_id)))).scalar_one()
assert high_risk.status == "committed"
assert commit.status_code == 200
assert await session.scalar(select(func.count(TruthNode.id))) == 1
@pytest.mark.asyncio
async def test_review_decision_request_changes_and_reject_do_not_commit_memory(db_env) -> None:
session = db_env
user, project = await _seed_user_project(session)
app = _build_app(session, SimpleNamespace(id=user.id, tenant_id=1, role="MEMBER"))
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
_, detail = await _create_message_and_extraction(client, project.id)
change_set_id = detail["changeSet"]["id"]
item_ids = [item["id"] for item in detail["items"]]
changes = await client.post(
f"/pmflow-os/change-sets/{change_set_id}/review-decisions",
json={"action": "request_changes", "reason": "摘要需要补证据。", "item_ids": [item_ids[0]]},
)
rejected = await client.post(
f"/pmflow-os/change-sets/{change_set_id}/review-decisions",
json={"action": "reject", "reason": "风险项本轮不采纳。", "item_ids": [item_ids[1]]},
)
assert changes.status_code == 200
assert changes.json()["data"]["truthState"]["reviewerRouting"]["items"][0]["approvalState"] == "changes_requested"
assert rejected.status_code == 200
assert rejected.json()["data"]["truthState"]["reviewerRouting"]["items"][0]["approvalState"] == "rejected"
assert await session.scalar(select(func.count(PMFlowApproval.id))) == 2
assert await session.scalar(select(func.count(TruthNode.id))) == 0
assert await session.scalar(select(func.count(Task.id))) == 0
rejected_item = (await session.execute(select(PMFlowChangeItem).where(PMFlowChangeItem.id == int(item_ids[1])))).scalar_one()
assert rejected_item.status == "deferred"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_change_set_commit_writes_truth_nodes_tasks_and_semantic_facts(db_env) -> None: async def test_change_set_commit_writes_truth_nodes_tasks_and_semantic_facts(db_env) -> None:
session = db_env session = db_env

View File

@ -1436,6 +1436,10 @@ async def test_truth_state_reviewer_routing_empty_without_pending_changesets(db_
"highRiskCount": 0, "highRiskCount": 0,
"missingReviewerCount": 0, "missingReviewerCount": 0,
"iterationDraftCount": 0, "iterationDraftCount": 0,
"requestedReviewCount": 0,
"approvedCount": 0,
"changesRequestedCount": 0,
"rejectedCount": 0,
}, },
} }

View File

@ -87,10 +87,10 @@
4. `ChangeReview` 的接受、修改、暂缓还没有写入正式对象、审批记录和证据链。 4. `ChangeReview` 的接受、修改、暂缓还没有写入正式对象、审批记录和证据链。
5. `ExecutionBridge` 已接入真实 execution-summary/dev-sync、execution handoff、运行端领取和 progress writeback;当前缺口不再是执行桥读写合同,而是执行质量、审查责任和个人队列的产品化。 5. `ExecutionBridge` 已接入真实 execution-summary/dev-sync、execution handoff、运行端领取和 progress writeback;当前缺口不再是执行桥读写合同,而是执行质量、审查责任和个人队列的产品化。
6. `ReleaseGrowth` 已接入 execution evidence gate、release validation run、feedback ingest 和 recommendation -> ChangeSet draft 回流;revenue、ROI、compliance 还没有串成完整发布后反馈循环。 6. `ReleaseGrowth` 已接入 execution evidence gate、release validation run、feedback ingest 和 recommendation -> ChangeSet draft 回流;revenue、ROI、compliance 还没有串成完整发布后反馈循环。
7. `TeamLayer` 仍是产品表达层,缺少真实成员角色、Role Inbox、Decision Desk、Team Feed 的数据模型和审查流程。 7. `TeamLayer` 已接入 reviewer routing 与 review decision 状态展示;Role Inbox、Decision Desk、Team Feed 仍缺完整真实数据模型。
5. `ExecutionBridge` 页面还没有消费 dev-sync / GitHub / PromptPackage 真实接口。 5. `ExecutionBridge` 页面还没有消费 dev-sync / GitHub / PromptPackage 真实接口。
6. `ReleaseGrowth` 已接入 execution evidence gate、release validation run 与 feedback ingest;revenue、ROI、compliance 还没有串成完整发布后反馈循环。 6. `ReleaseGrowth` 已接入 execution evidence gate、release validation run 与 feedback ingest;revenue、ROI、compliance 还没有串成完整发布后反馈循环。
7. `TeamLayer` 已接入 reviewer routing 读模型,能展示待审 ChangeSet、建议审查人、审查原因、风险类型和缺角色 warning;Role Inbox、Decision Desk、Team Feed 仍缺完整真实数据模型。 7. `TeamLayer` 已接入 reviewer routing / review decision 读模型,能展示待审 ChangeSet、建议审查人、审查原因、风险类型、缺角色 warning、审查状态和审查意见;Role Inbox、Decision Desk、Team Feed 仍缺完整真实数据模型。
## 2. 下一步推进方向 ## 2. 下一步推进方向
@ -718,13 +718,13 @@ commit gate 判定:
当前最建议下一步执行: 当前最建议下一步执行:
**P8:Review Decision Loop。** **P8:Review Decision Loop 剩余消费面收口。**
原因: 原因:
- P7 与 Team Review P1 已完成:recommendation group 可批量生成下一轮 ChangeSet draft,Space 已有 `nextActionQueue`,Account 已有提醒强度合同,TeamLayer 已能只读展示 `reviewerRouting`。 - P8 Main Flow 已完成:`reviewerRouting` 已从“只读建议”推进到 `review request -> reviewer decision -> approval audit`,并复用 `PMFlowApproval` 形成审计记录。
- 当前缺口不是“推荐谁来审”,而是把 reviewer routing 推进到真实审查决策闭环:`review request -> reviewer decision -> approval audit -> personal attention`。 - 当前剩余缺口在消费面:Space 需要把待审/已请求/已决策事项纳入个人下一步队列,Account 需要补 review decision / high-risk approval 的提醒偏好合同,最后由 integration owner 收口验证。
- P8 不新增战略页面,不改 router/nav,不接真实通知中心;继续使用既有 `/change-review / /team-layer / /space / /me` 表面。 - P8 仍不新增战略页面,不改 router/nav,不接真实通知中心;继续使用既有 `/change-review / /team-layer / /space / /me` 表面。
### 7.1 P8 并行建议 ### 7.1 P8 并行建议
@ -732,6 +732,8 @@ P8 可以三条 lane 并行,但共享高冲突文件必须由 Main Flow 或 Sh
#### Main Flow Packet:`main-flow-p8-review-decision-loop` #### Main Flow Packet:`main-flow-p8-review-decision-loop`
状态:已完成。
目标: 目标:
- 把 reviewerRouting 从“只读建议”推进到“审查决策闭环”。 - 把 reviewerRouting 从“只读建议”推进到“审查决策闭环”。
@ -766,6 +768,8 @@ P8 可以三条 lane 并行,但共享高冲突文件必须由 Main Flow 或 Sh
#### Space Packet:`space-p7-review-attention-queue` #### Space Packet:`space-p7-review-attention-queue`
状态:待推进。
目标: 目标:
- 把 Team Review 的待审事项接入 `/space` 的“我的下一步”队列。 - 把 Team Review 的待审事项接入 `/space` 的“我的下一步”队列。
@ -787,6 +791,8 @@ P8 可以三条 lane 并行,但共享高冲突文件必须由 Main Flow 或 Sh
#### Account Settings Packet:`account-p8-review-decision-preference` #### Account Settings Packet:`account-p8-review-decision-preference`
状态:待推进。
目标: 目标:
- 扩展 Account Settings 的提醒偏好合同,增加 review decision / reviewer request / high-risk approval 相关偏好。 - 扩展 Account Settings 的提醒偏好合同,增加 review decision / reviewer request / high-risk approval 相关偏好。
@ -817,6 +823,50 @@ P8 可以三条 lane 并行,但共享高冲突文件必须由 Main Flow 或 Sh
## 8. 进度台账 ## 8. 进度台账
### 2026-04-26 / main-flow-p8-review-decision-loop / done
完成内容:
- 新增 `POST /pmflow-os/change-sets/{change_set_id}/review-decisions`,支持 `request_review / approve / request_changes / reject`,统一 `{code,message,data}` 包络返回 `changeSetDetail / truthState / warnings`。
- `request_review` 校验 reviewer 必须属于当前项目且具备 `OWNER / REVIEWER` 角色;缺 reviewer 时 fail-closed 返回 `reviewer_missing` warning,不写入虚假审查状态。
- `approve / request_changes / reject` 复用 `PMFlowApproval` 写审查审计记录;`approve` 只把选中待审项标为 accepted,并使 high-risk item 满足 C2 approval gate,仍不自动 commit ChangeSet;`request_changes / reject` 不写 Project Memory、不创建正式任务。
- `ProjectTruthState.reviewerRouting` 扩展 `reviewDecision / approvalState / requestedAt / decidedAt / decisionReason` 和 requested/approved/changes/rejected 汇总计数;已决策项可继续在 Team layer 留痕,terminal decision 不再误报缺 reviewer。
- `ChangeReviewPage` 增加审查决策面板,展示审查状态、建议 reviewer、审查意见、高风险 approval gate 和四类决策动作。
- `TeamLayerPage` 增加待我审、已请求、已决策统计,并展示每个 routing item 的审查状态、审查意见与请求/决策时间。
涉及文件:
- `backend/app/api/pmflow_os_api/__init__.py`
- `backend/app/schemas/pmflow_os.py`
- `backend/app/services/pmflow_change_review_service.py`
- `backend/app/services/pmflow_os_projection_service.py`
- `backend/tests/test_pmflow_change_review_api.py`
- `backend/tests/test_pmflow_os_projection_api.py`
- `frontend/src/api/pmflowOs.ts`
- `frontend/src/features/pmflow-os/store.tsx`
- `frontend/src/features/pmflow-os/store.api.test.tsx`
- `frontend/src/features/pmflow-os/types.ts`
- `frontend/src/features/pmflow-os/pages/ChangeReviewPage.tsx`
- `frontend/src/features/pmflow-os/pages/TeamLayerPage.tsx`
- `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md`
- `docs/architecture/CODE_REFERENCE_INDEX.md`
- `docs/architecture/ARCHITECTURE_INVENTORY.md`
- `CLAUDE.md`
验证:
- `PYTHONPATH=backend /opt/homebrew/bin/python3.11 -m py_compile backend/app/api/pmflow_os_api/__init__.py backend/app/services/pmflow_change_review_service.py backend/app/services/pmflow_os_projection_service.py backend/app/schemas/pmflow_os.py` 通过。
- `PYTHONPATH=backend /opt/homebrew/bin/python3.11 -m pytest backend/tests/test_pmflow_change_review_api.py backend/tests/test_pmflow_os_projection_api.py -q` 通过:`31 passed`;仍有既有 `backend/app/schemas/recipe.py` Pydantic V2 class-based config deprecation warning。
- `cd frontend && npm run test -- src/features/pmflow-os` 通过:`13 passed`;仍有既有 React Router v7 future flag warning。
- `cd frontend && npm run typecheck` 未通过,阻塞在当前 worktree 缺失多项 `frontend/src/lib/*` 公共 helper(如 `@/lib/api`、`@/lib/utils`、`@/lib/datetime`),不属于本 Packet write scope。
- `cd frontend && npm run build` 未通过,原因同 `typecheck`,停在同一批缺失 `frontend/src/lib/*` 公共 helper。
- `docker compose ps backend` / `docker compose --env-file .env.example ps backend` 未能进入 backend health 检查:当前 worktree 缺少 `.env`,Compose 要求 `BACKEND_PORT / LLM_PROVIDER / DASHSCOPE_API_KEY` 等必填 runtime env,`.env.example` 中 API key 为空;未写宿主机配置,未用 dummy secret 重启服务。
未完成或风险:
- 本 Packet 不接 Space 个人队列、不扩展 Account review preference、不接真实通知中心;后续仍按 P8 剩余消费面推进。
- 本 Packet 不新增 migration、不改 router/nav、不改 Space/Account,不执行真实外部通知或自动 Project Memory commit。
### 2026-04-26 / shell-p8-rebaseline-next-plan / done ### 2026-04-26 / shell-p8-rebaseline-next-plan / done
完成内容: 完成内容:
@ -908,7 +958,7 @@ P8 可以三条 lane 并行,但共享高冲突文件必须由 Main Flow 或 Sh
未完成或风险: 未完成或风险:
- 当前只是 reviewer routing 读模型与 TeamLayer 展示,不代表正式 reviewer assignment、权限审批流、通知或自动审查任务已接入。 - 该历史记录对应 Team Review P1,当时只是 reviewer routing 读模型与 TeamLayer 展示;Main Flow P8 之后已接入 review decision / PMFlowApproval 审计,但仍不代表正式 reviewer assignment、权限审批流、通知或自动审查任务已接入。
- Role Inbox、Decision Desk、Team Feed 的完整真实数据模型仍是后续 Team lane 工作。 - Role Inbox、Decision Desk、Team Feed 的完整真实数据模型仍是后续 Team lane 工作。
### 2026-04-26 / shell-p7-rebaseline-next-plan / implemented ### 2026-04-26 / shell-p7-rebaseline-next-plan / implemented
@ -990,7 +1040,7 @@ P8 可以三条 lane 并行,但共享高冲突文件必须由 Main Flow 或 Sh
未完成或风险: 未完成或风险:
- 当前只是 reviewer routing 读模型与 TeamLayer 展示,不代表正式 reviewer assignment、权限审批流、通知或自动审查任务已接入。 - 该历史记录对应 Team Review P1,当时只是 reviewer routing 读模型与 TeamLayer 展示;Main Flow P8 之后已接入 review decision / PMFlowApproval 审计,但仍不代表正式 reviewer assignment、权限审批流、通知或自动审查任务已接入。
- Role Inbox、Decision Desk、Team Feed 的完整真实数据模型仍是后续 Team lane 工作。 - Role Inbox、Decision Desk、Team Feed 的完整真实数据模型仍是后续 Team lane 工作。
### 2026-04-26 / big-module-integration-p6 / done ### 2026-04-26 / big-module-integration-p6 / done

View File

@ -194,19 +194,19 @@
- AI Steward 问答浮窗现分三层显性化:`FloatingQAChat.tsx` 负责即时回答/历史回放归一化,`QAConversationStatusBar.tsx` 展示对象/来源/仓库/执行通道/plan handoff,`QAResponseMetaCard.tsx` 展示 `fit_brief / external_recommendation / recommended_next_step`,对应前端回归由 `FloatingQAChat.test.tsx` 与 `QAResponseMetaCard.test.tsx` 锁定。 - AI Steward 问答浮窗现分三层显性化:`FloatingQAChat.tsx` 负责即时回答/历史回放归一化,`QAConversationStatusBar.tsx` 展示对象/来源/仓库/执行通道/plan handoff,`QAResponseMetaCard.tsx` 展示 `fit_brief / external_recommendation / recommended_next_step`,对应前端回归由 `FloatingQAChat.test.tsx` 与 `QAResponseMetaCard.test.tsx` 锁定。
- 登录页与营销页仍共享 `frontend/src/components/marketing/` 的部分视觉基线;公开登录页现改由 `frontend/src/components/login/` 目录承载,其中 `LoginExperience.tsx` 负责登录壳层、`LoginWorkflowVisual.tsx` 负责左侧场景、`loginScene.ts` / `loginMath.ts` 拆出展示配置与路径几何、`login.css` 负责登录页专属样式边界,`frontend/src/pages/login/LoginPage.tsx` 继续负责真实鉴权、错误态和 redirect 提示;未登录访问 `/` 会经 `frontend/src/router/index.tsx` 直接重定向到 `/login`,无显式 `redirect` 的成功登录现在默认进入 `/command-center`。 - 登录页与营销页仍共享 `frontend/src/components/marketing/` 的部分视觉基线;公开登录页现改由 `frontend/src/components/login/` 目录承载,其中 `LoginExperience.tsx` 负责登录壳层、`LoginWorkflowVisual.tsx` 负责左侧场景、`loginScene.ts` / `loginMath.ts` 拆出展示配置与路径几何、`login.css` 负责登录页专属样式边界,`frontend/src/pages/login/LoginPage.tsx` 继续负责真实鉴权、错误态和 redirect 提示;未登录访问 `/` 会经 `frontend/src/router/index.tsx` 直接重定向到 `/login`,无显式 `redirect` 的成功登录现在默认进入 `/command-center`。
- 前端主包治理已切到“同步首屏最小集 + route/page-section lazy”模式:`frontend/src/router/index.tsx` 保留 `LoginPage / Layout` 这组同步入口,其余后台重页面通过 `React.lazy + Suspense` 懒加载;统一 fallback 在 `frontend/src/components/layout/LazyBoundary.tsx`。P0 公共壳冻结后,大模块 route 归属为:`/space` 归 Space lane,`/project-launch` 是启动层入口,`/pmflow-os` 与 `/command-center / /change-review / /execution-bridge / /release-growth` 归 Main Flow lane,`/me` 归 Account Settings lane,`/settings` 当前仅为 reserved route、尚未实现,新增必须由 Shell / Integration owner 串行处理。`frontend/src/features/personal-space/` 作为空间层入口,通过 `/space` 提供可直接打开的本地预览入口,承载继续推进、待我确认、AI 正在推进、暂存想法、草案箱与新建项目入口。`frontend/src/features/project-launch/` 作为首次进入页 / 新项目启动台产品域,通过 `/project-launch` 提供可直接打开的入口;Phase B 后默认调用真实 `pmflow-launch` draft/confirm API,创建成功态主动作跳转后端返回的 `/pmflow-os?projectId=<id>`,只有显式 `?demo=1` 或 `?dataSource=demo` 才启用本地 mock 状态机。`frontend/src/features/account-settings/` 作为 Account Settings 产品域,通过 `/me` 提供个人资料、默认语言、AI 回答深度、证据展示偏好、待确认/执行回写/发布门禁/失败提醒通知偏好和账户操作入口;该域通过 `frontend/src/api/accountSettings.ts` 建立前端合同,有 token 时优先调用已挂入 `backend/app/api/router.py` 的 `backend/app/api/account_settings_api/` `GET /account-settings/me` / `PUT /account-settings/me` 同步当前账号偏好,后端不可用时明确回落本机暂存,不修改 OAuth/JWT/auth core。`frontend/src/features/pmflow-os/` 作为新产品域,通过 `PMFlowOsProvider` 在应用根部挂载共享 truth store,并提供 `CommandCenter / ChangeReview / WorkGraph / ExecutionBridge / ReleaseGrowth / TeamLayer / ProjectMemory / Account` 主入口;当前 PMFlow OS 页面已切到专属轻壳,桌面端默认隐藏旧 TopBar,并由 `frontend/src/features/pmflow-os/components/PMFlowOsFrame.tsx` 统一承接“左侧稳定导航 + 顶部上下文条 + 中央视图 + 右侧 AI 副驾”的全局骨架,同时承接项目内 `个人空间 / 当前项目名` 面包屑和项目切换器;账号退出登录由 `frontend/src/features/pmflow-os/components/AccountControls.tsx` 统一承接桌面头像下拉、退出确认与移动端 bottom sheet,`frontend/src/features/pmflow-os/pages/AccountPage.tsx` 提供 `/me` 入口并委托 Account Settings 域渲染设置页;正式应用壳移动端底部导航统一由 `frontend/src/features/pmflow-os/components/MobileBottomNav.tsx` 承接,固定为 `空间 / 指挥台 / 待确认 / 动态 / 我的`,其中 `动态` 当前仍是 `/command-center` 占位。`/command-center` 额外提供免登录产品预览壳,`/pmflow-os` 是真实项目上下文 alias。主视觉按 `#2463EB + 白色` 的 Linear-like 高密度原型展开。`UsersPage` 把创建/删除/重置/导入/批量分配对话框拆为按需 chunk,`UserSkillsPage` 与 `WorkloadDetailPage` 的图表也分别拆到了 `frontend/src/pages/user-skills/components/UserSkillsRadarPanel.tsx` 与 `frontend/src/pages/workload-detail/components/WorkloadTrendChart.tsx`。 - 前端主包治理已切到“同步首屏最小集 + route/page-section lazy”模式:`frontend/src/router/index.tsx` 保留 `LoginPage / Layout` 这组同步入口,其余后台重页面通过 `React.lazy + Suspense` 懒加载;统一 fallback 在 `frontend/src/components/layout/LazyBoundary.tsx`。P0 公共壳冻结后,大模块 route 归属为:`/space` 归 Space lane,`/project-launch` 是启动层入口,`/pmflow-os` 与 `/command-center / /change-review / /execution-bridge / /release-growth` 归 Main Flow lane,`/me` 归 Account Settings lane,`/settings` 当前仅为 reserved route、尚未实现,新增必须由 Shell / Integration owner 串行处理。`frontend/src/features/personal-space/` 作为空间层入口,通过 `/space` 提供可直接打开的本地预览入口,承载继续推进、待我确认、AI 正在推进、暂存想法、草案箱与新建项目入口。`frontend/src/features/project-launch/` 作为首次进入页 / 新项目启动台产品域,通过 `/project-launch` 提供可直接打开的入口;Phase B 后默认调用真实 `pmflow-launch` draft/confirm API,创建成功态主动作跳转后端返回的 `/pmflow-os?projectId=<id>`,只有显式 `?demo=1` 或 `?dataSource=demo` 才启用本地 mock 状态机。`frontend/src/features/account-settings/` 作为 Account Settings 产品域,通过 `/me` 提供个人资料、默认语言、AI 回答深度、证据展示偏好、待确认/执行回写/发布门禁/失败提醒通知偏好和账户操作入口;该域通过 `frontend/src/api/accountSettings.ts` 建立前端合同,有 token 时优先调用已挂入 `backend/app/api/router.py` 的 `backend/app/api/account_settings_api/` `GET /account-settings/me` / `PUT /account-settings/me` 同步当前账号偏好,后端不可用时明确回落本机暂存,不修改 OAuth/JWT/auth core。`frontend/src/features/pmflow-os/` 作为新产品域,通过 `PMFlowOsProvider` 在应用根部挂载共享 truth store,并提供 `CommandCenter / ChangeReview / WorkGraph / ExecutionBridge / ReleaseGrowth / TeamLayer / ProjectMemory / Account` 主入口;当前 PMFlow OS 页面已切到专属轻壳,桌面端默认隐藏旧 TopBar,并由 `frontend/src/features/pmflow-os/components/PMFlowOsFrame.tsx` 统一承接“左侧稳定导航 + 顶部上下文条 + 中央视图 + 右侧 AI 副驾”的全局骨架,同时承接项目内 `个人空间 / 当前项目名` 面包屑和项目切换器;账号退出登录由 `frontend/src/features/pmflow-os/components/AccountControls.tsx` 统一承接桌面头像下拉、退出确认与移动端 bottom sheet,`frontend/src/features/pmflow-os/pages/AccountPage.tsx` 提供 `/me` 入口并委托 Account Settings 域渲染设置页;正式应用壳移动端底部导航统一由 `frontend/src/features/pmflow-os/components/MobileBottomNav.tsx` 承接,固定为 `空间 / 指挥台 / 待确认 / 动态 / 我的`,其中 `动态` 当前仍是 `/command-center` 占位。`/command-center` 额外提供免登录产品预览壳,`/pmflow-os` 是真实项目上下文 alias。主视觉按 `#2463EB + 白色` 的 Linear-like 高密度原型展开。`UsersPage` 把创建/删除/重置/导入/批量分配对话框拆为按需 chunk,`UserSkillsPage` 与 `WorkloadDetailPage` 的图表也分别拆到了 `frontend/src/pages/user-skills/components/UserSkillsRadarPanel.tsx` 与 `frontend/src/pages/workload-detail/components/WorkloadTrendChart.tsx`。
- PMFlow P8 当前推进真源已从已完成的 Team Review P1 重定为 Review Decision Loop:下一阶段基于 `ProjectTruthState.reviewerRouting` 只读模型推进 `review request -> reviewer decision -> approval audit -> personal attention`,建议并行边界为 `main-flow-p8-review-decision-loop`、`space-p7-review-attention-queue`、`account-p8-review-decision-preference`,最终由 `big-module-integration-p8` 收口;P8 不新增战略页面、不改 router/nav、不接真实通知中心,继续使用既有 `/change-review / /team-layer / /space / /me` 表面。 - PMFlow P8 当前推进真源已进入剩余消费面收口:Main Flow `main-flow-p8-review-decision-loop` 已把 `ProjectTruthState.reviewerRouting` 从只读建议推进到 `review request -> reviewer decision -> approval audit`,新增 `POST /pmflow-os/change-sets/{change_set_id}/review-decisions` 并复用 `PMFlowApproval` 审计;后续优先推进 `space-p7-review-attention-queue`、`account-p8-review-decision-preference`,最终由 `big-module-integration-p8` 收口 personal attention;P8 不新增战略页面、不改 router/nav、不接真实通知中心,继续使用既有 `/change-review / /team-layer / /space / /me` 表面。
- `frontend/src/features/personal-space/PersonalSpacePage.tsx` 是空间首页视图层,`frontend/src/api/space.ts` 是空间工作面只读聚合 API:从 `projects` summary、PMFlow OS `truth-state` 与 `execution-summary` 生成 `nextActionQueue` 我的下一步队列、继续推进、待确认、发布关注、发布后反馈关注、下一轮迭代草稿关注、执行中 / AI 正在推进、暂存想法、草案箱、最近项目摘要;Space P6 的 `nextActionQueue` 按高风险 ChangeSet 待确认、release blocked / failed evidence、handoff consumed 但无后续 progress、iteration draft 待确认、feedback/revenue/compliance 信号待处理排序,并只提供 `/change-review`、`/execution-bridge`、`/release-growth` 跳转。Space P3 的 `releaseAttention` 优先消费 `truth-state.releases` 中由 release validation run 投影出的 release 状态与检查项,把“发布验证阻塞 / 发布验证待确认”显示到 `/space`,没有发布验证投影时才回落到 `execution-summary.releaseGate` 的发布候选或发布门禁阻塞;Space P4/P5 的 `feedbackAttention` 只读消费 `execution-summary.releaseSignals`,把发布后 feedback、revenue、ROI、compliance、iteration recommendation 与已生成的下一轮 ChangeSet draft 关注项显示到 `/space`。发布验证、发布后信号或下一轮 draft 缺失时显示明确空态,不虚构验证、反馈、收入、ROI、合规、建议或 ChangeSet draft 结果。项目主动作进入 `/pmflow-os?projectId=<id>`,待确认项进入 `/change-review?projectId=<id>&changeSetId=<id>`,发布关注和发布后反馈关注项进入 `/release-growth?projectId=<id>`,下一轮 draft 关注项进入 `/change-review?projectId=<id>&changeSetId=<id>`,执行中项进入 `/execution-bridge?projectId=<id>`;暂存想法后端只读聚合尚未接入时只显示 empty/warning,不使用 mock 数据冒充真实内容;移动端为空间首页独立 App 布局,不直接压缩桌面卡片,并保留最近项目区块;`frontend/src/pages/PersonalSpacePage.tsx` 是薄入口。该域只表达个人空间和主流程导航,不做团队空间强切换、企业组织管理、Project Memory 写入、ChangeSet 状态写入、ProgressEvent 写入、ReleaseValidationRun 写入、FeedbackIngest 写入或发布执行。 - `frontend/src/features/personal-space/PersonalSpacePage.tsx` 是空间首页视图层,`frontend/src/api/space.ts` 是空间工作面只读聚合 API:从 `projects` summary、PMFlow OS `truth-state` 与 `execution-summary` 生成 `nextActionQueue` 我的下一步队列、继续推进、待确认、发布关注、发布后反馈关注、下一轮迭代草稿关注、执行中 / AI 正在推进、暂存想法、草案箱、最近项目摘要;Space P6 的 `nextActionQueue` 按高风险 ChangeSet 待确认、release blocked / failed evidence、handoff consumed 但无后续 progress、iteration draft 待确认、feedback/revenue/compliance 信号待处理排序,并只提供 `/change-review`、`/execution-bridge`、`/release-growth` 跳转。Space P3 的 `releaseAttention` 优先消费 `truth-state.releases` 中由 release validation run 投影出的 release 状态与检查项,把“发布验证阻塞 / 发布验证待确认”显示到 `/space`,没有发布验证投影时才回落到 `execution-summary.releaseGate` 的发布候选或发布门禁阻塞;Space P4/P5 的 `feedbackAttention` 只读消费 `execution-summary.releaseSignals`,把发布后 feedback、revenue、ROI、compliance、iteration recommendation 与已生成的下一轮 ChangeSet draft 关注项显示到 `/space`。发布验证、发布后信号或下一轮 draft 缺失时显示明确空态,不虚构验证、反馈、收入、ROI、合规、建议或 ChangeSet draft 结果。项目主动作进入 `/pmflow-os?projectId=<id>`,待确认项进入 `/change-review?projectId=<id>&changeSetId=<id>`,发布关注和发布后反馈关注项进入 `/release-growth?projectId=<id>`,下一轮 draft 关注项进入 `/change-review?projectId=<id>&changeSetId=<id>`,执行中项进入 `/execution-bridge?projectId=<id>`;暂存想法后端只读聚合尚未接入时只显示 empty/warning,不使用 mock 数据冒充真实内容;移动端为空间首页独立 App 布局,不直接压缩桌面卡片,并保留最近项目区块;`frontend/src/pages/PersonalSpacePage.tsx` 是薄入口。该域只表达个人空间和主流程导航,不做团队空间强切换、企业组织管理、Project Memory 写入、ChangeSet 状态写入、ProgressEvent 写入、ReleaseValidationRun 写入、FeedbackIngest 写入或发布执行。
- 前端主包治理已切到“同步首屏最小集 + route/page-section lazy”模式:`frontend/src/router/index.tsx` 保留 `LoginPage / Layout` 这组同步入口,其余后台重页面通过 `React.lazy + Suspense` 懒加载;统一 fallback 在 `frontend/src/components/layout/LazyBoundary.tsx`。P0 公共壳冻结后,大模块 route 归属为:`/space` 归 Space lane,`/project-launch` 是启动层入口,`/pmflow-os` 与 `/command-center / /change-review / /execution-bridge / /release-growth` 归 Main Flow lane,`/me` 归 Account Settings lane,`/settings` 当前仅为 reserved route、尚未实现,新增必须由 Shell / Integration owner 串行处理。`frontend/src/features/personal-space/` 作为空间层入口,通过 `/space` 提供可直接打开的本地预览入口,承载继续推进、待我确认、AI 正在推进、暂存想法、草案箱与新建项目入口。`frontend/src/features/project-launch/` 作为首次进入页 / 新项目启动台产品域,通过 `/project-launch` 提供可直接打开的入口;Phase B 后默认调用真实 `pmflow-launch` draft/confirm API,创建成功态主动作跳转后端返回的 `/pmflow-os?projectId=<id>`,只有显式 `?demo=1` 或 `?dataSource=demo` 才启用本地 mock 状态机。`frontend/src/features/account-settings/` 作为 Account Settings 产品域,通过 `/me` 提供个人资料、默认语言、AI 回答深度、证据展示偏好、待确认/执行回写/发布门禁/失败提醒通知偏好和账户操作入口;该域通过 `frontend/src/api/accountSettings.ts` 建立前端合同,有 token 时优先调用已挂入 `backend/app/api/router.py` 的 `backend/app/api/account_settings_api/` `GET /account-settings/me` / `PUT /account-settings/me` 同步当前账号偏好,后端不可用时明确回落本机暂存,不修改 OAuth/JWT/auth core。`frontend/src/features/pmflow-os/` 作为新产品域,通过 `PMFlowOsProvider` 在应用根部挂载共享 truth store,并提供 `CommandCenter / ChangeReview / WorkGraph / ExecutionBridge / ReleaseGrowth / TeamLayer / ProjectMemory / Account` 主入口;当前 PMFlow OS 页面已切到专属轻壳,桌面端默认隐藏旧 TopBar,并由 `frontend/src/features/pmflow-os/components/PMFlowOsFrame.tsx` 统一承接“左侧稳定导航 + 顶部上下文条 + 中央视图 + 右侧 AI 副驾”的全局骨架,同时承接项目内 `个人空间 / 当前项目名` 面包屑和项目切换器;`TeamLayerPage.tsx` 现在消费 `ProjectTruthState.reviewerRouting(status/summary/items)` 展示待审 ChangeSet、来源、风险等级、建议审查人或 fallback role、审查原因、待审 item 数、证据数和 warning,动作只跳 `/change-review?projectId=<id>&changeSetId=<id>`,不自动 assign reviewer;账号退出登录由 `frontend/src/features/pmflow-os/components/AccountControls.tsx` 统一承接桌面头像下拉、退出确认与移动端 bottom sheet,`frontend/src/features/pmflow-os/pages/AccountPage.tsx` 提供 `/me` 入口并委托 Account Settings 域渲染设置页;正式应用壳移动端底部导航统一由 `frontend/src/features/pmflow-os/components/MobileBottomNav.tsx` 承接,固定为 `空间 / 指挥台 / 待确认 / 动态 / 我的`,其中 `动态` 当前仍是 `/command-center` 占位。`/command-center` 额外提供免登录产品预览壳,`/pmflow-os` 是真实项目上下文 alias。主视觉按 `#2463EB + 白色` 的 Linear-like 高密度原型展开。`UsersPage` 把创建/删除/重置/导入/批量分配对话框拆为按需 chunk,`UserSkillsPage` 与 `WorkloadDetailPage` 的图表也分别拆到了 `frontend/src/pages/user-skills/components/UserSkillsRadarPanel.tsx` 与 `frontend/src/pages/workload-detail/components/WorkloadTrendChart.tsx`。 - 前端主包治理已切到“同步首屏最小集 + route/page-section lazy”模式:`frontend/src/router/index.tsx` 保留 `LoginPage / Layout` 这组同步入口,其余后台重页面通过 `React.lazy + Suspense` 懒加载;统一 fallback 在 `frontend/src/components/layout/LazyBoundary.tsx`。P0 公共壳冻结后,大模块 route 归属为:`/space` 归 Space lane,`/project-launch` 是启动层入口,`/pmflow-os` 与 `/command-center / /change-review / /execution-bridge / /release-growth` 归 Main Flow lane,`/me` 归 Account Settings lane,`/settings` 当前仅为 reserved route、尚未实现,新增必须由 Shell / Integration owner 串行处理。`frontend/src/features/personal-space/` 作为空间层入口,通过 `/space` 提供可直接打开的本地预览入口,承载继续推进、待我确认、AI 正在推进、暂存想法、草案箱与新建项目入口。`frontend/src/features/project-launch/` 作为首次进入页 / 新项目启动台产品域,通过 `/project-launch` 提供可直接打开的入口;Phase B 后默认调用真实 `pmflow-launch` draft/confirm API,创建成功态主动作跳转后端返回的 `/pmflow-os?projectId=<id>`,只有显式 `?demo=1` 或 `?dataSource=demo` 才启用本地 mock 状态机。`frontend/src/features/account-settings/` 作为 Account Settings 产品域,通过 `/me` 提供个人资料、默认语言、AI 回答深度、证据展示偏好、待确认/执行回写/发布门禁/失败提醒通知偏好和账户操作入口;该域通过 `frontend/src/api/accountSettings.ts` 建立前端合同,有 token 时优先调用已挂入 `backend/app/api/router.py` 的 `backend/app/api/account_settings_api/` `GET /account-settings/me` / `PUT /account-settings/me` 同步当前账号偏好,后端不可用时明确回落本机暂存,不修改 OAuth/JWT/auth core。`frontend/src/features/pmflow-os/` 作为新产品域,通过 `PMFlowOsProvider` 在应用根部挂载共享 truth store,并提供 `CommandCenter / ChangeReview / WorkGraph / ExecutionBridge / ReleaseGrowth / TeamLayer / ProjectMemory / Account` 主入口;当前 PMFlow OS 页面已切到专属轻壳,桌面端默认隐藏旧 TopBar,并由 `frontend/src/features/pmflow-os/components/PMFlowOsFrame.tsx` 统一承接“左侧稳定导航 + 顶部上下文条 + 中央视图 + 右侧 AI 副驾”的全局骨架,同时承接项目内 `个人空间 / 当前项目名` 面包屑和项目切换器;`ChangeReviewPage.tsx` 现在可以请求审查、通过、要求修改或拒绝当前 ChangeSet,并展示审查状态、审查意见与 high-risk approval gate;`TeamLayerPage.tsx` 现在消费 `ProjectTruthState.reviewerRouting(status/summary/items)` 展示待审 ChangeSet、来源、风险等级、建议审查人或 fallback role、审查原因、待审 item 数、证据数、warning、审查状态、审查意见和请求/决策时间,动作跳 `/change-review?projectId=<id>&changeSetId=<id>`;账号退出登录由 `frontend/src/features/pmflow-os/components/AccountControls.tsx` 统一承接桌面头像下拉、退出确认与移动端 bottom sheet,`frontend/src/features/pmflow-os/pages/AccountPage.tsx` 提供 `/me` 入口并委托 Account Settings 域渲染设置页;正式应用壳移动端底部导航统一由 `frontend/src/features/pmflow-os/components/MobileBottomNav.tsx` 承接,固定为 `空间 / 指挥台 / 待确认 / 动态 / 我的`,其中 `动态` 当前仍是 `/command-center` 占位。`/command-center` 额外提供免登录产品预览壳,`/pmflow-os` 是真实项目上下文 alias。主视觉按 `#2463EB + 白色` 的 Linear-like 高密度原型展开。`UsersPage` 把创建/删除/重置/导入/批量分配对话框拆为按需 chunk,`UserSkillsPage` 与 `WorkloadDetailPage` 的图表也分别拆到了 `frontend/src/pages/user-skills/components/UserSkillsRadarPanel.tsx` 与 `frontend/src/pages/workload-detail/components/WorkloadTrendChart.tsx`。
- `frontend/src/features/personal-space/PersonalSpacePage.tsx` 是空间首页视图层,`frontend/src/api/space.ts` 是空间工作面只读聚合 API:从 `projects` summary、PMFlow OS `truth-state` 与 `execution-summary` 生成继续推进、待确认、发布关注、发布后反馈关注、下一轮迭代草稿关注、执行中 / AI 正在推进、暂存想法、草案箱、最近项目摘要;Space P3 的 `releaseAttention` 优先消费 `truth-state.releases` 中由 release validation run 投影出的 release 状态与检查项,把“发布验证阻塞 / 发布验证待确认”显示到 `/space`,没有发布验证投影时才回落到 `execution-summary.releaseGate` 的发布候选或发布门禁阻塞;Space P4/P5 的 `feedbackAttention` 只读消费 `execution-summary.releaseSignals`,把发布后 feedback、revenue、ROI、compliance、iteration recommendation 与已生成的下一轮 ChangeSet draft 关注项显示到 `/space`。发布验证、发布后信号或下一轮 draft 缺失时显示明确空态,不虚构验证、反馈、收入、ROI、合规、建议或 ChangeSet draft 结果。项目主动作进入 `/pmflow-os?projectId=<id>`,待确认项进入 `/change-review?projectId=<id>&changeSetId=<id>`,发布关注和发布后反馈关注项进入 `/release-growth?projectId=<id>`,下一轮 draft 关注项进入 `/change-review?projectId=<id>&changeSetId=<id>`,执行中项进入 `/execution-bridge?projectId=<id>`;暂存想法后端只读聚合尚未接入时只显示 empty/warning,不使用 mock 数据冒充真实内容;移动端为空间首页独立 App 布局,不直接压缩桌面卡片,并保留最近项目区块;`frontend/src/pages/PersonalSpacePage.tsx` 是薄入口。该域只表达个人空间和主流程导航,不做团队空间强切换、企业组织管理、Project Memory 写入、ChangeSet 状态写入、ProgressEvent 写入、ReleaseValidationRun 写入、FeedbackIngest 写入或发布执行。 - `frontend/src/features/personal-space/PersonalSpacePage.tsx` 是空间首页视图层,`frontend/src/api/space.ts` 是空间工作面只读聚合 API:从 `projects` summary、PMFlow OS `truth-state` 与 `execution-summary` 生成继续推进、待确认、发布关注、发布后反馈关注、下一轮迭代草稿关注、执行中 / AI 正在推进、暂存想法、草案箱、最近项目摘要;Space P3 的 `releaseAttention` 优先消费 `truth-state.releases` 中由 release validation run 投影出的 release 状态与检查项,把“发布验证阻塞 / 发布验证待确认”显示到 `/space`,没有发布验证投影时才回落到 `execution-summary.releaseGate` 的发布候选或发布门禁阻塞;Space P4/P5 的 `feedbackAttention` 只读消费 `execution-summary.releaseSignals`,把发布后 feedback、revenue、ROI、compliance、iteration recommendation 与已生成的下一轮 ChangeSet draft 关注项显示到 `/space`。发布验证、发布后信号或下一轮 draft 缺失时显示明确空态,不虚构验证、反馈、收入、ROI、合规、建议或 ChangeSet draft 结果。项目主动作进入 `/pmflow-os?projectId=<id>`,待确认项进入 `/change-review?projectId=<id>&changeSetId=<id>`,发布关注和发布后反馈关注项进入 `/release-growth?projectId=<id>`,下一轮 draft 关注项进入 `/change-review?projectId=<id>&changeSetId=<id>`,执行中项进入 `/execution-bridge?projectId=<id>`;暂存想法后端只读聚合尚未接入时只显示 empty/warning,不使用 mock 数据冒充真实内容;移动端为空间首页独立 App 布局,不直接压缩桌面卡片,并保留最近项目区块;`frontend/src/pages/PersonalSpacePage.tsx` 是薄入口。该域只表达个人空间和主流程导航,不做团队空间强切换、企业组织管理、Project Memory 写入、ChangeSet 状态写入、ProgressEvent 写入、ReleaseValidationRun 写入、FeedbackIngest 写入或发布执行。
- `frontend/src/features/project-launch/types.ts`、`mockData.ts`、`useProjectLaunchState.ts` 是新项目启动台状态层;默认真实模式通过 `frontend/src/api/pmflowLaunch.ts` 生成草案并确认创建真实项目,API 失败进入明确失败态,不静默 fallback 成 mock 成功;`mockData.ts` 仅在显式 `?demo=1` 或 `?dataSource=demo` 下作为演示状态机使用。`components/LaunchShell.tsx` 负责桌面/移动分流,桌面端复用 `frontend/src/features/personal-space/components/SpaceChrome.tsx` 的空间左侧栏和顶部条,`LaunchCanvases.tsx` 和 `MobileLaunchLayout.tsx` 承接高保真图中的意图输入、AI 工作、人类确认、草案修改、生成失败、创建成功过渡与移动端 bottom sheet 交互。 - `frontend/src/features/project-launch/types.ts`、`mockData.ts`、`useProjectLaunchState.ts` 是新项目启动台状态层;默认真实模式通过 `frontend/src/api/pmflowLaunch.ts` 生成草案并确认创建真实项目,API 失败进入明确失败态,不静默 fallback 成 mock 成功;`mockData.ts` 仅在显式 `?demo=1` 或 `?dataSource=demo` 下作为演示状态机使用。`components/LaunchShell.tsx` 负责桌面/移动分流,桌面端复用 `frontend/src/features/personal-space/components/SpaceChrome.tsx` 的空间左侧栏和顶部条,`LaunchCanvases.tsx` 和 `MobileLaunchLayout.tsx` 承接高保真图中的意图输入、AI 工作、人类确认、草案修改、生成失败、创建成功过渡与移动端 bottom sheet 交互。
- `frontend/src/features/account-settings/AccountSettingsPanel.tsx` 是 `/me` 设置页实现:桌面端为个人资料摘要 + 分区设置面板,移动端为分组列表 + `MobileBottomSheet` 编辑;当前覆盖个人资料、默认语言、AI 回答深度、证据展示偏好、待确认、执行回写、发布门禁和失败提醒通知偏好,并在通知偏好区展示页面内提醒消费说明。`frontend/src/api/accountSettings.ts` 是前端合同层;有 token 时优先调用 `backend/app/api/account_settings_api/`,失败时回落浏览器本机暂存;P2 新增 `shouldShowAccountNotification` 与 `getAccountNotificationConsumption`,将四类通知偏好映射为本地 UI 的待确认、Execution Bridge、Release/Growth 和失败提醒显示/静默状态;P3 新增 `getReleaseGateNotificationPreference(settings, event)`,把 Release/Growth 的 `release_gate_* / release_validation_*` 事件映射到 `releaseGate` 偏好,把 `failed_event` 映射到 `failureAlert` 偏好;P5/P6 的 `getPostReleaseNotificationPreference(settings, event)` 把发布后 `feedback_received / revenue_signal / compliance_blocked` 映射到 `releaseGate` 偏好,把 `iteration_recommendation / next_change_review` 映射到 `pendingConfirmation` 偏好和 `change_review` surface,并返回 `spaceReadable / evidencePolicy / reminderPolicy` 供 Main Flow 与 Space 消费。该合同只控制页面内提醒表达,不代表邮件、桌面推送或系统通知中心已接入;偏好关闭也不得隐藏业务证据、recommendation 或 ChangeSet。`backend/app/schemas/account_settings.py` 定义账号偏好合同,`backend/app/services/account_settings_service.py` 当前复用既有 `integrations` 表按 `tenant_id + user_id` 保存 `account_settings_preferences` 配置包,`backend/app/api/router.py` 已挂载该 API;未改用户表、OAuth/JWT/auth core 或 PMFlow OS truth store。 - `frontend/src/features/account-settings/AccountSettingsPanel.tsx` 是 `/me` 设置页实现:桌面端为个人资料摘要 + 分区设置面板,移动端为分组列表 + `MobileBottomSheet` 编辑;当前覆盖个人资料、默认语言、AI 回答深度、证据展示偏好、待确认、执行回写、发布门禁和失败提醒通知偏好,并在通知偏好区展示页面内提醒消费说明。`frontend/src/api/accountSettings.ts` 是前端合同层;有 token 时优先调用 `backend/app/api/account_settings_api/`,失败时回落浏览器本机暂存;P2 新增 `shouldShowAccountNotification` 与 `getAccountNotificationConsumption`,将四类通知偏好映射为本地 UI 的待确认、Execution Bridge、Release/Growth 和失败提醒显示/静默状态;P3 新增 `getReleaseGateNotificationPreference(settings, event)`,把 Release/Growth 的 `release_gate_* / release_validation_*` 事件映射到 `releaseGate` 偏好,把 `failed_event` 映射到 `failureAlert` 偏好;P5/P6 的 `getPostReleaseNotificationPreference(settings, event)` 把发布后 `feedback_received / revenue_signal / compliance_blocked` 映射到 `releaseGate` 偏好,把 `iteration_recommendation / next_change_review` 映射到 `pendingConfirmation` 偏好和 `change_review` surface,并返回 `spaceReadable / evidencePolicy / reminderPolicy` 供 Main Flow 与 Space 消费。该合同只控制页面内提醒表达,不代表邮件、桌面推送或系统通知中心已接入;偏好关闭也不得隐藏业务证据、recommendation 或 ChangeSet。`backend/app/schemas/account_settings.py` 定义账号偏好合同,`backend/app/services/account_settings_service.py` 当前复用既有 `integrations` 表按 `tenant_id + user_id` 保存 `account_settings_preferences` 配置包,`backend/app/api/router.py` 已挂载该 API;未改用户表、OAuth/JWT/auth core 或 PMFlow OS truth store。
- `frontend/src/features/pmflow-os/store.tsx` 当前是 PMFlow OS 共享 truth store:`?projectId=<id>` 时通过 React Query 调用 `frontend/src/api/pmflowOs.ts` 读取真实 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,真实模式下项目内 message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest、单条 iteration recommendation -> ChangeSet draft 与同组 iteration recommendation 批量 -> ChangeSet draft 均调用 `/pmflow-os` 后端并 refetch;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功。 - `frontend/src/features/pmflow-os/store.tsx` 当前是 PMFlow OS 共享 truth store:`?projectId=<id>` 时通过 React Query 调用 `frontend/src/api/pmflowOs.ts` 读取真实 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,真实模式下项目内 message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、review decision、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest、单条 iteration recommendation -> ChangeSet draft 与同组 iteration recommendation 批量 -> ChangeSet draft 均调用 `/pmflow-os` 后端并 refetch;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功。
- `frontend/src/features/pmflow-os/store.tsx` 仍保留演示用项目内 AI 结构化闭环状态机:`command_idle / ai_extracting / extraction_ready / review_open / writeback_success`。`frontend/src/features/pmflow-os/pages/StructuredExtractionPage.tsx` 是本轮结构化提取页,`ChangeReviewPage.tsx` 是决策与待确认中心,`MemoryWritebackResultPage.tsx` 是项目记忆写入结果页,`WorkGraphPage.tsx` 是项目工作台关系总览与对象投影视图,`ExecutionBridgePage.tsx` 是执行桥上下文包、交接按钮、运行端领取确认、PR/测试与回写状态页;`ReleaseGrowthPage.tsx` 真实模式读取 `execution-summary.releaseGate`、`latestValidationRun` 与 `releaseSignals`,从 branch、commit、PR、test、failed evidence 展示 `可发布 / 阻塞 / 需要人工确认 / 缺少证据` 判断、阻塞原因、下一步动作、发布验证记录和发布后 feedback/revenue/recommendation 信号,缺少发布后信号时显示 empty/warning,不执行发布命令、不展示 mock 发布结果;页面可展示 recommendation quality grouping,把真实 recommendation 单条或同组批量生成下一轮待确认 ChangeSet draft,生成后仍进入 Change Review accept/edit/defer,不自动 commit、不写 Project Memory、不创建正式任务;`ChangeReviewPage.tsx` 会展示 `iteration_recommendation_group` evidence 的来源组、feedback 数、recommendation 数、风险、影响和审查优先级;同时读取 Account notification preference helper,用 `releaseGate` 偏好控制发布门禁强调提醒,用 `failureAlert` 偏好控制失败强调提醒,偏好关闭时只静默提醒表达强度,不隐藏 release evidence、阻塞原因、审计状态、validation run 或 releaseSignals;静态发布/增长原型只保留在显式 demo 模式。A3 已接入后端 truth-state,C1 已接入真实 message/extraction/review 状态机,C2 已接入真实 ChangeSet commit,Phase D 已接入真实 execution-summary/dev-sync,D.2 已接入 `POST /execution-handoffs` 创建 PromptPackage + handoff_created progress event,P1 已接入 `POST /execution-handoffs/{prompt_package_id}/consume` 创建 handoff_consumed progress event 并把 execution status 投影为 coding,Release Growth P1 已接入 execution evidence gate,Release Validation P1 已接入 `POST /release-validation-runs` 创建 `ReleaseValidationRun` 快照,Release Feedback P1 已接入 `POST /release-feedback-ingests` 写入 `FeedbackIngest` 并生成 `IterationRecommendation`,Iteration Loop P1 已接入 `POST /iteration-recommendations/{recommendation_id}/change-set-drafts` 把 recommendation 回流到 Change Review,Iteration Loop P2 已接入 `POST /iteration-recommendation-groups/{group_key}/change-set-drafts` 并在 `releaseSignals.recommendationGroups` 暴露分组/去重/风险/影响合同;当前仍不调用真实 AI 提取,不把 mock 当真实失败 fallback。`ProjectMemoryPage.tsx` 在真实模式下展示后端 `truth_nodes + semantic_facts` 的 `projectMemory` 投影,accepted/edited ChangeItem 只有 commit 成功后才进入 Project Memory。`frontend/src/features/pmflow-os/components/ProjectStructuredLoop.tsx` 提供 `StatusLabel / EvidenceSnippet / ObjectChangeCard / ProjectConversationPanel / StructuredExtractionPanel / ProjectAICopilotPanel` 等闭环组件,并复用契约化 `InlineNotice` 表达“未确认不得写入”的局部提示。 - `frontend/src/features/pmflow-os/store.tsx` 仍保留演示用项目内 AI 结构化闭环状态机:`command_idle / ai_extracting / extraction_ready / review_open / writeback_success`。`frontend/src/features/pmflow-os/pages/StructuredExtractionPage.tsx` 是本轮结构化提取页,`ChangeReviewPage.tsx` 是决策与待确认中心,`MemoryWritebackResultPage.tsx` 是项目记忆写入结果页,`WorkGraphPage.tsx` 是项目工作台关系总览与对象投影视图,`ExecutionBridgePage.tsx` 是执行桥上下文包、交接按钮、运行端领取确认、PR/测试与回写状态页;`ReleaseGrowthPage.tsx` 真实模式读取 `execution-summary.releaseGate`、`latestValidationRun` 与 `releaseSignals`,从 branch、commit、PR、test、failed evidence 展示 `可发布 / 阻塞 / 需要人工确认 / 缺少证据` 判断、阻塞原因、下一步动作、发布验证记录和发布后 feedback/revenue/recommendation 信号,缺少发布后信号时显示 empty/warning,不执行发布命令、不展示 mock 发布结果;页面可展示 recommendation quality grouping,把真实 recommendation 单条或同组批量生成下一轮待确认 ChangeSet draft,生成后仍进入 Change Review accept/edit/defer,不自动 commit、不写 Project Memory、不创建正式任务;`ChangeReviewPage.tsx` 会展示 `iteration_recommendation_group` evidence 的来源组、feedback 数、recommendation 数、风险、影响和审查优先级;同时读取 Account notification preference helper,用 `releaseGate` 偏好控制发布门禁强调提醒,用 `failureAlert` 偏好控制失败强调提醒,偏好关闭时只静默提醒表达强度,不隐藏 release evidence、阻塞原因、审计状态、validation run 或 releaseSignals;静态发布/增长原型只保留在显式 demo 模式。A3 已接入后端 truth-state,C1 已接入真实 message/extraction/review 状态机,C2 已接入真实 ChangeSet commit,Phase D 已接入真实 execution-summary/dev-sync,D.2 已接入 `POST /execution-handoffs` 创建 PromptPackage + handoff_created progress event,P1 已接入 `POST /execution-handoffs/{prompt_package_id}/consume` 创建 handoff_consumed progress event 并把 execution status 投影为 coding,Release Growth P1 已接入 execution evidence gate,Release Validation P1 已接入 `POST /release-validation-runs` 创建 `ReleaseValidationRun` 快照,Release Feedback P1 已接入 `POST /release-feedback-ingests` 写入 `FeedbackIngest` 并生成 `IterationRecommendation`,Iteration Loop P1 已接入 `POST /iteration-recommendations/{recommendation_id}/change-set-drafts` 把 recommendation 回流到 Change Review,Iteration Loop P2 已接入 `POST /iteration-recommendation-groups/{group_key}/change-set-drafts` 并在 `releaseSignals.recommendationGroups` 暴露分组/去重/风险/影响合同;当前仍不调用真实 AI 提取,不把 mock 当真实失败 fallback。`ProjectMemoryPage.tsx` 在真实模式下展示后端 `truth_nodes + semantic_facts` 的 `projectMemory` 投影,accepted/edited ChangeItem 只有 commit 成功后才进入 Project Memory。`frontend/src/features/pmflow-os/components/ProjectStructuredLoop.tsx` 提供 `StatusLabel / EvidenceSnippet / ObjectChangeCard / ProjectConversationPanel / StructuredExtractionPanel / ProjectAICopilotPanel` 等闭环组件,并复用契约化 `InlineNotice` 表达“未确认不得写入”的局部提示。
- PMFlow OS 后端入口位于 `backend/app/api/pmflow_os_api/`,projection service 位于 `backend/app/services/pmflow_os_projection_service.py`,Change Review / commit 服务位于 `backend/app/services/pmflow_change_review_service.py`,Pydantic 合同位于 `backend/app/schemas/pmflow_os.py`;当前聚合 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync`,`Thread / Message` 来自 `pmflow_threads / pmflow_thread_messages`,`ChangeSet / ChangeItem / Approval / EvidenceLink` 来自 `backend/app/models/pmflow_change_review.py` 对应真实表。C1 已实现 message、`proposed_items` extraction、ChangeSet detail、ChangeItem `accept/edit/defer`;C2 backend/frontend 已实现 `POST /pmflow-os/change-sets/{change_set_id}/commit` 与真实模式前端接入,默认 atomic,将 `Requirement / Decision / Risk` 写入 `truth_nodes`,将 `Task` 写入 `truth_nodes(node_type='task') + tasks`,高风险项强制 approval gate;D.2 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs`,校验 task/repo project 边界,生成/复用 `PromptPackage(package_kind='execution_handoff')` 并写 `ProgressEvent(event_type='handoff_created')` 起点;P1 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs/{prompt_package_id}/consume`,校验 execution handoff package 后更新 `PromptPackage.package_payload.latest_handoff.handoff_state=consumed` 并写 `ProgressEvent(event_type='handoff_consumed', derived_task_status='coding')`;Release Growth P1 在 `ExecutionSummaryProjection.releaseGate` 中从 `ProgressEvent` 推导发布门禁状态,失败事件 fail-closed 阻塞发布,无证据返回 empty/warning;Release Validation P1 新增 `POST /pmflow-os/projects/{project_id}/release-validation-runs`,复用现有 `ReleaseValidationRun` 模型固化当前 `releaseGate` 快照、人工决策、证据、阻塞原因和下一步动作,并在 `releaseGate.latestValidationRun` 返回最新记录;Release Feedback P1 新增 `POST /pmflow-os/projects/{project_id}/release-feedback-ingests`,复用现有 `FeedbackIngest / IterationRecommendation`,在 `ExecutionSummaryProjection.releaseSignals` 中返回真实发布后 feedback、revenue event 和 iteration recommendation 读模型;Iteration Loop P1 新增 `POST /pmflow-os/projects/{project_id}/iteration-recommendations/{recommendation_id}/change-set-drafts`,把真实 recommendation 转为 open ChangeSet、pending_review Requirement/Task/Risk/Decision items 与 `iteration_recommendation` evidence,重复调用复用既有 draft 并在 `releaseSignals` 标记 `changeSetCreated/changeSetId`;Iteration Loop P2 新增 `POST /pmflow-os/projects/{project_id}/iteration-recommendation-groups/{group_key}/change-set-drafts`,把同组 recommendation 批量转成一个 open ChangeSet draft 与 `iteration_recommendation_group` evidence,并在 `releaseSignals.recommendations / recommendationGroups` 投影 `groupKey / duplicateOf / mergedCount / impactLevel / riskLevel / suggestedChangeTypes / reviewPriority`;这些步骤都不直接执行本机命令、真实发布命令、外部支付、真实推送、自动 Project Memory commit 或正式任务创建;`ModelDecision / MetricSignal` 在 C2 V1 仍 fail-closed。 - PMFlow OS 后端入口位于 `backend/app/api/pmflow_os_api/`,projection service 位于 `backend/app/services/pmflow_os_projection_service.py`,Change Review / commit 服务位于 `backend/app/services/pmflow_change_review_service.py`,Pydantic 合同位于 `backend/app/schemas/pmflow_os.py`;当前聚合 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync`,`Thread / Message` 来自 `pmflow_threads / pmflow_thread_messages`,`ChangeSet / ChangeItem / Approval / EvidenceLink` 来自 `backend/app/models/pmflow_change_review.py` 对应真实表。C1 已实现 message、`proposed_items` extraction、ChangeSet detail、ChangeItem `accept/edit/defer`;C2 backend/frontend 已实现 `POST /pmflow-os/change-sets/{change_set_id}/commit` 与真实模式前端接入,默认 atomic,将 `Requirement / Decision / Risk` 写入 `truth_nodes`,将 `Task` 写入 `truth_nodes(node_type='task') + tasks`,高风险项强制 approval gate;D.2 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs`,校验 task/repo project 边界,生成/复用 `PromptPackage(package_kind='execution_handoff')` 并写 `ProgressEvent(event_type='handoff_created')` 起点;P1 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs/{prompt_package_id}/consume`,校验 execution handoff package 后更新 `PromptPackage.package_payload.latest_handoff.handoff_state=consumed` 并写 `ProgressEvent(event_type='handoff_consumed', derived_task_status='coding')`;Release Growth P1 在 `ExecutionSummaryProjection.releaseGate` 中从 `ProgressEvent` 推导发布门禁状态,失败事件 fail-closed 阻塞发布,无证据返回 empty/warning;Release Validation P1 新增 `POST /pmflow-os/projects/{project_id}/release-validation-runs`,复用现有 `ReleaseValidationRun` 模型固化当前 `releaseGate` 快照、人工决策、证据、阻塞原因和下一步动作,并在 `releaseGate.latestValidationRun` 返回最新记录;Release Feedback P1 新增 `POST /pmflow-os/projects/{project_id}/release-feedback-ingests`,复用现有 `FeedbackIngest / IterationRecommendation`,在 `ExecutionSummaryProjection.releaseSignals` 中返回真实发布后 feedback、revenue event 和 iteration recommendation 读模型;Iteration Loop P1 新增 `POST /pmflow-os/projects/{project_id}/iteration-recommendations/{recommendation_id}/change-set-drafts`,把真实 recommendation 转为 open ChangeSet、pending_review Requirement/Task/Risk/Decision items 与 `iteration_recommendation` evidence,重复调用复用既有 draft 并在 `releaseSignals` 标记 `changeSetCreated/changeSetId`;Iteration Loop P2 新增 `POST /pmflow-os/projects/{project_id}/iteration-recommendation-groups/{group_key}/change-set-drafts`,把同组 recommendation 批量转成一个 open ChangeSet draft 与 `iteration_recommendation_group` evidence,并在 `releaseSignals.recommendations / recommendationGroups` 投影 `groupKey / duplicateOf / mergedCount / impactLevel / riskLevel / suggestedChangeTypes / reviewPriority`;这些步骤都不直接执行本机命令、真实发布命令、外部支付、真实推送、自动 Project Memory commit 或正式任务创建;`ModelDecision / MetricSignal` 在 C2 V1 仍 fail-closed。
- `frontend/src/features/account-settings/AccountSettingsPanel.tsx` 是 `/me` 设置页实现:桌面端为个人资料摘要 + 分区设置面板,移动端为分组列表 + `MobileBottomSheet` 编辑;当前覆盖个人资料、默认语言、AI 回答深度、证据展示偏好、待确认、执行回写、发布门禁和失败提醒通知偏好,并在通知偏好区展示页面内提醒消费说明、提醒强度和证据展示配置。`frontend/src/api/accountSettings.ts` 是前端合同层;有 token 时优先调用 `backend/app/api/account_settings_api/`,失败时回落浏览器本机暂存;P2 新增 `shouldShowAccountNotification` 与 `getAccountNotificationConsumption`,将四类通知偏好映射为本地 UI 的待确认、Execution Bridge、Release/Growth 和失败提醒显示/静默状态;P3 新增 `getReleaseGateNotificationPreference(settings, event)`,把 Release/Growth 的 `release_gate_* / release_validation_*` 事件映射到 `releaseGate` 偏好,把 `failed_event` 映射到 `failureAlert` 偏好;P5/P6 的 `getPostReleaseNotificationPreference(settings, event)` 把发布后 `feedback_received / revenue_signal / compliance_blocked` 映射到 `releaseGate` 偏好,把 `iteration_recommendation / next_change_review` 映射到 `pendingConfirmation` 偏好和 `change_review` surface,并返回 `spaceReadable / evidencePolicy / reminderPolicy` 供 Main Flow 与 Space 消费;P7 将通知偏好扩展为 `reminderIntensity=silent|normal|prominent` 与 `evidenceDisplay=compact|expanded`,并新增 `getAccountReminderConsumption(settings, event)` 覆盖 `change_review_high_risk / iteration_draft_ready / release_blocked / execution_stalled`,输出 `notificationTransport=in_app_only / evidencePolicy=preserve_business_evidence / auditPolicy=preserve_audit_state`。该合同只控制页面内提醒表达,不代表邮件、桌面推送或系统通知中心已接入;偏好关闭也不得隐藏业务证据、recommendation、ChangeSet、执行状态或审计状态。`backend/app/schemas/account_settings.py` 定义账号偏好合同,`backend/app/services/account_settings_service.py` 当前复用既有 `integrations` 表按 `tenant_id + user_id` 保存 `account_settings_preferences` 配置包,`backend/app/api/router.py` 已挂载该 API;未改用户表、OAuth/JWT/auth core 或 PMFlow OS truth store。 - `frontend/src/features/account-settings/AccountSettingsPanel.tsx` 是 `/me` 设置页实现:桌面端为个人资料摘要 + 分区设置面板,移动端为分组列表 + `MobileBottomSheet` 编辑;当前覆盖个人资料、默认语言、AI 回答深度、证据展示偏好、待确认、执行回写、发布门禁和失败提醒通知偏好,并在通知偏好区展示页面内提醒消费说明、提醒强度和证据展示配置。`frontend/src/api/accountSettings.ts` 是前端合同层;有 token 时优先调用 `backend/app/api/account_settings_api/`,失败时回落浏览器本机暂存;P2 新增 `shouldShowAccountNotification` 与 `getAccountNotificationConsumption`,将四类通知偏好映射为本地 UI 的待确认、Execution Bridge、Release/Growth 和失败提醒显示/静默状态;P3 新增 `getReleaseGateNotificationPreference(settings, event)`,把 Release/Growth 的 `release_gate_* / release_validation_*` 事件映射到 `releaseGate` 偏好,把 `failed_event` 映射到 `failureAlert` 偏好;P5/P6 的 `getPostReleaseNotificationPreference(settings, event)` 把发布后 `feedback_received / revenue_signal / compliance_blocked` 映射到 `releaseGate` 偏好,把 `iteration_recommendation / next_change_review` 映射到 `pendingConfirmation` 偏好和 `change_review` surface,并返回 `spaceReadable / evidencePolicy / reminderPolicy` 供 Main Flow 与 Space 消费;P7 将通知偏好扩展为 `reminderIntensity=silent|normal|prominent` 与 `evidenceDisplay=compact|expanded`,并新增 `getAccountReminderConsumption(settings, event)` 覆盖 `change_review_high_risk / iteration_draft_ready / release_blocked / execution_stalled`,输出 `notificationTransport=in_app_only / evidencePolicy=preserve_business_evidence / auditPolicy=preserve_audit_state`。该合同只控制页面内提醒表达,不代表邮件、桌面推送或系统通知中心已接入;偏好关闭也不得隐藏业务证据、recommendation、ChangeSet、执行状态或审计状态。`backend/app/schemas/account_settings.py` 定义账号偏好合同,`backend/app/services/account_settings_service.py` 当前复用既有 `integrations` 表按 `tenant_id + user_id` 保存 `account_settings_preferences` 配置包,`backend/app/api/router.py` 已挂载该 API;未改用户表、OAuth/JWT/auth core 或 PMFlow OS truth store。
- `frontend/src/features/pmflow-os/store.tsx` 当前是 PMFlow OS 共享 truth store:`?projectId=<id>` 时通过 React Query 调用 `frontend/src/api/pmflowOs.ts` 读取真实 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,真实模式下项目内 message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest 与 iteration recommendation -> ChangeSet draft 均调用 `/pmflow-os` 后端并 refetch;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功。 - `frontend/src/features/pmflow-os/store.tsx` 当前是 PMFlow OS 共享 truth store:`?projectId=<id>` 时通过 React Query 调用 `frontend/src/api/pmflowOs.ts` 读取真实 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,真实模式下项目内 message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、review decision、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest 与 iteration recommendation -> ChangeSet draft 均调用 `/pmflow-os` 后端并 refetch;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功。
- `frontend/src/features/pmflow-os/store.tsx` 仍保留演示用项目内 AI 结构化闭环状态机:`command_idle / ai_extracting / extraction_ready / review_open / writeback_success`。`frontend/src/features/pmflow-os/pages/StructuredExtractionPage.tsx` 是本轮结构化提取页,`ChangeReviewPage.tsx` 是决策与待确认中心,`MemoryWritebackResultPage.tsx` 是项目记忆写入结果页,`WorkGraphPage.tsx` 是项目工作台关系总览与对象投影视图,`ExecutionBridgePage.tsx` 是执行桥上下文包、交接按钮、运行端领取确认、PR/测试与回写状态页;`ReleaseGrowthPage.tsx` 真实模式读取 `execution-summary.releaseGate`、`latestValidationRun` 与 `releaseSignals`,从 branch、commit、PR、test、failed evidence 展示 `可发布 / 阻塞 / 需要人工确认 / 缺少证据` 判断、阻塞原因、下一步动作、发布验证记录和发布后 feedback/revenue/recommendation 信号,缺少发布后信号时显示 empty/warning,不执行发布命令、不展示 mock 发布结果;页面可把真实 recommendation 生成下一轮待确认 ChangeSet draft,生成后仍进入 Change Review accept/edit/defer,不自动 commit、不写 Project Memory、不创建正式任务;同时读取 Account notification preference helper,用 `releaseGate` 偏好控制发布门禁强调提醒,用 `failureAlert` 偏好控制失败强调提醒,偏好关闭时只静默提醒表达强度,不隐藏 release evidence、阻塞原因、审计状态、validation run 或 releaseSignals;静态发布/增长原型只保留在显式 demo 模式。A3 已接入后端 truth-state,C1 已接入真实 message/extraction/review 状态机,C2 已接入真实 ChangeSet commit,Phase D 已接入真实 execution-summary/dev-sync,D.2 已接入 `POST /execution-handoffs` 创建 PromptPackage + handoff_created progress event,P1 已接入 `POST /execution-handoffs/{prompt_package_id}/consume` 创建 handoff_consumed progress event 并把 execution status 投影为 coding,Release Growth P1 已接入 execution evidence gate,Release Validation P1 已接入 `POST /release-validation-runs` 创建 `ReleaseValidationRun` 快照,Release Feedback P1 已接入 `POST /release-feedback-ingests` 写入 `FeedbackIngest` 并生成 `IterationRecommendation`,Iteration Loop P1 已接入 `POST /iteration-recommendations/{recommendation_id}/change-set-drafts` 把 recommendation 回流到 Change Review;当前仍不调用真实 AI 提取,不把 mock 当真实失败 fallback。`ProjectMemoryPage.tsx` 在真实模式下展示后端 `truth_nodes + semantic_facts` 的 `projectMemory` 投影,accepted/edited ChangeItem 只有 commit 成功后才进入 Project Memory。`frontend/src/features/pmflow-os/components/ProjectStructuredLoop.tsx` 提供 `StatusLabel / EvidenceSnippet / ObjectChangeCard / ProjectConversationPanel / StructuredExtractionPanel / ProjectAICopilotPanel` 等闭环组件,并复用契约化 `InlineNotice` 表达“未确认不得写入”的局部提示。 - `frontend/src/features/pmflow-os/store.tsx` 仍保留演示用项目内 AI 结构化闭环状态机:`command_idle / ai_extracting / extraction_ready / review_open / writeback_success`。`frontend/src/features/pmflow-os/pages/StructuredExtractionPage.tsx` 是本轮结构化提取页,`ChangeReviewPage.tsx` 是决策与待确认中心,`MemoryWritebackResultPage.tsx` 是项目记忆写入结果页,`WorkGraphPage.tsx` 是项目工作台关系总览与对象投影视图,`ExecutionBridgePage.tsx` 是执行桥上下文包、交接按钮、运行端领取确认、PR/测试与回写状态页;`ReleaseGrowthPage.tsx` 真实模式读取 `execution-summary.releaseGate`、`latestValidationRun` 与 `releaseSignals`,从 branch、commit、PR、test、failed evidence 展示 `可发布 / 阻塞 / 需要人工确认 / 缺少证据` 判断、阻塞原因、下一步动作、发布验证记录和发布后 feedback/revenue/recommendation 信号,缺少发布后信号时显示 empty/warning,不执行发布命令、不展示 mock 发布结果;页面可把真实 recommendation 生成下一轮待确认 ChangeSet draft,生成后仍进入 Change Review accept/edit/defer,不自动 commit、不写 Project Memory、不创建正式任务;同时读取 Account notification preference helper,用 `releaseGate` 偏好控制发布门禁强调提醒,用 `failureAlert` 偏好控制失败强调提醒,偏好关闭时只静默提醒表达强度,不隐藏 release evidence、阻塞原因、审计状态、validation run 或 releaseSignals;静态发布/增长原型只保留在显式 demo 模式。A3 已接入后端 truth-state,C1 已接入真实 message/extraction/review 状态机,C2 已接入真实 ChangeSet commit,Phase D 已接入真实 execution-summary/dev-sync,D.2 已接入 `POST /execution-handoffs` 创建 PromptPackage + handoff_created progress event,P1 已接入 `POST /execution-handoffs/{prompt_package_id}/consume` 创建 handoff_consumed progress event 并把 execution status 投影为 coding,Release Growth P1 已接入 execution evidence gate,Release Validation P1 已接入 `POST /release-validation-runs` 创建 `ReleaseValidationRun` 快照,Release Feedback P1 已接入 `POST /release-feedback-ingests` 写入 `FeedbackIngest` 并生成 `IterationRecommendation`,Iteration Loop P1 已接入 `POST /iteration-recommendations/{recommendation_id}/change-set-drafts` 把 recommendation 回流到 Change Review;当前仍不调用真实 AI 提取,不把 mock 当真实失败 fallback。`ProjectMemoryPage.tsx` 在真实模式下展示后端 `truth_nodes + semantic_facts` 的 `projectMemory` 投影,accepted/edited ChangeItem 只有 commit 成功后才进入 Project Memory。`frontend/src/features/pmflow-os/components/ProjectStructuredLoop.tsx` 提供 `StatusLabel / EvidenceSnippet / ObjectChangeCard / ProjectConversationPanel / StructuredExtractionPanel / ProjectAICopilotPanel` 等闭环组件,并复用契约化 `InlineNotice` 表达“未确认不得写入”的局部提示。
- PMFlow OS 后端入口位于 `backend/app/api/pmflow_os_api/`,projection service 位于 `backend/app/services/pmflow_os_projection_service.py`,Change Review / commit 服务位于 `backend/app/services/pmflow_change_review_service.py`,Pydantic 合同位于 `backend/app/schemas/pmflow_os.py`;当前聚合 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync`,`Thread / Message` 来自 `pmflow_threads / pmflow_thread_messages`,`ChangeSet / ChangeItem / Approval / EvidenceLink` 来自 `backend/app/models/pmflow_change_review.py` 对应真实表。`ProjectTruthState.reviewerRouting` 是 Team layer 的 reviewer routing 读模型:从 open/pending ChangeSet、待审 item 风险、跨对象类型、`iteration_recommendation` evidence、项目成员与 `OWNER / REVIEWER` 角色推导 `{status, warningCode, summary, items}`;item 内含 `sourceType / riskLevel / priority / suggestedReviewer / fallbackReviewerRole / targetUrl`;缺成员、缺角色或缺证据时 fail-closed 给 warning,不虚构 reviewer,不写 approval,不自动 assign。C1 已实现 message、`proposed_items` extraction、ChangeSet detail、ChangeItem `accept/edit/defer`;C2 backend/frontend 已实现 `POST /pmflow-os/change-sets/{change_set_id}/commit` 与真实模式前端接入,默认 atomic,将 `Requirement / Decision / Risk` 写入 `truth_nodes`,将 `Task` 写入 `truth_nodes(node_type='task') + tasks`,高风险项强制 approval gate;D.2 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs`,校验 task/repo project 边界,生成/复用 `PromptPackage(package_kind='execution_handoff')` 并写 `ProgressEvent(event_type='handoff_created')` 起点;P1 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs/{prompt_package_id}/consume`,校验 execution handoff package 后更新 `PromptPackage.package_payload.latest_handoff.handoff_state=consumed` 并写 `ProgressEvent(event_type='handoff_consumed', derived_task_status='coding')`;Release Growth P1 在 `ExecutionSummaryProjection.releaseGate` 中从 `ProgressEvent` 推导发布门禁状态,失败事件 fail-closed 阻塞发布,无证据返回 empty/warning;Release Validation P1 新增 `POST /pmflow-os/projects/{project_id}/release-validation-runs`,复用现有 `ReleaseValidationRun` 模型固化当前 `releaseGate` 快照、人工决策、证据、阻塞原因和下一步动作,并在 `releaseGate.latestValidationRun` 返回最新记录;Release Feedback P1 新增 `POST /pmflow-os/projects/{project_id}/release-feedback-ingests`,复用现有 `FeedbackIngest / IterationRecommendation`,在 `ExecutionSummaryProjection.releaseSignals` 中返回真实发布后 feedback、revenue event 和 iteration recommendation 读模型;Iteration Loop P1 新增 `POST /pmflow-os/projects/{project_id}/iteration-recommendations/{recommendation_id}/change-set-drafts`,把真实 recommendation 转为 open ChangeSet、pending_review Requirement/Task/Risk/Decision items 与 `iteration_recommendation` evidence,重复调用复用既有 draft 并在 `releaseSignals` 标记 `changeSetCreated/changeSetId`;这些步骤都不直接执行本机命令、真实发布命令、外部支付、真实推送、自动 Project Memory commit 或正式任务创建;`ModelDecision / MetricSignal` 在 C2 V1 仍 fail-closed。 - PMFlow OS 后端入口位于 `backend/app/api/pmflow_os_api/`,projection service 位于 `backend/app/services/pmflow_os_projection_service.py`,Change Review / commit / review decision 服务位于 `backend/app/services/pmflow_change_review_service.py`,Pydantic 合同位于 `backend/app/schemas/pmflow_os.py`;当前聚合 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync`,`Thread / Message` 来自 `pmflow_threads / pmflow_thread_messages`,`ChangeSet / ChangeItem / Approval / EvidenceLink` 来自 `backend/app/models/pmflow_change_review.py` 对应真实表。`ProjectTruthState.reviewerRouting` 是 Team layer 的 reviewer routing + decision 读模型:从 open/pending ChangeSet、待审 item 风险、跨对象类型、`iteration_recommendation` evidence、项目成员与 `OWNER / REVIEWER` 角色推导 `{status, warningCode, summary, items}`;item 内含 `sourceType / riskLevel / priority / suggestedReviewer / fallbackReviewerRole / targetUrl / reviewDecision / approvalState / requestedAt / decidedAt / decisionReason`;缺成员、缺角色或缺证据时 fail-closed 给 warning,不虚构 reviewer。P8 新增 `POST /pmflow-os/change-sets/{change_set_id}/review-decisions`,支持 `request_review / approve / request_changes / reject`,`approve / request_changes / reject` 写 `PMFlowApproval` 审计,high-risk approve 后才满足 C2 approval gate,所有 decision 都不自动 commit。C1 已实现 message、`proposed_items` extraction、ChangeSet detail、ChangeItem `accept/edit/defer`;C2 backend/frontend 已实现 `POST /pmflow-os/change-sets/{change_set_id}/commit` 与真实模式前端接入,默认 atomic,将 `Requirement / Decision / Risk` 写入 `truth_nodes`,将 `Task` 写入 `truth_nodes(node_type='task') + tasks`,高风险项强制 approval gate;D.2 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs`,校验 task/repo project 边界,生成/复用 `PromptPackage(package_kind='execution_handoff')` 并写 `ProgressEvent(event_type='handoff_created')` 起点;P1 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs/{prompt_package_id}/consume`,校验 execution handoff package 后更新 `PromptPackage.package_payload.latest_handoff.handoff_state=consumed` 并写 `ProgressEvent(event_type='handoff_consumed', derived_task_status='coding')`;Release Growth P1 在 `ExecutionSummaryProjection.releaseGate` 中从 `ProgressEvent` 推导发布门禁状态,失败事件 fail-closed 阻塞发布,无证据返回 empty/warning;Release Validation P1 新增 `POST /pmflow-os/projects/{project_id}/release-validation-runs`,复用现有 `ReleaseValidationRun` 模型固化当前 `releaseGate` 快照、人工决策、证据、阻塞原因和下一步动作,并在 `releaseGate.latestValidationRun` 返回最新记录;Release Feedback P1 新增 `POST /pmflow-os/projects/{project_id}/release-feedback-ingests`,复用现有 `FeedbackIngest / IterationRecommendation`,在 `ExecutionSummaryProjection.releaseSignals` 中返回真实发布后 feedback、revenue event 和 iteration recommendation 读模型;Iteration Loop P1 新增 `POST /pmflow-os/projects/{project_id}/iteration-recommendations/{recommendation_id}/change-set-drafts`,把真实 recommendation 转为 open ChangeSet、pending_review Requirement/Task/Risk/Decision items 与 `iteration_recommendation` evidence,重复调用复用既有 draft 并在 `releaseSignals` 标记 `changeSetCreated/changeSetId`;这些步骤都不直接执行本机命令、真实发布命令、外部支付、真实推送、自动 Project Memory commit 或正式任务创建;`ModelDecision / MetricSignal` 在 C2 V1 仍 fail-closed。
- PMFlow Launch 后端 Phase B 创建链位于 `backend/app/api/pmflow_launch_api/`、`backend/app/services/pmflow_launch_service.py`、`backend/app/schemas/pmflow_launch.py` 与 `backend/app/models/pmflow_launch.py`;`POST /pmflow-launch/drafts` 只写 `pmflow_launch_drafts`,`POST /pmflow-launch/drafts/{draft_id}/confirm` 在同一事务内写 `projects / user_projects / pmflow_threads / pmflow_thread_messages / truth_nodes / tasks / semantic_facts seed / operation_logs`,并把 `launch_draft_id / thread_id / thread_message_id` 写入正式对象 metadata。 - PMFlow Launch 后端 Phase B 创建链位于 `backend/app/api/pmflow_launch_api/`、`backend/app/services/pmflow_launch_service.py`、`backend/app/schemas/pmflow_launch.py` 与 `backend/app/models/pmflow_launch.py`;`POST /pmflow-launch/drafts` 只写 `pmflow_launch_drafts`,`POST /pmflow-launch/drafts/{draft_id}/confirm` 在同一事务内写 `projects / user_projects / pmflow_threads / pmflow_thread_messages / truth_nodes / tasks / semantic_facts seed / operation_logs`,并把 `launch_draft_id / thread_id / thread_message_id` 写入正式对象 metadata。
- `frontend/src/features/pmflow-os/components/ChangeReviewGroup.tsx`、`WorkbenchPanel.tsx`、`ExecutionBridgeCards.tsx`、`PMFlowOsFrame.tsx`、`AccountControls.tsx`、`FeedbackPrimitives.tsx` 是高保真原型新增的行为组件层:分别承载变更 diff 审查块、工作台对象卡组、执行桥上下文/日志卡片、全局上下文条与 AI 副驾、账号菜单/退出登录确认交互,以及 `PageBanner / InlineNotice / HumanDecisionBar / ConfirmDialog / MobileBottomSheet` 这组提示、提醒、确认与移动端 bottom sheet 基础组件,避免再把这些业务片段内联塞回 `pages/`。 - `frontend/src/features/pmflow-os/components/ChangeReviewGroup.tsx`、`WorkbenchPanel.tsx`、`ExecutionBridgeCards.tsx`、`PMFlowOsFrame.tsx`、`AccountControls.tsx`、`FeedbackPrimitives.tsx` 是高保真原型新增的行为组件层:分别承载变更 diff 审查块、工作台对象卡组、执行桥上下文/日志卡片、全局上下文条与 AI 副驾、账号菜单/退出登录确认交互,以及 `PageBanner / InlineNotice / HumanDecisionBar / ConfirmDialog / MobileBottomSheet` 这组提示、提醒、确认与移动端 bottom sheet 基础组件,避免再把这些业务片段内联塞回 `pages/`。
- `frontend/vite.config.ts` 现补了固定 vendor chunk 分组(`framework / state-query / ui-radix / icons / charts / markdown`)与业务 chunk 分组(`ai-steward / sstc / governance`),目标是让登录页与基础导航不再背 AI Steward、SSTC、治理页和图表代码。 - `frontend/vite.config.ts` 现补了固定 vendor chunk 分组(`framework / state-query / ui-radix / icons / charts / markdown`)与业务 chunk 分组(`ai-steward / sstc / governance`),目标是让登录页与基础导航不再背 AI Steward、SSTC、治理页和图表代码。

View File

@ -89,16 +89,16 @@
- SSTC 负责人推荐接口:参考 `POST /projects/{project_id}/tasks/{task_id}/recommend-assignee` - SSTC 负责人推荐接口:参考 `POST /projects/{project_id}/tasks/{task_id}/recommend-assignee`
- SSTC 任务更新接口:参考 `PATCH /projects/{project_id}/tasks/{task_id}` - SSTC 任务更新接口:参考 `PATCH /projects/{project_id}/tasks/{task_id}`
- SSTC 发布创建接口:参考 `POST /projects/{project_id}/releases` - SSTC 发布创建接口:参考 `POST /projects/{project_id}/releases`
- PMFlow OS 真实读路径、C1 Change Review API、C2 backend commit、Execution Handoff、Release Evidence Gate、Release Validation Run、Release Feedback Signal、Iteration Loop draft 与 Team reviewer routing:参考 `backend/app/api/pmflow_os_api/`、`backend/app/services/pmflow_os_projection_service.py`、`backend/app/services/pmflow_change_review_service.py`、`backend/app/schemas/pmflow_os.py`;只读提供 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,复用 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync`,并读取 `pmflow_threads / pmflow_thread_messages / pmflow_change_sets / pmflow_change_items / pmflow_approvals / pmflow_evidence_links`;`truth-state.reviewerRouting` 是 `{status, warningCode, summary, items}` 读模型,会从 open/pending ChangeSet、待审 item 风险、跨对象类型、evidence source、项目成员与 `OWNER / REVIEWER` 角色生成 `sourceType / riskLevel / priority / suggestedReviewer / fallbackReviewerRole / targetUrl`,只读展示,不写 approval、不自动 assign reviewer;C1 写 API 包括 thread message、`proposed_items` extraction、ChangeSet detail、ChangeItem `accept/edit/defer`;C2 backend 已实现 `POST /pmflow-os/change-sets/{change_set_id}/commit`,默认 `mode=atomic`,将 `Requirement / Decision / Risk` 写入 `truth_nodes`,将 `Task` 写入 `truth_nodes(node_type='task') + tasks`,高风险项强制 approval gate;D.2 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs`,对已确认任务生成/复用 `PromptPackage` 并创建 `ProgressEvent(event_type='handoff_created')` 起点;P1 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs/{prompt_package_id}/consume`,记录运行端领取、更新 `PromptPackage.package_payload.latest_handoff.handoff_state=consumed` 并创建 `ProgressEvent(event_type='handoff_consumed', derived_task_status='coding')`;Release Growth P1 已在 `execution-summary.releaseGate` 中从 branch / commit / PR / test_passed / checks_passed / failed / blocked 等 `ProgressEvent` 推导 `ready / blocked / needs_confirmation / empty`;Release Validation P1 新增 `POST /pmflow-os/projects/{project_id}/release-validation-runs`,从当前 `releaseGate` 固化 `ReleaseValidationRun` 快照并在 `releaseGate.latestValidationRun` 返回最新记录,无 evidence 时返回 `release_validation_evidence_empty`;Release Feedback P1 新增 `POST /pmflow-os/projects/{project_id}/release-feedback-ingests`,复用 `FeedbackIngest / IterationRecommendation` 写入真实发布后反馈,并在 `execution-summary.releaseSignals` 投影真实 `feedback / revenueEvents / recommendations` 与 release gate / validation run 关系;Iteration Loop P1 新增 `POST /pmflow-os/projects/{project_id}/iteration-recommendations/{recommendation_id}/change-set-drafts`,把真实 `IterationRecommendation` 转为 open ChangeSet、pending_review Requirement/Task/Risk/Decision items 与 iteration evidence,重复调用复用既有 draft;Iteration Loop P2 新增 `POST /pmflow-os/projects/{project_id}/iteration-recommendation-groups/{group_key}/change-set-drafts`,并在 `execution-summary.releaseSignals.recommendations` 补 `groupKey / duplicateOf / mergedCount / impactLevel / riskLevel / suggestedChangeTypes / reviewPriority`,在 `recommendationGroups` 聚合同组建议,支持同组 recommendation 批量生成一个可复用 ChangeSet draft 与 `iteration_recommendation_group` evidence;这些步骤均不执行发布命令、外部支付、真实推送、自动 Project Memory commit 或正式 Task 创建 - PMFlow OS 真实读路径、C1 Change Review API、C2 backend commit、Execution Handoff、Release Evidence Gate、Release Validation Run、Release Feedback Signal、Iteration Loop draft 与 Team reviewer decision loop:参考 `backend/app/api/pmflow_os_api/`、`backend/app/services/pmflow_os_projection_service.py`、`backend/app/services/pmflow_change_review_service.py`、`backend/app/schemas/pmflow_os.py`;只读提供 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,复用 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync`,并读取 `pmflow_threads / pmflow_thread_messages / pmflow_change_sets / pmflow_change_items / pmflow_approvals / pmflow_evidence_links`;`truth-state.reviewerRouting` 是 `{status, warningCode, summary, items}` 读模型,会从 open/pending ChangeSet、待审 item 风险、跨对象类型、evidence source、项目成员与 `OWNER / REVIEWER` 角色生成 `sourceType / riskLevel / priority / suggestedReviewer / fallbackReviewerRole / targetUrl`,并返回 `reviewDecision / approvalState / requestedAt / decidedAt / decisionReason` 审查决策只读字段;P8 新增 `POST /pmflow-os/change-sets/{change_set_id}/review-decisions`,支持 `request_review / approve / request_changes / reject`,复用 `PMFlowApproval` 写审计,`approve` 满足 high-risk C2 approval gate 但不自动 commit,`request_changes / reject` 不写 Project Memory;C1 写 API 包括 thread message、`proposed_items` extraction、ChangeSet detail、ChangeItem `accept/edit/defer`;C2 backend 已实现 `POST /pmflow-os/change-sets/{change_set_id}/commit`,默认 `mode=atomic`,将 `Requirement / Decision / Risk` 写入 `truth_nodes`,将 `Task` 写入 `truth_nodes(node_type='task') + tasks`,高风险项强制 approval gate;D.2 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs`,对已确认任务生成/复用 `PromptPackage` 并创建 `ProgressEvent(event_type='handoff_created')` 起点;P1 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs/{prompt_package_id}/consume`,记录运行端领取、更新 `PromptPackage.package_payload.latest_handoff.handoff_state=consumed` 并创建 `ProgressEvent(event_type='handoff_consumed', derived_task_status='coding')`;Release Growth P1 已在 `execution-summary.releaseGate` 中从 branch / commit / PR / test_passed / checks_passed / failed / blocked 等 `ProgressEvent` 推导 `ready / blocked / needs_confirmation / empty`;Release Validation P1 新增 `POST /pmflow-os/projects/{project_id}/release-validation-runs`,从当前 `releaseGate` 固化 `ReleaseValidationRun` 快照并在 `releaseGate.latestValidationRun` 返回最新记录,无 evidence 时返回 `release_validation_evidence_empty`;Release Feedback P1 新增 `POST /pmflow-os/projects/{project_id}/release-feedback-ingests`,复用 `FeedbackIngest / IterationRecommendation` 写入真实发布后反馈,并在 `execution-summary.releaseSignals` 投影真实 `feedback / revenueEvents / recommendations` 与 release gate / validation run 关系;Iteration Loop P1 新增 `POST /pmflow-os/projects/{project_id}/iteration-recommendations/{recommendation_id}/change-set-drafts`,把真实 `IterationRecommendation` 转为 open ChangeSet、pending_review Requirement/Task/Risk/Decision items 与 iteration evidence,重复调用复用既有 draft;Iteration Loop P2 新增 `POST /pmflow-os/projects/{project_id}/iteration-recommendation-groups/{group_key}/change-set-drafts`,并在 `execution-summary.releaseSignals.recommendations` 补 `groupKey / duplicateOf / mergedCount / impactLevel / riskLevel / suggestedChangeTypes / reviewPriority`,在 `recommendationGroups` 聚合同组建议,支持同组 recommendation 批量生成一个可复用 ChangeSet draft 与 `iteration_recommendation_group` evidence;这些步骤均不执行发布命令、外部支付、真实推送、自动 Project Memory commit 或正式 Task 创建
- Project Launch 真实创建链:参考 `backend/app/api/pmflow_launch_api/`、`backend/app/services/pmflow_launch_service.py`、`backend/app/schemas/pmflow_launch.py` 与模型 `backend/app/models/pmflow_launch.py`;接口为 `POST /pmflow-launch/drafts` 与 `POST /pmflow-launch/drafts/{draft_id}/confirm`,draft 只写 `pmflow_launch_drafts`,confirm 在同一事务内创建 `projects / user_projects / pmflow_threads / pmflow_thread_messages / truth_nodes / tasks / semantic_facts seed / operation_logs` - Project Launch 真实创建链:参考 `backend/app/api/pmflow_launch_api/`、`backend/app/services/pmflow_launch_service.py`、`backend/app/schemas/pmflow_launch.py` 与模型 `backend/app/models/pmflow_launch.py`;接口为 `POST /pmflow-launch/drafts` 与 `POST /pmflow-launch/drafts/{draft_id}/confirm`,draft 只写 `pmflow_launch_drafts`,confirm 在同一事务内创建 `projects / user_projects / pmflow_threads / pmflow_thread_messages / truth_nodes / tasks / semantic_facts seed / operation_logs`
- PMFlow Change Review 模型与 migration:参考 `backend/app/models/pmflow_change_review.py` 与 `backend/alembic/versions/20260426_add_pmflow_change_review_tables.py`;只包含 `pmflow_change_sets / pmflow_change_items / pmflow_approvals / pmflow_evidence_links`,本阶段不得新增或依赖 `/commit` - PMFlow Change Review 模型与 migration:参考 `backend/app/models/pmflow_change_review.py` 与 `backend/alembic/versions/20260426_add_pmflow_change_review_tables.py`;只包含 `pmflow_change_sets / pmflow_change_items / pmflow_approvals / pmflow_evidence_links`,本阶段不得新增或依赖 `/commit`
## 3. 前端参考 ## 3. 前端参考
- PMFlow UI 契约真源:先读 `docs/PMFLOW_UI_CONTRACT.md`,再修改 `project-launch`、`personal-space`、`pmflow-os` 相关页面;该文档锁定 Logo、色彩、移动端底部导航、状态标签、AI 操作/用户动作/待确认视觉语法和核心功能 canonical 契约 - PMFlow UI 契约真源:先读 `docs/PMFLOW_UI_CONTRACT.md`,再修改 `project-launch`、`personal-space`、`pmflow-os` 相关页面;该文档锁定 Logo、色彩、移动端底部导航、状态标签、AI 操作/用户动作/待确认视觉语法和核心功能 canonical 契约
- PMFlow 大模块并行公共壳:参考 `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md` 的 `4.4.1-4.4.3` 与 `frontend/src/router/index.tsx`、`frontend/src/main.tsx`、`frontend/src/features/pmflow-os/components/MobileBottomNav.tsx`、`frontend/src/features/pmflow-os/components/PMFlowOsFrame.tsx`;`/space` 归 Space lane,`/pmflow-os / /command-center / /change-review / /execution-bridge / /release-growth` 归 Main Flow lane,`/me` 归 Account Settings lane,`/settings` 当前仅为 reserved route,新增或改动必须由 Shell / Integration owner 串行处理 - PMFlow 大模块并行公共壳:参考 `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md` 的 `4.4.1-4.4.3` 与 `frontend/src/router/index.tsx`、`frontend/src/main.tsx`、`frontend/src/features/pmflow-os/components/MobileBottomNav.tsx`、`frontend/src/features/pmflow-os/components/PMFlowOsFrame.tsx`;`/space` 归 Space lane,`/pmflow-os / /command-center / /change-review / /execution-bridge / /release-growth` 归 Main Flow lane,`/me` 归 Account Settings lane,`/settings` 当前仅为 reserved route,新增或改动必须由 Shell / Integration owner 串行处理
- PMFlow P8 当前推荐任务:参考 `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md` 第 7 节;P7 与 Team Review P1 已完成,下一步从 `truth-state.reviewerRouting` 只读建议推进到 `review request -> reviewer decision -> approval audit -> personal attention` 的 Review Decision Loop;建议并行推进 `main-flow-p8-review-decision-loop`、`space-p7-review-attention-queue`、`account-p8-review-decision-preference`,再由 `big-module-integration-p8` 收口;P8 不新增战略页面、不改 router/nav、不接真实通知中心,继续使用既有 `/change-review / /team-layer / /space / /me` 表面 - PMFlow P8 当前推荐任务:参考 `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md` 第 7 节;Main Flow 的 `main-flow-p8-review-decision-loop` 已完成,`truth-state.reviewerRouting` 已推进到 `review request -> reviewer decision -> approval audit`;后续优先推进 `space-p7-review-attention-queue`、`account-p8-review-decision-preference`,再由 `big-module-integration-p8` 收口到 personal attention;P8 不新增战略页面、不改 router/nav、不接真实通知中心,继续使用既有 `/change-review / /team-layer / /space / /me` 表面
- PMFlow OS 产品合同真源:先读 `docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md`,再实现 `pmflow-os` 真实后端接入;该文档锁定 `ProjectTruthState / Thread / ChangeSet / ChangeItem / Evidence / Approval / Project Memory / ExecutionContext` 与 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync` 的复用边界和后续 API 草案 - PMFlow OS 产品合同真源:先读 `docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md`,再实现 `pmflow-os` 真实后端接入;该文档锁定 `ProjectTruthState / Thread / ChangeSet / ChangeItem / Evidence / Approval / Project Memory / ExecutionContext` 与 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync` 的复用边界和后续 API 草案
- 新增前端 API 调用:参考 `frontend/src/api/sstc.ts`;Space 工作面只读聚合参考 `frontend/src/api/space.ts`,只消费项目摘要与 PMFlow OS `truth-state / execution-summary`,不写 Project Memory、ChangeSet 状态、ProgressEvent 或 ReleaseValidationRun;Space P6 的 `nextActionQueue` 在 Space 本地读模型内把高风险待确认、发布阻塞/失败证据、handoff consumed 后缺少后续 progress、下一轮 draft、发布后 feedback/revenue/compliance 信号统一排序为“我的下一步”,所有 item 只跳 `/change-review`、`/execution-bridge` 或 `/release-growth`;Space P3 的 `releaseAttention` 优先从 `truth-state.releases` 的 release validation 投影生成“发布验证阻塞 / 发布验证待确认”,没有发布验证投影时才回落到 `PMFlowExecutionSummary.releaseGate` 的“发布候选 / 发布门禁阻塞”,所有动作只跳转 `/release-growth?projectId=<id>`;PMFlow OS 真实接入参考 `frontend/src/api/pmflowOs.ts` 的 `getPMFlowTruthState(projectId)`、`getPMFlowExecutionSummary(projectId)`、`createPMFlowExecutionHandoff`、`consumePMFlowExecutionHandoff`、`createPMFlowExecutionProgressEvent`、`createPMFlowReleaseValidationRun`、`createPMFlowReleaseFeedbackIngest`、`createPMFlowIterationRecommendationChangeSetDraft`、`createPMFlowIterationRecommendationGroupChangeSetDraft`、`createPMFlowThreadMessage`、`createPMFlowExtraction`、`getPMFlowChangeSet`、`acceptPMFlowChangeItem`、`editPMFlowChangeItem`、`deferPMFlowChangeItem`、`commitPMFlowChangeSet`;`PMFlowExecutionSummary.releaseGate` 是 ReleaseGrowth 真实发布门禁前端合同,`releaseGate.latestValidationRun` 是最新发布验证记录前端合同,`execution-summary.releaseSignals` 是 Release/Growth 发布后 feedback/revenue/recommendation 信号、recommendation quality grouping 与下一轮 ChangeSet draft 状态前端合同;Project Launch 创建链参考 `frontend/src/api/pmflowLaunch.ts` 的 `createPMFlowLaunchDraft` 与 `confirmPMFlowLaunchDraft` - 新增前端 API 调用:参考 `frontend/src/api/sstc.ts`;Space 工作面只读聚合参考 `frontend/src/api/space.ts`,只消费项目摘要与 PMFlow OS `truth-state / execution-summary`,不写 Project Memory、ChangeSet 状态、ProgressEvent 或 ReleaseValidationRun;Space P6 的 `nextActionQueue` 在 Space 本地读模型内把高风险待确认、发布阻塞/失败证据、handoff consumed 后缺少后续 progress、下一轮 draft、发布后 feedback/revenue/compliance 信号统一排序为“我的下一步”,所有 item 只跳 `/change-review`、`/execution-bridge` 或 `/release-growth`;Space P3 的 `releaseAttention` 优先从 `truth-state.releases` 的 release validation 投影生成“发布验证阻塞 / 发布验证待确认”,没有发布验证投影时才回落到 `PMFlowExecutionSummary.releaseGate` 的“发布候选 / 发布门禁阻塞”,所有动作只跳转 `/release-growth?projectId=<id>`;PMFlow OS 真实接入参考 `frontend/src/api/pmflowOs.ts` 的 `getPMFlowTruthState(projectId)`、`getPMFlowExecutionSummary(projectId)`、`createPMFlowExecutionHandoff`、`consumePMFlowExecutionHandoff`、`createPMFlowExecutionProgressEvent`、`createPMFlowReleaseValidationRun`、`createPMFlowReleaseFeedbackIngest`、`createPMFlowIterationRecommendationChangeSetDraft`、`createPMFlowIterationRecommendationGroupChangeSetDraft`、`createPMFlowReviewDecision`、`createPMFlowThreadMessage`、`createPMFlowExtraction`、`getPMFlowChangeSet`、`acceptPMFlowChangeItem`、`editPMFlowChangeItem`、`deferPMFlowChangeItem`、`commitPMFlowChangeSet`;`PMFlowExecutionSummary.releaseGate` 是 ReleaseGrowth 真实发布门禁前端合同,`releaseGate.latestValidationRun` 是最新发布验证记录前端合同,`execution-summary.releaseSignals` 是 Release/Growth 发布后 feedback/revenue/recommendation 信号、recommendation quality grouping 与下一轮 ChangeSet draft 状态前端合同;Project Launch 创建链参考 `frontend/src/api/pmflowLaunch.ts` 的 `createPMFlowLaunchDraft` 与 `confirmPMFlowLaunchDraft`
- AI Steward 页面入口:参考 `frontend/src/pages/ai-steward/` - AI Steward 页面入口:参考 `frontend/src/pages/ai-steward/`
- AI Steward 问答浮窗与上下文显性化组件:参考 `frontend/src/components/ai-steward/FloatingQAChat.tsx`、`frontend/src/components/ai-steward/QAConversationStatusBar.tsx`、`frontend/src/components/ai-steward/QAResponseMetaCard.tsx` - AI Steward 问答浮窗与上下文显性化组件:参考 `frontend/src/components/ai-steward/FloatingQAChat.tsx`、`frontend/src/components/ai-steward/QAConversationStatusBar.tsx`、`frontend/src/components/ai-steward/QAResponseMetaCard.tsx`
- AI Steward QA contract 前端类型(含 `analysis_packet`):参考 `frontend/src/api/aiSteward.ts` - AI Steward QA contract 前端类型(含 `analysis_packet`):参考 `frontend/src/api/aiSteward.ts`
@ -107,9 +107,9 @@
- PMFlow OS 前端 MVP 产品域:参考 `frontend/src/features/pmflow-os/` - PMFlow OS 前端 MVP 产品域:参考 `frontend/src/features/pmflow-os/`
- 个人空间首页 / 空间层入口前端产品域:参考 `frontend/src/features/personal-space/`、`frontend/src/api/space.ts` 与薄入口 `frontend/src/pages/PersonalSpacePage.tsx`;本地预览路由入口为 `/space`,首屏以 `nextActionQueue` 展示“我的下一步”,并保留继续推进、待我确认、发布关注、发布后反馈关注、下一轮迭代草稿关注、执行中 / AI 正在推进、暂存想法、草案箱与最近项目明细;点击项目主动作进入 `/pmflow-os?projectId=<id>`,待确认项进入 `/change-review?projectId=<id>&changeSetId=<id>`,执行中项进入 `/execution-bridge?projectId=<id>`,发布关注和发布后反馈关注项进入 `/release-growth?projectId=<id>`,已生成 ChangeSet draft 的下一轮建议进入 `/change-review?projectId=<id>&changeSetId=<id>`;Space 只读聚合项目摘要、PMFlow OS `truth-state.releases / execution-summary.releaseGate / execution-summary.releaseSignals`,发布验证、发布后信号或 iteration draft 缺失时显示明确空态,不虚构验证、反馈、收入、ROI、合规、iteration recommendation 或 ChangeSet draft,不直接写 Project Memory、ChangeSet 状态、execution event、ReleaseValidationRun、FeedbackIngest、RevenueEvent 或 ComplianceCheckRun;暂存想法后端只读聚合尚未接入时必须显示 empty/warning,不得用 mock 数据冒充真实内容;空间层共享左侧栏、顶部条与空间用量组件在 `frontend/src/features/personal-space/components/SpaceChrome.tsx`,`/project-launch` 桌面首次新建壳也复用该组件 - 个人空间首页 / 空间层入口前端产品域:参考 `frontend/src/features/personal-space/`、`frontend/src/api/space.ts` 与薄入口 `frontend/src/pages/PersonalSpacePage.tsx`;本地预览路由入口为 `/space`,首屏以 `nextActionQueue` 展示“我的下一步”,并保留继续推进、待我确认、发布关注、发布后反馈关注、下一轮迭代草稿关注、执行中 / AI 正在推进、暂存想法、草案箱与最近项目明细;点击项目主动作进入 `/pmflow-os?projectId=<id>`,待确认项进入 `/change-review?projectId=<id>&changeSetId=<id>`,执行中项进入 `/execution-bridge?projectId=<id>`,发布关注和发布后反馈关注项进入 `/release-growth?projectId=<id>`,已生成 ChangeSet draft 的下一轮建议进入 `/change-review?projectId=<id>&changeSetId=<id>`;Space 只读聚合项目摘要、PMFlow OS `truth-state.releases / execution-summary.releaseGate / execution-summary.releaseSignals`,发布验证、发布后信号或 iteration draft 缺失时显示明确空态,不虚构验证、反馈、收入、ROI、合规、iteration recommendation 或 ChangeSet draft,不直接写 Project Memory、ChangeSet 状态、execution event、ReleaseValidationRun、FeedbackIngest、RevenueEvent 或 ComplianceCheckRun;暂存想法后端只读聚合尚未接入时必须显示 empty/warning,不得用 mock 数据冒充真实内容;空间层共享左侧栏、顶部条与空间用量组件在 `frontend/src/features/personal-space/components/SpaceChrome.tsx`,`/project-launch` 桌面首次新建壳也复用该组件
- 新项目启动台 / 首次进入页前端产品域:参考 `frontend/src/features/project-launch/` 与薄入口 `frontend/src/pages/ProjectLaunchPage.tsx`;真实模式默认通过 `frontend/src/api/pmflowLaunch.ts` 调用后端 draft/confirm API,只有显式 `?demo=1` 或 `?dataSource=demo` 才启用 `mockData.ts` 本地演示状态机;本地预览路由入口为 `/project-launch` - 新项目启动台 / 首次进入页前端产品域:参考 `frontend/src/features/project-launch/` 与薄入口 `frontend/src/pages/ProjectLaunchPage.tsx`;真实模式默认通过 `frontend/src/api/pmflowLaunch.ts` 调用后端 draft/confirm API,只有显式 `?demo=1` 或 `?dataSource=demo` 才启用 `mockData.ts` 本地演示状态机;本地预览路由入口为 `/project-launch`
- PMFlow OS 共享 truth store 与主链状态动作:参考 `frontend/src/features/pmflow-os/store.tsx`;当前支持 `?projectId=<id>` 走 React Query 读取真实 `truth-state` 与 `execution-summary`,真实模式下 thread message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest、单条 iteration recommendation -> ChangeSet draft 与同组 iteration recommendation 批量 -> ChangeSet draft 均调后端并 refetch,不本地 promote 入 Project Memory、不伪造 execution event、发布验证记录、发布后信号或下一轮 draft;`?demo=1` 才启用本地 mock 演示状态机和本地动作;项目内 AI 结构化闭环状态机为 `command_idle / ai_extracting / extraction_ready / review_open / writeback_success` - PMFlow OS 共享 truth store 与主链状态动作:参考 `frontend/src/features/pmflow-os/store.tsx`;当前支持 `?projectId=<id>` 走 React Query 读取真实 `truth-state` 与 `execution-summary`,真实模式下 thread message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、review decision、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest、单条 iteration recommendation -> ChangeSet draft 与同组 iteration recommendation 批量 -> ChangeSet draft 均调后端并 refetch,不本地 promote 入 Project Memory、不伪造 approval、execution event、发布验证记录、发布后信号或下一轮 draft;`?demo=1` 才启用本地 mock 演示状态机和本地动作;项目内 AI 结构化闭环状态机为 `command_idle / ai_extracting / extraction_ready / review_open / writeback_success`
- Account Settings / `/me` 前端产品域与 P1-P7 偏好合同:参考 `frontend/src/features/account-settings/AccountSettingsPanel.tsx`、`frontend/src/features/pmflow-os/pages/AccountPage.tsx`、`frontend/src/api/accountSettings.ts`、`backend/app/api/account_settings_api/`、`backend/app/schemas/account_settings.py` 与 `backend/app/services/account_settings_service.py`;当前覆盖个人资料、默认语言、AI 回答深度、证据展示偏好、待确认、执行回写、发布门禁和失败提醒通知偏好,移动端使用分组列表与 bottom sheet,账户退出仍复用 `AccountControls.tsx`,不改 OAuth/JWT/auth core;P1 后端合同提供 `GET /account-settings/me` 与 `PUT /account-settings/me`,已由 `backend/app/api/router.py` 挂入核心路由组,按当前 `tenant_id + user_id` 保存账号偏好,前端有 token 时优先账号同步,后端不可用时明确回落 `仅本机暂存 / 保存失败`;P2 在 `frontend/src/api/accountSettings.ts` 提供 `shouldShowAccountNotification` 与 `getAccountNotificationConsumption`,把通知偏好映射为页面内提醒消费模型;P3 新增 `getReleaseGateNotificationPreference(settings, event)` 供 Release/Growth 后续消费:`release_gate_*` 与 `release_validation_*` 使用 `releaseGate` 偏好,`failed_event` 使用 `failureAlert` 偏好;P5/P6 的 `getPostReleaseNotificationPreference(settings, event)` 供发布后反馈与下一轮待确认消费:`feedback_received / revenue_signal / compliance_blocked` 使用 `releaseGate` 偏好,`iteration_recommendation / next_change_review` 使用 `pendingConfirmation` 偏好并落到 `change_review` surface;P7 将每类通知偏好扩展为 `reminderIntensity=silent|normal|prominent` 与 `evidenceDisplay=compact|expanded`,并新增 `getAccountReminderConsumption(settings, event)` 覆盖 `change_review_high_risk / iteration_draft_ready / release_blocked / execution_stalled`,稳定返回 `notificationTransport=in_app_only / evidencePolicy=preserve_business_evidence / auditPolicy=preserve_audit_state`;ReleaseGrowthPage 当前消费 `releaseGate` 与 `failureAlert` 两个偏好,只静默强调型提醒,不隐藏 release evidence、阻塞原因、审计状态、validation run、releaseSignals 或下一轮 ChangeSet draft;这些 helper 只控制本地 UI 提醒消费合同,不代表真实邮件、桌面推送或系统通知中心已接入 - Account Settings / `/me` 前端产品域与 P1-P7 偏好合同:参考 `frontend/src/features/account-settings/AccountSettingsPanel.tsx`、`frontend/src/features/pmflow-os/pages/AccountPage.tsx`、`frontend/src/api/accountSettings.ts`、`backend/app/api/account_settings_api/`、`backend/app/schemas/account_settings.py` 与 `backend/app/services/account_settings_service.py`;当前覆盖个人资料、默认语言、AI 回答深度、证据展示偏好、待确认、执行回写、发布门禁和失败提醒通知偏好,移动端使用分组列表与 bottom sheet,账户退出仍复用 `AccountControls.tsx`,不改 OAuth/JWT/auth core;P1 后端合同提供 `GET /account-settings/me` 与 `PUT /account-settings/me`,已由 `backend/app/api/router.py` 挂入核心路由组,按当前 `tenant_id + user_id` 保存账号偏好,前端有 token 时优先账号同步,后端不可用时明确回落 `仅本机暂存 / 保存失败`;P2 在 `frontend/src/api/accountSettings.ts` 提供 `shouldShowAccountNotification` 与 `getAccountNotificationConsumption`,把通知偏好映射为页面内提醒消费模型;P3 新增 `getReleaseGateNotificationPreference(settings, event)` 供 Release/Growth 后续消费:`release_gate_*` 与 `release_validation_*` 使用 `releaseGate` 偏好,`failed_event` 使用 `failureAlert` 偏好;P5/P6 的 `getPostReleaseNotificationPreference(settings, event)` 供发布后反馈与下一轮待确认消费:`feedback_received / revenue_signal / compliance_blocked` 使用 `releaseGate` 偏好,`iteration_recommendation / next_change_review` 使用 `pendingConfirmation` 偏好并落到 `change_review` surface;P7 将每类通知偏好扩展为 `reminderIntensity=silent|normal|prominent` 与 `evidenceDisplay=compact|expanded`,并新增 `getAccountReminderConsumption(settings, event)` 覆盖 `change_review_high_risk / iteration_draft_ready / release_blocked / execution_stalled`,稳定返回 `notificationTransport=in_app_only / evidencePolicy=preserve_business_evidence / auditPolicy=preserve_audit_state`;ReleaseGrowthPage 当前消费 `releaseGate` 与 `failureAlert` 两个偏好,只静默强调型提醒,不隐藏 release evidence、阻塞原因、审计状态、validation run、releaseSignals 或下一轮 ChangeSet draft;这些 helper 只控制本地 UI 提醒消费合同,不代表真实邮件、桌面推送或系统通知中心已接入
- PMFlow OS 产品页入口:参考 `frontend/src/features/pmflow-os/pages/CommandCenterPage.tsx`、`StructuredExtractionPage.tsx`、`ChangeReviewPage.tsx`、`MemoryWritebackResultPage.tsx`、`WorkGraphPage.tsx`、`ExecutionBridgePage.tsx`、`ReleaseGrowthPage.tsx`、`TeamLayerPage.tsx`、`ProjectMemoryPage.tsx`、`AccountPage.tsx`;`TeamLayerPage.tsx` 真实模式读取 `state.reviewerRouting` 展示 ready/empty/warning、待审 ChangeSet、来源(下一轮迭代 / AI 提取 / 手动)、风险等级、建议审查人或 fallback role、审查原因、待审 item 数、证据数,并只跳转 `/change-review?projectId=<id>&changeSetId=<id>`,不自动分派 reviewer;`ReleaseGrowthPage.tsx` 真实模式展示 `execution-summary.releaseGate`、execution evidence、阻塞原因、下一步动作、`latestValidationRun` 和 `releaseSignals`,缺证据或缺发布后信号时显示 empty/warning,不展示 mock 发布结果;页面可调用真实 API 创建 release validation run、摄取 release feedback,从真实 recommendation 单条或同组批量生成下一轮待确认 ChangeSet draft,并解释 `iteration_recommendation_group` evidence 的组、反馈、风险、影响和审查优先级;生成后仍进入 Change Review accept/edit/defer,不自动 commit、不写 Project Memory、不执行部署命令、不接外部支付;同时消费 Account notification preference helper,`releaseGate` 关闭时静默发布门禁强调提醒,`failureAlert` 关闭时静默失败强调提醒,所有真实证据和审计状态仍可见;静态高保真发布/增长数据仅保留在显式 demo 模式 - PMFlow OS 产品页入口:参考 `frontend/src/features/pmflow-os/pages/CommandCenterPage.tsx`、`StructuredExtractionPage.tsx`、`ChangeReviewPage.tsx`、`MemoryWritebackResultPage.tsx`、`WorkGraphPage.tsx`、`ExecutionBridgePage.tsx`、`ReleaseGrowthPage.tsx`、`TeamLayerPage.tsx`、`ProjectMemoryPage.tsx`、`AccountPage.tsx`;`ChangeReviewPage.tsx` 真实模式可对当前 ChangeSet 请求审查、通过、要求修改或拒绝,展示审查状态、建议 reviewer、审查意见和 high-risk approval gate,但不自动 commit;`TeamLayerPage.tsx` 真实模式读取 `state.reviewerRouting` 展示 ready/empty/warning、待审 ChangeSet、来源(下一轮迭代 / AI 提取 / 手动)、风险等级、建议审查人或 fallback role、审查原因、待审 item 数、证据数、审查状态、审查意见、请求/决策时间,并跳转 `/change-review?projectId=<id>&changeSetId=<id>`;`ReleaseGrowthPage.tsx` 真实模式展示 `execution-summary.releaseGate`、execution evidence、阻塞原因、下一步动作、`latestValidationRun` 和 `releaseSignals`,缺证据或缺发布后信号时显示 empty/warning,不展示 mock 发布结果;页面可调用真实 API 创建 release validation run、摄取 release feedback,从真实 recommendation 单条或同组批量生成下一轮待确认 ChangeSet draft,并解释 `iteration_recommendation_group` evidence 的组、反馈、风险、影响和审查优先级;生成后仍进入 Change Review accept/edit/defer,不自动 commit、不写 Project Memory、不执行部署命令、不接外部支付;同时消费 Account notification preference helper,`releaseGate` 关闭时静默发布门禁强调提醒,`failureAlert` 关闭时静默失败强调提醒,所有真实证据和审计状态仍可见;静态高保真发布/增长数据仅保留在显式 demo 模式
- PMFlow OS 行为组件:参考 `frontend/src/features/pmflow-os/components/ProjectStructuredLoop.tsx`、`ProjectStageRail.tsx`、`ThreadComposer.tsx`、`ExtractionRail.tsx`、`ChangeGroupPanel.tsx`、`ObjectGraphSection.tsx`、`ContextPacketPanel.tsx`、`ExecutionTimeline.tsx`、`ReleaseGatePanel.tsx`、`TeamFeedPanel.tsx`、`ChangeReviewGroup.tsx`、`WorkbenchPanel.tsx`、`ExecutionBridgeCards.tsx`、`PMFlowOsFrame.tsx`、`AccountControls.tsx`、`FeedbackPrimitives.tsx`、`MobileBottomNav.tsx`;其中 `ProjectStructuredLoop.tsx` 承接 `StatusLabel / EvidenceSnippet / ObjectChangeCard / ProjectConversationPanel / StructuredExtractionPanel / ProjectAICopilotPanel`,`PMFlowOsFrame.tsx` 承接项目内面包屑和项目切换器,`AccountControls.tsx` 承接桌面头像菜单、退出登录确认与移动端 bottom sheet,`FeedbackPrimitives.tsx` 承接 `PageBanner / InlineNotice / HumanDecisionBar / ConfirmDialog / MobileBottomSheet`,`MobileBottomNav.tsx` 承接正式应用壳移动端固定底部导航 - PMFlow OS 行为组件:参考 `frontend/src/features/pmflow-os/components/ProjectStructuredLoop.tsx`、`ProjectStageRail.tsx`、`ThreadComposer.tsx`、`ExtractionRail.tsx`、`ChangeGroupPanel.tsx`、`ObjectGraphSection.tsx`、`ContextPacketPanel.tsx`、`ExecutionTimeline.tsx`、`ReleaseGatePanel.tsx`、`TeamFeedPanel.tsx`、`ChangeReviewGroup.tsx`、`WorkbenchPanel.tsx`、`ExecutionBridgeCards.tsx`、`PMFlowOsFrame.tsx`、`AccountControls.tsx`、`FeedbackPrimitives.tsx`、`MobileBottomNav.tsx`;其中 `ProjectStructuredLoop.tsx` 承接 `StatusLabel / EvidenceSnippet / ObjectChangeCard / ProjectConversationPanel / StructuredExtractionPanel / ProjectAICopilotPanel`,`PMFlowOsFrame.tsx` 承接项目内面包屑和项目切换器,`AccountControls.tsx` 承接桌面头像菜单、退出登录确认与移动端 bottom sheet,`FeedbackPrimitives.tsx` 承接 `PageBanner / InlineNotice / HumanDecisionBar / ConfirmDialog / MobileBottomSheet`,`MobileBottomNav.tsx` 承接正式应用壳移动端固定底部导航
- PMFlow OS 原型状态回归测试:参考 `frontend/src/features/pmflow-os/store.test.ts`;真实 API 动作回归参考 `frontend/src/features/pmflow-os/store.api.test.tsx` - PMFlow OS 原型状态回归测试:参考 `frontend/src/features/pmflow-os/store.test.ts`;真实 API 动作回归参考 `frontend/src/features/pmflow-os/store.api.test.tsx`
- Project Launch 真实/演示分支回归测试:参考 `frontend/src/features/project-launch/useProjectLaunchState.test.ts` - Project Launch 真实/演示分支回归测试:参考 `frontend/src/features/project-launch/useProjectLaunchState.test.ts`

View File

@ -1,4 +1,10 @@
import type { ChangeItem, PMFlowExecutionContext, PMFlowRiskLevel, ProjectTruthState } from "@/features/pmflow-os/types"; import type {
ChangeItem,
PMFlowExecutionContext,
PMFlowReviewDecisionAction,
PMFlowRiskLevel,
ProjectTruthState,
} from "@/features/pmflow-os/types";
export type { PMFlowReviewerRouting, PMFlowReviewerRoutingItem, PMFlowReviewerSuggestion } from "@/features/pmflow-os/types"; export type { PMFlowReviewerRouting, PMFlowReviewerRoutingItem, PMFlowReviewerSuggestion } from "@/features/pmflow-os/types";
type ApiEnvelope<T> = { code: number; message: string; data: T }; type ApiEnvelope<T> = { code: number; message: string; data: T };
@ -419,6 +425,19 @@ export interface PMFlowChangeSetCommitResult {
projectTruthState?: ProjectTruthState | null; projectTruthState?: ProjectTruthState | null;
} }
export interface PMFlowReviewDecisionRequest {
action: PMFlowReviewDecisionAction;
reviewer_id?: number;
reason?: string | null;
item_ids?: string[];
}
export interface PMFlowReviewDecisionResult {
changeSetDetail: PMFlowChangeSetDetail;
truthState: ProjectTruthState;
warnings: string[];
}
export async function getPMFlowTruthState(projectId: string): Promise<ProjectTruthState> { export async function getPMFlowTruthState(projectId: string): Promise<ProjectTruthState> {
return get<ProjectTruthState>(`/pmflow-os/projects/${projectId}/truth-state`); return get<ProjectTruthState>(`/pmflow-os/projects/${projectId}/truth-state`);
} }
@ -533,3 +552,10 @@ export async function commitPMFlowChangeSet(
): Promise<PMFlowChangeSetCommitResult> { ): Promise<PMFlowChangeSetCommitResult> {
return post<PMFlowChangeSetCommitResult>(`/pmflow-os/change-sets/${changeSetId}/commit`, { mode: "atomic", ...payload }); return post<PMFlowChangeSetCommitResult>(`/pmflow-os/change-sets/${changeSetId}/commit`, { mode: "atomic", ...payload });
} }
export async function createPMFlowReviewDecision(
changeSetId: string,
payload: PMFlowReviewDecisionRequest
): Promise<PMFlowReviewDecisionResult> {
return post<PMFlowReviewDecisionResult>(`/pmflow-os/change-sets/${changeSetId}/review-decisions`, payload);
}

View File

@ -6,6 +6,7 @@ import {
reviewToolbarIcons, reviewToolbarIcons,
} from "@/features/pmflow-os/components/ProjectStructuredLoop"; } from "@/features/pmflow-os/components/ProjectStructuredLoop";
import { usePMFlowOsStore } from "@/features/pmflow-os/store"; import { usePMFlowOsStore } from "@/features/pmflow-os/store";
import type { PMFlowReviewerRoutingItem } from "@/features/pmflow-os/types";
const { Filter, ListFilter } = reviewToolbarIcons; const { Filter, ListFilter } = reviewToolbarIcons;
@ -38,14 +39,111 @@ function getIterationEvidence(state: ReturnType<typeof usePMFlowOsStore>["state"
); );
} }
function reviewStateLabel(state?: string | null) {
if (state === "requested") return "已请求审查";
if (state === "approved") return "已通过";
if (state === "changes_requested") return "要求修改";
if (state === "rejected") return "已拒绝";
return "待请求审查";
}
function reviewDecisionTone(state?: string | null) {
if (state === "approved") return "border-emerald-200 bg-emerald-50 text-emerald-700";
if (state === "changes_requested") return "border-orange-200 bg-orange-50 text-orange-700";
if (state === "rejected") return "border-rose-200 bg-rose-50 text-rose-700";
if (state === "requested") return "border-blue-200 bg-blue-50 text-blue-700";
return "border-slate-200 bg-slate-50 text-slate-600";
}
function ReviewDecisionPanel({
routing,
hasHighRisk,
highRiskApproved,
onRequestReview,
onApprove,
onRequestChanges,
onReject,
}: {
routing?: PMFlowReviewerRoutingItem | null;
hasHighRisk: boolean;
highRiskApproved: boolean;
onRequestReview: () => void;
onApprove: () => void;
onRequestChanges: () => void;
onReject: () => void;
}) {
const approvalState = routing?.approvalState || "not_requested";
return (
<div className="rounded-xl border border-[#e2e9f5] bg-[#fbfdff] p-4 text-sm leading-6">
<div className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
<div>
<div className="font-semibold text-slate-900">Reviewer 审查决策</div>
<div className="mt-1 text-slate-500">
{routing?.suggestedReviewer?.displayName
? `建议审查人:${routing.suggestedReviewer.displayName}(${routing.suggestedReviewer.role || "REVIEWER"})`
: "当前没有明确 reviewer;请求审查会 fail-closed 返回 warning,不会虚构审查人。"}
</div>
<div className="mt-2 text-xs text-slate-500">{routing?.reviewReason || "等待 reviewer routing 返回审查建议。"}</div>
</div>
<span className={`rounded-lg border px-3 py-1 text-xs font-semibold ${reviewDecisionTone(approvalState)}`}>
{reviewStateLabel(approvalState)}
</span>
</div>
{routing?.decisionReason ? <div className="mt-3 rounded-lg bg-white px-3 py-2 text-slate-600">审查意见:{routing.decisionReason}</div> : null}
{hasHighRisk ? (
<div className={`mt-3 rounded-lg border px-3 py-2 text-xs ${highRiskApproved ? "border-emerald-200 bg-emerald-50 text-emerald-700" : "border-orange-200 bg-orange-50 text-orange-700"}`}>
高风险 approval gate:{highRiskApproved ? "reviewer approval 已满足,仍需人工点击确认写入项目记忆。" : "高风险项需要 reviewer approve 后才允许 C2 commit。"}
</div>
) : null}
<div className="mt-4 flex flex-wrap gap-2">
<button type="button" onClick={onRequestReview} className="rounded-lg border border-[#cfe0ff] bg-white px-3 py-2 text-xs font-semibold text-[#2463EB] transition hover:bg-[#eef4ff]">
请求审查
</button>
<button type="button" onClick={onApprove} className="rounded-lg bg-emerald-600 px-3 py-2 text-xs font-semibold text-white transition hover:bg-emerald-700">
reviewer 通过
</button>
<button type="button" onClick={onRequestChanges} className="rounded-lg bg-orange-500 px-3 py-2 text-xs font-semibold text-white transition hover:bg-orange-600">
要求修改
</button>
<button type="button" onClick={onReject} className="rounded-lg bg-rose-600 px-3 py-2 text-xs font-semibold text-white transition hover:bg-rose-700">
拒绝
</button>
</div>
</div>
);
}
export default function ChangeReviewPage() { export default function ChangeReviewPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const { state, dataSourceMode, acceptChange, editChange, deferChange, promoteAcceptedToGraph } = usePMFlowOsStore(); const { state, dataSourceMode, acceptChange, editChange, deferChange, promoteAcceptedToGraph, createReviewDecision } = usePMFlowOsStore();
const activeChangeSet = state.changeSets.find((entry) => entry.id === state.activeChangeSetId) || state.changeSets[0] || null; const activeChangeSet = state.changeSets.find((entry) => entry.id === state.activeChangeSetId) || state.changeSets[0] || null;
const items = state.changeItems.filter((item) => item.changeSetId === activeChangeSet?.id); const items = state.changeItems.filter((item) => item.changeSetId === activeChangeSet?.id);
const selectedItems = items.filter((item) => item.status === "pending_review").slice(0, 2); const selectedItems = items.filter((item) => item.status === "pending_review").slice(0, 2);
const iterationEvidence = getIterationEvidence(state, activeChangeSet?.id); const iterationEvidence = getIterationEvidence(state, activeChangeSet?.id);
const iterationMeta = (iterationEvidence as (typeof iterationEvidence & IterationEvidenceMeta) | null)?.metadata; const iterationMeta = (iterationEvidence as (typeof iterationEvidence & IterationEvidenceMeta) | null)?.metadata;
const activeRouting = state.reviewerRouting?.items.find((entry) => entry.changeSetId === activeChangeSet?.id) || null;
const selectedItemIds = items.filter((item) => item.status === "pending_review").map((item) => item.id);
const reviewerId = Number(activeRouting?.suggestedReviewer?.userId || "");
const hasHighRisk = items.some((item) => item.riskLevel === "high");
const highRiskApproved = items
.filter((item) => item.riskLevel === "high")
.every((item) =>
state.approvals.some(
(approval) =>
approval.changeItemId === item.id &&
approval.action === "approve" &&
approval.decision === "approved"
)
);
const submitReviewDecision = (action: "request_review" | "approve" | "request_changes" | "reject", reason: string) => {
if (!activeChangeSet) return;
createReviewDecision(activeChangeSet.id, {
action,
reviewer_id: Number.isFinite(reviewerId) && reviewerId > 0 ? reviewerId : undefined,
reason,
item_ids: selectedItemIds,
});
};
const writeback = () => { const writeback = () => {
if (!activeChangeSet) return; if (!activeChangeSet) return;
@ -78,6 +176,17 @@ export default function ChangeReviewPage() {
</div> </div>
<ReviewExtractionHero activeChangeSet={activeChangeSet} /> <ReviewExtractionHero activeChangeSet={activeChangeSet} />
{activeChangeSet ? (
<ReviewDecisionPanel
routing={activeRouting}
hasHighRisk={hasHighRisk}
highRiskApproved={highRiskApproved}
onRequestReview={() => submitReviewDecision("request_review", "ChangeReviewPage 请求 reviewer 审查。")}
onApprove={() => submitReviewDecision("approve", "reviewer 通过本轮待审变更;不自动 commit。")}
onRequestChanges={() => submitReviewDecision("request_changes", "reviewer 要求修改后再审查;不写 Project Memory。")}
onReject={() => submitReviewDecision("reject", "reviewer 拒绝本轮变更;不写 Project Memory。")}
/>
) : null}
{iterationEvidence ? ( {iterationEvidence ? (
<div className="rounded-xl border border-indigo-100 bg-indigo-50 p-4 text-sm leading-6 text-indigo-800"> <div className="rounded-xl border border-indigo-100 bg-indigo-50 p-4 text-sm leading-6 text-indigo-800">
<div className="font-semibold"> <div className="font-semibold">

View File

@ -20,7 +20,8 @@ import {
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { usePMFlowOsStore } from "@/features/pmflow-os/store"; import { usePMFlowOsStore } from "@/features/pmflow-os/store";
import type { PMFlowReviewerRouting, PMFlowReviewerRoutingItem, PMFlowRiskLevel } from "@/features/pmflow-os/types"; import type { PMFlowReviewerRouting, PMFlowReviewerRoutingItem, PMFlowRiskLevel } from "@/features/pmflow-os/types";
import { cn } from "@/lib/utils";
const cn = (...classes: Array<string | false | null | undefined>) => classes.filter(Boolean).join(" ");
const activityItems = [ const activityItems = [
["新增提取对象", "AI 从对话中提取了 3 个新对象:会议纪要分享、自动任务创建、跨时区支持", "10:24 · 由 PMFlow AI 提取", CirclePlus, "brand"], ["新增提取对象", "AI 从对话中提取了 3 个新对象:会议纪要分享、自动任务创建、跨时区支持", "10:24 · 由 PMFlow AI 提取", CirclePlus, "brand"],
@ -116,6 +117,9 @@ function ReviewerRoutingPanel({
<MetricPill label="待审变更" value={String(pendingReviewCount)} /> <MetricPill label="待审变更" value={String(pendingReviewCount)} />
<MetricPill label="高风险" value={String(routings.summary.highRiskCount)} /> <MetricPill label="高风险" value={String(routings.summary.highRiskCount)} />
<MetricPill label="缺审查人" value={String(routings.summary.missingReviewerCount)} /> <MetricPill label="缺审查人" value={String(routings.summary.missingReviewerCount)} />
<MetricPill label="待我审" value={String(routings.items.filter((item) => (item.approvalState || "not_requested") === "not_requested").length)} />
<MetricPill label="已请求" value={String(routings.summary.requestedReviewCount || 0)} />
<MetricPill label="已决策" value={String((routings.summary.approvedCount || 0) + (routings.summary.changesRequestedCount || 0) + (routings.summary.rejectedCount || 0))} />
</div> </div>
</div> </div>
{routings.status === "warning" ? ( {routings.status === "warning" ? (
@ -139,6 +143,9 @@ function ReviewerRoutingPanel({
<span className="rounded-lg bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-600"> <span className="rounded-lg bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-600">
{priorityLabel(routing.priority)} {priorityLabel(routing.priority)}
</span> </span>
<span className={cn("rounded-lg px-2 py-1 text-xs font-semibold", reviewDecisionTone(routing.approvalState))}>
{reviewDecisionLabel(routing.approvalState)}
</span>
</div> </div>
<h3 className="mt-3 text-base font-semibold text-slate-800">{routing.changeSetTitle}</h3> <h3 className="mt-3 text-base font-semibold text-slate-800">{routing.changeSetTitle}</h3>
<p className="mt-2 text-sm leading-6 text-slate-500">{routing.reviewReason}</p> <p className="mt-2 text-sm leading-6 text-slate-500">{routing.reviewReason}</p>
@ -164,6 +171,16 @@ function ReviewerRoutingPanel({
证据 {routing.evidenceCount} 证据 {routing.evidenceCount}
</span> </span>
</div> </div>
{routing.decisionReason ? (
<div className="mt-3 rounded-lg border border-[#e2e9f5] bg-white px-3 py-2 text-xs leading-5 text-slate-500">
审查意见:{routing.decisionReason}
</div>
) : null}
{routing.requestedAt || routing.decidedAt ? (
<div className="mt-2 text-xs text-slate-400">
{routing.decidedAt ? `决策于 ${new Date(routing.decidedAt).toLocaleString("zh-CN")}` : `请求于 ${new Date(routing.requestedAt || "").toLocaleString("zh-CN")}`}
</div>
) : null}
<div className="mt-4 space-y-2"> <div className="mt-4 space-y-2">
<div className="text-xs font-semibold text-slate-400">建议审查人</div> <div className="text-xs font-semibold text-slate-400">建议审查人</div>
{routing.suggestedReviewer ? ( {routing.suggestedReviewer ? (
@ -469,6 +486,22 @@ function priorityLabel(priority: PMFlowReviewerRoutingItem["priority"]) {
return "常规"; return "常规";
} }
function reviewDecisionLabel(state: PMFlowReviewerRoutingItem["approvalState"]) {
if (state === "requested") return "已请求审查";
if (state === "approved") return "已通过";
if (state === "changes_requested") return "要求修改";
if (state === "rejected") return "拒绝";
return "待我审";
}
function reviewDecisionTone(state: PMFlowReviewerRoutingItem["approvalState"]) {
if (state === "approved") return "bg-emerald-50 text-emerald-600";
if (state === "changes_requested") return "bg-orange-50 text-orange-600";
if (state === "rejected") return "bg-red-50 text-red-600";
if (state === "requested") return "bg-[#eef4ff] text-[#2463EB]";
return "bg-slate-100 text-slate-600";
}
function reviewerRoutingWarningText(code: string | null | undefined, warnings: string[]) { function reviewerRoutingWarningText(code: string | null | undefined, warnings: string[]) {
if (code === "reviewer_missing") return "当前项目缺少 REVIEWER/OWNER,建议先补充项目审查人。"; if (code === "reviewer_missing") return "当前项目缺少 REVIEWER/OWNER,建议先补充项目审查人。";
if (code === "evidence_missing") return "待审变更缺少足够证据,建议回到 Change Review 查看来源。"; if (code === "evidence_missing") return "待审变更缺少足够证据,建议回到 Change Review 查看来源。";
@ -486,6 +519,10 @@ function emptyReviewerRouting(): PMFlowReviewerRouting {
highRiskCount: 0, highRiskCount: 0,
missingReviewerCount: 0, missingReviewerCount: 0,
iterationDraftCount: 0, iterationDraftCount: 0,
requestedReviewCount: 0,
approvedCount: 0,
changesRequestedCount: 0,
rejectedCount: 0,
}, },
}; };
} }

View File

@ -15,6 +15,7 @@ import {
createPMFlowIterationRecommendationChangeSetDraft, createPMFlowIterationRecommendationChangeSetDraft,
createPMFlowReleaseValidationRun, createPMFlowReleaseValidationRun,
createPMFlowReleaseFeedbackIngest, createPMFlowReleaseFeedbackIngest,
createPMFlowReviewDecision,
createPMFlowThreadMessage, createPMFlowThreadMessage,
deferPMFlowChangeItem, deferPMFlowChangeItem,
editPMFlowChangeItem, editPMFlowChangeItem,
@ -55,6 +56,7 @@ vi.mock("@/api/pmflowOs", () => ({
createPMFlowIterationRecommendationChangeSetDraft: vi.fn(), createPMFlowIterationRecommendationChangeSetDraft: vi.fn(),
createPMFlowReleaseValidationRun: vi.fn(), createPMFlowReleaseValidationRun: vi.fn(),
createPMFlowReleaseFeedbackIngest: vi.fn(), createPMFlowReleaseFeedbackIngest: vi.fn(),
createPMFlowReviewDecision: vi.fn(),
editPMFlowChangeItem: vi.fn(), editPMFlowChangeItem: vi.fn(),
deferPMFlowChangeItem: vi.fn(), deferPMFlowChangeItem: vi.fn(),
})); }));
@ -70,6 +72,7 @@ const createIterationRecommendationGroupChangeSetDraftMock = vi.mocked(createPMF
const createIterationRecommendationChangeSetDraftMock = vi.mocked(createPMFlowIterationRecommendationChangeSetDraft); const createIterationRecommendationChangeSetDraftMock = vi.mocked(createPMFlowIterationRecommendationChangeSetDraft);
const createReleaseValidationRunMock = vi.mocked(createPMFlowReleaseValidationRun); const createReleaseValidationRunMock = vi.mocked(createPMFlowReleaseValidationRun);
const createReleaseFeedbackIngestMock = vi.mocked(createPMFlowReleaseFeedbackIngest); const createReleaseFeedbackIngestMock = vi.mocked(createPMFlowReleaseFeedbackIngest);
const createReviewDecisionMock = vi.mocked(createPMFlowReviewDecision);
const acceptItemMock = vi.mocked(acceptPMFlowChangeItem); const acceptItemMock = vi.mocked(acceptPMFlowChangeItem);
const commitChangeSetMock = vi.mocked(commitPMFlowChangeSet); const commitChangeSetMock = vi.mocked(commitPMFlowChangeSet);
const editItemMock = vi.mocked(editPMFlowChangeItem); const editItemMock = vi.mocked(editPMFlowChangeItem);
@ -145,6 +148,8 @@ function reviewerRoutingFixture(): PMFlowReviewerRouting {
itemCount: 2, itemCount: 2,
pendingItemCount: 1, pendingItemCount: 1,
evidenceCount: 1, evidenceCount: 1,
reviewDecision: null,
approvalState: "not_requested",
suggestedReviewer: { suggestedReviewer: {
userId: "9", userId: "9",
displayName: "审查负责人", displayName: "审查负责人",
@ -506,6 +511,16 @@ describe("PMFlowOsProvider real change review actions", () => {
}, },
warnings: [], warnings: [],
}); });
createReviewDecisionMock.mockResolvedValue({
changeSetDetail: {
changeSet: { id: "11", status: "reviewed" },
items: [{ id: "21", status: "accepted" }],
evidence: [],
approvals: [{ id: "91", action: "approve", decision: "approved", changeSetId: "11", changeItemId: "21" }],
},
truthState: realState(),
warnings: [],
});
}); });
afterEach(() => { afterEach(() => {
@ -528,12 +543,39 @@ describe("PMFlowOsProvider real change review actions", () => {
expect(screen.getByText("REVIEWER")).toBeInTheDocument(); expect(screen.getByText("REVIEWER")).toBeInTheDocument();
expect(screen.getByText("下一轮迭代")).toBeInTheDocument(); expect(screen.getByText("下一轮迭代")).toBeInTheDocument();
expect(screen.getByText("证据 1")).toBeInTheDocument(); expect(screen.getByText("证据 1")).toBeInTheDocument();
expect(screen.getAllByText("待我审").length).toBeGreaterThan(0);
fireEvent.click(screen.getByRole("button", { name: /去审查/ })); fireEvent.click(screen.getByRole("button", { name: /去审查/ }));
await waitFor(() => expect(screen.getByTestId("location").textContent).toBe("/change-review?projectId=123&changeSetId=11")); await waitFor(() => expect(screen.getByTestId("location").textContent).toBe("/change-review?projectId=123&changeSetId=11"));
expect(acceptItemMock).not.toHaveBeenCalled(); expect(acceptItemMock).not.toHaveBeenCalled();
expect(commitChangeSetMock).not.toHaveBeenCalled(); expect(commitChangeSetMock).not.toHaveBeenCalled();
}); });
it("records reviewer decisions through the PMFlow OS api in real mode", async () => {
const { result } = renderHook(() => usePMFlowOsStore(), { wrapper: wrapper() });
await waitFor(() => expect(result.current.dataSourceStatus).toBe("success"));
act(() =>
result.current.createReviewDecision("11", {
action: "approve",
reviewer_id: 9,
reason: "reviewer 通过,不自动 commit。",
item_ids: ["21"],
})
);
await waitFor(() =>
expect(createReviewDecisionMock).toHaveBeenCalledWith(
"11",
expect.objectContaining({
action: "approve",
reviewer_id: 9,
item_ids: ["21"],
})
)
);
expect(commitChangeSetMock).not.toHaveBeenCalled();
});
it("keeps reviewer routing empty when payload omits it", async () => { it("keeps reviewer routing empty when payload omits it", async () => {
const state = realState(); const state = realState();
delete state.reviewerRouting; delete state.reviewerRouting;

View File

@ -11,6 +11,7 @@ import {
createPMFlowIterationRecommendationChangeSetDraft, createPMFlowIterationRecommendationChangeSetDraft,
createPMFlowReleaseFeedbackIngest, createPMFlowReleaseFeedbackIngest,
createPMFlowReleaseValidationRun, createPMFlowReleaseValidationRun,
createPMFlowReviewDecision,
createPMFlowThreadMessage, createPMFlowThreadMessage,
deferPMFlowChangeItem, deferPMFlowChangeItem,
editPMFlowChangeItem, editPMFlowChangeItem,
@ -24,6 +25,7 @@ import {
type PMFlowProposedChangeItem, type PMFlowProposedChangeItem,
type PMFlowReleaseFeedbackIngestRequest, type PMFlowReleaseFeedbackIngestRequest,
type PMFlowReleaseValidationRunRequest, type PMFlowReleaseValidationRunRequest,
type PMFlowReviewDecisionRequest,
} from "@/api/pmflowOs"; } from "@/api/pmflowOs";
import { createInitialPMFlowState } from "@/features/pmflow-os/mockData"; import { createInitialPMFlowState } from "@/features/pmflow-os/mockData";
import type { import type {
@ -566,6 +568,7 @@ interface PMFlowOsStoreValue {
groupKey: string, groupKey: string,
payload?: PMFlowIterationRecommendationGroupChangeSetDraftRequest payload?: PMFlowIterationRecommendationGroupChangeSetDraftRequest
) => void; ) => void;
createReviewDecision: (changeSetId: string, payload: PMFlowReviewDecisionRequest) => void;
advanceExecution: () => void; advanceExecution: () => void;
writeBackExecution: (outcome: "passed" | "failed") => void; writeBackExecution: (outcome: "passed" | "failed") => void;
publishRelease: () => void; publishRelease: () => void;
@ -831,6 +834,13 @@ export function PMFlowOsProvider({ children }: { children: ReactNode }) {
} }
mutateDemoOnly((current) => ingestGrowthSignalMutation(current)); mutateDemoOnly((current) => ingestGrowthSignalMutation(current));
}, },
createReviewDecision: (changeSetId, payload) => {
if (dataSource.mode === "api") {
runApiAction(() => createPMFlowReviewDecision(changeSetId, payload));
return;
}
mutateDemoOnly((current) => current);
},
advanceExecution: () => mutateDemoOnly((current) => advanceExecutionMutation(current)), advanceExecution: () => mutateDemoOnly((current) => advanceExecutionMutation(current)),
writeBackExecution: (outcome) => mutateDemoOnly((current) => writeBackExecutionMutation(current, outcome)), writeBackExecution: (outcome) => mutateDemoOnly((current) => writeBackExecutionMutation(current, outcome)),
publishRelease: () => mutateDemoOnly((current) => publishReleaseMutation(current)), publishRelease: () => mutateDemoOnly((current) => publishReleaseMutation(current)),

View File

@ -172,6 +172,11 @@ export interface Approval {
stage: PMFlowStage; stage: PMFlowStage;
status: PMFlowObjectStatus; status: PMFlowObjectStatus;
owner: string; owner: string;
changeSetId?: string;
changeItemId?: string;
action?: string;
decision?: string;
createdAt?: string;
} }
export interface PMFlowFeedEvent { export interface PMFlowFeedEvent {
@ -204,6 +209,8 @@ export type PMFlowReviewerRoutingSourceType = "manual" | "ai_extraction" | "iter
export type PMFlowReviewerRoutingPriority = "normal" | "elevated" | "urgent"; export type PMFlowReviewerRoutingPriority = "normal" | "elevated" | "urgent";
export type PMFlowReviewerConfidence = "low" | "medium" | "high"; export type PMFlowReviewerConfidence = "low" | "medium" | "high";
export type PMFlowFallbackReviewerRole = "OWNER" | "REVIEWER" | "MEMBER"; export type PMFlowFallbackReviewerRole = "OWNER" | "REVIEWER" | "MEMBER";
export type PMFlowReviewDecisionAction = "request_review" | "approve" | "request_changes" | "reject";
export type PMFlowApprovalState = "not_requested" | "requested" | "approved" | "changes_requested" | "rejected";
export interface PMFlowReviewerSuggestion { export interface PMFlowReviewerSuggestion {
userId?: string | null; userId?: string | null;
@ -228,6 +235,11 @@ export interface PMFlowReviewerRoutingItem {
evidenceCount: number; evidenceCount: number;
suggestedReviewer?: PMFlowReviewerSuggestion | null; suggestedReviewer?: PMFlowReviewerSuggestion | null;
fallbackReviewerRole?: PMFlowFallbackReviewerRole | null; fallbackReviewerRole?: PMFlowFallbackReviewerRole | null;
reviewDecision?: PMFlowReviewDecisionAction | null;
approvalState?: PMFlowApprovalState;
requestedAt?: string | null;
decidedAt?: string | null;
decisionReason?: string | null;
targetUrl: string; targetUrl: string;
createdAt?: string | null; createdAt?: string | null;
updatedAt?: string | null; updatedAt?: string | null;
@ -238,6 +250,10 @@ export interface PMFlowReviewerRoutingSummary {
highRiskCount: number; highRiskCount: number;
missingReviewerCount: number; missingReviewerCount: number;
iterationDraftCount: number; iterationDraftCount: number;
requestedReviewCount?: number;
approvedCount?: number;
changesRequestedCount?: number;
rejectedCount?: number;
} }
export interface PMFlowReviewerRouting { export interface PMFlowReviewerRouting {