feat: wire PMFlow changeset commit frontend

This commit is contained in:
Albert 2026-04-26 09:19:35 +08:00
parent b87951935a
commit 1d34e32788
10 changed files with 133 additions and 22 deletions

View File

@ -554,15 +554,15 @@ commit gate 判定:
当前最建议下一步执行: 当前最建议下一步执行:
**Phase C2:ChangeSet commit 前端接入。** **Phase D:Execution Bridge 真实数据接入。**
原因: 原因:
- A3 已完成真实只读投影,Phase B 已完成真实 Project Launch 创建链。 - A3 已完成真实只读投影,Phase B 已完成真实 Project Launch 创建链。
- Phase C1 已把项目内消息、结构化候选、审查动作和证据链落到真实表。 - Phase C1 已把项目内消息、结构化候选、审查动作和证据链落到真实表。
- C2-contract 已锁定 commit API、原子提交规则、响应合同和失败语义。 - C2-contract 已锁定 commit API、原子提交规则、响应合同和失败语义。
- C2-backend 已按合同实现 atomic commit route/service/schema/tests。 - C2-backend 已按合同实现 atomic commit route/service/schema/tests,C2-frontend 已把真实模式写入接到 commit API。
- 当前缺口是前端真实模式仍未调用 `POST /pmflow-os/change-sets/{change_set_id}/commit`,需要替换 demo-only `promoteAcceptedToGraph`。 - 当前缺口是执行桥页面仍偏 demo 编排,下一步应把 `ExecutionBridgePage` 切到 `execution-summary` 与 dev-sync 真实数据。
### 7.1 Phase A 建议拆成三个小任务 ### 7.1 Phase A 建议拆成三个小任务
@ -612,6 +612,46 @@ commit gate 判定:
## 8. 进度台账 ## 8. 进度台账
### 2026-04-26 / Phase C2 Frontend - ChangeSet Commit Wiring / done
完成内容:
- 在 `frontend/src/api/pmflowOs.ts` 增加 `commitPMFlowChangeSet`,调用 `POST /pmflow-os/change-sets/{change_set_id}/commit`,默认 `mode=atomic`。
- 扩展 PMFlow OS 前端类型,允许后端投影返回 `ChangeSet.status=committed/partially_committed` 与 `ChangeItem.status=committed/skipped`。
- 将 `PMFlowOsProvider.promoteAcceptedToGraph` 的真实模式接到后端 commit API,commit 后 refetch `truth-state`;demo 模式继续使用本地 mutation。
- 将 `ChangeReviewPage` 的真实模式主按钮从“下一阶段开放”改为“确认写入项目记忆”,不再用前端本地状态冒充正式写入。
- 补充 API 模式 store 测试,确认 `promoteAcceptedToGraph("11")` 会调用 `commitPMFlowChangeSet("11", { mode: "atomic", ... })`,且不会本地写入 `requirements`。
涉及文件:
- `frontend/src/api/pmflowOs.ts`
- `frontend/src/features/pmflow-os/types.ts`
- `frontend/src/features/pmflow-os/store.tsx`
- `frontend/src/features/pmflow-os/store.api.test.tsx`
- `frontend/src/features/pmflow-os/pages/ChangeReviewPage.tsx`
- `frontend/src/features/pmflow-os/components/ProjectStructuredLoop.tsx`
- `frontend/src/features/pmflow-os/components/chrome.tsx`
- `docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md`
- `docs/architecture/CODE_REFERENCE_INDEX.md`
- `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md`
验证:
- `npm run test -- src/features/pmflow-os/store.api.test.tsx` 通过:`3 passed`。
- `npm run test -- src/features/pmflow-os` 通过:`4 passed`。
- `npm run typecheck` 通过。
- `npm run build` 通过;Vite 仍输出既有 manual chunk circular warning,未阻断构建。
未完成或风险:
- 前端真实 commit 依赖后端已完成审查状态;若仍存在 `pending_review` item,后端会按合同显式返回 `changeset_not_reviewed`。
- Release / ModelDecision / MetricSignal commit 仍按 C2 合同暂缓或 fail-closed。
- 当前为本地开发工作区验证,未执行部署环境热重启。
下一步建议:
- 执行 Phase D:把 `ExecutionBridgePage` 切到 `GET /pmflow-os/projects/{project_id}/execution-summary` 与 dev-sync 真实数据,继续保留 demo fallback。
### 2026-04-26 / Phase C2 Backend - ChangeSet Atomic Commit / done ### 2026-04-26 / Phase C2 Backend - ChangeSet Atomic Commit / done
完成内容: 完成内容:
@ -642,14 +682,13 @@ commit gate 判定:
未完成或风险: 未完成或风险:
- 前端真实模式尚未调用 commit API,`promoteAcceptedToGraph` 在 API 模式仍不会写入项目记忆。
- C2 V1 暂不支持 `ModelDecision / MetricSignal` commit;按合同必须 fail-closed。 - C2 V1 暂不支持 `ModelDecision / MetricSignal` commit;按合同必须 fail-closed。
- 本地仍有未提交删除痕迹,未纳入本次提交。 - 本地仍有未提交删除痕迹,未纳入本次提交。
- 当前为本地开发工作区验证,未执行部署环境热重启。 - 当前为本地开发工作区验证,未执行部署环境热重启。
下一步建议: 下一步建议:
- 执行 `C2-frontend`:在 `frontend/src/api/pmflowOs.ts` 增加 commit wrapper,并让 `frontend/src/features/pmflow-os/store.tsx` 真实模式调用 commit 后 refetch truth-state。 - 已执行 `C2-frontend`;下一步进入 Phase D Execution Bridge 真实数据接入。
### 2026-04-26 / G0 Freeze + C2 Contract / done ### 2026-04-26 / G0 Freeze + C2 Contract / done

View File

@ -96,7 +96,7 @@
## 3. 前端参考 ## 3. 前端参考
- PMFlow UI 契约真源:先读 `docs/PMFLOW_UI_CONTRACT.md`,再修改 `project-launch`、`personal-space`、`pmflow-os` 相关页面;该文档锁定 Logo、色彩、移动端底部导航、状态标签、AI 操作/用户动作/待确认视觉语法和核心功能 canonical 契约 - PMFlow UI 契约真源:先读 `docs/PMFLOW_UI_CONTRACT.md`,再修改 `project-launch`、`personal-space`、`pmflow-os` 相关页面;该文档锁定 Logo、色彩、移动端底部导航、状态标签、AI 操作/用户动作/待确认视觉语法和核心功能 canonical 契约
- PMFlow OS 产品合同真源:先读 `docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md`,再实现 `pmflow-os` 真实后端接入;该文档锁定 `ProjectTruthState / Thread / ChangeSet / ChangeItem / Evidence / Approval / Project Memory / ExecutionContext` 与 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync` 的复用边界和后续 API 草案 - PMFlow OS 产品合同真源:先读 `docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md`,再实现 `pmflow-os` 真实后端接入;该文档锁定 `ProjectTruthState / Thread / ChangeSet / ChangeItem / Evidence / Approval / Project Memory / ExecutionContext` 与 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync` 的复用边界和后续 API 草案
- 新增前端 API 调用:参考 `frontend/src/api/sstc.ts`;PMFlow OS 真实接入参考 `frontend/src/api/pmflowOs.ts` 的 `getPMFlowTruthState(projectId)`、`getPMFlowExecutionSummary(projectId)`、`createPMFlowThreadMessage`、`createPMFlowExtraction`、`getPMFlowChangeSet`、`acceptPMFlowChangeItem`、`editPMFlowChangeItem`、`deferPMFlowChangeItem`;Project Launch 创建链参考 `frontend/src/api/pmflowLaunch.ts` 的 `createPMFlowLaunchDraft` 与 `confirmPMFlowLaunchDraft` - 新增前端 API 调用:参考 `frontend/src/api/sstc.ts`;PMFlow OS 真实接入参考 `frontend/src/api/pmflowOs.ts` 的 `getPMFlowTruthState(projectId)`、`getPMFlowExecutionSummary(projectId)`、`createPMFlowThreadMessage`、`createPMFlowExtraction`、`getPMFlowChangeSet`、`acceptPMFlowChangeItem`、`editPMFlowChangeItem`、`deferPMFlowChangeItem`、`commitPMFlowChangeSet`;Project Launch 创建链参考 `frontend/src/api/pmflowLaunch.ts` 的 `createPMFlowLaunchDraft` 与 `confirmPMFlowLaunchDraft`
- AI Steward 页面入口:参考 `frontend/src/pages/ai-steward/` - AI Steward 页面入口:参考 `frontend/src/pages/ai-steward/`
- AI Steward 问答浮窗与上下文显性化组件:参考 `frontend/src/components/ai-steward/FloatingQAChat.tsx`、`frontend/src/components/ai-steward/QAConversationStatusBar.tsx`、`frontend/src/components/ai-steward/QAResponseMetaCard.tsx` - AI Steward 问答浮窗与上下文显性化组件:参考 `frontend/src/components/ai-steward/FloatingQAChat.tsx`、`frontend/src/components/ai-steward/QAConversationStatusBar.tsx`、`frontend/src/components/ai-steward/QAResponseMetaCard.tsx`
- AI Steward QA contract 前端类型(含 `analysis_packet`):参考 `frontend/src/api/aiSteward.ts` - AI Steward QA contract 前端类型(含 `analysis_packet`):参考 `frontend/src/api/aiSteward.ts`

View File

@ -40,7 +40,7 @@ pmflow_approvals
pmflow_evidence_links pmflow_evidence_links
``` ```
`/commit` 后端代码已实现;前端真实模式接入完成前,仍不得用本地 `promoteAcceptedToGraph` 冒充正式 Project Memory 写入。C2 后续前端实现必须遵守本文 4.6 和第 5 节合同。 `/commit` 后端代码与前端真实模式接入已实现;真实模式下 `promoteAcceptedToGraph` 必须调用后端 `POST /pmflow-os/change-sets/{change_set_id}/commit`,不得用本地 mutation 冒充正式 Project Memory 写入。
## 2. 对象映射表 ## 2. 对象映射表
@ -440,7 +440,7 @@ PMFlow OS 不重造开发执行系统。执行桥复用:
2. Phase B:已把 `/project-launch` 的草案确认接入真实项目创建和初始 Project Memory 写入。 2. Phase B:已把 `/project-launch` 的草案确认接入真实项目创建和初始 Project Memory 写入。
3. Phase C1:已实现 ChangeSet/ChangeItem/Approval/EvidenceLink 表与 `accept / edit / defer` 审查 API。 3. Phase C1:已实现 ChangeSet/ChangeItem/Approval/EvidenceLink 表与 `accept / edit / defer` 审查 API。
4. Phase C2 backend:已实现 ChangeSet atomic commit,把 `accepted / edited` 写入 `truth_nodes / tasks / semantic_facts` 并补齐高风险 approval gate。 4. Phase C2 backend:已实现 ChangeSet atomic commit,把 `accepted / edited` 写入 `truth_nodes / tasks / semantic_facts` 并补齐高风险 approval gate。
5. Phase C2 frontend:下一步把真实模式下的 `promoteAcceptedToGraph` 接到 `POST /pmflow-os/change-sets/{change_set_id}/commit`。 5. Phase C2 frontend:已把真实模式下的 `promoteAcceptedToGraph` 接到 `POST /pmflow-os/change-sets/{change_set_id}/commit`,commit 后 refetch truth-state。
6. Phase D:把 `ExecutionBridgePage` 切到 `execution-summary` 和 dev-sync 真实数据。 6. Phase D:把 `ExecutionBridgePage` 切到 `execution-summary` 和 dev-sync 真实数据。
7. Phase E:把 Release/Growth 投影接入 release validation、feedback、revenue、ROI、compliance。 7. Phase E:把 Release/Growth 投影接入 release validation、feedback、revenue、ROI、compliance。

View File

@ -54,6 +54,14 @@ export interface PMFlowChangeSetDetail {
approvals: Array<Record<string, unknown>>; approvals: Array<Record<string, unknown>>;
} }
export interface PMFlowChangeSetCommitResult {
changeSet: Record<string, unknown>;
committedItems: Array<Record<string, unknown>>;
skippedItems: Array<Record<string, unknown>>;
failedItems: Array<Record<string, unknown>>;
projectTruthState?: ProjectTruthState | null;
}
export async function getPMFlowTruthState(projectId: string): Promise<ProjectTruthState> { export async function getPMFlowTruthState(projectId: string): Promise<ProjectTruthState> {
const { data } = await api.get<ApiEnvelope<ProjectTruthState>>(`/pmflow-os/projects/${projectId}/truth-state`); const { data } = await api.get<ApiEnvelope<ProjectTruthState>>(`/pmflow-os/projects/${projectId}/truth-state`);
return unwrap(data); return unwrap(data);
@ -127,3 +135,14 @@ export async function deferPMFlowChangeItem(changeItemId: string, reason?: strin
); );
return unwrap(data); return unwrap(data);
} }
export async function commitPMFlowChangeSet(
changeSetId: string,
payload: { item_ids?: string[]; mode?: "atomic"; note?: string } = {}
): Promise<PMFlowChangeSetCommitResult> {
const { data } = await api.post<ApiEnvelope<PMFlowChangeSetCommitResult>>(
`/pmflow-os/change-sets/${changeSetId}/commit`,
{ mode: "atomic", ...payload }
);
return unwrap(data);
}

View File

@ -30,7 +30,7 @@ import { Link } from "react-router-dom";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { InlineNotice } from "@/features/pmflow-os/components/FeedbackPrimitives"; import { InlineNotice } from "@/features/pmflow-os/components/FeedbackPrimitives";
import { PMFlowMark } from "@/features/pmflow-os/components/chrome"; import { PMFlowMark } from "@/features/pmflow-os/components/chrome";
import type { ChangeItem, Evidence, PMFlowObjectType, PMFlowRiskLevel } from "@/features/pmflow-os/types"; import type { ChangeItem, Evidence, PMFlowChangeStatus, PMFlowObjectType, PMFlowRiskLevel } from "@/features/pmflow-os/types";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
export function StatusLabel({ export function StatusLabel({
@ -116,8 +116,19 @@ export function ObjectChangeCard({
onDefer?: (id: string) => void; onDefer?: (id: string) => void;
}) { }) {
const Icon = objectIcon(item.objectType); const Icon = objectIcon(item.objectType);
const statusTone = item.status === "accepted" ? "success" : item.status === "edited" ? "brand" : item.status === "deferred" ? "muted" : "brand"; const statusTone = item.status === "accepted" || item.status === "committed" ? "success" : item.status === "edited" ? "brand" : item.status === "deferred" || item.status === "skipped" ? "muted" : "brand";
const statusText = item.status === "accepted" ? "已接受" : item.status === "edited" ? "已修改" : item.status === "deferred" ? "暂不落库" : "待你确认"; const statusText =
item.status === "accepted"
? "已接受"
: item.status === "edited"
? "已修改"
: item.status === "deferred"
? "暂不落库"
: item.status === "committed"
? "已入库"
: item.status === "skipped"
? "已跳过"
: "待你确认";
return ( return (
<article className={cn("rounded-xl border bg-white p-4", selected ? "border-[#2463EB] shadow-[0_14px_34px_rgba(36,99,235,0.10)]" : "border-[#e6edf7]")}> <article className={cn("rounded-xl border bg-white p-4", selected ? "border-[#2463EB] shadow-[0_14px_34px_rgba(36,99,235,0.10)]" : "border-[#e6edf7]")}>
<div className="grid gap-4 md:grid-cols-[28px_minmax(0,1fr)] md:items-start"> <div className="grid gap-4 md:grid-cols-[28px_minmax(0,1fr)] md:items-start">
@ -195,7 +206,7 @@ type ReviewDisplayRow = {
confidence: number; confidence: number;
risk: PMFlowRiskLevel; risk: PMFlowRiskLevel;
relation: string; relation: string;
status?: "pending_review" | "accepted" | "edited" | "deferred"; status?: PMFlowChangeStatus;
expanded?: boolean; expanded?: boolean;
details?: { details?: {
description: string; description: string;
@ -213,6 +224,8 @@ const reviewStatusText: Record<NonNullable<ReviewDisplayRow["status"]>, string>
accepted: "已接受", accepted: "已接受",
edited: "已修改", edited: "已修改",
deferred: "暂不落库", deferred: "暂不落库",
committed: "已入库",
skipped: "已跳过",
}; };
export function ReviewExtractionHero({ activeChangeSet }: { activeChangeSet?: { createdAt?: string } | null }) { export function ReviewExtractionHero({ activeChangeSet }: { activeChangeSet?: { createdAt?: string } | null }) {

View File

@ -4,6 +4,7 @@ import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import type { import type {
PMFlowExecutionStatus, PMFlowExecutionStatus,
PMFlowChangeStatus,
PMFlowObjectStatus, PMFlowObjectStatus,
PMFlowReleaseStatus, PMFlowReleaseStatus,
PMFlowRiskLevel, PMFlowRiskLevel,
@ -26,7 +27,7 @@ export function statusTone(
} }
export function formatStatusLabel( export function formatStatusLabel(
status: PMFlowObjectStatus | PMFlowExecutionStatus | PMFlowReleaseStatus | PMFlowRiskLevel | "pending_review" | "accepted" | "edited" | "deferred" status: PMFlowObjectStatus | PMFlowExecutionStatus | PMFlowReleaseStatus | PMFlowRiskLevel | PMFlowChangeStatus
) { ) {
const labelMap: Record<string, string> = { const labelMap: Record<string, string> = {
low: "低风险", low: "低风险",
@ -42,6 +43,8 @@ export function formatStatusLabel(
accepted: "已接受", accepted: "已接受",
edited: "已修改", edited: "已修改",
deferred: "暂缓", deferred: "暂缓",
committed: "已入库",
skipped: "已跳过",
queued: "待推送", queued: "待推送",
pushed: "已推送", pushed: "已推送",
coding: "开发中", coding: "开发中",

View File

@ -17,10 +17,14 @@ export default function ChangeReviewPage() {
const selectedItems = items.filter((item) => item.status === "pending_review").slice(0, 2); const selectedItems = items.filter((item) => item.status === "pending_review").slice(0, 2);
const writeback = () => { const writeback = () => {
if (dataSourceMode !== "demo") return; if (!activeChangeSet) return;
if (dataSourceMode === "demo") {
selectedItems.forEach((item) => acceptChange(item.id)); selectedItems.forEach((item) => acceptChange(item.id));
}
promoteAcceptedToGraph(activeChangeSet?.id); promoteAcceptedToGraph(activeChangeSet?.id);
if (dataSourceMode === "demo") {
navigate("/memory-writeback-result"); navigate("/memory-writeback-result");
}
}; };
return ( return (
@ -53,8 +57,8 @@ export default function ChangeReviewPage() {
</main> </main>
<div className="hidden min-[1440px]:block"> <div className="hidden min-[1440px]:block">
<ChangeReviewAiCopilotPanel <ChangeReviewAiCopilotPanel
onPrimary={dataSourceMode === "demo" ? writeback : undefined} onPrimary={activeChangeSet ? writeback : undefined}
primaryLabel={dataSourceMode === "demo" ? "接受高置信项" : "提交入库将在下一阶段开放"} primaryLabel={dataSourceMode === "demo" ? "接受高置信项" : "确认写入项目记忆"}
/> />
</div> </div>
</div> </div>

View File

@ -5,6 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { import {
acceptPMFlowChangeItem, acceptPMFlowChangeItem,
commitPMFlowChangeSet,
createPMFlowThreadMessage, createPMFlowThreadMessage,
deferPMFlowChangeItem, deferPMFlowChangeItem,
editPMFlowChangeItem, editPMFlowChangeItem,
@ -20,6 +21,7 @@ vi.mock("@/api/pmflowOs", () => ({
createPMFlowExtraction: vi.fn(), createPMFlowExtraction: vi.fn(),
getPMFlowChangeSet: vi.fn(), getPMFlowChangeSet: vi.fn(),
acceptPMFlowChangeItem: vi.fn(), acceptPMFlowChangeItem: vi.fn(),
commitPMFlowChangeSet: vi.fn(),
editPMFlowChangeItem: vi.fn(), editPMFlowChangeItem: vi.fn(),
deferPMFlowChangeItem: vi.fn(), deferPMFlowChangeItem: vi.fn(),
})); }));
@ -27,6 +29,7 @@ vi.mock("@/api/pmflowOs", () => ({
const getTruthStateMock = vi.mocked(getPMFlowTruthState); const getTruthStateMock = vi.mocked(getPMFlowTruthState);
const createThreadMessageMock = vi.mocked(createPMFlowThreadMessage); const createThreadMessageMock = vi.mocked(createPMFlowThreadMessage);
const acceptItemMock = vi.mocked(acceptPMFlowChangeItem); const acceptItemMock = vi.mocked(acceptPMFlowChangeItem);
const commitChangeSetMock = vi.mocked(commitPMFlowChangeSet);
const editItemMock = vi.mocked(editPMFlowChangeItem); const editItemMock = vi.mocked(editPMFlowChangeItem);
const deferItemMock = vi.mocked(deferPMFlowChangeItem); const deferItemMock = vi.mocked(deferPMFlowChangeItem);
@ -104,6 +107,12 @@ describe("PMFlowOsProvider real change review actions", () => {
metadata: {}, metadata: {},
}); });
acceptItemMock.mockResolvedValue({ changeSet: {}, items: [], evidence: [], approvals: [] }); acceptItemMock.mockResolvedValue({ changeSet: {}, items: [], evidence: [], approvals: [] });
commitChangeSetMock.mockResolvedValue({
changeSet: { id: "11", status: "committed" },
committedItems: [{ id: "21", status: "committed" }],
skippedItems: [],
failedItems: [],
});
editItemMock.mockResolvedValue({ changeSet: {}, items: [], evidence: [], approvals: [] }); editItemMock.mockResolvedValue({ changeSet: {}, items: [], evidence: [], approvals: [] });
deferItemMock.mockResolvedValue({ changeSet: {}, items: [], evidence: [], approvals: [] }); deferItemMock.mockResolvedValue({ changeSet: {}, items: [], evidence: [], approvals: [] });
}); });
@ -129,6 +138,14 @@ describe("PMFlowOsProvider real change review actions", () => {
act(() => result.current.deferChange("21")); act(() => result.current.deferChange("21"));
await waitFor(() => expect(deferItemMock).toHaveBeenCalledWith("21", "用户暂缓")); await waitFor(() => expect(deferItemMock).toHaveBeenCalledWith("21", "用户暂缓"));
act(() => result.current.promoteAcceptedToGraph("11"));
await waitFor(() =>
expect(commitChangeSetMock).toHaveBeenCalledWith(
"11",
expect.objectContaining({ mode: "atomic", note: "用户确认写入项目记忆" })
)
);
expect(result.current.state.requirements).toEqual([]); expect(result.current.state.requirements).toEqual([]);
}); });

View File

@ -2,6 +2,7 @@ import { createContext, useContext, useEffect, useMemo, useState, type ReactNode
import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useQuery, useQueryClient } from "@tanstack/react-query";
import { import {
acceptPMFlowChangeItem, acceptPMFlowChangeItem,
commitPMFlowChangeSet,
createPMFlowExtraction, createPMFlowExtraction,
createPMFlowThreadMessage, createPMFlowThreadMessage,
deferPMFlowChangeItem, deferPMFlowChangeItem,
@ -665,8 +666,23 @@ export function PMFlowOsProvider({ children }: { children: ReactNode }) {
} }
mutateDemoOnly((current) => deferChangeMutation(current, changeId)); mutateDemoOnly((current) => deferChangeMutation(current, changeId));
}, },
promoteAcceptedToGraph: (changeSetId) => promoteAcceptedToGraph: (changeSetId) => {
mutateDemoOnly((current) => promoteAcceptedToGraphMutation(current, changeSetId)), if (dataSource.mode === "api") {
const targetChangeSetId = changeSetId || state.activeChangeSetId;
if (!targetChangeSetId) {
setReadError("缺少可提交的 ChangeSet,无法写入项目记忆。");
return;
}
runApiAction(() =>
commitPMFlowChangeSet(targetChangeSetId, {
mode: "atomic",
note: "用户确认写入项目记忆",
})
);
return;
}
mutateDemoOnly((current) => promoteAcceptedToGraphMutation(current, changeSetId));
},
pushTaskToExecution: (taskId) => mutateDemoOnly((current) => pushTaskToExecutionMutation(current, taskId)), pushTaskToExecution: (taskId) => mutateDemoOnly((current) => pushTaskToExecutionMutation(current, taskId)),
advanceExecution: () => mutateDemoOnly((current) => advanceExecutionMutation(current)), advanceExecution: () => mutateDemoOnly((current) => advanceExecutionMutation(current)),
writeBackExecution: (outcome) => mutateDemoOnly((current) => writeBackExecutionMutation(current, outcome)), writeBackExecution: (outcome) => mutateDemoOnly((current) => writeBackExecutionMutation(current, outcome)),

View File

@ -8,7 +8,7 @@ export type PMFlowStage =
| "growth"; | "growth";
export type PMFlowObjectStatus = "draft" | "confirmed" | "in_progress" | "blocked" | "done" | "released"; export type PMFlowObjectStatus = "draft" | "confirmed" | "in_progress" | "blocked" | "done" | "released";
export type PMFlowChangeStatus = "pending_review" | "accepted" | "edited" | "deferred"; export type PMFlowChangeStatus = "pending_review" | "accepted" | "edited" | "deferred" | "committed" | "skipped";
export type PMFlowExecutionStatus = "queued" | "pushed" | "coding" | "pr_open" | "testing" | "synced" | "failed"; export type PMFlowExecutionStatus = "queued" | "pushed" | "coding" | "pr_open" | "testing" | "synced" | "failed";
export type PMFlowReleaseStatus = "check_pending" | "ready" | "shipping" | "live" | "watch"; export type PMFlowReleaseStatus = "check_pending" | "ready" | "shipping" | "live" | "watch";
export type ProjectLoopStage = export type ProjectLoopStage =
@ -84,7 +84,7 @@ export interface ChangeSet {
summary: string; summary: string;
headline: string; headline: string;
createdAt: string; createdAt: string;
status: "open" | "reviewed"; status: "open" | "reviewed" | "committed" | "partially_committed";
itemIds: string[]; itemIds: string[];
} }