批准 GDD 后直接启动建造
Project CI / Repository checks (pull_request) Successful in 3m19s
Project CI / Frontend tests (pull_request) Successful in 3m54s
Project CI / Backend tests (pull_request) Failing after 4m26s
Project CI / Native shell tests (pull_request) Failing after 6m1s

点击做成游戏后直接创建自动游戏工作区
导入 fast_gdd.md 并携带固定建造指令启动 Direct Codex
清理首页 GDD 预填链路并补充重复点击回归测试
同步 Fast GDD 技术方案与共享决策记录
This commit is contained in:
2026-08-30 11:36:31 +00:00
parent 94bc96c610
commit fb5b5d585c
8 changed files with 122 additions and 204 deletions
@@ -77,10 +77,7 @@ export function WorkspaceLauncherShell({
activeProjectAgentResults,
setAgentResults: setActiveProjectAgentResults,
resetLauncherHomeDraft,
homePrefillAttachments,
homePrefillPrompt,
clearHomePrefill,
prepareHomeGameFromApprovedGdd,
startGameFromApprovedGdd,
createHomeDraftAutomatically,
openProject,
} = homeProject;
@@ -264,7 +261,6 @@ export function WorkspaceLauncherShell({
currentUser={currentUser}
onLogout={() => {
resetLauncherHomeDraft();
clearHomePrefill();
accountWallet.resetWalletBalance();
onLogout();
}}
@@ -298,9 +294,6 @@ export function WorkspaceLauncherShell({
onStatusChange={setStatus}
recentProjectRows={recentProjectRows}
onCreateDraftAutomatically={createHomeDraftAutomatically}
initialAttachments={homePrefillAttachments}
initialPrompt={homePrefillPrompt}
onInitialPrefillApplied={clearHomePrefill}
onProjectsOpen={() => setLauncherView('projects')}
onProjectOpen={(path) => {
setProjectPath(path);
@@ -359,7 +352,7 @@ export function WorkspaceLauncherShell({
setActiveProjectAgentRuntimeSummaries
}
onAgentResultsChange={setActiveProjectAgentResults}
onMakeGameFromApprovedGdd={prepareHomeGameFromApprovedGdd}
onMakeGameFromApprovedGdd={startGameFromApprovedGdd}
/>
}
/>
@@ -49,7 +49,7 @@ type UseHomeProjectCreationOptions = {
rememberRecentWorkspace: (projectPath: string) => void;
};
const APPROVED_GDD_HOME_PROMPT = [
const APPROVED_GDD_BUILD_PROMPT = [
'请按照附件中的已批准 GDD 开始建造这款游戏。',
'',
'这份 GDD 已覆盖游戏定位与一句话概念、类型与美术方向、游戏支柱、核心循环、目标用户、平台与输入事实、MVP 系统、暂不纳入范围、创作者提示和原型验证项。',
@@ -57,6 +57,19 @@ const APPROVED_GDD_HOME_PROMPT = [
'请先阅读并理解附件中的 fast_gdd.md,以它作为本次建造的主要依据,优先实现其中 MVP 范围内的可运行游戏原型。',
].join('\n');
function createTextAttachmentFile(content: string) {
const file = new File([content], 'fast_gdd.md', {
type: 'text/markdown',
lastModified: Date.now(),
});
if (typeof file.arrayBuffer !== 'function') {
Object.defineProperty(file, 'arrayBuffer', {
value: async () => new TextEncoder().encode(content).buffer,
});
}
return file;
}
export function useHomeProjectCreation({
setStatus,
setLauncherView,
@@ -86,13 +99,7 @@ export function useHomeProjectCreation({
const resetLauncherHomeDraft = useLauncherHomeDraftStore(
(state) => state.reset,
);
const setHomeCreationType = useLauncherHomeDraftStore(
(state) => state.setCreationType,
);
const [homePrefillAttachments, setHomePrefillAttachments] = useState<
HomeAttachmentDraft[]
>([]);
const [homePrefillPrompt, setHomePrefillPrompt] = useState('');
const approvedGddStartInFlightRef = useRef(false);
function validateProjectPath(nextProjectPath: string) {
const trimmedProjectPath = nextProjectPath.trim();
@@ -107,41 +114,48 @@ export function useHomeProjectCreation({
return trimmedProjectPath;
}
async function prepareHomeGameFromApprovedGdd(nextProjectPath: string) {
async function startGameFromApprovedGdd(nextProjectPath: string) {
if (approvedGddStartInFlightRef.current) {
return;
}
approvedGddStartInFlightRef.current = true;
const invoke = resolveTauriInvoke();
if (!invoke) {
throw new Error('需要在陶泥儿客户端内运行');
}
const projectPath = nextProjectPath.trim();
if (!projectPath) {
throw new Error('当前项目路径无效');
}
try {
if (!invoke) {
throw new Error('需要在陶泥儿客户端内运行');
}
const projectPath = nextProjectPath.trim();
if (!projectPath) {
throw new Error('当前项目路径无效');
}
setStatus('正在读取已批准 GDD');
const result = await invoke<LocalProjectFileResult>(
'read_local_project_file',
{
projectPath,
relativePath: 'game/fast_gdd.md',
commandId: 'file.read',
},
);
const file = new File([result.content], 'fast_gdd.md', {
type: 'text/markdown',
lastModified: Date.now(),
});
setStatus('正在读取已批准 GDD');
const result = await invoke<LocalProjectFileResult>(
'read_local_project_file',
{
projectPath,
relativePath: 'game/fast_gdd.md',
commandId: 'file.read',
},
);
const file = createTextAttachmentFile(result.content);
resetLauncherHomeDraft();
setHomeCreationType('game');
setHomePrefillPrompt(APPROVED_GDD_HOME_PROMPT);
setHomePrefillAttachments([
{
id: `approved-gdd-${Date.now().toString(36)}`,
file,
},
]);
setLauncherView('home');
setStatus('已回到做游戏入口');
await createHomeDraftAutomatically(
{
creationType: 'game',
prompt: APPROVED_GDD_BUILD_PROMPT,
attachments: [
{
id: `approved-gdd-${Date.now().toString(36)}`,
file,
},
],
},
'direct-build',
);
} finally {
approvedGddStartInFlightRef.current = false;
}
}
function enterProjectDevelopment(context: LauncherProjectContext) {
@@ -653,13 +667,7 @@ export function useHomeProjectCreation({
projectBusy: projectAction !== null,
pendingNonEmptyProject,
resetLauncherHomeDraft,
homePrefillAttachments,
homePrefillPrompt,
clearHomePrefill: () => {
setHomePrefillAttachments([]);
setHomePrefillPrompt('');
},
prepareHomeGameFromApprovedGdd,
startGameFromApprovedGdd,
createHomeDraft,
createHomeDraftAutomatically,
openProject,
@@ -221,7 +221,7 @@ export function PlanGddStageProgress({
.finally(() => setMakingGame(false));
}}
>
{makingGame ? '正在准备' : '做成游戏'}
{makingGame ? '正在启动' : '做成游戏'}
</button>
) : null}
</div>
@@ -28,9 +28,6 @@ type RichInputAreaProps = {
placeholder: string;
onChange: (value: Draft) => void;
onEnter: () => void;
initialAttachments?: readonly HomeAttachmentDraft[];
initialPrompt?: string;
onInitialPrefillApplied?: () => void;
children?: React.ReactNode;
};
const INSERT_ATTACHMENTS_COMMAND: LexicalCommand<HomeAttachmentDraft[]> =
@@ -107,19 +104,8 @@ function selectEditableEndWhenNeeded() {
function EditorPlugins({
onChange,
onEnter,
initialAttachments = [],
initialPrompt = '',
onInitialPrefillApplied,
}: Pick<
RichInputAreaProps,
| 'onChange'
| 'onEnter'
| 'initialAttachments'
| 'initialPrompt'
| 'onInitialPrefillApplied'
>) {
}: Pick<RichInputAreaProps, 'onChange' | 'onEnter'>) {
const [editor] = useLexicalComposerContext();
const initialPrefillAppliedRef = useRef(false);
useEffect(
() =>
@@ -202,32 +188,6 @@ function EditorPlugins({
[editor],
);
useEffect(() => {
if (
initialPrefillAppliedRef.current ||
(initialPrompt.trim().length === 0 && initialAttachments.length === 0)
) {
return;
}
initialPrefillAppliedRef.current = true;
if (initialPrompt.length > 0) {
editor.dispatchCommand(INSERT_CLIPBOARD_TEXT_COMMAND, initialPrompt);
}
if (initialAttachments.length > 0) {
editor.dispatchCommand(INSERT_ATTACHMENTS_COMMAND, [
...initialAttachments,
]);
}
onChange(editor.getEditorState());
onInitialPrefillApplied?.();
}, [
editor,
initialAttachments,
initialPrompt,
onChange,
onInitialPrefillApplied,
]);
return (
<OnChangePlugin
onChange={(editorState) => {
@@ -296,13 +256,7 @@ export default function RichInputArea(props: RichInputAreaProps) {
/>
{props.children}
</div>
<EditorPlugins
onChange={props.onChange}
onEnter={props.onEnter}
initialAttachments={props.initialAttachments}
initialPrompt={props.initialPrompt}
onInitialPrefillApplied={props.onInitialPrefillApplied}
/>
<EditorPlugins onChange={props.onChange} onEnter={props.onEnter} />
</LexicalComposer>
);
}
@@ -20,7 +20,6 @@ import {
} from './components/RichInputArea/richTextToPrompt';
import InspirationGallery from './InspirationGallery';
import {
type HomeAttachmentDraft,
type HomeCreationType,
type HomeDraft,
useLauncherHomeDraftStore,
@@ -104,9 +103,6 @@ type HomeViewProps = {
onProjectsOpen: () => void;
onProjectOpen: (path: string) => void;
onProjectPick: () => void;
initialAttachments?: readonly HomeAttachmentDraft[];
initialPrompt?: string;
onInitialPrefillApplied?: () => void;
};
export default function HomeView({
@@ -117,9 +113,6 @@ export default function HomeView({
onProjectsOpen,
onProjectOpen,
onProjectPick,
initialAttachments = [],
initialPrompt = '',
onInitialPrefillApplied,
}: HomeViewProps) {
const homeCreationType = useLauncherHomeDraftStore(
(state) => state.creationType,
@@ -241,9 +234,6 @@ export default function HomeView({
value={homeRichText}
placeholder={activeCreationType.placeholder}
onChange={setHomeRichText}
initialAttachments={initialAttachments}
initialPrompt={initialPrompt}
onInitialPrefillApplied={onInitialPrefillApplied}
onEnter={() => {
void createFromHome();
}}
@@ -1,9 +1,7 @@
import { PROJECT_SUPERVISOR_PLAN_SOURCE } from '../../src/app/constants';
import type { ProjectStartMode } from '../../src/app/types';
import type { ProjectSupervisorComponentProps } from '../../src/features/app-shell/model';
import { useHomeProjectCreation } from '../../src/features/app-shell/useHomeProjectCreation';
import { WorkspaceLauncherShell } from '../../src/features/app-shell/WorkspaceLauncher';
import HomeView, { type HomeDraft } from '../../src/view/home';
import type { LauncherView } from '../../src/view/layout';
import {
act,
@@ -31,15 +29,8 @@ import {
within,
} from './harness';
function ApprovedGddHomeBridgeHarness({
onCreateDraftAutomatically,
}: {
onCreateDraftAutomatically: (
draft: HomeDraft,
startMode: ProjectStartMode,
) => Promise<string>;
}) {
const [launcherView, setLauncherView] = React.useState<LauncherView>(
function ApprovedGddStartHarness() {
const [, setLauncherView] = React.useState<LauncherView>(
'project-development',
);
const [, setStatus] = React.useState('');
@@ -51,19 +42,12 @@ function ApprovedGddHomeBridgeHarness({
rememberRecentWorkspace: () => undefined,
});
if (launcherView === 'home') {
return React.createElement(HomeView, {
hasPromo: false,
onStatusChange: setStatus,
recentProjectRows: [],
onCreateDraftAutomatically,
onProjectsOpen: () => undefined,
onProjectOpen: () => undefined,
onProjectPick: () => undefined,
initialAttachments: controller.homePrefillAttachments,
initialPrompt: controller.homePrefillPrompt,
onInitialPrefillApplied: controller.clearHomePrefill,
});
if (controller.currentProjectContext) {
return React.createElement(
'p',
{ 'aria-label': '已进入自动游戏项目' },
controller.currentProjectContext.initialPrompt,
);
}
return React.createElement(
@@ -71,9 +55,9 @@ function ApprovedGddHomeBridgeHarness({
{
type: 'button',
onClick: () =>
void controller.prepareHomeGameFromApprovedGdd('/tmp/planning-project'),
void controller.startGameFromApprovedGdd('/tmp/planning-project'),
},
'已批准 GDD 带到做游戏',
'直接用已批准 GDD 开始建造',
);
}
@@ -1552,75 +1536,71 @@ export function registerHomeProjectCreationTests() {
);
});
it('returns to the game entry with the approved GDD without starting creation', async () => {
const onCreateDraftAutomatically = vi.fn(async () => 'unexpected');
it('starts an automatic game project from the approved GDD', async () => {
const automaticProjectPath = '/tmp/approved-gdd-game';
const manifest = createGameCreationAppManifest(
'approved-gdd-game',
'已批准 GDD 游戏',
);
const gddContent = '# Fast GDD\n\n批准后的方案内容';
const invoke = vi.fn(async (command: string) => {
if (command === 'read_local_project_file') {
return {
path: 'game/fast_gdd.md',
absolutePath: '/tmp/planning-project/game/fast_gdd.md',
content: '# Fast GDD\n\n批准后的方案内容',
content: gddContent,
};
}
if (command === 'create_automatic_local_game_project') {
return {
projectPath: automaticProjectPath,
manifestPath: `${automaticProjectPath}/.agent/manifest.json`,
manifest,
};
}
if (command === 'upload_local_asset') {
return {
id: 'approved-gdd-asset',
localPath: 'assets/uploads/fast_gdd.md',
absolutePath: `${automaticProjectPath}/assets/uploads/fast_gdd.md`,
manifestPath: `${automaticProjectPath}/.agent/manifest.json`,
};
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
render(
React.createElement(ApprovedGddHomeBridgeHarness, {
onCreateDraftAutomatically,
}),
);
render(React.createElement(ApprovedGddStartHarness));
fireEvent.click(
screen.getByRole('button', { name: '已批准 GDD 带到做游戏' }),
screen.getByRole('button', { name: '直接用已批准 GDD 开始建造' }),
);
fireEvent.click(
screen.getByRole('button', { name: '直接用已批准 GDD 开始建造' }),
);
await screen.findByText('fast_gdd.md');
await waitFor(() => {
expect(screen.getByLabelText('创作想法').textContent).toContain(
'请按照附件中的已批准 GDD 开始建造这款游戏。',
);
expect(
invoke.mock.calls.filter(
([command]) => command === 'create_automatic_local_game_project',
),
).toHaveLength(1);
});
expect(screen.getByLabelText('创作想法').textContent).toContain(
'这份 GDD 已覆盖游戏定位与一句话概念、类型与美术方向、游戏支柱、核心循环、目标用户、平台与输入事实、MVP 系统、暂不纳入范围、创作者提示和原型验证项。',
expect(screen.getByLabelText('已进入自动游戏项目').textContent).toContain(
'请按照附件中的已批准 GDD 开始建造这款游戏。',
);
const creationTypes = screen.getByRole('group', { name: '创作类型' });
expect(
within(creationTypes)
.getByRole('button', { name: '做游戏' })
.getAttribute('aria-pressed'),
).toBe('true');
expect(onCreateDraftAutomatically).not.toHaveBeenCalled();
expect(invoke).toHaveBeenCalledWith('read_local_project_file', {
projectPath: '/tmp/planning-project',
relativePath: 'game/fast_gdd.md',
commandId: 'file.read',
});
expect(invoke).not.toHaveBeenCalledWith(
'create_automatic_local_game_project',
);
expect(invoke).not.toHaveBeenCalledWith(
'start_game_creator_supervisor_runtime_task',
expect.anything(),
);
expect(invoke).not.toHaveBeenCalledWith(
'chat_with_game_creator_direct_codex',
expect.anything(),
);
fireEvent.click(screen.getByRole('button', { name: '开启创作' }));
await waitFor(() => {
expect(onCreateDraftAutomatically).toHaveBeenCalledTimes(1);
expect(invoke).toHaveBeenCalledWith('upload_local_asset', {
projectPath: automaticProjectPath,
fileName: 'fast_gdd.md',
mediaType: 'text/markdown',
bytes: Array.from(new TextEncoder().encode(gddContent)),
});
});
const submittedDraft = onCreateDraftAutomatically.mock.calls[0]?.[0] as
| HomeDraft
| undefined;
expect(submittedDraft?.prompt).toContain(
'请按照附件中的已批准 GDD 开始建造这款游戏。',
);
expect(submittedDraft?.attachments.map(({ file }) => file.name)).toEqual([
'fast_gdd.md',
]);
});
it('keeps the home composer out of chat mode while automatic project creation is pending', async () => {
@@ -14,12 +14,12 @@
- 关联文档:相关 PRD、技术文档、提交或 Issue
```
## 2026-08-30 批准 GDD 通过首页做游戏入口回填参考附件
## 2026-08-30 批准 GDD 直接进入做游戏链路
- 背景:立项策划 GDD 批准后需要给用户一个进入做游戏的自然出口,但用户明确要求点击按钮时不要直接创建项目或启动完整制作流程
- 决策:批准态 GDD 交付行提供“做成游戏”按钮。点击后读取当前项目的权威 `game/fast_gdd.md`回到首页并预选“做游戏”、把该文件作为 `text/markdown` 参考附件放入输入框;只有用户随后主动提交首页输入,才沿现有自动建项、附件导入和 Direct Codex 链路继续。该动作不复制原项目的 `approvedGddRef`、planning sidecar 或 approval receipt。
- 影响范围:AGC 前端 GDD 交付行、WorkspaceLauncher 首页预填状态、首页 RichInputArea 一次性附件注入;不新增 HTTP API、SpacetimeDB schema、迁移、OpenAPI 或正式构建绑定。
- 验证方式:批准态按钮、首页预填附件、回填过程不触发创建项目/启动 Runtime/Direct Codex 的 appSurface 回归;类型检查、编码检查和 `git diff --check` 通过。
- 背景:立项策划 GDD 批准后需要给用户一个进入做游戏的自然出口,产品决策改为点击按钮后直接开始建造
- 决策:批准态 GDD 交付行提供“做成游戏”按钮。点击后读取当前项目的权威 `game/fast_gdd.md`直接创建自动游戏工作区、导入 `text/markdown` 参考附件,并以固定建造指令自动启动 Direct Codex;不再回首页等待用户二次提交。该动作不复制原项目的 `approvedGddRef`、planning sidecar 或 approval receipt。
- 影响范围:AGC 前端 GDD 交付行与现有自动建项/附件导入/Direct Codex 链路;移除首页 RichInputArea 的 GDD 一次性预填链路;不新增 HTTP API、SpacetimeDB schema、迁移、OpenAPI 或正式构建绑定。
- 验证方式:批准态按钮直接创建工作区、导入附件、携带固定首条指令进入项目工作台且重复点击不重复创建的 appSurface 回归;类型检查、编码检查和 `git diff --check` 通过。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md`
---
@@ -7835,9 +7835,3 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- `autonomous-game-build` 中,manifest `dependencies` 只作为上下文,不阻塞 ready;代码、设计、美术、音频和发布任务允许并行启动,child 不依赖固定回执顺序或固定 run 身份才能推进。
- 任务最终状态不再提前绑定平台画布、preview、static smoke 或发布产物检查;这些内容不参与该档位的完成判定,也不会因缺失而重置已完成任务。父 run 在任务图进入终态后直接收束并回复。
- 本档位仍沿用现有项目根和工具权限边界;本次调整只解除流程编排与平台产物验收前置,不新增第二套任务系统。
## 2026-08-30 批准 GDD 回填建造指令
- “做成游戏”回首页时,除了把当前有效的 `game/fast_gdd.md` 作为 `text/markdown` 参考附件预填,还要在首页输入框预填固定建造指令。
- 固定指令只说明 GDD 覆盖的栏目(定位、类型与美术、支柱、核心循环、目标用户、平台与输入、MVP 系统、范围边界、创作者提示、原型验证),不读取具体 GDD 做总结或缩写。
- 预填动作仍不创建项目、不发送首页需求、不启动 Agent;用户主动提交后,固定文字与附件才作为现有做游戏链路的输入。
@@ -180,7 +180,7 @@ D9/D10 描述的「manifest ready-task 调度器在 Supervisor 下游启动策
| 构建引用 | `approvedGddRef {gddId, version, fingerprint}` |
| 现有 design 组 UI 名 | `设计实现组`,替代原“策划 Agent”卡片名称 |
| 新组件名 | `GDD 审批卡` |
| 固定入口动作 | 当前客户端为 `做成游戏`回首页预填 `game/fast_gdd.md` 参考附件,不自动创建项目或启动 Runtime;用户次提交首页输入后才进入做游戏链路 |
| 固定入口动作 | 当前客户端为 `做成游戏`读取 `game/fast_gdd.md`,直接创建自动游戏工作区、导入参考附件并以固定建造指令启动 Direct Codex;不再回首页等待用户次提交 |
`approvalRequestId``responseId` 是两个不同的持久 ID:前者由 Runtime 在 GDD 提交前生成、进入不可变 GDD,一张审批卡终身不变;后者由 UI 在用户执行一次决定时生成,并在传输重试中复用。不得继续用含义不明的单个 `requestId` 同时承担两种职责。
@@ -278,8 +278,7 @@ flowchart TD
SPLAN -->|"在自己的 runtime/session 上建 pending,向用户提问"| U
SPLAN -->|"answersSha256 绑回 delivery,再发 continuation 委派<br/>(最多 3 轮,受 clarification_round 上限约束,见第 23.5 节)"| PLANAGENT
PLANAGENT --> GDD["不可变 GDD + approve receipt"]
GDD -->|"用户动作:做成游戏;回首页预填 game/fast_gdd.md"| HOME["首页做游戏入口<br/>参考附件:fast_gdd.md<br/>不自动创建/启动"]
HOME -->|"用户再次提交首页输入"| BUILD
GDD -->|"用户动作:做成游戏;读取并导入 game/fast_gdd.md"| BUILD["自动创建游戏工作区<br/>参考附件:fast_gdd.md<br/>固定建造指令"]
U -.->|"直接开建"| BUILD
BUILD --> DAG["现行 16 任务 DAG"]
U --> CHAT
@@ -1518,8 +1517,8 @@ type PlanningBaselineInput =
- 仅首页“做方案”新建项目提交 `standard + project-supervisor-plan`2026-08-13 按 D11 更正,旧值 `project-supervisor-plan-chat` 作废);“做游戏”和“做素材”保持 `autonomous-game-build` 直接开建。前端只提交 Supervisor 根 run 的身份,**不提交也不感知策划子 Agent**——后者由 Supervisor 在服务端通过 `agent.delegate` 派生,页面侧不得直接创建或引用它。
- 项目页新建、打开既有项目和 Godot 导入不新增“进入立项策划”入口,保持现行构建/打开语义;只有已存在 planning sidecar 或 active plan lineage 的项目恢复原有策划链路。
- 策划阶段聊天输入属于当前 run:有活跃决策卡/审批卡时,输入回到该卡片对应 action;无 active run 时才可创建新的 plan continuation。
- approved 后显示“做成游戏”。点击后读取当前有效的 `game/fast_gdd.md`回到首页并把它作为 `text/markdown` 参考附件预填到“做游戏”输入框,同时预填固定建造指令:说明 GDD 覆盖的栏目,并要求 Agent 先阅读附件、按 GDD 的 MVP 范围开始建造;这段文字不根据具体 GDD 内容生成。该动作不创建新项目、不发送首页需求、不启动 `autonomous-game-build`
- 用户随后主动提交首页输入时,才沿现有做游戏链路创建新的自动项目、导入附件并启动 Direct Codex。这里的附件和预填文字都是本次建造的输入,不把原项目的 `approvedGddRef` 复制到新项目
- approved 后显示“做成游戏”。点击后读取当前有效的 `game/fast_gdd.md`直接沿现有自动做游戏链路创建新的工作区、导入 `text/markdown` 参考附件,并以固定建造指令作为 `initialSupervisorMessage` 自动启动 Direct Codex;固定指令只说明 GDD 覆盖的栏目,不根据具体 GDD 内容生成总结。该动作不回首页等待二次提交,不把原项目的 `approvedGddRef` 复制到新项目
- 用户仍可在项目工作台继续补充需求;普通首页“做游戏”入口的手动提交行为保持不变
### 18.2 GDD 审批卡
@@ -1705,7 +1704,7 @@ receipt 永远压过 stale pending:一旦该版本存在有效 receiptpendi
| agent.db | 专用幂等 helpersame key conflict;日志达到普通容量、尾部截断与压缩后仍能补齐并保留决定记录 |
| source/security | durable exact identity;三个 action tool`file.read` / `file.list` / `plan.submit_gdd`)广告与执行;MCP 空且 webSearchEnabled=falsecontrol functions 单列;tool-plan/batch/ledger/context/repair/completion 全部跳过 collaborationplan retry 保留 source/profile;除 Runtime-owned submit 外的副作用工具拒绝 |
| Prompt | **2026-08-13 按 D11 改写**:不新增 compositionSupervisor 根 run 沿用现役 supervisor composition、策划子 Agent 复用 `runtime` composition(见第 4.2 节);`decision-checkpoint` 请求 kind 随 D10 作废(见第 5.1 节)。仍冻结:3 轮/单题/固定选项;回答后设计解释与 prototype item;平台事实;恢复摘要;direct-out 条件 |
| frontend | 首页“做方案”目录提交与 Enter 自动创建均为 `standard + project-supervisor-plan`;“做游戏/做素材”均保持 `autonomous-game-build`;项目页新建/打开不首次注入 planningstable approvalRequestId/responseIdbusystale cardhydrate strict input/view;无目录空态;receipt 隐藏 stale pendingcorrupt authority typed errorproject open/reload/resume/submit/decision 刷新;recovery pending;批准 GDD 后“做成游戏”回首页预填 `fast_gdd.md` 且不启动创建/Runtime;用户再次提交后沿现有附件导入链路执行 |
| frontend | 首页“做方案”目录提交与 Enter 自动创建均为 `standard + project-supervisor-plan`;“做游戏/做素材”均保持 `autonomous-game-build`;项目页新建/打开不首次注入 planningstable approvalRequestId/responseIdbusystale cardhydrate strict input/view;无目录空态;receipt 隐藏 stale pendingcorrupt authority typed errorproject open/reload/resume/submit/decision 刷新;recovery pending;批准 GDD 后“做成游戏”直接创建自动工作区、导入 `fast_gdd.md` 并自动启动 Direct Codex;重复点击不重复创建;普通首页链路不回归 |
| M2 integration | explicit approved/direct mode;锁内重验 receiptref 贯穿 task/run/completion/context;无 ref 非回归;恢复不换稿 |
关键强杀点逐项覆盖:
@@ -1996,7 +1995,7 @@ M0 完成不表示完整策划闭环已经上线。`M1A-1``M1A-4`、`M1B-1`
| `M1C-2b` | 澄清中转接线、轮次派生、预算注入 | `M1C-2a` | **实现与本包门禁已完成并已快进合回 `feat/five_min_design`**:首 child 的 revision 1 session、`NeedsUserInput → awaiting_user_input`、回答绑定后 continuation 的确定性 session 投影、审批后 `revise/reject` 用户修订谱系及 Provider 活跃时间 usage fact/fold 已接线;末次 `plan.submit_gdd` usage 在 receipt/session successor 落盘且 standalone/v4 anchors 精确消费后于同一项目锁内折叠,真实 receipt 回归证明累计值恰好推进一次,重复审批与 recovery reconcile 不二次推进。`planning_clarification_*` **13 passed / 0 failed**(M1C-2c 语义回归另见本包),另有 static deliveries 44、planning storage 13、planning submit 53、Provider usage 4、末次 usage receipt 1、barrier detail 3 条定向回归通过;格式、offline all-targets、编码与 diff 门禁通过。锁序承诺只适用于 **M1C-2b 新增的 planning 澄清写投影路径**`main_loop` 既有通用 completion blocker 的 execution→project 路径不在本包。第 4 轮信封在正常路径不可达:`agent.delegate` 已在工具边界按血缘上限硬拒并返回 failed observationcoordinator 的超三轮 reconciliation 仅用于损坏血缘纵深防御。审批 UI、hydrate、构建准入和下游完整构建不在本包范围 |
| `M1C-2c` | 决策卡 A/B 语义(第 23.9 节,2026-08-18 实现完成并合回):选项 → 台账映射改为 A/B 均 `confirmed/user_option`、信封 label 形状校验、planning role brief 与 Supervisor playbook/final-reply 文案(B 必须是真实岔路、第 3 项恒定且 description 须给出可执行验证方式、改口转述规则、提问纪律) | `M1C-2b` | **实现与门禁完成,已由 `6e4bd9703` 合回 `feat/five_min_design`**Runtime 已实现 A/B/固定第三项校验、B 不再生成 `default_pending``answerSummary` 逐字保真;非法 C/缺项 fail-closedA/B/自由填写回归已通过。`planning_clarification_*` 13、`project_planning` prompt 5、`planning_submit` 定向回归、prompt bundle、格式、编码、diff、offline all-targets 均通过;不含 M1D-1 前端、hydrate、构建准入或下游完整构建 |
| `M1D-1` | 前端 hydrate 与 GDD 审批卡;决策卡按第 23.9 节实现(label 动态渲染、默认焦点 A、Other 槽不变) | `M1C-2b` | **已完成并合入 `feat/five_min_design`(落地 `0052a80da`,其后 ESLint 修正 `5b11a0530`**:新增严格 `{projectPath}` hydrate command、`plan-gdd-state-view.v1` Rust read model、审批卡与独立 GDD 正文详情弹层;页面只消费 hydrate,决定 responseId 按审批请求/动作复用,`recoveryPending` 仅提供恢复重试;审批前置 pending 与错绑 session 继续 fail-closed。 |
| `M1D-2` | 入口分流与阶段进度 | `M1D-1` | **2026-08-19 更正 `bf2185fba` 的错误入口映射**:仅首页“做方案”新项目以 `standard + project-supervisor-plan` 启动;“做游戏/做素材”保持 `autonomous-game-build`,不再展示额外“直接开建”按钮;项目页新建、打开和 Godot 导入不新增策划入口,仅恢复已有 planning lineage。阶段进度显示轮次 x/3、当前版本和状态徽章;实际项目总控页面挂载 hydrate/审批卡,并将 `project-planning` / 设计组展示名收口。**2026-08-30 补充:批准 GDD 后的“做成游戏”只回首页预填 `game/fast_gdd.md` 参考附件,不自动创建项目或启动 Runtime;仍未接入正式 `approvedGddRef` 构建绑定。** |
| `M1D-2` | 入口分流与阶段进度 | `M1D-1` | **2026-08-19 更正 `bf2185fba` 的错误入口映射**:仅首页“做方案”新项目以 `standard + project-supervisor-plan` 启动;“做游戏/做素材”保持 `autonomous-game-build`,不再展示额外“直接开建”按钮;项目页新建、打开和 Godot 导入不新增策划入口,仅恢复已有 planning lineage。阶段进度显示轮次 x/3、当前版本和状态徽章;实际项目总控页面挂载 hydrate/审批卡,并将 `project-planning` / 设计组展示名收口。**2026-08-30 变更:批准 GDD 后的“做成游戏”直接创建自动工作区、导入 `game/fast_gdd.md` 并自动启动 Direct Codex;仍未接入正式 `approvedGddRef` 构建绑定。** |
| `M1E` | 端到端与故障注入收口 | `M1D-2` | **已完成**planning 覆盖审计与 submit 拒绝上限收口完成。`PLAN_INVALID_REQUEST` / Provider input 或候选 GDD 的 `PLAN_SIZE_LIMIT` 每 child run 最多 5 次 rejected observation,第 5 次在 observation durable 后终态失败;counter durable,重启不清零。若在第五条 rejected observation 落盘与终态失败之间崩溃,恢复入口会按 durable counter 直接终态失败,不请求第六次 Provider tool-plan。既有不可变 GDD/receipt 的超限读取及 lineage 版本已耗尽均改走 reconciliation,不误耗 Provider 重试额度。第 21 节已存在的三轮、续跑、receipt/replay、投影恢复及 hydrate 回归复核通过;不为“拼接已有单测”新增脆弱大 E2E。 |
**2026-08-18 M1D 审查修复快照**:对 `14c00017c..bf2185fba` 做规格对照审查后,修复三条决定链路缺陷并补齐回归。① `decidePlanGdd` 的失败分支原来不 hydrate,命中后端任一 `PLAN_STALE_APPROVAL` 分支后卡片会停在已失效的 pending 身份上、`recoveryPending` 永不翻真导致「重试恢复」入口不渲染,现已按第 18.3 节在失败分支同样重灌(顺序钉死:`hydratePlanGddState` 入口会清空错误,必须先 hydrate 再写决定错误)。② responseId 复用键原为 `approvalRequestId:action`,不含 comment,违反第 13.2 节「改变 action/comment 必须换新 responseId」,现改为比对 `{action, comment}` 完整意图,判据方向为宁可多换不可少换。③ 第 18.2 节「`recoveryPending` 时不允许提交决定」原来只作用于三个触发按钮,已打开的评论弹层仍可提交,现已同门控并保留用户已输入内容。回归位于 `tests/appSurface/plan-gdd.suite.ts`,三条均经变异验证(逆转对应修复即变红);`appSurface.test.ts` 381 passed`agc:typecheck`、ESLint、编码检查通过。其中锁错误回传绝对路径、阶段进度轮次差一格与 design 组展示名收口三条已于同日补修(见 decision-log 同日两条);仅 hydrate 身份校验与落盘投影修复的顺序一条单列后续,未并入。