diff --git a/CLAUDE.md b/CLAUDE.md index 5bcf6ac0..3388cec7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -114,7 +114,7 @@ frontend/ - `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/pmflow-os/`(PMFlow OS 前端产品域;共享 truth store 已支持 `?projectId=` 读取真实后端 projection,真实模式下 message、`proposed_items` extraction、ChangeItem `accept/edit/defer` 与 ChangeSet `commit` 调后端,显式 `?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`;复用 `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`;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 真实路径;提供 `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;复用 `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`;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) - `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/`(消息台) @@ -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=`。`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` 并生成继续推进、待确认、发布关注、AI 正在推进、草案箱、最近项目摘要;Space P3 的发布关注优先消费 `truth-state.releases` 的 release validation 投影,没有验证投影时才回落 `execution-summary.releaseGate`,点击只跳 `/release-growth?projectId=`;不在 Space 写 Project Memory、ProgressEvent、ReleaseValidationRun 或发布执行。`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,`releaseGate / failureAlert` 只影响页面内提醒表达,不代表真实邮件、桌面推送或系统通知中心已接入,不改 OAuth/JWT/auth core;`frontend/src/features/pmflow-os/pages/AccountPage.tsx` 仅做薄入口并复用既有退出登录 bottom sheet。 -- `frontend/src/features/pmflow-os/` 当前承载新的对话驱动前端产品域:`store.tsx` 提供共享 truth store;真实模式通过 `?projectId=` 调用 `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 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` 主页面;`ExecutionBridgePage` 已支持创建 handoff、运行端领取确认和执行进度回写;`ReleaseGrowthPage` 真实模式展示 `execution-summary.releaseGate`、execution evidence、阻塞原因、下一步动作和 `latestValidationRun`,可创建 release validation run,并消费 Account notification preference 静默强调型提醒,但不隐藏真实证据或审计状态;静态发布/增长原型仅保留在显式 demo 模式;后端只记录 progress/release gate/release validation 元数据,不执行本机命令或真实发布命令;`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=` 调用 `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 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` 主页面;`ExecutionBridgePage` 已支持创建 handoff、运行端领取确认和执行进度回写;`ReleaseGrowthPage` 真实模式展示 `execution-summary.releaseGate`、execution evidence、阻塞原因、下一步动作、`latestValidationRun` 和 `releaseSignals`,可创建 release validation run、摄取发布后 feedback,并消费 Account notification preference 静默强调型提醒,但不隐藏真实证据、审计状态或发布后信号;静态发布/增长原型仅保留在显式 demo 模式;后端只记录 progress/release gate/release validation/release feedback 元数据,不执行本机命令、真实发布命令、外部支付或真实推送;`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/` 是当前唯一登录真源。 ### 5.1 `frontend/src/components/sstc/` 目录说明(模块化拆分后) diff --git a/backend/app/api/pmflow_os_api/__init__.py b/backend/app/api/pmflow_os_api/__init__.py index 2c270843..722e2f27 100644 --- a/backend/app/api/pmflow_os_api/__init__.py +++ b/backend/app/api/pmflow_os_api/__init__.py @@ -17,6 +17,7 @@ from ...schemas.pmflow_os import ( PMFlowExecutionHandoffRequest, PMFlowExecutionProgressEventRequest, PMFlowExtractionCreateRequest, + PMFlowReleaseFeedbackIngestRequest, PMFlowReleaseValidationRunCreateRequest, PMFlowThreadMessageCreateRequest, ) @@ -351,6 +352,39 @@ async def create_pmflow_os_release_validation_run( return success_response(payload) +@router.post("/projects/{project_id}/release-feedback-ingests") +async def create_pmflow_os_release_feedback_ingest( + project_id: int, + body: PMFlowReleaseFeedbackIngestRequest, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(get_current_user), +): + tenant_id = int(current_user.tenant_id) + await _ensure_project_member_or_manager( + db, + tenant_id=tenant_id, + project_id=int(project_id), + current_user=current_user, + ) + service = PMFlowOSProjectionService(db, tenant_id=tenant_id) + try: + payload = await service.create_release_feedback_ingest( + project_id=int(project_id), + body=body, + current_user_id=int(current_user.id), + ) + except ValueError as exc: + code = str(exc) + if code in {"feedback_content_empty", "反馈内容不能为空"}: + return JSONResponse(status_code=400, content=error_response(400, "feedback_content_empty")) + if code == "charter_not_found": + return JSONResponse(status_code=404, content=error_response(404, "charter_not_found")) + if code == "project_not_found": + raise HTTPException(status_code=404, detail="project_not_found") from exc + raise + return success_response(payload) + + @router.post("/projects/{project_id}/execution-handoffs/{prompt_package_id}/consume") async def consume_pmflow_os_execution_handoff( project_id: int, diff --git a/backend/app/schemas/pmflow_os.py b/backend/app/schemas/pmflow_os.py index 8e877153..1a7e8467 100644 --- a/backend/app/schemas/pmflow_os.py +++ b/backend/app/schemas/pmflow_os.py @@ -137,6 +137,15 @@ class PMFlowReleaseValidationRunCreateRequest(BaseModel): note: Optional[str] = Field(default=None, max_length=4000) +class PMFlowReleaseFeedbackIngestRequest(BaseModel): + source_type: str = Field(default="manual", min_length=1, max_length=32) + title: Optional[str] = Field(default=None, max_length=255) + content: str = Field(min_length=1, max_length=12000) + charter_id: Optional[str] = None + metrics: dict[str, Any] = Field(default_factory=dict) + samples: list[dict[str, Any]] = Field(default_factory=list) + + class PMFlowChangeSetDetailResponse(BaseModel): changeSet: dict[str, Any] items: list[dict[str, Any]] = Field(default_factory=list) @@ -300,6 +309,7 @@ class ExecutionSummaryProjection(BaseModel): projectId: str execution: PMFlowExecutionContext releaseGate: dict[str, Any] = Field(default_factory=dict) + releaseSignals: dict[str, Any] = Field(default_factory=dict) devSync: dict[str, Any] = Field(default_factory=dict) promptPackage: Optional[dict[str, Any]] = None repoBindings: list[dict[str, Any]] = Field(default_factory=list) diff --git a/backend/app/services/pmflow_os_projection_service.py b/backend/app/services/pmflow_os_projection_service.py index 5ef2134a..be3518b3 100644 --- a/backend/app/services/pmflow_os_projection_service.py +++ b/backend/app/services/pmflow_os_projection_service.py @@ -12,6 +12,7 @@ from sqlalchemy.orm.attributes import flag_modified from ..models import ( FeedbackIngest, + IterationRecommendation, ModelCatalogItem, ModelEvalRun, ModelRecommendationSnapshot, @@ -47,6 +48,7 @@ from ..schemas.pmflow_os import ( PMFlowProjectMemory, PMFlowProjectSummary, PMFlowRelease, + PMFlowReleaseFeedbackIngestRequest, PMFlowReleaseValidationRunCreateRequest, PMFlowRequirement, PMFlowRisk, @@ -196,6 +198,11 @@ class PMFlowOSProjectionService: release_gate = self._build_release_gate(events=events, deliveries=deliveries) if latest_release_validation is not None: release_gate["latestValidationRun"] = self._serialize_release_validation_run(latest_release_validation) + release_signals = await self._build_release_signals( + project_id=project_id, + release_gate=release_gate, + latest_release_validation=latest_release_validation, + ) return ExecutionSummaryProjection( projectId=str(project_id), @@ -209,6 +216,7 @@ class PMFlowOSProjectionService: writebackStatus=writeback, ), releaseGate=release_gate, + releaseSignals=release_signals, devSync=dev_sync.get("summary") or {}, promptPackage=None if latest_package is None else self._serialize_prompt_package(latest_package), repoBindings=repos, @@ -321,6 +329,54 @@ class PMFlowOSProjectionService: "warnings": [] if status != "blocked" else list(release_gate.get("blockingReasons") or []), } + async def create_release_feedback_ingest( + self, + *, + project_id: int, + body: PMFlowReleaseFeedbackIngestRequest, + current_user_id: Optional[int] = None, + ) -> dict[str, Any]: + await self._load_project(project_id=project_id) + charter_id = self._optional_int(body.charter_id, error_code="charter_not_found") + control = AIProjectControlService(self.db, tenant_id=self.tenant_id) + try: + payload = await control.ingest_feedback( + project_id=int(project_id), + actor_user_id=current_user_id, + source_type=body.source_type, + title=body.title, + content=body.content, + charter_id=charter_id, + metrics=dict(body.metrics or {}), + samples=list(body.samples or []), + ) + except ValueError as exc: + if str(exc) == "反馈内容不能为空": + raise ValueError("feedback_content_empty") from exc + raise + updated_summary = await self.get_execution_summary(project_id=project_id, current_user_id=current_user_id) + release_signals = dict(updated_summary.releaseSignals or {}) + raw_ingest_id = str((payload.get("ingest") or {}).get("id") or "") + projected_ingest = next( + (item for item in list(release_signals.get("feedback") or []) if str(item.get("id")) == raw_ingest_id), + None, + ) + projected_recommendation = next( + ( + item + for item in list(release_signals.get("recommendations") or []) + if str(item.get("feedbackIngestId") or "") == raw_ingest_id + ), + None, + ) + return { + "ingest": projected_ingest or payload.get("ingest"), + "recommendation": projected_recommendation or payload.get("recommendation"), + "releaseSignals": release_signals, + "executionSummary": updated_summary.model_dump(mode="json"), + "warnings": list(release_signals.get("warnings") or []), + } + async def consume_execution_handoff( self, *, @@ -490,6 +546,79 @@ class PMFlowOSProjectionService: ) return result.scalar_one_or_none() + async def _build_release_signals( + self, + *, + project_id: int, + release_gate: dict[str, Any], + latest_release_validation: Optional[ReleaseValidationRun], + ) -> dict[str, Any]: + feedback_result = await self.db.execute( + select(FeedbackIngest) + .where(and_(FeedbackIngest.tenant_id == self.tenant_id, FeedbackIngest.project_id == int(project_id))) + .order_by(FeedbackIngest.updated_at.desc(), FeedbackIngest.created_at.desc(), FeedbackIngest.id.desc()) + .limit(10) + ) + feedback_rows = list(feedback_result.scalars().all()) + feedback_items = [self._serialize_feedback_ingest(item) for item in feedback_rows] + + recommendation_result = await self.db.execute( + select(IterationRecommendation) + .where(and_(IterationRecommendation.tenant_id == self.tenant_id, IterationRecommendation.project_id == int(project_id))) + .order_by(IterationRecommendation.updated_at.desc(), IterationRecommendation.created_at.desc(), IterationRecommendation.id.desc()) + .limit(10) + ) + recommendation_rows = list(recommendation_result.scalars().all()) + recommendations = [self._serialize_iteration_recommendation(item) for item in recommendation_rows] + + revenue_result = await self.db.execute( + select(RevenueEvent) + .where(and_(RevenueEvent.tenant_id == self.tenant_id, RevenueEvent.project_id == int(project_id))) + .order_by(RevenueEvent.occurred_at.desc(), RevenueEvent.created_at.desc(), RevenueEvent.id.desc()) + .limit(10) + ) + revenue_rows = list(revenue_result.scalars().all()) + revenue_events = [self._serialize_revenue_event(item) for item in revenue_rows] + + warnings: list[str] = [] + if not feedback_items: + warnings.append("release_feedback_empty: no post-release feedback has been ingested.") + if not revenue_events: + warnings.append("release_revenue_empty: no revenue event has been recorded.") + if not recommendations: + warnings.append("iteration_recommendations_empty: no iteration recommendation has been generated.") + + serialized_times = [ + str(item.get("updatedAt") or item.get("createdAt")) + for item in [*feedback_items, *recommendations, *revenue_events] + if item.get("updatedAt") or item.get("createdAt") + ] + return { + "status": "active" if feedback_items or revenue_events or recommendations else "empty", + "relationship": { + "releaseGateStatus": release_gate.get("status") or "empty", + "latestValidationRunId": None if latest_release_validation is None else str(latest_release_validation.id), + "latestValidationRunStatus": None if latest_release_validation is None else latest_release_validation.status, + "source": "pmflow_os_release_growth", + "changeSetCreated": False, + }, + "summary": { + "feedbackCount": len(feedback_items), + "revenueEventCount": len(revenue_events), + "iterationRecommendationCount": len(recommendations), + "latestSignalAt": max(serialized_times) if serialized_times else None, + }, + "feedback": feedback_items, + "revenueEvents": revenue_events, + "recommendations": recommendations, + "nextActions": self._release_signal_next_actions( + feedback_items=feedback_items, + recommendations=recommendations, + release_gate=release_gate, + ), + "warnings": warnings, + } + async def _load_active_prompt_package_for_task(self, *, project_id: int, task_id: int) -> Optional[PromptPackage]: result = await self.db.execute( select(PromptPackage) @@ -1531,6 +1660,78 @@ class PMFlowOSProjectionService: "updatedAt": run.updated_at.isoformat() if run.updated_at else None, } + def _serialize_feedback_ingest(self, ingest: FeedbackIngest) -> dict[str, Any]: + return { + "id": str(ingest.id), + "projectId": str(ingest.project_id), + "charterId": None if ingest.charter_id is None else str(ingest.charter_id), + "sourceType": ingest.source_type, + "title": ingest.title, + "content": ingest.content, + "metrics": dict(ingest.metrics or {}), + "samples": list(ingest.samples or []), + "status": ingest.status, + "createdAt": ingest.created_at.isoformat() if ingest.created_at else None, + "updatedAt": ingest.updated_at.isoformat() if ingest.updated_at else None, + } + + def _serialize_iteration_recommendation(self, recommendation: IterationRecommendation) -> dict[str, Any]: + return { + "id": str(recommendation.id), + "projectId": str(recommendation.project_id), + "charterId": None if recommendation.charter_id is None else str(recommendation.charter_id), + "feedbackIngestId": None if recommendation.feedback_ingest_id is None else str(recommendation.feedback_ingest_id), + "status": recommendation.status, + "priority": recommendation.priority, + "recommendationSummary": dict(recommendation.recommendation_summary or {}), + "proposedCharterPatch": dict(recommendation.proposed_charter_patch or {}), + "proposedTasks": list(recommendation.proposed_tasks or []), + "changeSetCreated": False, + "createdAt": recommendation.created_at.isoformat() if recommendation.created_at else None, + "updatedAt": recommendation.updated_at.isoformat() if recommendation.updated_at else None, + } + + def _serialize_revenue_event(self, event: RevenueEvent) -> dict[str, Any]: + return { + "id": str(event.id), + "projectId": str(event.project_id), + "sourceType": event.source_type, + "category": event.category, + "currency": event.currency, + "amountUsd": self._jsonable(event.amount_usd), + "externalRef": event.external_ref, + "eventPayload": dict(event.event_payload or {}), + "occurredAt": event.occurred_at.isoformat() if event.occurred_at else None, + "createdAt": event.created_at.isoformat() if event.created_at else None, + "updatedAt": event.updated_at.isoformat() if event.updated_at else None, + } + + def _release_signal_next_actions( + self, + *, + feedback_items: list[dict[str, Any]], + recommendations: list[dict[str, Any]], + release_gate: dict[str, Any], + ) -> list[str]: + actions: list[str] = [] + if not feedback_items: + actions.append("发布后先摄取用户反馈、运营记录或人工观察,避免用 mock 增长数据判断下一轮。") + if feedback_items and recommendations: + actions.append("基于真实反馈查看 iteration recommendation,但本阶段不自动创建 ChangeSet。") + if str(release_gate.get("status") or "") == "blocked": + actions.append("发布门禁仍阻塞时,先处理 release gate,再把反馈作为下一轮规划输入。") + if not actions: + actions.append("继续沉淀 feedback / revenue / compliance 信号,为下一轮 iteration recommendation 做准备。") + return actions + + def _optional_int(self, value: Optional[Any], *, error_code: str) -> Optional[int]: + if value is None or str(value).strip() == "": + return None + try: + return int(str(value).strip()) + except (TypeError, ValueError) as exc: + raise ValueError(error_code) from exc + def _serialize_prompt_package(self, package: PromptPackage) -> dict[str, Any]: return { "id": str(package.id), diff --git a/backend/tests/test_pmflow_os_projection_api.py b/backend/tests/test_pmflow_os_projection_api.py index 9e0a132a..cfb748c9 100644 --- a/backend/tests/test_pmflow_os_projection_api.py +++ b/backend/tests/test_pmflow_os_projection_api.py @@ -21,6 +21,7 @@ from app.models import ( # noqa: E402 FeedbackIngest, FeishuItem, GitHubWebhookDelivery, + IterationRecommendation, ModelCatalogItem, ModelEvalRun, ModelRecommendationSnapshot, @@ -77,6 +78,7 @@ TABLES = [ ProgressEvent.__table__, ReleaseValidationRun.__table__, FeedbackIngest.__table__, + IterationRecommendation.__table__, RevenueEvent.__table__, ROISnapshot.__table__, GitHubWebhookDelivery.__table__, @@ -282,6 +284,8 @@ async def test_execution_summary_empty_state_is_pending_with_warnings(db_env) -> assert payload["execution"]["testSummary"] == "pending" assert payload["releaseGate"]["status"] == "empty" assert "缺少执行证据" in payload["releaseGate"]["title"] + assert payload["releaseSignals"]["status"] == "empty" + assert "release_feedback_empty" in " ".join(payload["releaseSignals"]["warnings"]) assert "prompt_package_empty" in " ".join(payload["warnings"]) assert payload["activeDevTasks"] == [] @@ -1015,6 +1019,44 @@ async def test_release_validation_run_rejects_empty_gate(db_env) -> None: assert rows == [] +@pytest.mark.asyncio +async def test_release_feedback_ingest_creates_signal_and_iteration_recommendation(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: + response = await client.post( + f"/pmflow-os/projects/{project.id}/release-feedback-ingests", + json={ + "source_type": "manual", + "title": "首批试用用户反馈", + "content": "用户反馈上传入口难找,希望下一轮优化 onboarding。", + "metrics": {"negative_feedback_count": 3, "activation_drop": "12%"}, + "samples": [{"channel": "访谈", "text": "入口难找"}], + }, + ) + summary_response = await client.get(f"/pmflow-os/projects/{project.id}/execution-summary") + + assert response.status_code == 200 + payload = response.json()["data"] + assert payload["ingest"]["title"] == "首批试用用户反馈" + assert payload["recommendation"]["feedbackIngestId"] == payload["ingest"]["id"] + assert payload["recommendation"]["changeSetCreated"] is False + assert payload["releaseSignals"]["status"] == "active" + assert payload["releaseSignals"]["feedback"][0]["metrics"]["negative_feedback_count"] == 3 + assert payload["releaseSignals"]["recommendations"][0]["feedbackIngestId"] == payload["ingest"]["id"] + assert payload["releaseSignals"]["relationship"]["changeSetCreated"] is False + assert payload["executionSummary"]["releaseSignals"]["status"] == "active" + assert summary_response.json()["data"]["releaseSignals"]["feedback"][0]["title"] == "首批试用用户反馈" + feedback_rows = (await session.execute(select(FeedbackIngest).where(FeedbackIngest.project_id == project.id))).scalars().all() + recommendation_rows = ( + await session.execute(select(IterationRecommendation).where(IterationRecommendation.project_id == project.id)) + ).scalars().all() + assert len(feedback_rows) == 1 + assert len(recommendation_rows) == 1 + + @pytest.mark.asyncio async def test_execution_progress_writeback_rejects_repo_outside_project(db_env) -> None: session = db_env diff --git a/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md b/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md index 472b42be..a2e97d24 100644 --- a/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md +++ b/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md @@ -807,6 +807,45 @@ P0 公共壳冻结已完成,下一批可以进入 `Batch P1` 三模块并行 ## 8. 进度台账 +### 2026-04-26 / release-feedback-p1-feedback-ingest-readmodel / implemented + +完成内容: + +- 在 PMFlow OS 后端新增轻量写入口 `POST /pmflow-os/projects/{project_id}/release-feedback-ingests`,复用现有 `AIProjectControlService.ingest_feedback` 与 `FeedbackIngest / IterationRecommendation` 模型,不新增 migration。 +- `GET /pmflow-os/projects/{project_id}/execution-summary` 新增 `releaseSignals` 投影,聚合真实发布后 `feedback / revenueEvents / recommendations`,并显式返回 `releaseGateStatus / latestValidationRunId / changeSetCreated=false` 关系信息。 +- `ReleaseGrowthPage` 真实模式新增“发布后反馈信号”区域和“摄取发布后反馈”表单;无信号时展示明确 empty/warning,不用 mock 成功数据补位。 +- 前端 `PMFlowOsProvider` 新增 `createReleaseFeedbackIngest` 动作,真实模式调用 PMFlow OS API 并 refetch truth-state / execution-summary;demo 模式仍只在显式 `?demo=1` 下走本地演示 mutation。 +- 本阶段只沉淀下一轮 iteration recommendation 所需数据,不自动创建 ChangeSet,不触发真实发布,不接外部支付,不接邮件/桌面推送。 + +涉及文件: + +- `backend/app/api/pmflow_os_api/__init__.py` +- `backend/app/schemas/pmflow_os.py` +- `backend/app/services/pmflow_os_projection_service.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/pages/ReleaseGrowthPage.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_os_projection_service.py backend/app/schemas/pmflow_os.py` 通过。 +- `PYTHONPATH=backend /opt/homebrew/bin/python3.11 -m pytest backend/tests/test_pmflow_os_projection_api.py -q` 通过:`18 passed`;仍有既有 `backend/app/schemas/recipe.py` Pydantic V2 class-based config deprecation warning。 +- `cd frontend && npm run test -- src/features/pmflow-os` 通过:`2 passed / 8 passed`。 +- `cd frontend && npm run typecheck` 未通过:当前独立 worktree 没有被 `.gitignore` 忽略的 `frontend/src/lib/*` 本地 helper 文件,导致全仓 `@/lib/api`、`@/lib/utils`、`@/lib/datetime`、`@/lib/display`、`@/lib/rbac` 等 alias 解析失败;这些文件不在本 Packet write scope,未越界补入。 +- `cd frontend && npm run build` 同样停在上述 `frontend/src/lib/*` 缺失的 TypeScript 解析错误,未进入 Vite build。 + +未完成或风险: + +- 本阶段只接入 feedback ingest 与 releaseSignals 读模型;RevenueEvent / ComplianceCheckRun 已可被后续扩展继续纳入更完整发布后运营读模型。 +- iteration recommendation 目前作为建议底座展示,不自动生成 ChangeSet;下一轮如果要从 recommendation 进入待确认中心,需要单独开 Main Flow packet。 +- 全量前端 typecheck/build 需在包含 `frontend/src/lib/*` helper 的集成环境或由 Shell / Integration owner 修正该 ignored 文件边界后重新验证。 + ### 2026-04-26 / big-module-integration-p4 / done 完成内容: diff --git a/docs/architecture/ARCHITECTURE_INVENTORY.md b/docs/architecture/ARCHITECTURE_INVENTORY.md index 808a7f9e..3d18497d 100644 --- a/docs/architecture/ARCHITECTURE_INVENTORY.md +++ b/docs/architecture/ARCHITECTURE_INVENTORY.md @@ -197,9 +197,9 @@ - `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` 的发布候选或发布门禁阻塞;发布验证缺失时显示明确空态,不虚构验证结果。项目主动作进入 `/pmflow-os?projectId=`,待确认项进入 `/change-review?projectId=&changeSetId=`,发布关注项进入 `/release-growth?projectId=`,执行中项进入 `/execution-bridge?projectId=`;暂存想法后端只读聚合尚未接入时只显示 empty/warning,不使用 mock 数据冒充真实内容;移动端为空间首页独立 App 布局,不直接压缩桌面卡片,并保留最近项目区块;`frontend/src/pages/PersonalSpacePage.tsx` 是薄入口。该域只表达个人空间和主流程导航,不做团队空间强切换、企业组织管理、Project Memory 写入、ChangeSet 状态写入、ProgressEvent 写入、ReleaseValidationRun 写入或发布执行。 - `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` 偏好,并返回 `inAppEnabled / desktopPreference / emailDigestPreference` 供后续 Main Flow 页面消费。该合同不代表邮件、桌面推送或系统通知中心已接入。`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`,真实模式下项目内 message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、Execution Handoff create/consume、execution progress writeback 与 release validation run create 均调用 `/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`,从 branch、commit、PR、test、failed evidence 展示 `可发布 / 阻塞 / 需要人工确认 / 缺少证据` 判断、阻塞原因、下一步动作和发布验证记录创建动作,不执行发布命令、不展示 mock 发布结果;同时读取 Account notification preference helper,用 `releaseGate` 偏好控制发布门禁强调提醒,用 `failureAlert` 偏好控制失败强调提醒,偏好关闭时只静默提醒表达强度,不隐藏 release evidence、阻塞原因、审计状态或 validation run;静态发布/增长原型只保留在显式 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` 快照;当前仍不调用真实 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` 返回最新记录;这些步骤都不直接执行本机命令或真实发布命令;`ModelDecision / MetricSignal` 在 C2 V1 仍 fail-closed。 +- `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`,真实模式下项目内 message、`proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、Execution Handoff create/consume、execution progress writeback、release validation run create 与 release feedback ingest 均调用 `/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 发布结果;同时读取 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`;当前仍不调用真实 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 读模型;这些步骤都不直接执行本机命令、真实发布命令、外部支付或真实推送;`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。 - `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、治理页和图表代码。 diff --git a/docs/architecture/CODE_REFERENCE_INDEX.md b/docs/architecture/CODE_REFERENCE_INDEX.md index df114e75..59e7b0cb 100644 --- a/docs/architecture/CODE_REFERENCE_INDEX.md +++ b/docs/architecture/CODE_REFERENCE_INDEX.md @@ -89,7 +89,7 @@ - SSTC 负责人推荐接口:参考 `POST /projects/{project_id}/tasks/{task_id}/recommend-assignee` - SSTC 任务更新接口:参考 `PATCH /projects/{project_id}/tasks/{task_id}` - SSTC 发布创建接口:参考 `POST /projects/{project_id}/releases` -- PMFlow OS 真实读路径、C1 Change Review API、C2 backend commit、Execution Handoff、Release Evidence Gate 与 Release Validation Run:参考 `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`;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`,这些步骤均不执行发布命令 +- PMFlow OS 真实读路径、C1 Change Review API、C2 backend commit、Execution Handoff、Release Evidence Gate、Release Validation Run 与 Release Feedback Signal:参考 `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`;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 关系;这些步骤均不执行发布命令、外部支付或真实推送 - 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` @@ -97,7 +97,7 @@ - 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 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 P3 的 `releaseAttention` 优先从 `truth-state.releases` 的 release validation 投影生成“发布验证阻塞 / 发布验证待确认”,没有发布验证投影时才回落到 `PMFlowExecutionSummary.releaseGate` 的“发布候选 / 发布门禁阻塞”,所有动作只跳转 `/release-growth?projectId=`;PMFlow OS 真实接入参考 `frontend/src/api/pmflowOs.ts` 的 `getPMFlowTruthState(projectId)`、`getPMFlowExecutionSummary(projectId)`、`createPMFlowExecutionHandoff`、`consumePMFlowExecutionHandoff`、`createPMFlowExecutionProgressEvent`、`createPMFlowReleaseValidationRun`、`createPMFlowThreadMessage`、`createPMFlowExtraction`、`getPMFlowChangeSet`、`acceptPMFlowChangeItem`、`editPMFlowChangeItem`、`deferPMFlowChangeItem`、`commitPMFlowChangeSet`;`PMFlowExecutionSummary.releaseGate` 是 ReleaseGrowth 真实发布门禁前端合同,`releaseGate.latestValidationRun` 是最新发布验证记录前端合同;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 P3 的 `releaseAttention` 优先从 `truth-state.releases` 的 release validation 投影生成“发布验证阻塞 / 发布验证待确认”,没有发布验证投影时才回落到 `PMFlowExecutionSummary.releaseGate` 的“发布候选 / 发布门禁阻塞”,所有动作只跳转 `/release-growth?projectId=`;PMFlow OS 真实接入参考 `frontend/src/api/pmflowOs.ts` 的 `getPMFlowTruthState(projectId)`、`getPMFlowExecutionSummary(projectId)`、`createPMFlowExecutionHandoff`、`consumePMFlowExecutionHandoff`、`createPMFlowExecutionProgressEvent`、`createPMFlowReleaseValidationRun`、`createPMFlowReleaseFeedbackIngest`、`createPMFlowThreadMessage`、`createPMFlowExtraction`、`getPMFlowChangeSet`、`acceptPMFlowChangeItem`、`editPMFlowChangeItem`、`deferPMFlowChangeItem`、`commitPMFlowChangeSet`;`PMFlowExecutionSummary.releaseGate` 是 ReleaseGrowth 真实发布门禁前端合同,`releaseGate.latestValidationRun` 是最新发布验证记录前端合同,`execution-summary.releaseSignals` 是 Release/Growth 发布后 feedback/revenue/recommendation 信号前端合同;Project Launch 创建链参考 `frontend/src/api/pmflowLaunch.ts` 的 `createPMFlowLaunchDraft` 与 `confirmPMFlowLaunchDraft` - 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 QA contract 前端类型(含 `analysis_packet`):参考 `frontend/src/api/aiSteward.ts` @@ -107,8 +107,8 @@ - 个人空间首页 / 空间层入口前端产品域:参考 `frontend/src/features/personal-space/`、`frontend/src/api/space.ts` 与薄入口 `frontend/src/pages/PersonalSpacePage.tsx`;本地预览路由入口为 `/space`,覆盖继续推进、待我确认、发布关注、执行中 / AI 正在推进、暂存想法、草案箱与最近项目,点击项目主动作进入 `/pmflow-os?projectId=`,待确认项进入 `/change-review?projectId=&changeSetId=`,执行中项进入 `/execution-bridge?projectId=`,发布关注项进入 `/release-growth?projectId=`;Space 只读聚合项目摘要、PMFlow OS `truth-state.releases / execution-summary.releaseGate`,发布验证缺失时显示明确空态,不虚构验证结果,不直接写 Project Memory、ChangeSet 状态、execution event 或 ReleaseValidationRun;暂存想法后端只读聚合尚未接入时必须显示 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` - Account Settings / `/me` 前端产品域与 P1/P2/P3 偏好合同:参考 `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` 偏好,返回 `inAppEnabled / desktopPreference / emailDigestPreference`;ReleaseGrowthPage 当前消费 `releaseGate` 与 `failureAlert` 两个偏好,只静默强调型提醒,不隐藏 release evidence、阻塞原因、审计状态或 validation run;这些 helper 只控制本地 UI 提醒消费合同,不代表真实邮件、桌面推送或系统通知中心已接入 -- PMFlow OS 共享 truth store 与主链状态动作:参考 `frontend/src/features/pmflow-os/store.tsx`;当前支持 `?projectId=` 走 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 均调后端并 refetch,不本地 promote 入 Project Memory、不伪造 execution event 或发布验证记录;`?demo=1` 才启用本地 mock 演示状态机和本地动作;项目内 AI 结构化闭环状态机为 `command_idle / ai_extracting / extraction_ready / review_open / writeback_success` -- 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`;`ReleaseGrowthPage.tsx` 真实模式只展示 `execution-summary.releaseGate`、execution evidence、阻塞原因、下一步动作和 `latestValidationRun`,缺证据时显示 empty/warning,不展示 mock 发布结果;页面可调用真实 API 创建 release validation run,但仍不执行部署命令;同时消费 Account notification preference helper,`releaseGate` 关闭时静默发布门禁强调提醒,`failureAlert` 关闭时静默失败强调提醒,所有真实证据和审计状态仍可见;静态高保真发布/增长数据仅保留在显式 demo 模式 +- PMFlow OS 共享 truth store 与主链状态动作:参考 `frontend/src/features/pmflow-os/store.tsx`;当前支持 `?projectId=` 走 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 均调后端并 refetch,不本地 promote 入 Project Memory、不伪造 execution event、发布验证记录或发布后信号;`?demo=1` 才启用本地 mock 演示状态机和本地动作;项目内 AI 结构化闭环状态机为 `command_idle / ai_extracting / extraction_ready / review_open / writeback_success` +- 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`;`ReleaseGrowthPage.tsx` 真实模式展示 `execution-summary.releaseGate`、execution evidence、阻塞原因、下一步动作、`latestValidationRun` 和 `releaseSignals`,缺证据或缺发布后信号时显示 empty/warning,不展示 mock 发布结果;页面可调用真实 API 创建 release validation run 和摄取 release feedback,但仍不执行部署命令、不自动创建 ChangeSet、不接外部支付;同时消费 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/store.test.ts`;真实 API 动作回归参考 `frontend/src/features/pmflow-os/store.api.test.tsx` - Project Launch 真实/演示分支回归测试:参考 `frontend/src/features/project-launch/useProjectLaunchState.test.ts` diff --git a/frontend/src/api/pmflowOs.ts b/frontend/src/api/pmflowOs.ts index c8434d0a..dec0dd1e 100644 --- a/frontend/src/api/pmflowOs.ts +++ b/frontend/src/api/pmflowOs.ts @@ -59,6 +59,7 @@ export interface PMFlowExecutionSummary { projectId: string; execution: PMFlowExecutionContext; releaseGate: PMFlowReleaseGate; + releaseSignals?: PMFlowReleaseSignals; devSync: Record; promptPackage?: PMFlowPromptPackage | null; repoBindings: PMFlowRepoBinding[]; @@ -145,6 +146,88 @@ export interface PMFlowReleaseValidationRunResult { warnings: string[]; } +export interface PMFlowReleaseSignals { + status: "active" | "empty" | string; + relationship: { + releaseGateStatus?: string | null; + latestValidationRunId?: string | null; + latestValidationRunStatus?: string | null; + source?: string | null; + changeSetCreated?: boolean; + }; + summary: { + feedbackCount?: number; + revenueEventCount?: number; + iterationRecommendationCount?: number; + latestSignalAt?: string | null; + }; + feedback: PMFlowReleaseFeedbackIngest[]; + revenueEvents: PMFlowReleaseRevenueEvent[]; + recommendations: PMFlowIterationRecommendation[]; + nextActions: string[]; + warnings: string[]; +} + +export interface PMFlowReleaseFeedbackIngest { + id: string; + projectId: string; + charterId?: string | null; + sourceType: string; + title?: string | null; + content: string; + metrics: Record; + samples: Array>; + status: string; + createdAt?: string | null; + updatedAt?: string | null; +} + +export interface PMFlowReleaseRevenueEvent { + id: string; + projectId: string; + sourceType: string; + category: string; + currency: string; + amountUsd: number; + externalRef?: string | null; + eventPayload: Record; + occurredAt?: string | null; + createdAt?: string | null; + updatedAt?: string | null; +} + +export interface PMFlowIterationRecommendation { + id: string; + projectId: string; + charterId?: string | null; + feedbackIngestId?: string | null; + status: string; + priority: string; + recommendationSummary: Record; + proposedCharterPatch: Record; + proposedTasks: Array>; + changeSetCreated: boolean; + createdAt?: string | null; + updatedAt?: string | null; +} + +export interface PMFlowReleaseFeedbackIngestRequest { + source_type?: string; + title?: string | null; + content: string; + charter_id?: string | null; + metrics?: Record; + samples?: Array>; +} + +export interface PMFlowReleaseFeedbackIngestResult { + ingest: PMFlowReleaseFeedbackIngest; + recommendation: PMFlowIterationRecommendation; + releaseSignals: PMFlowReleaseSignals; + executionSummary: PMFlowExecutionSummary; + warnings: string[]; +} + export interface PMFlowExecutionHandoffRequest { task_id: string; repo_binding_id?: string | null; @@ -333,6 +416,13 @@ export async function createPMFlowReleaseValidationRun( return post(`/pmflow-os/projects/${projectId}/release-validation-runs`, payload); } +export async function createPMFlowReleaseFeedbackIngest( + projectId: string, + payload: PMFlowReleaseFeedbackIngestRequest +): Promise { + return post(`/pmflow-os/projects/${projectId}/release-feedback-ingests`, payload); +} + export async function createPMFlowThreadMessage( projectId: string, threadId: string, diff --git a/frontend/src/features/pmflow-os/pages/ReleaseGrowthPage.tsx b/frontend/src/features/pmflow-os/pages/ReleaseGrowthPage.tsx index 39275264..2c5e6199 100644 --- a/frontend/src/features/pmflow-os/pages/ReleaseGrowthPage.tsx +++ b/frontend/src/features/pmflow-os/pages/ReleaseGrowthPage.tsx @@ -19,10 +19,14 @@ import { X, } from "lucide-react"; import { useQuery } from "@tanstack/react-query"; -import type { ButtonHTMLAttributes, ReactNode } from "react"; +import { useState, type ButtonHTMLAttributes, type ReactNode } from "react"; import { getAccountSettings, shouldShowAccountNotification } from "@/api/accountSettings"; import type { + PMFlowIterationRecommendation, + PMFlowReleaseFeedbackIngest, PMFlowReleaseGateEvidence, + PMFlowReleaseRevenueEvent, + PMFlowReleaseSignals, PMFlowReleaseValidationDecision, PMFlowReleaseValidationRun, } from "@/api/pmflowOs"; @@ -378,6 +382,18 @@ function validationSummaryText(run?: PMFlowReleaseValidationRun | null) { return `基于 ${sourceStatus} release gate 固化,部署执行:${summary.deploymentExecuted ? "已执行" : "未执行"}`; } +function releaseSignalSummary(signals?: PMFlowReleaseSignals | null) { + if (!signals || signals.status === "empty") return "暂无真实发布后反馈、收入或合规信号。"; + const summary = signals.summary || {}; + return `反馈 ${summary.feedbackCount || 0} 条 · 收入事件 ${summary.revenueEventCount || 0} 条 · 迭代建议 ${summary.iterationRecommendationCount || 0} 条`; +} + +function recommendationText(item: PMFlowIterationRecommendation) { + const summary = item.recommendationSummary || {}; + const text = summary.summary || summary.recommendation || summary.next_step || summary.feedback_title; + return typeof text === "string" && text.trim() ? text : "已基于真实反馈生成下一轮建议,等待进入后续 iteration recommendation 主链。"; +} + function checkTone(status?: string): StatusTone { if (status === "passed") return "success"; if (status === "failed") return "danger"; @@ -427,7 +443,44 @@ function EvidenceRow({ item }: { item: PMFlowReleaseGateEvidence }) { ); } +function FeedbackSignalRow({ item }: { item: PMFlowReleaseFeedbackIngest }) { + const metrics = Object.entries(item.metrics || {}); + return ( +
+
+ + {item.title || "未命名反馈"} +
+

{item.content}

+ {metrics.length ? ( +
+ {metrics.slice(0, 4).map(([key, value]) => ( + + {key}: {String(value)} + + ))} +
+ ) : null} +
{item.createdAt ? new Date(item.createdAt).toLocaleString("zh-CN") : "未记录时间"}
+
+ ); +} + +function RevenueSignalRow({ item }: { item: PMFlowReleaseRevenueEvent }) { + return ( +
+
+ {item.category || "revenue"} + {item.currency} {item.amountUsd} +
+
{item.externalRef || item.sourceType || "manual"}
+
+ ); +} + function RealReleaseGrowthPage({ store }: { store: PMFlowOsStoreSnapshot }) { + const [feedbackTitle, setFeedbackTitle] = useState(""); + const [feedbackContent, setFeedbackContent] = useState(""); const { state, dataSourceMode, @@ -439,8 +492,10 @@ function RealReleaseGrowthPage({ store }: { store: PMFlowOsStoreSnapshot }) { projectionWarnings, refetchExecutionSummary, createReleaseValidationRun, + createReleaseFeedbackIngest, } = store; const gate = executionSummary?.releaseGate; + const releaseSignals = executionSummary?.releaseSignals; const latestValidationRun = gate?.latestValidationRun; const accountSettingsQuery = useQuery({ queryKey: ["account-settings", "release-growth-notifications"], @@ -456,6 +511,10 @@ function RealReleaseGrowthPage({ store }: { store: PMFlowOsStoreSnapshot }) { const showReleaseGateReminder = releaseGateReminderEnabled && !emptyMode; const showFailureAlertReminder = failureAlertReminderEnabled && failureSignal; const canCreateValidationRun = Boolean(!emptyMode && gate?.evidence?.length); + const hasReleaseSignals = Boolean( + releaseSignals && + ((releaseSignals.feedback || []).length || (releaseSignals.revenueEvents || []).length || (releaseSignals.recommendations || []).length) + ); const handleCreateValidationRun = (decision: PMFlowReleaseValidationDecision) => { const decisionLabel = decision === "enter_release_candidate" ? "进入发布候选" : "保持阻塞"; createReleaseValidationRun({ @@ -463,6 +522,19 @@ function RealReleaseGrowthPage({ store }: { store: PMFlowOsStoreSnapshot }) { note: `ReleaseGrowthPage 人工确认:${decisionLabel};PMFlow 仅记录发布前验证,不执行部署。`, }); }; + const handleCreateFeedback = () => { + const content = feedbackContent.trim(); + if (!content) return; + createReleaseFeedbackIngest({ + source_type: "manual", + title: feedbackTitle.trim() || "Release/Growth 人工反馈", + content, + metrics: {}, + samples: [], + }); + setFeedbackTitle(""); + setFeedbackContent(""); + }; return (
@@ -581,9 +653,67 @@ function RealReleaseGrowthPage({ store }: { store: PMFlowOsStoreSnapshot }) { {!gate?.evidence?.length ?
暂无真实 execution evidence。
: null}
+ + + +
+
信号关系
+
+ release gate: {releaseSignals?.relationship?.releaseGateStatus || gate?.status || "empty"};validation run:{" "} + {releaseSignals?.relationship?.latestValidationRunId || "未固化"};本阶段不自动创建 ChangeSet。 +
+
+
+ {(releaseSignals?.feedback || []).map((item) => ( + + ))} + {(releaseSignals?.revenueEvents || []).map((item) => ( + + ))} + {(releaseSignals?.recommendations || []).map((item) => ( +
+
下一轮建议 · {item.priority}
+
{recommendationText(item)}
+
+ ))} + {!hasReleaseSignals ? ( +
+ 暂无真实发布后信号。请摄取用户反馈、收入事件或合规检查结果;页面不会用 mock 成功数据冒充增长表现。 +
+ ) : null} +
+