From d3ef506c9755432125cde3bd1b5f1b2fb26613a4 Mon Sep 17 00:00:00 2001 From: Albert Date: Wed, 6 May 2026 00:30:55 +0800 Subject: [PATCH] feat(pmflow-os): redesign execution run workspace --- docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md | 47 +- docs/architecture/ARCHITECTURE_INVENTORY.md | 2 +- docs/architecture/CODE_REFERENCE_INDEX.md | 4 +- docs/architecture/RUNTIME_GUARDRAILS.md | 2 +- .../pmflow-os/pages/ExecutionBridgePage.tsx | 990 ++++++++++++++++-- .../src/features/pmflow-os/store.api.test.tsx | 16 +- 6 files changed, 989 insertions(+), 72 deletions(-) diff --git a/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md b/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md index 0f76a5c8..f6bfdeb9 100644 --- a/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md +++ b/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md @@ -451,6 +451,7 @@ PMFlow 允许多个 Codex 线程并行推进。产品上可以按“大模块 la - `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md` - `docs/architecture/CODE_REFERENCE_INDEX.md` - `docs/architecture/ARCHITECTURE_INVENTORY.md` +- `docs/architecture/RUNTIME_GUARDRAILS.md` - `docs/architecture/API_CONTRACTS.md` ### 4.4.2 当前大模块 lane 划分 @@ -765,13 +766,14 @@ commit gate 判定: 当前已完成: -**main-flow-d4-execution-runtime-capability:Execution Capability Graph 接入开发执行页。** +**main-flow-d4.5-execution-run-workspace-redesign:开发执行 Run 工作台重构。** 原因: -- `/execution-bridge` 已从字段调试页推进到产品化开发执行工作面,并进一步消费 `execution-capabilities`,把 `AI 直接执行 / AI 执行 + 用户验收 / AI 执行 + 技术审查 / 需要人类先补判断 / 当前不可执行` 变成首屏执行模式判定。 -- PMFlow OS store 已把 `GET /pmflow-os/projects/{project_id}/execution-capabilities` 纳入共享状态,执行交接除正式 Task gate 外,也会在 reviewer 缺失时阻断 handoff。 -- `/execution-bridge` 已展示“本次执行结果”汇总,把运行端、代码证据、测试证据、阻塞与下一步收敛成验收前判断,不把缺证据状态表达成发布成功。 +- `/execution-bridge` 已从模块堆叠重构为 `Execution Run Workspace`:顶部 Run Header、阶段 rail、左侧执行源包、中间当前阶段工作台、右侧 AI 决策面板、底部证据流水。 +- 执行页现在明确表达 `已确认需求 / 上下文与 Prompt 包 / 开发 / 测试 / 审查合并 / 回写` 的业务流转,所有主按钮由单点 Run 状态模型推导,避免散落按钮各自判断。 +- 手动回写从常驻表单收进 drawer;页面主界面优先展示阶段、源头、当前证据和下一步,不再像 dev-sync 调试录入台。 +- 本轮只改前端信息架构和测试,复用既有 `execution-summary / execution-capabilities / execution-progress-events`,不新增后端合同,不执行命令,不自动验收、合并或写 Project Memory。 ### 7.1 下一步推荐 Packet:`main-flow-d5-execution-acceptance-loop` @@ -806,6 +808,43 @@ commit gate 判定: ## 8. 进度台账 +### 2026-05-06 / main-flow-d4.5-execution-run-workspace-redesign / implemented + +完成内容: + +- 将 `/execution-bridge` 重构为开发执行 Run 工作台:首屏从“能力卡片 + 结果卡片 + 回写表单”改为 Run Header、阶段流转、执行源包、当前阶段工作台和 AI 副驾决策区。 +- 新增单点 Run 状态模型,统一推导 `已确认需求 / 上下文包 / 开发 / 测试 / 审查合并 / 回写` 六阶段、当前阶段、证据数、阻塞原因和唯一主动作。 +- 左侧执行源包固定展示正式 Task、项目记忆节点、Prompt Package、仓库、负责人和状态,强调开发必须基于已确认需求与 Project Memory。 +- 中间工作区按当前阶段展示上下文、开发证据、测试、审查合并或回写内容;手动回写进展从常驻表单改为 drawer,避免页面表单化。 +- 右侧 AI 副驾改为阶段决策面板,围绕当前主动作、阶段审查重点和下一步建议工作;仍不调用新 LLM,不自动合并、不自动写 Project Memory。 +- 焦点测试覆盖执行 Run 工作台渲染、阶段 rail、执行源包、AI 副驾、主按钮和回写 drawer。 + +涉及文件: + +- `frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx` +- `frontend/src/features/pmflow-os/store.api.test.tsx` +- `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md` +- `docs/architecture/CODE_REFERENCE_INDEX.md` +- `docs/architecture/ARCHITECTURE_INVENTORY.md` + +验证: + +- `cd frontend && npx tsc --noEmit --pretty false --incremental false` 通过。 +- `cd frontend && npm run test -- src/features/pmflow-os/store.api.test.tsx -t "execution run workspace|technical review|execution bridge"` 通过:2 passed,32 skipped。 +- `cd frontend && npm run test -- src/features/pmflow-os` 通过:35 passed;仍有既有 React Router future flag warning。 +- `cd frontend && npm run build` 通过。 +- Playwright / Chromium 打开 `http://localhost:5177/execution-bridge?demo=1`,桌面 1440x1100 与移动 390x844 均能渲染 Run 工作台、阶段流转、执行源包、AI 副驾与“补充测试结论”主按钮;移动端 `main` 容器无横向溢出,截图输出到 `output/playwright/execution-run-workspace-d45-desktop.png`、`output/playwright/execution-run-workspace-d45-mobile.png` 与 `output/playwright/execution-run-workspace-d45-mobile-main-scrolled.png`。 + +未完成或风险: + +- 本轮没有新增真实验收、技术审查、合并或 Project Memory 回写后端合同;这些仍属于 `main-flow-d5-execution-acceptance-loop`。 +- `Execution Run` 阶段仍由现有 `execution-summary` 与 `ProgressEvent` 派生,后续 D5 需要把 `review / acceptance / merge / memory_writeback` 做成更稳定的后端状态合同。 +- 浏览器实测仍会出现项目壳既有 `GET /api/ai-readiness/project-launch` 401,本地 demo 的 Run 工作台渲染与主流程不受影响。 + +下一步建议: + +- 进入 `main-flow-d5-execution-acceptance-loop`:补真实用户验收、技术审查、合并确认和项目记忆回写状态,供 Space next action 与 Release/Growth 门禁稳定消费。 + ### 2026-05-05 / main-flow-d4-execution-runtime-capability / implemented 完成内容: diff --git a/docs/architecture/ARCHITECTURE_INVENTORY.md b/docs/architecture/ARCHITECTURE_INVENTORY.md index de273ea8..20d49fed 100644 --- a/docs/architecture/ARCHITECTURE_INVENTORY.md +++ b/docs/architecture/ARCHITECTURE_INVENTORY.md @@ -213,7 +213,7 @@ - `frontend/src/features/pmflow-os/store.tsx` 当前是 PMFlow OS 共享 truth store:`?projectId=` 时通过 React Query 调用 `frontend/src/api/pmflowOs.ts` 读取真实 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,真实模式下项目内 `sendMessage` 会写 thread message 并触发后端真实 AI extraction;显式 `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 成功;AI extraction 失败显示“结构化失败/待补充”,不创建模板 ChangeSet。 - `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` 进入时通过 `origin=change_review / returnTo=change_review / changeSetId / itemId` 保留审查上下文,主动作返回待确认队列,补充证据才回指挥台;已 committed 的轮次显示为结构化写入快照并转向项目记忆或新变更链路。`ChangeReviewPage.tsx` 是决策与待确认中心,真实模式下顶部识别统计、待确认列表与右侧 AI 副驾评估都只消费后端 `changeSets / changeItems / evidence`,没有真实待审项时显示空态,禁止用 demo 审查项补位;确认写入 Project Memory 前,`pending_review / accepted / edited / deferred` ChangeItem 均可继续 accept/edit/defer,只有 `committed / skipped` 或关闭轮次不可再操作;`MemoryWritebackResultPage.tsx` 是项目记忆写入结果页,`WorkGraphPage.tsx` 是项目工作台关系总览与对象投影视图,`ExecutionBridgePage.tsx` 是执行桥上下文包、交接按钮、运行端领取确认、PR/测试与回写状态页;`ReleaseGrowthPage.tsx` 真实模式读取 `execution-summary.releaseGate`、`latestValidationRun` 与 `releaseSignals`,从 branch、commit、PR、test、failed evidence 展示 `可发布 / 阻塞 / 需要人工确认 / 缺少证据` 判断、阻塞原因、下一步动作、发布验证记录和发布后 feedback/revenue/ROI/compliance/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 extraction 已接入独立 `change_review` protected scene、`pmflow_change_review_extraction_v1` 运行合同、`PROMPT_PMFLOW_CHANGE_REVIEW_EXTRACTION` Prompt 资产与后端 `llm_service.generate_structured_object`,成功时生成 open ChangeSet、pending_review ChangeItems 与 thread message evidence,失败时 fail-closed,不把 mock 当真实失败 fallback,也不复用 Project Launch draft prompt/function。`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` 表达“未确认不得写入”的局部提示。 - Execution Result Protocol P1 将 Execution Bridge 的执行结果回写收敛为 `execution_result_protocol_v1`:`backend/app/schemas/pmflow_os.py` 接收 `execution_status` 与 `blocker_summary`,`backend/app/services/pmflow_os_projection_service.py` 写入 `ProgressEvent.payload.protocol_version` 与 `execution_result` envelope,同时保留 `branch_name / commit_sha / pr_number / test_summary / workflow_state` flat 字段兼容现有投影;`frontend/src/api/pmflowOs.ts` 和 `ExecutionBridgePage.tsx` 支持手动记录 status/blocker。该链路只记录 Codex/IDE/Local Bridge/GitHub agent 已产生的结果,不执行本机命令、不推送、不发布、不自动创建正式任务;`execution-summary`、`releaseGate` 与 Team Feed 均从 `ProgressEvent` 只读消费。 -- Main Flow D4 已把 Execution Capability Graph 接入 `/execution-bridge`:`frontend/src/features/pmflow-os/store.tsx` 现在读取并刷新真实 `GET /pmflow-os/projects/{project_id}/execution-capabilities`,`ExecutionBridgePage.tsx` 首屏展示执行模式判定、本次执行结果、执行链路、上下文包、回写视图和 AI 执行裁决建议;handoff 主按钮受正式 Task、待确认范围、repo/prompt package 与 reviewer gate 约束,技术审查责任缺失时 store 会阻止真实 handoff,缺 repo 时只允许表达交接草案。该层仍只做能力判定、交接合同和结果回写展示,不执行本机命令、不触发真实 agent、不自动验收、不自动发布。 +- Main Flow D4/D4.5 已把 Execution Capability Graph 接入并重构 `/execution-bridge` 为开发执行 Run 工作台:`frontend/src/features/pmflow-os/store.tsx` 读取并刷新真实 `GET /pmflow-os/projects/{project_id}/execution-capabilities`,`ExecutionBridgePage.tsx` 通过单点 Run 状态模型推导 `已确认需求 / 上下文包 / 开发 / 测试 / 审查合并 / 回写` 阶段、当前主动作、证据数和阻塞原因;页面结构为 Run Header、阶段 rail、执行源包、当前阶段工作台、AI 阶段决策面板和底部证据流水。handoff 主按钮受正式 Task、待确认范围、repo/prompt package 与 reviewer gate 约束,技术审查责任缺失时 store 会阻止真实 handoff,缺 repo 时只允许表达交接草案;手动回写在 drawer 中记录已发生的 branch/commit/PR/test/blocker/status。该层仍只做能力判定、交接合同和结果回写展示,不执行本机命令、不触发真实 agent、不自动验收、不自动合并、不自动发布。 - Project Knowledge Hub 读模型由 `GET /pmflow-os/projects/{project_id}/project-memory/overview`、`items`、`items/{item_id}`、`assets`、`context-packs`、`writebacks` 承载,后端在 `backend/app/services/pmflow_os_projection_service.py` 从 `truth_nodes / tasks / semantic_facts / pmflow_evidence_links / pmflow_thread_messages / pmflow_change_sets / pmflow_change_items` 聚合首页雷达、全部记忆、项目资产、上下文包和写入版本记录。前端对应 `ProjectMemoryPage.tsx`、`ProjectMemoryCollectionPages.tsx` 与 `projectMemoryUtils.ts`:列表时间使用短格式,抽屉显示完整时间和北京时间;首页卡片与二级列表单条点击打开右侧详情抽屉,展示来源证据、影响对象和 AI 执行动作跳转;全部记忆支持类型、阶段、来源、状态、搜索筛选。该读模型只读,资产可自动索引,进入项目真相层仍必须经过 Change Review 或可信规则确认。 - `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`;P8 新增 `getReviewDecisionNotificationPreference(settings, event)` 覆盖 `review_requested / review_approved / review_changes_requested / review_rejected / high_risk_approval_required`,继续使用 `pendingConfirmation` 偏好和 `change_review` surface,并声明 reviewerRouting、approval、evidence、audit state 均必须保留。该合同只控制页面内提醒表达,不代表邮件、桌面推送或系统通知中心已接入;偏好关闭也不得隐藏业务证据、reviewerRouting、approval、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=` 时通过 React Query 调用 `frontend/src/api/pmflowOs.ts` 读取真实 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,真实模式下项目内 `sendMessage` 会写 thread message 并触发后端真实 AI extraction;显式 `proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、review decision、AI feedback、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest 与 iteration recommendation -> ChangeSet draft 均调用 `/pmflow-os` 后端并 refetch;AI feedback 写入 `pmflow_ai_feedback_events`,在 `truth-state.aiFeedback` 恢复当前用户选择,并由后端按 `prompt_key` 生成待复核 `JudgeRun / OptimizerCandidate` 质量优化候选,用户改回赞同时挂起此前候选,Prompt Asset quality summary 会聚合 PMFlow 反馈与优化候选;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功;AI extraction 失败显示“结构化失败/待补充”,不创建模板 ChangeSet。 diff --git a/docs/architecture/CODE_REFERENCE_INDEX.md b/docs/architecture/CODE_REFERENCE_INDEX.md index 28a57405..3db45014 100644 --- a/docs/architecture/CODE_REFERENCE_INDEX.md +++ b/docs/architecture/CODE_REFERENCE_INDEX.md @@ -126,9 +126,9 @@ - 个人空间首页 / 空间层入口前端产品域:参考 `frontend/src/features/personal-space/`、`frontend/src/api/space.ts` 与薄入口 `frontend/src/pages/PersonalSpacePage.tsx`;登录后路由入口为 `/space`,未登录由 `frontend/src/auth/RouteBoundary.tsx` 跳转 `/login?redirect=/space`;首屏以 `nextActionQueue` 展示“我的下一步”,并保留执行准备度、继续推进、待我确认、发布关注、发布后反馈关注、下一轮迭代草稿关注、执行中 / AI 正在推进、暂存想法、草案箱、缺项目归属补全与最近项目明细;红框三块在首页只作为工作雷达预览,暂存想法、草案箱、最近项目均最多露出少量高优先级条目,完整处理分别进入 `/space/ideas`、`/space/drafts` 与 `/space/projects?view=recent`。`frontend/src/features/personal-space/SpaceCollectionPages.tsx` 承载二级页:`/space/ideas` 支持暂存想法筛选、搜索、补充、归档与转 Project Launch 草案,`/space/drafts` 支持待确认草案筛选、搜索与回主流程确认;这些页面仍使用空间壳,不直接写项目真相层。Space 同时展示 AI readiness 客户视图卡片,管理员可从侧边栏或顶部状态进入 `/ai-config?tab=setup`。空间级 AI 管家在 `frontend/src/features/personal-space/components/SpaceAIConcierge.tsx`,入口只挂 `/space`,向现有 `/rag/ask` 注入个人空间上下文包并复用 QA session 历史能力;它只能围绕个人空间、跨项目下一步、待确认、执行中、草案、暂存想法和 AI 能力状态工作,真实写操作必须回主流程确认或跳转,不得声称已执行。全局 `Layout` 不再挂载旧 `FloatingQAChat` 悬浮球,普通后台页不显示无上下文聊天球,项目页继续使用项目级 `AI 副驾`。Space 左侧导航只承载稳定空间目的地,当前保留“空间首页”“暂存想法”“草案箱”“所有项目”和管理员“AI 使用设置”。点击项目主动作进入 `/pmflow-os?projectId=`,待确认项进入 `/change-review?projectId=&changeSetId=`,review attention 待审项进入 `/change-review?projectId=&changeSetId=`,reviewer 缺失 warning 进入已有 `/team-layer?projectId=`,执行中与执行准备度项进入 `/execution-bridge?projectId=`,发布关注和发布后反馈关注项进入 `/release-growth?projectId=`;执行失败和回写失败项进入 `/execution-bridge?projectId=`;Space 聚合项目摘要、PMFlow OS `truth-state.reviewerRouting / truth-state.releases / execution-summary.releaseGate / execution-summary.releaseSignals / execution-summary.recentProgressEvents / execution-summary.recentDeliveries / execution-capabilities` 和真实 `GET /space/saved-ideas`,并兼容读取旧 AI PMO `waiting_manual / draft_pending_confirm` 工作项;`missing_project_scope` 仅在 Space 选择项目后复用 `POST /ai-pmo/work-items/{id}/decision`,不新增旁路接口,不直接写项目真相层。缺数据时显示明确空态或 warning,不虚构 reviewer、审查意见、待审 ChangeSet、执行能力、工具连接、验证、反馈、收入、ROI、合规、iteration recommendation 或 ChangeSet draft;暂存想法是唯一允许 Space 写入的 pre-project seed,保存原始输入、来源、状态、准备度和 draft 关联,生成草案只进入 Project Launch human review;除此之外不直接写 Project Memory、ChangeSet 状态、PMFlowApproval、execution event、ReleaseValidationRun、FeedbackIngest、RevenueEvent 或 ComplianceCheckRun;空间层共享左侧栏、顶部条与空间用量组件在 `frontend/src/features/personal-space/components/SpaceChrome.tsx`,`/project-launch` 桌面首次新建壳也复用该空间 Chrome - 新项目启动台 / 首次进入页前端产品域:参考 `frontend/src/features/project-launch/` 与薄入口 `frontend/src/pages/ProjectLaunchPage.tsx`;真实模式登录后路由入口为 `/project-launch`,默认通过 `frontend/src/api/pmflowLaunch.ts` 调用后端 taskized draft job/confirm API;AI 草案创建链路是 `POST /pmflow-launch/draft-jobs -> GET /pmflow-launch/draft-jobs/{job_id} -> POST /pmflow-launch/drafts/{draft_id}/confirm`,补充一句走 `POST /pmflow-launch/drafts/{draft_id}/revise` 或创建新的 `merge_supplement` job,旧 `POST /ai-steward/project-from-text` 只返回 410 迁移错误;只有显式 `?demo=1` 或 `?dataSource=demo` 才允许匿名并启用 `mockData.ts` 本地演示状态机,真实模式的操作流、生成过程、审查建议、附件引用和计数从当前输入、job 状态与后端返回草案派生,不再使用周报 demo 文案兜底;真实模式下 AI 未就绪时先渲染 `AISetupRequiredPanel`,按权限展示记录想法、查看空间状态或平台 AI 能力处理 CTA,不提供空项目创建入口,避免在缺少结构化上下文时提前创建项目壳。401/无 token 通过 `frontend/src/lib/apiError.ts` 归一为 `auth_required/session_expired`,通过 `frontend/src/lib/pendingIntent.ts` 保留输入;`input_needs_more_info` 422 归一为 `input_invalid` 并停在补充输入失败态,用户可见失败态不得展示 `Request failed with status code 401` 或把 401 归为网络异常 - 前端身份边界与产品化错误:参考 `frontend/src/auth/RouteBoundary.tsx`、`frontend/src/lib/rbac.ts`、`frontend/src/lib/apiError.ts`、`frontend/src/lib/pendingIntent.ts`;`/space`、真实 `/project-launch`、`/me`、主链 standalone、空间管理入口与平台运营入口均在前端边界收口。空间管理入口使用 workspace role,`/platform/*` 和平台治理入口使用 `platform_context.capabilities`,后端 JWT/RBAC 仍是最终校验;pending intent 只能恢复 project launch 输入或承接已经生成好的 Project Launch draft,不允许登录后自动重放真实写请求 -- PMFlow OS 共享 truth store 与主链状态动作:参考 `frontend/src/features/pmflow-os/store.tsx`;当前支持 `?projectId=` 走 React Query 读取真实 `truth-state`、`execution-summary` 与 `execution-capabilities`,真实模式下 `sendMessage` 会先写 thread message,再调用 `createPMFlowExtraction` 触发后端真实 AI extraction,成功后 refetch 出 open ChangeSet / pending_review ChangeItems;显式 `proposed_items` extraction 仍保留给受控 draft 场景;ChangeItem `accept/edit/defer`、ChangeSet commit、review decision、AI feedback、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;D4 后 `createExecutionHandoff` 除正式 Task gate 外会读取 `execution-capabilities`,在技术审查责任缺失时阻止 handoff,避免把 agent 执行绕过 reviewer gate;AI feedback 通过 `createPMFlowAIFeedback` 写入 `pmflow_ai_feedback_events` 并在 `truth-state.aiFeedback` 恢复当前用户选择;AI extraction 失败在 CommandCenter / StructuredExtraction 显示“结构化失败/待补充”,不展示模板假候选;`?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=` 走 React Query 读取真实 `truth-state`、`execution-summary` 与 `execution-capabilities`,真实模式下 `sendMessage` 会先写 thread message,再调用 `createPMFlowExtraction` 触发后端真实 AI extraction,成功后 refetch 出 open ChangeSet / pending_review ChangeItems;显式 `proposed_items` extraction 仍保留给受控 draft 场景;ChangeItem `accept/edit/defer`、ChangeSet commit、review decision、AI feedback、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;D4 后 `createExecutionHandoff` 除正式 Task gate 外会读取 `execution-capabilities`,在技术审查责任缺失时阻止 handoff,避免把 agent 执行绕过 reviewer gate;D4.5 后 `/execution-bridge` 的主按钮由页面级 Run 状态模型消费 store 数据统一推导,不再散落在上下文包、回写视图和 AI 副驾卡片里;AI feedback 通过 `createPMFlowAIFeedback` 写入 `pmflow_ai_feedback_events` 并在 `truth-state.aiFeedback` 恢复当前用户选择;AI extraction 失败在 CommandCenter / StructuredExtraction 显示“结构化失败/待补充”,不展示模板假候选;`?demo=1` 才启用本地 mock 演示状态机和本地动作;项目内 AI 结构化闭环状态机为 `command_idle / ai_extracting / extraction_ready / review_open / writeback_success` - Account Settings / `/me` 前端产品域与 P1-P8 偏好合同:参考 `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`;P8 新增 `getReviewDecisionNotificationPreference(settings, event)` 覆盖 `review_requested / review_approved / review_changes_requested / review_rejected / high_risk_approval_required`,统一使用 `pendingConfirmation` 偏好和 `change_review` surface,返回 `reviewerRoutingPolicy=preserve_reviewer_routing / approvalPolicy=preserve_approval_state`,偏好关闭只静默页面内提醒,不隐藏 reviewerRouting、approval、evidence 或 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`;`StructuredExtractionPage.tsx` 根据 `origin=change_review / returnTo=change_review / changeSetId / itemId` 区分指挥台读取检查、待确认证据检查和已写入快照,从待确认进入时主动作必须返回 `/change-review` 原轮次,不把用户强制带回指挥台;`ChangeReviewPage.tsx` 真实模式可对当前变更集请求审查、通过、要求修改或拒绝,展示审查状态、建议审查人、审查意见和高风险审批门禁,但不自动 commit,且在确认写入 Project Memory 前允许 `pending_review / accepted / edited / deferred` ChangeItem 继续 accept/edit/defer;`ExecutionBridgePage.tsx` 真实模式读取 `execution-summary` 与 `execution-capabilities`,首屏展示执行模式判定、本次执行结果、执行链路、上下文包、回写视图和 AI 执行裁决建议;handoff 主按钮必须受正式 Task、待确认范围、repo/prompt package 与 reviewer gate 约束,缺 repo 只能生成交接草案,技术审查责任缺失时不能发起 handoff;执行结果只汇总运行端、PR/commit、测试、阻塞和下一步,不把测试通过自动视为验收完成;`TeamLayerPage.tsx` 真实模式读取 `state.reviewerRouting`、`state.feed` 与 `execution-capabilities`,以 `团队动态 / 决策台 / 角色收件箱 / 智能体工位 / AI 副驾` 展示团队协作态势、待审变更责任路由、执行能力与容量信号、个人执行准备度和工具准备度,只给责任建议、阻塞解释与跳转,不自动分派、不自动审查、不写 ChangeSet 或 Project Memory;决策台与待审路由卡片不得预留独立空白操作列,正文应使用完整卡片宽度,1-2 条路由按双列或单列密度展示;`ReleaseGrowthPage.tsx` 真实模式展示 `execution-summary.releaseGate`、execution evidence、阻塞原因、下一步动作、`latestValidationRun` 和 `releaseSignals`,缺证据或缺发布后信号时显示 empty/warning,不展示 mock 发布结果;页面可调用真实 API 创建 release validation run、摄取 release feedback,并展示真实 feedback、revenue、ROI、compliance 与 recommendation 风险态,从真实 recommendation 单条或同组批量生成下一轮待确认 ChangeSet draft;生成后仍进入 Change Review accept/edit/defer,不自动 commit、不写 Project Memory、不执行部署命令、不接外部支付、不接真实合规平台;所有真实证据和审计状态仍可见,静态高保真发布/增长数据仅保留在显式 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`;`StructuredExtractionPage.tsx` 根据 `origin=change_review / returnTo=change_review / changeSetId / itemId` 区分指挥台读取检查、待确认证据检查和已写入快照,从待确认进入时主动作必须返回 `/change-review` 原轮次,不把用户强制带回指挥台;`ChangeReviewPage.tsx` 真实模式可对当前变更集请求审查、通过、要求修改或拒绝,展示审查状态、建议审查人、审查意见和高风险审批门禁,但不自动 commit,且在确认写入 Project Memory 前允许 `pending_review / accepted / edited / deferred` ChangeItem 继续 accept/edit/defer;`ExecutionBridgePage.tsx` 真实模式读取 `execution-summary` 与 `execution-capabilities`,D4.5 后页面形态为开发执行 Run 工作台:Run Header、`已确认需求 / 上下文包 / 开发 / 测试 / 审查合并 / 回写` 阶段 rail、左侧执行源包、中间当前阶段工作台、右侧 AI 阶段决策面板和底部证据流水;主动作由单点 Run 状态模型推导,必须受正式 Task、待确认范围、repo/prompt package 与 reviewer gate 约束,缺 repo 只能生成交接草案,技术审查责任缺失时不能发起 handoff;手动回写在 drawer 内记录已发生的 branch/commit/PR/test/blocker/status,不执行命令;执行结果只汇总运行端、PR/commit、测试、阻塞和下一步,不把测试通过自动视为验收完成;`TeamLayerPage.tsx` 真实模式读取 `state.reviewerRouting`、`state.feed` 与 `execution-capabilities`,以 `团队动态 / 决策台 / 角色收件箱 / 智能体工位 / AI 副驾` 展示团队协作态势、待审变更责任路由、执行能力与容量信号、个人执行准备度和工具准备度,只给责任建议、阻塞解释与跳转,不自动分派、不自动审查、不写 ChangeSet 或 Project Memory;决策台与待审路由卡片不得预留独立空白操作列,正文应使用完整卡片宽度,1-2 条路由按双列或单列密度展示;`ReleaseGrowthPage.tsx` 真实模式展示 `execution-summary.releaseGate`、execution evidence、阻塞原因、下一步动作、`latestValidationRun` 和 `releaseSignals`,缺证据或缺发布后信号时显示 empty/warning,不展示 mock 发布结果;页面可调用真实 API 创建 release validation run、摄取 release feedback,并展示真实 feedback、revenue、ROI、compliance 与 recommendation 风险态,从真实 recommendation 单条或同组批量生成下一轮待确认 ChangeSet draft;生成后仍进入 Change Review accept/edit/defer,不自动 commit、不写 Project Memory、不执行部署命令、不接外部支付、不接真实合规平台;所有真实证据和审计状态仍可见,静态高保真发布/增长数据仅保留在显式 demo 模式 - 团队层只读动态与协作态势:`TeamLayerPage.tsx` 直接消费 `ProjectTruthState.feed` 聚合的 review request、review decision、变更集状态、execution progress 与 release gate 事件,并结合 `reviewerRouting`、`execution-capabilities` 和容量计划生成首屏摘要;前端只做空态、摘要、中文化展示和跳转,不写审批、不写 ChangeSet、不写通知,也不在真实模式 fallback 到 mock 动态。 - 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` diff --git a/docs/architecture/RUNTIME_GUARDRAILS.md b/docs/architecture/RUNTIME_GUARDRAILS.md index cb714c80..1d53cf9f 100644 --- a/docs/architecture/RUNTIME_GUARDRAILS.md +++ b/docs/architecture/RUNTIME_GUARDRAILS.md @@ -47,7 +47,7 @@ - 技术层 fallback 仅允许用于重试、缓存、解析修复、传输兼容等基础设施环节,禁止上浮为用户可见业务结论。 - AI Steward 与其它生成链的成功/失败判定必须优先使用结构校验、字段完整性和证据一致性。 - PMFlow OS `ChangeReviewPage` 真实模式只能展示 `ProjectTruthState.changeSets / changeItems / evidence` 的后端投影;顶部统计、待确认列表和右侧 AI 副驾评估都必须由真实 ChangeItem/Evidence 派生。没有真实待审项时必须显示空态或待补充,禁止用 demo 的知识库搜索、混合召回、固定风险/建议/计数补位。`accepted / edited / deferred` 只表示候选项已被人工处理但尚未写入 Project Memory;只要 ChangeSet 未 committed 且 ChangeItem 未 committed/skipped,就必须允许继续 accept/edit/defer,真正锁定边界是 ChangeSet commit。 -- PMFlow OS 团队审查与执行边界必须保持单向主链:`TeamLayerPage` 只允许消费 `reviewerRouting / reviewDecision / approvalState / TeamFeed` 做责任路由、角色收件箱、团队动态、审查状态、阻塞解释和跳转,不得直接执行 ChangeSet commit、写 Project Memory 或成为第二套 Change Review;Execution Bridge handoff 只能针对 Project Memory commit 后已进入正式项目对象的 Task,禁止从 `pending_review` ChangeItem、候选 Task、reviewer suggestion 或 team routing item 直接创建正式执行分派。缺 reviewer、repo/runtime、PromptPackage、测试命令、权限或回写证据时必须返回 blocked/warning,禁止冒充已开始开发或已分配成功。 +- PMFlow OS 团队审查与执行边界必须保持单向主链:`TeamLayerPage` 只允许消费 `reviewerRouting / reviewDecision / approvalState / TeamFeed` 做责任路由、角色收件箱、团队动态、审查状态、阻塞解释和跳转,不得直接执行 ChangeSet commit、写 Project Memory 或成为第二套 Change Review;Execution Bridge handoff 只能针对 Project Memory commit 后已进入正式项目对象的 Task,禁止从 `pending_review` ChangeItem、候选 Task、reviewer suggestion 或 team routing item 直接创建正式执行分派。`ExecutionBridgePage` 的 Run 工作台只能把 `已确认需求 / 上下文包 / 开发 / 测试 / 审查合并 / 回写` 作为前端流转表达和证据 gate,不得因为 UI 阶段推进而自动验收、自动合并、自动写 Project Memory 或自动进入发布成功。缺 reviewer、repo/runtime、PromptPackage、测试命令、权限或回写证据时必须返回 blocked/warning,禁止冒充已开始开发或已分配成功。 ### 3.3 业务动作归因主链 - 运行时业务动作归因必须优先走: diff --git a/frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx b/frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx index fe098aa5..7917bdd0 100644 --- a/frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx +++ b/frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx @@ -20,6 +20,7 @@ import { ShieldCheck, Terminal, TestTube2, + X, } from "lucide-react"; import type { @@ -93,6 +94,55 @@ const TAB_ITEMS: Array<{ key: ExecutionTabKey; label: string }> = [ type ExecutionTabKey = "changes" | "terminal" | "commits" | "pr" | "tests" | "writeback"; type PipelineTone = "done" | "current" | "waiting" | "blocked"; type CapabilityTone = "ready" | "partial" | "blocked" | "unknown"; +type ExecutionRunStageKey = "source" | "package" | "development" | "testing" | "review_merge" | "memory_writeback"; +type ExecutionStageTone = "done" | "current" | "waiting" | "blocked"; + +interface ExecutionRunStage { + key: ExecutionRunStageKey; + label: string; + summary: string; + tone: ExecutionStageTone; + evidenceCount: number; + blocker?: string | null; +} + +interface ExecutionRunAction { + label: string; + reason: string; + kind: + | "change_review" + | "team_layer" + | "create_handoff" + | "consume_handoff" + | "open_writeback" + | "review" + | "release" + | "disabled"; + enabled: boolean; + variant: "primary" | "warning" | "danger" | "muted"; +} + +interface ExecutionRunState { + task: PMFlowDevTask | null; + formalTask: PMFlowDevTask | null; + repo: PMFlowRepoBinding | null; + prompt: PMFlowExecutionSummary["promptPackage"]; + mode: PMFlowExecutionModeCapability | null; + currentStage: ExecutionRunStage; + stages: ExecutionRunStage[]; + primaryAction: ExecutionRunAction; + result: ReturnType; + hasHandoff: boolean; + hasConsumed: boolean; + hasCommit: boolean; + hasPr: boolean; + hasTest: boolean; + hasTestPassed: boolean; + hasFailure: boolean; + hasMerged: boolean; + hasAccepted: boolean; + sourceItems: Array<{ label: string; value: string; tone?: "default" | "warning" | "success" }>; +} function isFormalDevTask(task?: PMFlowDevTask | null) { const status = String(task?.status || "").toLowerCase(); @@ -243,76 +293,594 @@ export default function ExecutionBridgePage() { const isDemo = dataSourceMode === "demo"; const isLoading = dataSourceMode === "api" && executionSummaryStatus === "loading"; const error = dataSourceMode === "api" ? executionSummaryError : null; + const [writebackOpen, setWritebackOpen] = useState(false); + const run = summary ? buildExecutionRunState(summary, capabilities) : null; + const handlePrimaryAction = () => { + if (!summary || !run || !run.primaryAction.enabled) return; + if (run.primaryAction.kind === "create_handoff" && !isDemo && run.formalTask) { + createExecutionHandoff({ + task_id: run.formalTask.id, + repo_binding_id: run.repo?.id || null, + runtime_target: "local_bridge", + note: "从 PMFlow 开发执行 Run 工作台发起执行交接", + }); + return; + } + if (run.primaryAction.kind === "consume_handoff" && !isDemo && run.prompt?.id) { + consumeExecutionHandoff(run.prompt.id); + return; + } + if (run.primaryAction.kind === "open_writeback") { + if (isDemo) { + advanceExecution(); + return; + } + setWritebackOpen(true); + return; + } + if (run.primaryAction.kind === "change_review") { + window.location.assign(`/change-review?projectId=${summary.projectId}`); + return; + } + if (run.primaryAction.kind === "team_layer") { + window.location.assign(`/team-layer?projectId=${summary.projectId}`); + return; + } + if (run.primaryAction.kind === "release") { + window.location.assign(`/release-growth?projectId=${summary.projectId}`); + } + }; return ( -
-
-
-

开发执行联动

-
- 项目真相层 - - 执行上下文包 - - 开发与测试回写 -
-
- {dataSourceMode === "api" ? ( - - ) : null} -
- - {isDemo ? ( -
- 当前为演示模式,仅因 URL 显式带有 ?demo=1 才使用本地状态。 -
- ) : null} - - {isLoading ? : null} - {error ? : null} - {!summary && !isLoading && !error ? ( - - ) : null} - - {summary ? ( - <> - - - - + ) : ( +
+
+

开发执行 Run 工作台

+
+ 已确认需求 + + 上下文包 + + 开发测试回写 +
+
+
+ )} -
- - - + {isDemo ? ( +
+ 当前为演示模式,仅因 URL 显式带有 ?demo=1 才使用本地状态。 +
+ ) : null} + + {isLoading ? : null} + {error ? : null} + {!summary && !isLoading && !error ? ( + + ) : null} + + {summary && run ? ( + <> + + {dataSourceMode === "api" && executionCapabilitiesStatus === "loading" ? ( + + ) : null} + {dataSourceMode === "api" && executionCapabilitiesStatus === "error" ? ( + + ) : null} + + +
+ + setWritebackOpen(true)} + onDemoPush={isDemo ? advanceExecution : undefined} + /> + setWritebackOpen(true)} + /> +
+ + + {writebackOpen && !isDemo ? ( + setWritebackOpen(false)} + /> + ) : null} + + ) : null} +
+
+ ); +} + +function ExecutionRunHeader({ + summary, + run, + dataSourceMode, + onRefresh, + onPrimaryAction, +}: { + summary: PMFlowExecutionSummary; + run: ExecutionRunState; + dataSourceMode: "api" | "demo" | "empty"; + onRefresh: () => void; + onPrimaryAction: () => void; +}) { + return ( +
+
+
+
+ 已确认需求 + + 上下文与 Prompt 包 + + 开发 / 测试 / 审查合并 / 回写
+

开发执行 Run 工作台

+
+ {run.task?.title || "等待正式开发任务"} · 当前阶段:{run.currentStage.label} +
+
+ + + + +
+
+
+ +

{run.primaryAction.reason}

+
+ {dataSourceMode === "api" ? ( + + ) : null} + +
+
Project {summary.projectId}
+
+
+
+ ); +} - - +function ExecutionRunStageRail({ run }: { run: ExecutionRunState }) { + return ( +
+
+
+
开发执行流转
+

每一步只看真实来源、真实证据和明确责任;测试通过不会自动等于验收完成。

+
+ +
+
+ {run.stages.map((stage, index) => ( +
+ {index < run.stages.length - 1 ?
: null} +
+
+ + {index + 1} + + {stage.evidenceCount ? {stage.evidenceCount} 证据 : null} +
+
{stage.label}
+
{stage.blocker || stage.summary}
+
+
+ ))} +
+
+ ); +} + +function ExecutionRunSourcePanel({ run, summary }: { run: ExecutionRunState; summary: PMFlowExecutionSummary }) { + const prompt = run.prompt; + return ( + + ); +} + +function ExecutionRunWorkspacePanel({ + run, + summary, + onOpenWriteback, + onDemoPush, +}: { + run: ExecutionRunState; + summary: PMFlowExecutionSummary; + onOpenWriteback: () => void; + onDemoPush?: () => void; +}) { + return ( +
+
+
+

{run.currentStage.label}工作台

+

{run.currentStage.summary}

+
+
+ + {onDemoPush ? ( + + ) : null} +
+
+ +
+ +
+ + +
+
+
+ ); +} + +function CurrentStagePanel({ run, summary }: { run: ExecutionRunState; summary: PMFlowExecutionSummary }) { + if (run.currentStage.key === "source") { + return ( +
+

来源锁定

+

{run.primaryAction.reason}

+
+ + +
+
+ ); + } + if (run.currentStage.key === "package") { + return ( +
+

上下文与 Prompt 包

+

+ 当前阶段要把已确认需求、验收标准、约束、仓库和运行端边界压成一个可领取的执行包。 +

+
+ + +
+ {run.prompt ? ( +
+            {run.prompt.package_text}
+          
+ ) : null} +
+ ); + } + if (run.currentStage.key === "development") { + return ; + } + if (run.currentStage.key === "testing") { + return ; + } + if (run.currentStage.key === "review_merge") { + return ( +
+ +
+

审查合并门禁

+
+ + +
+

这里不执行合并命令,只显示 PR、测试和验收责任是否足够进入下一步。

+
+
+ ); + } + return ( +
+

项目记忆回写

+

+ 合并或验收完成后,PMFlow 需要把最终决策、交付证据和后续风险回写到项目记忆,再进入发布门禁。 +

+
+ + + +
+
+ ); +} + +function ExecutionDevelopmentEvidence({ run, summary }: { run: ExecutionRunState; summary: PMFlowExecutionSummary }) { + return ( +
+ +
+ + + +
+ +
+ ); +} + +function ExecutionResultCompactPanel({ run }: { run: ExecutionRunState }) { + return ( +
+
+

本次执行结果

+ +
+
+ + + + +
+ {run.result.blocker ? ( +
{run.result.blocker}
) : null} +
+ ); +} + +function ExecutionRunDecisionPanel({ + run, + summary, + capabilities, + onPrimaryAction, + onOpenWriteback, +}: { + run: ExecutionRunState; + summary: PMFlowExecutionSummary; + capabilities?: PMFlowExecutionCapabilitySnapshot | null; + onPrimaryAction: () => void; + onOpenWriteback: () => void; +}) { + const insights = buildReviewInsights(summary, capabilities); + const nextActions = buildNextActions(summary, capabilities); + return ( + + ); +} + +function ExecutionWritebackDrawer({ + summary, + onRecordProgress, + onClose, +}: { + summary: PMFlowExecutionSummary; + onRecordProgress: Parameters[0]["onRecordProgress"]; + onClose: () => void; +}) { + return ( +
+
+
+
+
回写执行证据
+
只记录已经发生的运行端结果,不执行本机命令。
+
+ +
+
+ +
+
); } @@ -1256,6 +1824,308 @@ function StatusChip({ label, tone }: { label: string; tone: "brand" | "success" ); } +function buildExecutionRunState( + summary: PMFlowExecutionSummary, + capabilities?: PMFlowExecutionCapabilitySnapshot | null +): ExecutionRunState { + const task = + summary.activeDevTasks.find((item) => item.id === summary.execution.taskId) || summary.activeDevTasks[0] || null; + const formalTask = isFormalDevTask(task) ? task : null; + const repo = task?.code_link?.repo || summary.repoBindings[0] || null; + const prompt = summary.promptPackage || null; + const events = summary.recentProgressEvents; + const eventType = (value: string) => events.some((event) => String(event.event_type) === value); + const eventIncludes = (value: string) => events.some((event) => String(event.event_type).includes(value)); + const hasHandoff = + eventType("handoff_created") || + eventType("handoff_consumed") || + ["pushed", "coding", "pr_open", "testing", "synced"].includes(summary.execution.status); + const hasConsumed = eventType("handoff_consumed") || ["coding", "pr_open", "testing", "synced"].includes(summary.execution.status); + const hasCommit = eventIncludes("commit") || events.some((event) => Boolean(event.commit_sha)); + const hasPr = + Boolean(summary.execution.prStatus && summary.execution.prStatus !== "pending") || + eventIncludes("pr") || + events.some((event) => Boolean(event.pr_number)); + const hasTest = + Boolean(summary.execution.testSummary && summary.execution.testSummary !== "pending") || eventIncludes("test") || eventType("checks_passed"); + const hasTestPassed = eventType("test_passed") || eventType("checks_passed"); + const hasFailure = + summary.execution.status === "failed" || events.some((event) => ["test_failed", "failed", "blocked"].includes(String(event.event_type))); + const hasMerged = eventType("merged"); + const hasAccepted = eventType("accepted"); + const hasWriteback = summary.execution.status === "synced" || normalizePending(summary.execution.writebackStatus) !== "待回写"; + const mode = primaryExecutionMode(capabilities); + const result = deriveExecutionResult(summary, capabilities); + const handoffGate = buildHandoffGate(summary, capabilities); + const firstBlockingReason = handoffGate.canCreate ? null : handoffGate.reason; + + const sourceDone = Boolean(formalTask); + const packageDone = sourceDone && Boolean(prompt); + const developmentDone = packageDone && (hasCommit || hasPr || ["coding", "pr_open", "testing", "synced"].includes(summary.execution.status)); + const testingDone = developmentDone && hasTestPassed; + const reviewDone = testingDone && (hasAccepted || hasMerged); + const writebackDone = reviewDone && hasWriteback; + const stageDefinitions: Array & { done: boolean; blocked?: boolean }> = [ + { + key: "source", + label: "已确认需求", + summary: sourceDone ? "正式 Task 已来自项目记忆。" : "等待 Change Review 写入项目记忆。", + evidenceCount: formalTask ? 1 : 0, + done: sourceDone, + blocker: !sourceDone ? firstBlockingReason : null, + blocked: !sourceDone, + }, + { + key: "package", + label: "上下文包", + summary: packageDone ? "Prompt Package 已生成。" : "等待生成上下文与 Prompt 包。", + evidenceCount: prompt ? 1 : 0, + done: packageDone, + blocker: sourceDone && !repo ? "缺少仓库绑定,执行包只能停在草案。" : null, + blocked: sourceDone && !repo, + }, + { + key: "development", + label: "开发", + summary: developmentDone ? "已有开发证据回流。" : hasHandoff ? "运行端已领取,等待代码证据。" : "等待交接给运行端。", + evidenceCount: evidenceCount(events, ["handoff", "branch", "commit", "pr"]), + done: developmentDone, + }, + { + key: "testing", + label: "测试", + summary: testingDone ? "测试或检查已通过。" : hasTest ? "测试证据已回流,需要判断风险。" : "等待测试证据。", + evidenceCount: evidenceCount(events, ["test", "checks"]), + done: testingDone, + blocker: hasFailure ? result.blocker || "存在失败或阻塞事件。" : null, + blocked: hasFailure, + }, + { + key: "review_merge", + label: "审查合并", + summary: reviewDone ? "验收或合并证据已回流。" : testingDone ? "等待技术审查或用户验收。" : "测试通过后进入审查合并。", + evidenceCount: evidenceCount(events, ["pr", "accepted", "merged"]), + done: reviewDone, + }, + { + key: "memory_writeback", + label: "回写", + summary: writebackDone ? "执行结果已可进入发布门禁。" : reviewDone ? "等待写回项目记忆。" : "审查合并后回写项目记忆。", + evidenceCount: evidenceCount(events, ["accepted", "merged", "manual_progress"]), + done: writebackDone, + }, + ]; + const firstOpenIndex = stageDefinitions.findIndex((stage) => !stage.done); + const stages = stageDefinitions.map((stage, index) => ({ + key: stage.key, + label: stage.label, + summary: stage.summary, + evidenceCount: stage.evidenceCount, + blocker: stage.blocker, + tone: stage.blocked + ? ("blocked" as const) + : stage.done + ? ("done" as const) + : index === Math.max(firstOpenIndex, 0) + ? ("current" as const) + : ("waiting" as const), + })); + const currentStage = stages.find((stage) => stage.tone === "blocked") || stages.find((stage) => stage.tone === "current") || stages[stages.length - 1]; + const primaryAction = buildRunPrimaryAction({ + summary, + handoffGate, + prompt, + sourceDone, + packageDone, + hasHandoff, + hasConsumed, + developmentDone, + hasTest, + hasTestPassed, + hasFailure, + hasAccepted, + hasMerged, + reviewDone, + writebackDone, + mode, + }); + + return { + task, + formalTask, + repo, + prompt, + mode, + currentStage, + stages, + primaryAction, + result, + hasHandoff, + hasConsumed, + hasCommit, + hasPr, + hasTest, + hasTestPassed, + hasFailure, + hasMerged, + hasAccepted, + sourceItems: [ + { label: "需求来源", value: formalTask ? "Project Memory" : "待写入", tone: formalTask ? "success" : "warning" }, + { label: "正式任务", value: task ? taskStatusLabel(task.status) : "缺失", tone: formalTask ? "success" : "warning" }, + { label: "Prompt 包", value: prompt ? packageStatusLabel(prompt.status) : "未生成", tone: prompt ? "success" : "warning" }, + { label: "执行模式", value: mode?.label || "未同步", tone: mode?.status === "blocked" ? "warning" : "default" }, + { label: "仓库", value: repo?.full_name || "未绑定", tone: repo ? "success" : "warning" }, + { label: "当前结果", value: result.statusLabel, tone: result.tone === "risk" ? "warning" : "default" }, + ], + }; +} + +function buildRunPrimaryAction({ + summary, + handoffGate, + prompt, + sourceDone, + packageDone, + hasHandoff, + hasConsumed, + developmentDone, + hasTest, + hasTestPassed, + hasFailure, + hasAccepted, + hasMerged, + reviewDone, + writebackDone, + mode, +}: { + summary: PMFlowExecutionSummary; + handoffGate: ReturnType; + prompt: PMFlowExecutionSummary["promptPackage"]; + sourceDone: boolean; + packageDone: boolean; + hasHandoff: boolean; + hasConsumed: boolean; + developmentDone: boolean; + hasTest: boolean; + hasTestPassed: boolean; + hasFailure: boolean; + hasAccepted: boolean; + hasMerged: boolean; + reviewDone: boolean; + writebackDone: boolean; + mode: PMFlowExecutionModeCapability | null; +}): ExecutionRunAction { + if (!sourceDone) { + return { + label: handoffGate.actionLabel || "回到待确认", + reason: handoffGate.reason || "当前还没有来自项目记忆的正式 Task。", + kind: "change_review", + enabled: true, + variant: "warning", + }; + } + if (!handoffGate.canCreate) { + return { + label: handoffGate.actionLabel, + reason: handoffGate.reason, + kind: handoffGate.requiresTechnicalReview ? "team_layer" : "change_review", + enabled: true, + variant: "warning", + }; + } + if (!packageDone) { + return { + label: "生成上下文包", + reason: "正式 Task 已就绪,先生成可交给 Codex / Local Bridge / GitHub Agent 的执行上下文包。", + kind: "create_handoff", + enabled: true, + variant: "primary", + }; + } + if (packageDone && !hasConsumed) { + return { + label: hasHandoff || prompt ? "标记运行端已领取" : "发起执行交接", + reason: "上下文包已生成,下一步应确认运行端已领取,之后再回写开发证据。", + kind: prompt ? "consume_handoff" : "create_handoff", + enabled: Boolean(prompt), + variant: prompt ? "primary" : "muted", + }; + } + if (hasFailure) { + return { + label: "生成修复上下文包", + reason: "存在失败或阻塞事件,应先形成修复轮上下文并回写新的测试结果。", + kind: "create_handoff", + enabled: true, + variant: "danger", + }; + } + if (!developmentDone || !hasTest) { + return { + label: "回写开发 / 测试证据", + reason: "运行端已领取,等待 branch、commit、PR 或测试结果回流。", + kind: "open_writeback", + enabled: true, + variant: "primary", + }; + } + if (hasTest && !hasTestPassed) { + return { + label: "补充测试结论", + reason: "已有测试摘要,但还没有可进入验收或发布门禁的通过证据。", + kind: "open_writeback", + enabled: true, + variant: "warning", + }; + } + if (hasTestPassed && !reviewDone) { + return { + label: mode?.mode === "ai_with_technical_review" ? "发起技术审查" : "标记用户验收", + reason: "测试证据已通过,但还不能自动视为完成;需要进入审查或验收责任。", + kind: mode?.mode === "ai_with_technical_review" ? "team_layer" : "open_writeback", + enabled: true, + variant: "warning", + }; + } + if ((hasAccepted || hasMerged) && !writebackDone) { + return { + label: "回写项目记忆", + reason: "审查或验收已经完成,下一步把最终证据和决策回写到项目记忆。", + kind: "open_writeback", + enabled: true, + variant: "primary", + }; + } + return { + label: "进入发布门禁", + reason: summary.releaseGate.status === "ready" ? "执行闭环已具备发布门禁输入。" : summary.releaseGate.summary, + kind: "release", + enabled: true, + variant: "primary", + }; +} + +function evidenceCount(events: PMFlowProgressEvent[], keys: string[]) { + return events.filter((event) => keys.some((key) => String(event.event_type).includes(key))).length; +} + +function stageToneToChip(tone: ExecutionStageTone): "brand" | "success" | "warning" | "risk" | "muted" { + if (tone === "done") return "success"; + if (tone === "current") return "brand"; + if (tone === "blocked") return "risk"; + return "muted"; +} + +function releaseGateLabel(status: string) { + const map: Record = { + ready: "可进入", + blocked: "阻塞", + needs_confirmation: "待确认", + empty: "缺证据", + }; + return map[status] || status; +} + function buildPipelineSteps(summary: PMFlowExecutionSummary) { const failed = summary.execution.status === "failed"; const hasPrompt = Boolean(summary.promptPackage); diff --git a/frontend/src/features/pmflow-os/store.api.test.tsx b/frontend/src/features/pmflow-os/store.api.test.tsx index 16afa4a4..d9756a90 100644 --- a/frontend/src/features/pmflow-os/store.api.test.tsx +++ b/frontend/src/features/pmflow-os/store.api.test.tsx @@ -705,7 +705,7 @@ describe("PMFlowOsProvider real change review actions", () => { expect(screen.queryByText(/Team Layer/)).not.toBeInTheDocument(); }); - it("renders execution capability mode and result summary on execution bridge", async () => { + it("renders execution run workspace with staged flow and a single primary action", async () => { getExecutionCapabilitiesMock.mockResolvedValueOnce({ projectId: "123", executionModes: [ @@ -764,11 +764,19 @@ describe("PMFlowOsProvider real change review actions", () => { { wrapper: wrapper() } ); - expect(await screen.findByText("执行模式判定")).toBeInTheDocument(); + expect(await screen.findByText("开发执行流转")).toBeInTheDocument(); + expect(screen.getByText("开发执行 Run 工作台")).toBeInTheDocument(); + expect(screen.getByText("执行源包")).toBeInTheDocument(); + expect(screen.getByText("AI 副驾")).toBeInTheDocument(); expect(screen.getAllByText("AI 直接执行").length).toBeGreaterThan(0); expect(screen.getByText("本次执行结果")).toBeInTheDocument(); - expect(screen.getAllByText("发起执行交接").length).toBeGreaterThan(0); - expect(screen.getByText("正式任务、仓库和上下文包已就绪。")).toBeInTheDocument(); + expect(screen.getAllByText("已确认需求").length).toBeGreaterThan(0); + expect(screen.getAllByText("上下文包").length).toBeGreaterThan(0); + expect(screen.getByText("审查合并")).toBeInTheDocument(); + expect(screen.getAllByRole("button", { name: /回写开发 \/ 测试证据/ }).length).toBeGreaterThan(0); + fireEvent.click(screen.getAllByRole("button", { name: /回写开发 \/ 测试证据/ })[0]); + expect(screen.getByText("回写执行证据")).toBeInTheDocument(); + expect(screen.getAllByText("运行端已领取,等待 branch、commit、PR 或测试结果回流。").length).toBeGreaterThan(0); }); it("renders Change Review from real projection and does not show demo fallback rows", async () => {