@zhushanwen/pi-plan 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,13 +1,11 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { getLogger } from "@zhushanwen/pi-extension-logger";
3
2
 
4
3
  import { registerPlanCommand } from "./command.js";
4
+ import { registerPlanEventHandlers } from "./compact.js";
5
5
  import { type PlanSessionMap, reconstructPlanState } from "./state.js";
6
6
  import { registerPlanTool } from "./tool.js";
7
7
  import { updatePlanWidget } from "./widget.js";
8
8
 
9
- const logger = getLogger("pi-plan");
10
-
11
9
  export default function planExtension(pi: ExtensionAPI) {
12
10
  // Per-session state cache — keyed by sessionId
13
11
  const sessions: PlanSessionMap = new Map();
@@ -16,12 +14,8 @@ export default function planExtension(pi: ExtensionAPI) {
16
14
  registerPlanTool(pi, sessions);
17
15
  registerPlanCommand(pi, sessions);
18
16
 
19
- // Dynamic import compact handlers — avoids cross-group static import
20
- import("./compact.js").then(({ registerPlanEventHandlers }) => {
21
- registerPlanEventHandlers(pi, sessions);
22
- }).catch((_e: unknown) => {
23
- logger.warn('compact handlers load failed', { error: String(_e) });
24
- });
17
+ // Register compact/tree event handlers
18
+ registerPlanEventHandlers(pi, sessions);
25
19
 
26
20
  // Reconstruct state on session start
27
21
  pi.on("session_start", async (_event: unknown, ctx: ExtensionContext) => {
package/src/state.ts CHANGED
@@ -1,10 +1,7 @@
1
1
  import type { CustomEntry, ExtensionAPI, ExtensionContext, SessionEntry } from "@earendil-works/pi-coding-agent";
2
2
 
3
- export type PlanPhase = "idle" | "brainstorming" | "writing" | "complete";
4
-
5
3
  export interface PlanState {
6
4
  isActive: boolean;
7
- phase: PlanPhase;
8
5
  planFilePath: string;
9
6
  requirement: string;
10
7
  templateName: string;
@@ -12,7 +9,6 @@ export interface PlanState {
12
9
 
13
10
  export const DEFAULT_PLAN_STATE: PlanState = {
14
11
  isActive: false,
15
- phase: "idle",
16
12
  planFilePath: "",
17
13
  requirement: "",
18
14
  templateName: "",
@@ -41,7 +37,6 @@ export function getPlanState(
41
37
  export function persistPlanState(pi: ExtensionAPI, state: PlanState): void {
42
38
  pi.appendEntry("plan-state", {
43
39
  isActive: state.isActive,
44
- phase: state.phase,
45
40
  planFilePath: state.planFilePath,
46
41
  requirement: state.requirement,
47
42
  templateName: state.templateName,
@@ -57,7 +52,6 @@ export function resetPlanState(
57
52
  ): PlanState {
58
53
  const state = getPlanState(sessions, sessionId, ctx);
59
54
  state.isActive = false;
60
- state.phase = "idle";
61
55
  state.planFilePath = "";
62
56
  state.requirement = "";
63
57
  state.templateName = "";
@@ -85,8 +79,8 @@ export function reconstructPlanState(ctx: ExtensionContext): PlanState {
85
79
  const entry = entries[i];
86
80
  if (!isPlanStateEntry(entry)) continue;
87
81
  const data = entry.data;
82
+ // 逐字段 ?? 白名单式读取:旧版 entry 残留的 phase 字段被自然忽略(D6 兼容读)
88
83
  state.isActive = data?.isActive ?? false;
89
- state.phase = data?.phase ?? "idle";
90
84
  state.planFilePath = data?.planFilePath ?? "";
91
85
  state.requirement = data?.requirement ?? "";
92
86
  state.templateName = data?.templateName ?? "";
package/src/templates.ts CHANGED
@@ -2,13 +2,10 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
- import { getAgentDir } from "@earendil-works/pi-coding-agent";
6
-
7
5
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
6
 
9
7
  export interface TemplateInfo {
10
8
  name: string;
11
- source: "builtin" | "global" | "project";
12
9
  path: string;
13
10
  }
14
11
 
@@ -16,42 +13,23 @@ export function getBuiltinTemplateDir(): string {
16
13
  return path.resolve(__dirname, "..", "templates");
17
14
  }
18
15
 
19
- function scanTemplateDir(dir: string, source: TemplateInfo["source"], seen: Set<string>): TemplateInfo[] {
16
+ function scanTemplateDir(dir: string): TemplateInfo[] {
20
17
  const results: TemplateInfo[] = [];
21
18
  if (!fs.existsSync(dir)) return results;
22
19
  for (const file of fs.readdirSync(dir)) {
23
20
  if (file.endsWith(".md")) {
24
- const name = file.replace(/\.md$/, "");
25
- if (!seen.has(name)) {
26
- results.push({ name, source, path: path.join(dir, file) });
27
- seen.add(name);
28
- }
21
+ results.push({ name: file.replace(/\.md$/, ""), path: path.join(dir, file) });
29
22
  }
30
23
  }
31
24
  return results;
32
25
  }
33
26
 
34
- export function listTemplates(projectDir?: string): TemplateInfo[] {
35
- const seen = new Set<string>();
36
- const templates: TemplateInfo[] = [];
37
-
38
- // 1. Project-level templates (highest priority)
39
- if (projectDir) {
40
- templates.push(...scanTemplateDir(path.join(projectDir, ".pi", "plan-templates"), "project", seen));
41
- }
42
-
43
- // 2. Global templates(getAgentDir 派生,实例隔离:PI_CODING_AGENT_DIR 场景读隔离目录)
44
- templates.push(...scanTemplateDir(path.join(getAgentDir(), "plan-templates"), "global", seen));
45
-
46
- // 3. Builtin templates (lowest priority)
47
- templates.push(...scanTemplateDir(getBuiltinTemplateDir(), "builtin", seen));
48
-
49
- return templates;
27
+ export function listTemplates(): TemplateInfo[] {
28
+ return scanTemplateDir(getBuiltinTemplateDir());
50
29
  }
51
30
 
52
- export function loadTemplate(name: string, projectDir?: string): string | null {
53
- const templates = listTemplates(projectDir);
54
- const template = templates.find((t) => t.name === name);
31
+ export function loadTemplate(name: string): string | null {
32
+ const template = listTemplates().find((t) => t.name === name);
55
33
  if (!template) return null;
56
34
 
57
35
  try {
package/src/tool.ts CHANGED
@@ -1,11 +1,11 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
-
4
1
  import { StringEnum } from "@earendil-works/pi-ai";
5
2
  import type { ExtensionAPI, ExtensionContext, Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
6
3
  import { Text } from "@earendil-works/pi-tui";
4
+ import { firstContentText } from "@taiji/extension-protocol";
7
5
  import { Type } from "typebox";
8
6
 
7
+ import { detectGoalCapability, GOAL_FAILURE_RECOVERY, handlePlanComplete } from "./compact.js";
8
+ import type { GoalBridgeOutcome } from "./compact.js";
9
9
  import type { PlanSessionMap, PlanState } from "./state.js";
10
10
  import { getPlanState, persistPlanState, resetPlanState } from "./state.js";
11
11
  import { listTemplates, loadTemplate } from "./templates.js";
@@ -16,7 +16,6 @@ import { updatePlanWidget } from "./widget.js";
16
16
  export const PLAN_ACTIONS = [
17
17
  "list-template",
18
18
  "select-template",
19
- "create-template",
20
19
  "complete",
21
20
  "abort",
22
21
  ] as const;
@@ -31,20 +30,13 @@ export function validateAction(action: string): action is PlanAction {
31
30
 
32
31
  interface ListTemplateDetails {
33
32
  action: "list-template";
34
- templates: Array<{ name: string; source: string; path: string }>;
33
+ templates: Array<{ name: string; path: string }>;
35
34
  }
36
35
 
37
36
  interface SelectTemplateDetails {
38
37
  action: "select-template";
39
38
  templateName: string;
40
39
  content: string;
41
- phase: string;
42
- }
43
-
44
- interface CreateTemplateDetails {
45
- action: "create-template";
46
- templateName: string;
47
- templateDir: string;
48
40
  }
49
41
 
50
42
  interface CompleteDetails {
@@ -52,6 +44,8 @@ interface CompleteDetails {
52
44
  planFilePath: string;
53
45
  isolation: string;
54
46
  execMode: string;
47
+ /** D2:direct 档 goalInit 的同步结果;compact 档在 onComplete 回调内执行,不进 result */
48
+ goalOutcome?: GoalBridgeOutcome;
55
49
  }
56
50
 
57
51
  interface CompleteCancelledDetails {
@@ -66,7 +60,6 @@ interface AbortDetails {
66
60
  type PlanDetails =
67
61
  | ListTemplateDetails
68
62
  | SelectTemplateDetails
69
- | CreateTemplateDetails
70
63
  | CompleteDetails
71
64
  | CompleteCancelledDetails
72
65
  | AbortDetails;
@@ -80,10 +73,8 @@ function restoreFullToolSet(pi: ExtensionAPI): void {
80
73
  }
81
74
 
82
75
  /** Compact template list for TUI display. Two-column, max 5 lines. */
83
- function formatTemplateList(
84
- templates: Array<{ name: string; source: string }>,
85
- ): string {
86
- const names = templates.map((t) => `${t.name} (${t.source})`);
76
+ function formatTemplateList(templates: Array<{ name: string }>): string {
77
+ const names = templates.map((t) => t.name);
87
78
  if (names.length === 0) return "No templates available.";
88
79
 
89
80
  const MAX_DISPLAY = 8;
@@ -120,11 +111,10 @@ function renderPlanResult(
120
111
  _options: unknown,
121
112
  theme: Theme,
122
113
  ): Text {
123
- const details = result.details;
124
- if (!details) {
125
- const text = result.content[0];
126
- return new Text(text?.type === "text" ? (text.text ?? "") : "", 0, 0);
127
- }
114
+ const details = result.details;
115
+ if (!details) {
116
+ return new Text(firstContentText(result), 0, 0);
117
+ }
128
118
 
129
119
  const fg = (token: ThemeColor, text: string) => theme.fg(token, text);
130
120
  const NL = "\n";
@@ -139,15 +129,8 @@ function renderPlanResult(
139
129
 
140
130
  case "select-template": {
141
131
  const header = fg("success", `✓ ${details.templateName}`) + NL;
142
- const body = fg("dim", ` brainstorming → ${details.phase}`) + NL;
143
132
  const hint = fg("dim", "→ 按模板章节顺序写 plan.md");
144
- return new Text(header + body + hint, 0, 0);
145
- }
146
-
147
- case "create-template": {
148
- const header = fg("success", `✓ 已创建: ${details.templateName}`) + NL;
149
- const body = fg("dim", ` ${details.templateDir}`);
150
- return new Text(header + body, 0, 0);
133
+ return new Text(header + hint, 0, 0);
151
134
  }
152
135
 
153
136
  case "complete": {
@@ -179,8 +162,8 @@ interface ActionResult {
179
162
  details: PlanDetails;
180
163
  }
181
164
 
182
- function executeListTemplate(projectDir: string): ActionResult {
183
- const templates = listTemplates(projectDir);
165
+ function executeListTemplate(): ActionResult {
166
+ const templates = listTemplates();
184
167
  return {
185
168
  content: [{ type: "text" as const, text: `${templates.length} templates available` }],
186
169
  details: { action: "list-template", templates },
@@ -191,46 +174,20 @@ function executeSelectTemplate(
191
174
  pi: ExtensionAPI,
192
175
  params: Record<string, unknown>,
193
176
  state: PlanState,
194
- projectDir: string,
195
177
  ): ActionResult {
196
178
  const templateName = params.templateName as string;
197
179
  if (!templateName) {
198
180
  throw new Error("templateName is required for select-template");
199
181
  }
200
- const content = loadTemplate(templateName, projectDir);
182
+ const content = loadTemplate(templateName);
201
183
  if (!content) {
202
184
  throw new Error(`Template not found: ${templateName}`);
203
185
  }
204
186
  state.templateName = templateName;
205
- state.phase = "writing";
206
187
  persistPlanState(pi, state);
207
188
  return {
208
189
  content: [{ type: "text" as const, text: `Template selected: ${templateName}` }],
209
- details: { action: "select-template", templateName, content, phase: state.phase },
210
- };
211
- }
212
-
213
- function executeCreateTemplate(params: Record<string, unknown>, projectDir: string): ActionResult {
214
- const templateName = params.templateName as string;
215
- const templateContent = params.templateContent as string;
216
- if (!templateName || !templateContent) {
217
- throw new Error("templateName and templateContent are required for create-template");
218
- }
219
- const sanitizedName = templateName.replace(/[^a-zA-Z0-9_-]/g, "");
220
- if (!sanitizedName) {
221
- throw new Error("Invalid template name: must contain alphanumeric characters");
222
- }
223
- const templateDir = path.join(projectDir, ".pi", "plan-templates");
224
- fs.mkdirSync(templateDir, { recursive: true });
225
- const filePath = path.join(templateDir, `${sanitizedName}.md`);
226
- fs.writeFileSync(filePath, templateContent);
227
- return {
228
- content: [{ type: "text" as const, text: `Template created: ${sanitizedName}` }],
229
- details: {
230
- action: "create-template",
231
- templateName: sanitizedName,
232
- templateDir: relativePath(filePath, projectDir),
233
- },
190
+ details: { action: "select-template", templateName, content },
234
191
  };
235
192
  }
236
193
 
@@ -249,21 +206,30 @@ function executeAbort(
249
206
  };
250
207
  }
251
208
 
209
+ /**
210
+ * 执行方式对话框的 label→mode 映射表(发现 8)——SDK `ui.select(title, options: string[])`
211
+ * 只收字符串数组,无法传结构化选项,故用本地查表而非文案反查。
212
+ */
213
+ const EXEC_MODE_OPTIONS: Array<{ label: string; mode: string }> = [
214
+ { label: "Subagent-driven execution", mode: "subagent" },
215
+ { label: "Goal-driven execution (/goal)", mode: "goal" },
216
+ { label: "Single-agent (current session)", mode: "single-agent" },
217
+ ];
218
+
219
+ /** 对话框尾部的两个"留在 plan mode"选项(complete-cancelled 路径) */
220
+ const CANCEL_OPTIONS = ["Modify the plan first", "Save for later"];
221
+
252
222
  /** Build execution options filtered by available capabilities. */
253
- async function buildExecOptions(pi: ExtensionAPI): Promise<string[]> {
254
- const execOptions = ["Subagent-driven execution"];
255
- const hasGoal = (await import("./compact.js")).detectGoalCapability(pi);
256
- if (hasGoal) execOptions.push("Goal-driven execution (/goal)");
257
- execOptions.push("Single-agent (current session)");
258
- execOptions.push("Modify the plan first", "Save for later");
259
- return execOptions;
223
+ function buildExecOptions(): string[] {
224
+ const modeLabels = EXEC_MODE_OPTIONS
225
+ .filter((opt) => opt.mode !== "goal" || detectGoalCapability())
226
+ .map((opt) => opt.label);
227
+ return [...modeLabels, ...CANCEL_OPTIONS];
260
228
  }
261
229
 
262
- /** Map the user's execution-method choice to the chosenMode string. */
230
+ /** Map the user's execution-method choice (dialog label) to the chosenMode string. */
263
231
  function chosenModeFromChoice(choice: string): string {
264
- if (choice === "Subagent-driven execution") return "subagent";
265
- if (choice === "Goal-driven execution (/goal)") return "goal";
266
- return "single-agent";
232
+ return EXEC_MODE_OPTIONS.find((opt) => opt.label === choice)?.mode ?? "single-agent";
267
233
  }
268
234
 
269
235
  /** Outcome of the complete-action execution-method prompt. */
@@ -276,8 +242,8 @@ type CompleteChoiceOutcome =
276
242
  * Cancel / "Modify the plan first" / "Save for later" → cancelled with a
277
243
  * complete-cancelled result; otherwise the mapped chosenMode.
278
244
  */
279
- async function resolveCompleteChoice(ctx: ExtensionContext, pi: ExtensionAPI): Promise<CompleteChoiceOutcome> {
280
- const execOptions = await buildExecOptions(pi);
245
+ async function resolveCompleteChoice(ctx: ExtensionContext): Promise<CompleteChoiceOutcome> {
246
+ const execOptions = buildExecOptions();
281
247
 
282
248
  if (typeof ctx.ui.select !== "function") {
283
249
  return { kind: "mode", chosenMode: "single-agent" };
@@ -297,7 +263,20 @@ async function resolveCompleteChoice(ctx: ExtensionContext, pi: ExtensionAPI): P
297
263
  return { kind: "mode", chosenMode: chosenModeFromChoice(choice) };
298
264
  }
299
265
 
300
- /** complete action: prompt for execution mode, persist final phase, restore tools, reset state. */
266
+ /**
267
+ * complete 的 result 正文:direct 档 goalInit 同步完成,追加 goal 结果行(D2);
268
+ * compact 档 goalInit 在 onComplete 回调内执行、result 已返回,不携带(通道差异
269
+ * 为设计 §6.2 D2 登记的终态)。
270
+ */
271
+ function completeResultText(displayPath: string, goalOutcome: GoalBridgeOutcome | undefined): string {
272
+ const base = `Plan approved. File: ${displayPath}`;
273
+ if (goalOutcome === undefined) return base;
274
+ return goalOutcome.started
275
+ ? `${base}\nGoal execution started via /goal.`
276
+ : `${base}\nGoal execution was not started (${goalOutcome.reason}). ${GOAL_FAILURE_RECOVERY[goalOutcome.reason]}`;
277
+ }
278
+
279
+ /** complete action: prompt for execution mode, restore tools, reset state. */
301
280
  async function executeComplete(
302
281
  pi: ExtensionAPI,
303
282
  ctx: ExtensionContext,
@@ -307,24 +286,23 @@ async function executeComplete(
307
286
  sessionId: string,
308
287
  projectDir: string,
309
288
  ): Promise<ActionResult> {
310
- const choice = await resolveCompleteChoice(ctx, pi);
289
+ const choice = await resolveCompleteChoice(ctx);
311
290
  if (choice.kind === "cancelled") {
312
291
  return choice.result;
313
292
  }
314
293
  const chosenMode = choice.chosenMode;
315
294
 
316
- // Persist final phase before cleanup
295
+ // D6:原「persist final phase (complete)」为死状态落盘(P1 实证不可观测),
296
+ // phase 删除后该 persist 与上一条 entry 完全重复,随死状态一并移除——
297
+ // 最终态由下方 resetPlanState 的 isActive=false entry 权威记录。
317
298
  const planFilePath = state.planFilePath;
318
299
  const isolation = (params.isolation as string) ?? "direct";
319
- state.phase = "complete";
320
- persistPlanState(pi, state);
321
300
 
322
301
  // Restore full tool set
323
302
  restoreFullToolSet(pi);
324
303
 
325
- // Execute completion handler (compact/tree setup)
326
- const { handlePlanComplete } = await import("./compact.js");
327
- handlePlanComplete(pi, ctx, state, isolation, chosenMode);
304
+ // Execute completion handler (compact setup + steer/goalInit delivery)
305
+ const goalOutcome = handlePlanComplete(pi, ctx, state, isolation, chosenMode);
328
306
 
329
307
  // Reset state and clear widget — same as abort
330
308
  const updatedState = resetPlanState(pi, sessions, sessionId, ctx);
@@ -332,8 +310,8 @@ async function executeComplete(
332
310
 
333
311
  const displayPath = relativePath(planFilePath, projectDir);
334
312
  return {
335
- content: [{ type: "text" as const, text: `Plan approved. File: ${displayPath}` }],
336
- details: { action: "complete", planFilePath: displayPath, isolation, execMode: chosenMode },
313
+ content: [{ type: "text" as const, text: completeResultText(displayPath, goalOutcome) }],
314
+ details: { action: "complete", planFilePath: displayPath, isolation, execMode: chosenMode, goalOutcome },
337
315
  };
338
316
  }
339
317
 
@@ -349,13 +327,12 @@ export function registerPlanTool(
349
327
  description:
350
328
  "Manages plan mode lifecycle (template selection, state transitions, completion). " +
351
329
  "NOT for writing plan content — write plan.md via the bash tool (e.g. cat heredoc). " +
352
- "Actions: list-template, select-template, create-template, complete, abort.",
330
+ "Actions: list-template, select-template, complete, abort.",
353
331
  parameters: Type.Object({
354
332
  action: StringEnum(PLAN_ACTIONS, { description: "Action to perform" }),
355
333
  templateName: Type.Optional(Type.String({ description: "Template name (for select-template)" })),
356
- templateContent: Type.Optional(Type.String({ description: "Template content (for create-template)" })),
357
334
  isolation: Type.Optional(
358
- StringEnum(["compact", "tree", "direct"], {
335
+ StringEnum(["compact", "direct"], {
359
336
  description: "Isolation mode for plan execution (for complete action)",
360
337
  }),
361
338
  ),
@@ -363,7 +340,7 @@ export function registerPlanTool(
363
340
  promptSnippet:
364
341
  "## When to use this tool vs the bash tool\n" +
365
342
  "Use 'plan' tool ONLY for plan mode state management:\n" +
366
- "- list-template / select-template / create-template — template operations\n" +
343
+ "- list-template / select-template — template operations\n" +
367
344
  "- complete — user approved plan, exit plan mode\n" +
368
345
  "- abort — cancel plan mode\n" +
369
346
  "\n" +
@@ -372,15 +349,11 @@ export function registerPlanTool(
372
349
  "## End-to-end workflow example\n" +
373
350
  "1. /plan 'add dark mode' — user enters plan mode\n" +
374
351
  "2. AI explores codebase (read, grep, bash) — brainstorming\n" +
375
- "3. plan(action='list-template') show available templates\n" +
376
- "4. User picks template plan(action='select-template', templateName='feature-plan')\n" +
377
- "5. bash: cat > \"$PLAN_FILE\" <<'EOF' ... EOF write plan content\n" +
378
- "6. User reviews → plan(action='complete', isolation='compact') — exit plan mode\n" +
352
+ "3. plan(action='list-template') user picks → plan(action='select-template', templateName='...')\n" +
353
+ "4. bash: cat > \"$PLAN_FILE\" <<'EOF' ... EOF — write plan content\n" +
354
+ "5. User reviews plan(action='complete', isolation='compact')exit plan mode\n" +
379
355
  "\n" +
380
- "## Common mistakes\n" +
381
356
  "❌ plan(action='complete') to 'write the plan' — WRONG, write plan.md via the bash tool\n" +
382
- "❌ Calling plan tool when user says 'write plan to file' — use the bash tool\n" +
383
- "✅ plan(action='list-template') to discover templates\n" +
384
357
  "✅ plan(action='complete') AFTER plan.md is written AND user approves",
385
358
  renderResult(
386
359
  result: { content: Array<{ type: string; text?: string }>; details?: PlanDetails },
@@ -407,13 +380,10 @@ export function registerPlanTool(
407
380
 
408
381
  switch (action) {
409
382
  case "list-template":
410
- return executeListTemplate(projectDir);
383
+ return executeListTemplate();
411
384
 
412
385
  case "select-template":
413
- return executeSelectTemplate(pi, params, state, projectDir);
414
-
415
- case "create-template":
416
- return executeCreateTemplate(params, projectDir);
386
+ return executeSelectTemplate(pi, params, state);
417
387
 
418
388
  case "complete":
419
389
  return await executeComplete(pi, ctx, params, state, sessions, sessionId, projectDir);
@@ -12,7 +12,7 @@ status: draft
12
12
  ## 根因分析
13
13
  <!-- 通过代码探索和日志分析得出的根因 -->
14
14
 
15
- ## 修复策略
15
+ ## Implementation Steps
16
16
  <!-- 修复方案和替代方案 -->
17
17
 
18
18
  ## 受影响文件
@@ -9,11 +9,9 @@ status: draft
9
9
  ## Spec 摘要
10
10
  <!-- 对应 spec 的关键要求 -->
11
11
 
12
- ## 任务分解
13
- <!-- 分解为可执行的任务 -->
14
-
15
- ## 实现顺序
16
- <!-- 任务的依赖关系和执行顺序 -->
12
+ ## Implementation Steps
13
+ <!-- 任务分解:分解为可执行的任务 -->
14
+ <!-- 实现顺序:任务的依赖关系和执行顺序 -->
17
15
 
18
16
  ## 验证
19
17
  <!-- 如何验证实现正确性 -->
@@ -12,7 +12,7 @@ status: draft
12
12
  ## 目标结构
13
13
  <!-- 重构后的目标架构 -->
14
14
 
15
- ## 分步骤计划
15
+ ## Implementation Steps
16
16
  <!-- 重构的分步执行计划 -->
17
17
 
18
18
  ## 风险与缓解
@@ -18,5 +18,5 @@ status: draft
18
18
  ## 推荐
19
19
  <!-- 推荐方案和理由 -->
20
20
 
21
- ## 后续步骤
21
+ ## Implementation Steps
22
22
  <!-- 调研结论后的下一步 -->