修正M1首页立项入口
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Successful in 2m54s
Project CI / Native shell tests (pull_request) Failing after 6m13s

仅让首页做方案进入立项策划

恢复做游戏和做素材的直接构建路径

同步入口合同与回归测试
This commit is contained in:
2026-08-19 05:27:53 +00:00
parent 7eee259cea
commit 6d5afd9f6d
7 changed files with 167 additions and 85 deletions
@@ -63,7 +63,6 @@ export function WorkspaceLauncherShell({
setAgentResults: setActiveProjectAgentResults,
resetLauncherHomeDraft,
createHomeDraft,
createHomeDraftDirectBuild,
createHomeDraftAutomatically,
openProject,
} = homeProject;
@@ -261,7 +260,6 @@ export function WorkspaceLauncherShell({
homeAgentModeItems={homeAgentModeItems}
recentProjectRows={recentProjectRows}
onCreateDraft={createHomeDraft}
onCreateDraftDirectBuild={createHomeDraftDirectBuild}
onCreateDraftAutomatically={createHomeDraftAutomatically}
onProjectsOpen={() => setLauncherView('projects')}
onProjectOpen={(path) => {
@@ -56,6 +56,10 @@ type UseHomeProjectCreationOptions = {
rememberRecentWorkspace: (projectPath: string) => void;
};
function homeDraftStartMode(mode: HomeAgentMode): ProjectStartMode {
return mode === 'doc' ? 'planning' : 'direct-build';
}
export function useHomeProjectCreation({
setStatus,
setLauncherView,
@@ -533,28 +537,7 @@ export function useHomeProjectCreation({
draft.mode,
draft.prompt,
draft.attachments,
draft.mode === 'game' ? 'planning' : 'direct-build',
);
}
async function createHomeDraftDirectBuild(draft: HomeDraft) {
const invoke = resolveTauriInvoke();
if (!invoke) {
throw new Error('需要在 Tauri App 内运行');
}
const selectedPath = await invoke<string | null>(
'pick_local_project_directory',
projectPath.trim() ? { initialPath: projectPath.trim() } : undefined,
);
if (!selectedPath) {
return '已取消';
}
return createHomeProjectFromDirectory(
selectedPath,
draft.mode,
draft.prompt,
draft.attachments,
'direct-build',
homeDraftStartMode(draft.mode),
);
}
@@ -572,7 +555,7 @@ export function useHomeProjectCreation({
draft.mode,
draft.prompt,
draft.attachments,
draft.mode === 'game' ? 'planning' : 'direct-build',
homeDraftStartMode(draft.mode),
);
}
@@ -662,7 +645,6 @@ export function useHomeProjectCreation({
pendingNonEmptyProject,
resetLauncherHomeDraft,
createHomeDraft,
createHomeDraftDirectBuild,
createHomeDraftAutomatically,
openProject,
pickAndOpenProject,
-22
View File
@@ -3303,28 +3303,6 @@ textarea {
color: #1f6feb;
}
.home-creation-actions {
display: flex;
align-items: center;
gap: 8px;
}
.home-direct-build-button {
min-height: 28px;
padding: 0 9px;
border: 1px solid var(--platform-surface-border);
border-radius: 999px;
background: var(--platform-button-secondary-fill);
color: var(--platform-button-secondary-text);
font-size: 11px;
cursor: pointer;
}
.home-direct-build-button:disabled {
cursor: not-allowed;
opacity: 0.55;
}
.gdd-approval-card__header {
display: flex;
align-items: flex-start;
@@ -47,7 +47,6 @@ type HomeViewProps = {
homeAgentModeItems: readonly HomeAgentModeItem[];
recentProjectRows: readonly HomeProjectRow[];
onCreateDraft: (draft: HomeDraft) => Promise<string>;
onCreateDraftDirectBuild: (draft: HomeDraft) => Promise<string>;
onCreateDraftAutomatically: (draft: HomeDraft) => Promise<string>;
onProjectsOpen: () => void;
onProjectOpen: (path: string) => void;
@@ -61,7 +60,6 @@ export default function HomeView({
homeAgentModeItems,
recentProjectRows,
onCreateDraft,
onCreateDraftDirectBuild,
onCreateDraftAutomatically,
onProjectsOpen,
onProjectOpen,
@@ -185,33 +183,16 @@ export default function HomeView({
>
<div className="grid grid-cols-[1fr_auto] items-center gap-2.5 text-[12px] text-(--platform-text-soft)">
<UploadButton />
<div className="home-creation-actions">
{homeAgentMode === 'game' ? (
<button
className="home-direct-build-button"
type="button"
disabled={homeCreationBusy}
onClick={() =>
void createFromHome(
onCreateDraftDirectBuild,
'正在创建工作区',
)
}
>
</button>
) : null}
<button
className="grid size-6 cursor-pointer place-items-center rounded-full border-0 bg-(image:--platform-button-primary-fill) p-0 text-(--platform-button-primary-text) shadow-(--platform-profile-action-shadow) disabled:cursor-not-allowed disabled:opacity-55"
type="submit"
aria-label={
homeAgentMode === 'game' ? '进入立项策划' : '开启创作'
}
disabled={homeCreationBusy}
>
<Plus size={16} aria-hidden="true" />
</button>
</div>
<button
className="grid size-6 cursor-pointer place-items-center rounded-full border-0 bg-(image:--platform-button-primary-fill) p-0 text-(--platform-button-primary-text) shadow-(--platform-profile-action-shadow) disabled:cursor-not-allowed disabled:opacity-55"
type="submit"
aria-label={
homeAgentMode === 'doc' ? '进入立项策划' : '开启创作'
}
disabled={homeCreationBusy}
>
<Plus size={16} aria-hidden="true" />
</button>
</div>
</RichInputArea>
</form>
@@ -1,3 +1,4 @@
import { PROJECT_SUPERVISOR_PLAN_SOURCE } from '../../src/app/constants';
import type { ProjectSupervisorComponentProps } from '../../src/features/app-shell/model';
import { WorkspaceLauncherShell } from '../../src/features/app-shell/WorkspaceLauncher';
import {
@@ -1291,7 +1292,7 @@ export function registerHomeProjectCreationTests() {
await waitFor(() => {
expect(promptInput.textContent).toContain('做一个像素风横版游戏');
});
const createButton = screen.getByRole('button', { name: '进入立项策划' });
const createButton = screen.getByRole('button', { name: '开启创作' });
fireEvent.click(createButton);
await waitFor(() => {
@@ -1455,6 +1456,7 @@ export function registerHomeProjectCreationTests() {
runId: expect.stringMatching(/^project-supervisor-task-/),
runProfile: 'autonomous-game-build',
});
expect(startCalls[0]?.[1]).not.toHaveProperty('source');
expect(startCalls[0]?.[1]).toMatchObject({
task: expect.stringContaining(
'第一行\n第二行\n第三行{1st attachment: reference.png}',
@@ -1494,6 +1496,140 @@ export function registerHomeProjectCreationTests() {
expect.anything(),
);
});
it.each([
['做游戏', false, 'autonomous-game-build', undefined],
['做方案', false, 'standard', PROJECT_SUPERVISOR_PLAN_SOURCE],
['做方案', true, 'standard', PROJECT_SUPERVISOR_PLAN_SOURCE],
] as const)(
'routes %s %s creation to the expected root run',
async (modeLabel, automatic, runProfile, source) => {
const projectPath = `/tmp/home-${modeLabel}-${automatic ? 'enter' : 'submit'}`;
const manifest = createGameCreationAppManifest(
'local-project-draft',
'home-entry-route',
);
const supervisorHarness = createProjectSupervisorRuntimeHarness({
projectPath,
expectedRunProfile: runProfile,
});
const invoke = vi.fn(
async (command: string, args?: Record<string, unknown>) => {
if (command === 'pick_local_project_directory') {
return projectPath;
}
if (command === 'is_local_project_directory_non_empty') {
return false;
}
if (command === 'create_automatic_local_game_project') {
return {
projectPath,
manifestPath: `${projectPath}/.agent/manifest.json`,
manifest,
};
}
return supervisorHarness.invoke(command, args);
},
);
window.__TAURI__ = {
core: { invoke },
event: { listen: supervisorHarness.listen },
};
renderLauncherAt('/?launcher', 'home', true);
if (modeLabel === '做方案') {
fireEvent.click(screen.getByRole('button', { name: modeLabel }));
}
const promptInput = screen.getByLabelText('创作想法');
nativeClipboardMock.text = '整理一个可玩原型';
fireEvent.paste(promptInput);
await waitFor(() => {
expect(promptInput.textContent).toContain('整理一个可玩原型');
});
if (automatic) {
fireEvent.keyDown(promptInput, { key: 'Enter', code: 'Enter' });
} else {
fireEvent.click(
screen.getByRole('button', {
name: source ? '进入立项策划' : '开启创作',
}),
);
}
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith(
'start_game_creator_supervisor_runtime_task',
expect.objectContaining({
projectPath,
runProfile,
...(source ? { source } : {}),
}),
);
});
const startCall = invoke.mock.calls.find(
([command]) => command === 'start_game_creator_supervisor_runtime_task',
);
if (source) {
expect(startCall?.[1]).toMatchObject({ source });
} else {
expect(startCall?.[1]).not.toHaveProperty('source');
}
expect(
invoke.mock.calls.filter(
([command]) => command === 'create_automatic_local_game_project',
),
).toHaveLength(automatic ? 1 : 0);
},
);
it('keeps the planning entry after confirming a non-empty folder from 做方案', async () => {
const projectPath = '/tmp/home-doc-non-empty';
const supervisorHarness = createProjectSupervisorRuntimeHarness({
projectPath,
expectedRunProfile: 'standard',
});
const invoke = vi.fn(
async (command: string, args?: Record<string, unknown>) => {
if (command === 'pick_local_project_directory') {
return projectPath;
}
if (command === 'is_local_project_directory_non_empty') {
return true;
}
return supervisorHarness.invoke(command, args);
},
);
window.__TAURI__ = {
core: { invoke },
event: { listen: supervisorHarness.listen },
};
renderLauncherAt('/?launcher', 'home', true);
fireEvent.click(screen.getByRole('button', { name: '做方案' }));
const promptInput = screen.getByLabelText('创作想法');
nativeClipboardMock.text = '整理可执行的玩法立项方案';
fireEvent.paste(promptInput);
await waitFor(() => {
expect(promptInput.textContent).toContain('整理可执行的玩法立项方案');
});
fireEvent.click(screen.getByRole('button', { name: '进入立项策划' }));
expect(
await screen.findByRole('dialog', { name: '文件夹不是空的' }),
).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '继续新建' }));
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith(
'start_game_creator_supervisor_runtime_task',
expect.objectContaining({
projectPath,
runProfile: 'standard',
source: PROJECT_SUPERVISOR_PLAN_SOURCE,
}),
);
});
});
}
export function registerRecentProjectsTests() {
@@ -1,5 +1,12 @@
# 决策记录
## 2026-08-19 更正 M1D-2 首页入口映射
- **更正**`bf2185fba` 把“做游戏”误接为 `standard + project-supervisor-plan`,并额外增加“直接开建”按钮;这与既有产品决定“做方案入口独立成链,不动做游戏路径”冲突。
- **当前入口合同**:仅首页“做方案”新建项目以 `standard + project-supervisor-plan` 进入立项策划;“做游戏”和“做素材”保持 `autonomous-game-build` 直接开建。目录提交与 Enter 自动创建使用同一映射。
- **边界**:项目页新建、打开既有项目和 Godot 导入不新增策划入口;已有 planning sidecar 或 active plan lineage 只恢复其原有链路。
- **回归**:覆盖做游戏目录提交、做方案目录提交、做方案 Enter 自动创建,以及既有做素材 Enter 自动创建,分别断言首个 Supervisor run 的 profile/source。
## 2026-08-18 M1D 收口:design 组展示名改完
- **背景更正(先于结论)**:此前把这条的严重度建立在「新项目默认主路径上『立项策划』与『策划 Agent』同屏共存」上,**该说法未经验证且不成立**。用 appSurface harness 实测策划路径:`子 Agent 状态栏` 根本不渲染,`策划 Agent` 出现 0 次、`立项策划` 出现 2 次。结构上二者确在 `launcherView === 'project-development'` 分支的同一棵树里(`ProjectDevelopmentView` 的 dock + 作为 `supervisor` 传入的 `ProjectSupervisorView`),但未能把用例驱动到该分支,故不作为事实主张。若真会撞,也是**批准后进入完整制作**那一段(此时 `state=approved`,阶段进度卡守卫仍放行),比原描述窄得多。
@@ -42,7 +49,7 @@
## 2026-08-18 M1D-2 隔离工作树实现:入口分流与阶段进度
- **隔离范围**:在 `codex/genarrative-isolated`、基线 `5b11a0530` 上开工;只接入口分流、阶段进度和实际项目总控页面的现有审批卡挂载,不接 M2 `approvedGddRef` 构建绑定、完整构建按钮或 M1E 端到端故障注入。
- **入口合同**:游戏新项目默认 `standard + project-supervisor-plan` 启动;“直接开建”显式走既有 `autonomous-game-build` 路径。做素材/做方案入口保持原有自动构建行为;空目录确认会携带同一 start mode,不因二次确认改变路径。项目打开/项目页新建不注入 start mode,保持老项目行为。
- **入口合同(已被 2026-08-19 更正)**本条原将游戏新项目默认接为 `standard + project-supervisor-plan`,现改为仅首页“做方案”进入该路径;做游戏/做素材保持既有直接构建。项目打开/项目页新建不注入 start mode,保持老项目行为。
- **页面接线**`ProjectSupervisorView` 现在复用 M1D-1 的 hydrate、GDD 审批卡和阶段进度;阶段进度只消费 `plan-gdd-state-view.v1`,显示 `轮次 x/3`、当前版本与状态徽章。`project-planning` 显示为“立项策划 Agent”,设计组展示名改为“设计实现组”。
- **Runtime 路由**:项目总控聊天提交在规划入口或已读到 `project-supervisor-plan` 时继续使用 `standard + project-supervisor-plan`;规划 Run 尚未终态时不另起通用聊天 Run,要求先完成当前策划步骤。直接开建及非规划入口保留原提交 profile/source。
- **自审边界与验证**:只接受有完整触发链路的问题;本包不改变 Runtime source 门禁、审批命令、构建准入或老项目 direct-build 语义。改动文件 ESLint、Shell TypeScript 两套 typecheck、`agentRuntimeModel.test.ts`29 passed)、编码检查与 `git diff --check` 通过;appSurface UI suite 受仓库现有缺失 Tauri plugin 依赖阻断,未把该基线失败归因于本包。自审未发现测试失败或修复边界明确的 M1D-2 之外缺陷;M2 approved-GDD 构建绑定与 M1E 跨层故障注入保留。
@@ -133,7 +133,7 @@ WP1 定稿的澄清轮次/返工深度语义(供 D11 依赖,权威定义见
| 编号 | 冻结结论 |
| --- | --- |
| D1 | 每个项目生成 2~4 条游戏支柱;可感知体验、角色成长、探索、构建变体只作为候选,不固定套用。 |
| D2 | 新项目目标态默认进入“立项策划”,同时保留“直接开建”;直接开建等价于当前无 GDD 基线的完整构建路径。 |
| D2 | 仅首页“做方案”新建项目进入“立项策划”;“做游戏”和“做素材”保持直接开建等价于当前无 GDD 基线的完整构建路径。 |
| D3 | 阶段、source、composition、工具、schema、审批命令和 UI 名称按第 3 节注册表冻结。 |
| D4 | M1 使用 `.agent/planning/` sidecar,不改 `shared-contracts`;是否把 `approvedGddRef` 提升进 manifest 留给 M2,不能在 M1 临时决定。 |
| D5 | game-chat 只在 M3 可选只读消费有效批准 GDD;没有批准 GDD 时继续走现役快车道。 |
@@ -1393,7 +1393,7 @@ type PlanGddDecisionResult = {
| 磁盘或运行状态 | 冻结行为 |
| --- | --- |
| 无 `.agent/planning/` | 老项目;直接开建零差异;首次策划在项目锁内懒创建 |
| 无 `.agent/planning/` | 老项目或未走首页“做方案”入口的新项目;直接开建零差异;仅首页“做方案”首次创建可在项目锁内懒创建 planning sidecar |
| valid v1 | 重算全部指纹、连续版本、项目/run identity 和 receipt 引用后读取 |
| unknown schema/field | `PLAN_UNSUPPORTED_SCHEMA`,只读失败;不覆盖、不降级 |
| GDD/receipt target 旁有 tmp | tmp 不是事实;持锁确认无活跃 writer 后清理 |
@@ -1527,8 +1527,8 @@ game-chat 不强制先策划、不改变单主结构。项目存在有效 approv
### 18.1 入口
- 新项目目标态默认提交 `standard + project-supervisor-plan`2026-08-13 按 D11 更正,旧值 `project-supervisor-plan-chat` 作废);创建界面保留明确的“直接开建。前端只提交 Supervisor 根 run 的身份,**不提交也不感知策划子 Agent**——后者由 Supervisor 在服务端通过 `agent.delegate` 派生,页面侧不得直接创建或引用它。
- 已有项目可从项目开发页进入“立项策划”或继续现行构建;一个项目同时最多显示一个 active plan run
- 仅首页“做方案”新建项目提交 `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 后显示“开始完整制作”;“批准并开建”只是先审批、后开建的快捷交互,不合并后端命令或 durable 记录。
@@ -1722,7 +1722,7 @@ Canvas 是条件外部能力,不是 M0-3 四个固定 owner 的通用前置:
| 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 | 默认入口与直接开建stable approvalRequestId/responseIdbusystale cardhydrate strict input/view;无目录空态;receipt 隐藏 stale pendingcorrupt authority typed errorproject open/reload/resume/submit/decision 刷新;recovery pending;批准并开建两命令顺序 |
| 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;批准并开建两命令顺序 |
| M2 integration | explicit approved/direct mode;锁内重验 receiptref 贯穿 task/run/completion/context;无 ref 非回归;恢复不换稿 |
| M3 integration | 有批准 GDD 的只读注入;无 GDD 零差异;不改变 game-chat 单主 lineage |
@@ -2026,7 +2026,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` | **已完成并以 `bf2185fba` 合入 `feat/five_min_design`**:游戏新项目默认 `standard + project-supervisor-plan`,显式“直接开建”保持 `autonomous-game-build`阶段进度显示轮次 x/3、当前版本和状态徽章;实际项目总控页面挂载 hydrate/审批卡,并将 `project-planning` / 设计组展示名收口。未接 M2 approved-GDD 构建绑定或完整构建按钮。 |
| `M1D-2` | 入口分流与阶段进度 | `M1D-1` | **2026-08-19 更正 `bf2185fba` 的错误入口映射**:仅首页“做方案”新项目 `standard + project-supervisor-plan` 启动;“做游戏/做素材”保持 `autonomous-game-build`,不再展示额外“直接开建”按钮;项目页新建、打开和 Godot 导入不新增策划入口,仅恢复已有 planning lineage。阶段进度显示轮次 x/3、当前版本和状态徽章;实际项目总控页面挂载 hydrate/审批卡,并将 `project-planning` / 设计组展示名收口。未接 M2 approved-GDD 构建绑定或完整构建按钮。 |
| `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 身份校验与落盘投影修复的顺序一条单列后续,未并入。