kcode-pi 0.1.14 → 0.1.16

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.
@@ -18,6 +18,7 @@ import {
18
18
  } from "../src/harness/state.ts";
19
19
  import { readArtifact } from "../src/harness/artifacts.ts";
20
20
  import { flagshipWriteBlockReason, isSourceLikePath, planWriteBlockReason } from "../src/harness/path-policy.ts";
21
+ import { sdkSignatureProductionWriteBlockReason } from "../src/harness/sdk-policy.ts";
21
22
  import { tddProductionWriteBlockReason } from "../src/harness/tdd-policy.ts";
22
23
  import { readProjectContext } from "../src/context/project-context.ts";
23
24
  import { windowsPathHint } from "../src/platform/path.ts";
@@ -84,7 +85,7 @@ function sendWorkflowPrompt(pi: ExtensionAPI, ctx: ExtensionContext, run: NonNul
84
85
  return;
85
86
  }
86
87
  pi.sendUserMessage(prompt, { deliverAs: "followUp" });
87
- if (ctx.hasUI) ctx.ui.notify("KCode harness message queued.", "info");
88
+ if (ctx.hasUI) ctx.ui.notify("KCode 工作流消息已排队。", "info");
88
89
  }
89
90
 
90
91
  function workflowPromptForRun(cwd: string, run: NonNullable<ReturnType<typeof readActiveRun>>, userText: string): string {
@@ -123,9 +124,11 @@ function workflowPromptForRun(cwd: string, run: NonNullable<ReturnType<typeof re
123
124
  "",
124
125
  phaseGuidance[run.phase],
125
126
  "必须先理解当前业务项目已有目录、模块、包名、基类和本地封装,再决定文件位置和实现方式。",
127
+ "禁止凭记忆、模型知识或随包知识库直接编写 SDK 方法调用。Java/C# 代码中出现的 SDK 类、方法、构造器、枚举和属性,必须来自 kd_sdk_signature 对当前项目 jar/dll 的成功结果、项目构建输出或官方元数据证据。",
128
+ "kd_search、kd_cosmic_api 和随包知识只能用于找线索;没有 evidence/sdk-signature.md 或明确构建证据时,不得进入 execute,也不得写生产源码。",
126
129
  "路径规则:在 Windows 工作区内,优先使用项目相对路径;如需绝对路径必须使用 `D:\\...` 这类 Windows 路径,禁止把路径改写成 `/mnt/d/...`、`/d/...` 等 WSL/MSYS 风格路径。",
127
130
  "execute 阶段只能写 PLAN.md 明确列出的源码文件;如果目标文件不在计划内,必须先回到 plan 更新 PLAN.md。",
128
- "写生产源码前必须先有红灯证据 evidence/tdd-red.md;红绿证据可以是 kd_sdk_signature 本地 SDK 签名、API/基类/方法签名、元数据、编译、既有测试框架或外部接口最小验证,不要为了测试引入额外 jar。",
131
+ "写生产源码前必须先有红灯证据 evidence/tdd-red.md;Java/C# 还必须有 SDK 签名证据 evidence/sdk-signature.md。红绿证据可以是 kd_sdk_signature 本地 SDK 签名、API/基类/方法签名、元数据、编译、既有测试框架或外部接口最小验证,不要为了测试引入额外 jar。",
129
132
  ].join("\n");
130
133
  }
131
134
 
@@ -158,13 +161,17 @@ function codeWriteBlockReason(cwd: string, path: string | undefined): string | u
158
161
  return `当前 KCode 阶段是 ${run.phase},不能写产品代码。请先完成 discuss -> spec -> plan 并进入 execute。`;
159
162
  }
160
163
 
161
- return tddProductionWriteBlockReason(cwd, run, path) ?? planWriteBlockReason(cwd, run, path, readArtifact(cwd, run, "plan") ?? "");
164
+ return (
165
+ sdkSignatureProductionWriteBlockReason(cwd, run, path) ??
166
+ tddProductionWriteBlockReason(cwd, run, path) ??
167
+ planWriteBlockReason(cwd, run, path, readArtifact(cwd, run, "plan") ?? "")
168
+ );
162
169
  }
163
170
 
164
171
  const kdPlanStatusTool = defineTool({
165
172
  name: "kd_plan_status",
166
- label: "KD Status",
167
- description: "Inspect the active Kingdee Harness Engineering run, current phase, required artifacts, and gate status.",
173
+ label: "KD 状态",
174
+ description: "查看当前金蝶 Harness run、当前阶段、必需文档和门禁状态。",
168
175
  parameters: Type.Object({}),
169
176
 
170
177
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
@@ -178,24 +185,24 @@ const kdPlanStatusTool = defineTool({
178
185
 
179
186
  const kdQuestionTool = defineTool({
180
187
  name: "kd_question",
181
- label: "KD Question",
188
+ label: "KD 问题",
182
189
  description:
183
- "Create, answer, or list structured Kingdee Harness questions. Ask exactly one short blocking question at a time; do not batch a checklist.",
190
+ "创建、回答或列出金蝶 Harness 结构化问题。每次只能问一个最阻塞的短问题,不要批量列清单。",
184
191
  parameters: Type.Object({
185
- action: Type.Optional(Type.String({ description: "ask, answer, or list. Defaults to ask." })),
186
- id: Type.Optional(Type.String({ description: "Question id when action=answer, for example Q-001." })),
187
- question: Type.Optional(Type.String({ description: "One short question to ask when action=ask. No numbered lists or multiple questions." })),
188
- answer: Type.Optional(Type.String({ description: "User answer when action=answer." })),
189
- reason: Type.Optional(Type.String({ description: "Why this question blocks or matters." })),
190
- choices: Type.Optional(Type.Array(Type.String(), { description: "Optional concrete choices for the user, maximum 3 short labels." })),
191
- blocking: Type.Optional(Type.Boolean({ description: "Whether the question blocks phase advancement. Defaults to true." })),
192
+ action: Type.Optional(Type.String({ description: "操作类型:askanswer list,默认 ask" })),
193
+ id: Type.Optional(Type.String({ description: "回答问题时的问题编号,例如 Q-001" })),
194
+ question: Type.Optional(Type.String({ description: "提问内容。只能是一个短问题,不要写编号清单或多个问题。" })),
195
+ answer: Type.Optional(Type.String({ description: "用户答案,action=answer 时使用。" })),
196
+ reason: Type.Optional(Type.String({ description: "说明为什么这个问题会阻塞当前阶段。" })),
197
+ choices: Type.Optional(Type.Array(Type.String(), { description: "可选项,最多 3 个简短选项。" })),
198
+ blocking: Type.Optional(Type.Boolean({ description: "是否阻塞阶段推进,默认 true" })),
192
199
  }),
193
200
 
194
201
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
195
202
  const run = readActiveRun(ctx.cwd);
196
203
  if (!run) {
197
204
  return {
198
- content: [{ type: "text", text: "No active Kingdee harness run. Start one with /kd-start <goal> first." }],
205
+ content: [{ type: "text", text: "当前没有 active Kingdee Harness run。请先使用 /kd-start <需求> 创建。" }],
199
206
  details: { error: "no-active-run" },
200
207
  };
201
208
  }
@@ -209,34 +216,34 @@ const kdQuestionTool = defineTool({
209
216
  if (action === "answer") {
210
217
  if (!params.id || !params.answer) {
211
218
  return {
212
- content: [{ type: "text", text: "kd_question action=answer requires id and answer." }],
219
+ content: [{ type: "text", text: "kd_question action=answer 需要同时提供 id answer" }],
213
220
  details: { error: "missing-answer-params" },
214
221
  };
215
222
  }
216
223
  const answered = answerQuestion(ctx.cwd, run, params.id, params.answer);
217
224
  if (!answered) {
218
225
  return {
219
- content: [{ type: "text", text: `Question not found: ${params.id}` }],
226
+ content: [{ type: "text", text: `未找到问题:${params.id}` }],
220
227
  details: { error: "question-not-found", id: params.id },
221
228
  };
222
229
  }
223
- appendQuestionEventToArtifact(ctx.cwd, run, [`- Answered ${answered.id}: ${answered.answer}`]);
230
+ appendQuestionEventToArtifact(ctx.cwd, run, [`- 已回答 ${answered.id}:${answered.answer}`]);
224
231
  return {
225
- content: [{ type: "text", text: `Recorded answer for ${answered.id}. Gate refreshed.` }],
232
+ content: [{ type: "text", text: `已记录 ${answered.id} 的答案,并刷新门禁。` }],
226
233
  details: { question: answered, gate: readActiveRun(ctx.cwd)?.gate },
227
234
  };
228
235
  }
229
236
 
230
237
  if (action !== "ask") {
231
238
  return {
232
- content: [{ type: "text", text: `Unknown kd_question action: ${params.action}. Use ask, answer, or list.` }],
239
+ content: [{ type: "text", text: `未知 kd_question action:${params.action}。请使用 askanswer list。` }],
233
240
  details: { error: "unknown-action", action: params.action },
234
241
  };
235
242
  }
236
243
 
237
244
  if (!params.question?.trim()) {
238
245
  return {
239
- content: [{ type: "text", text: "kd_question action=ask requires question." }],
246
+ content: [{ type: "text", text: "kd_question action=ask 需要提供 question" }],
240
247
  details: { error: "missing-question" },
241
248
  };
242
249
  }
@@ -272,15 +279,15 @@ const kdQuestionTool = defineTool({
272
279
  if (interactiveAnswer) {
273
280
  const answered = answerQuestion(ctx.cwd, run, question.id, interactiveAnswer);
274
281
  if (answered) {
275
- appendQuestionEventToArtifact(ctx.cwd, run, [`- Answered ${answered.id}: ${answered.answer}`]);
282
+ appendQuestionEventToArtifact(ctx.cwd, run, [`- 已回答 ${answered.id}:${answered.answer}`]);
276
283
  return {
277
- content: [{ type: "text", text: `User answered ${answered.id}: ${answered.answer}` }],
284
+ content: [{ type: "text", text: `用户已回答 ${answered.id}:${answered.answer}` }],
278
285
  details: { question: answered, gate: readActiveRun(ctx.cwd)?.gate, answered: true },
279
286
  };
280
287
  }
281
288
  }
282
289
 
283
- const text = `${formatQuestionCard(question)}\n\nInteractive answer was not provided; keep this question open until the user answers.`;
290
+ const text = `${formatQuestionCard(question)}\n\n未获得交互式答案;在用户回答前,该问题会保持 open。`;
284
291
  return { content: [{ type: "text", text }], details: { question, gate: readActiveRun(ctx.cwd)?.gate, answered: false } };
285
292
  },
286
293
  });
@@ -309,7 +316,7 @@ export default function (pi: ExtensionAPI) {
309
316
  if (!run && shouldStartHarnessFromInput(event.text)) {
310
317
  run = createActiveRun(ctx.cwd, event.text);
311
318
  if (ctx.hasUI) {
312
- ctx.ui.notify(`Started Kingdee harness run: ${run.id} (${run.profile?.product}/${run.profile?.techStack})`, "info");
319
+ ctx.ui.notify(`已启动 Kingdee Harness run:${run.id}(${run.profile?.product}/${run.profile?.techStack})`, "info");
313
320
  }
314
321
  }
315
322
 
@@ -341,14 +348,14 @@ export default function (pi: ExtensionAPI) {
341
348
  });
342
349
 
343
350
  pi.registerCommand("kd-status", {
344
- description: "Show the active Kingdee harness run and gate status",
351
+ description: "查看当前 Kingdee Harness run 和门禁状态",
345
352
  handler: async (_args, ctx) => {
346
353
  ctx.ui.notify(formatStatus(ctx.cwd, readActiveRun(ctx.cwd)), "info");
347
354
  },
348
355
  });
349
356
 
350
357
  pi.registerCommand("kd-runs", {
351
- description: "List Kingdee harness runs for this project",
358
+ description: "列出当前项目的 Kingdee Harness run",
352
359
  handler: async (_args, ctx) => {
353
360
  const active = readActiveRun(ctx.cwd);
354
361
  ctx.ui.notify(formatRuns(listRuns(ctx.cwd), active?.id), "info");
@@ -356,46 +363,46 @@ export default function (pi: ExtensionAPI) {
356
363
  });
357
364
 
358
365
  pi.registerCommand("kd-gate", {
359
- description: "Refresh and show the active Kingdee harness gate",
366
+ description: "刷新并查看当前 Kingdee Harness 门禁",
360
367
  handler: async (_args, ctx) => {
361
368
  const run = requireRun(ctx.cwd);
362
369
  if (!run) {
363
- ctx.ui.notify("No active Kingdee harness run. Use /kd-start <goal>.", "error");
370
+ ctx.ui.notify("当前没有 active Kingdee Harness run。请使用 /kd-start <需求>。", "error");
364
371
  return;
365
372
  }
366
373
  const refreshed = refreshGate(ctx.cwd, run);
367
- ctx.ui.notify(refreshed.gate.passed ? "Gate passed" : `Gate blocked: ${refreshed.gate.reason}`, "info");
374
+ ctx.ui.notify(refreshed.gate.passed ? "门禁通过" : `门禁阻塞:${refreshed.gate.reason}`, "info");
368
375
  },
369
376
  });
370
377
 
371
378
  pi.registerCommand("kd-start", {
372
- description: "Start a Kingdee harness run: /kd-start [--product product] [--version version] <goal>",
379
+ description: "启动一个 Kingdee Harness run:/kd-start [--product 产品] [--version 版本] <需求>",
373
380
  handler: async (args, ctx) => {
374
381
  const parsed = parseStartArgs(args);
375
382
  const goal = parsed.goal;
376
383
  if (!goal) {
377
- ctx.ui.notify("Usage: /kd-start [--product product] [--version version] <goal>", "error");
384
+ ctx.ui.notify("用法:/kd-start [--product 产品] [--version 版本] <需求>", "error");
378
385
  return;
379
386
  }
380
387
 
381
388
  const run = createActiveRun(ctx.cwd, goal, parsed.product, parsed.version);
382
- ctx.ui.notify(`Started Kingdee harness run: ${run.id} (${run.profile?.product}/${run.profile?.techStack})`, "info");
389
+ ctx.ui.notify(`已启动 Kingdee Harness run:${run.id}(${run.profile?.product}/${run.profile?.techStack})`, "info");
383
390
  sendWorkflowPrompt(pi, ctx, run, `继续 KCode Harness run ${run.id}:${goal}`);
384
391
  },
385
392
  });
386
393
 
387
394
  pi.registerCommand("kd-resume", {
388
- description: "Resume the active Kingdee harness run and inject persisted context",
395
+ description: "接续当前 Kingdee Harness run,并注入已落盘上下文",
389
396
  handler: async (args, ctx) => {
390
397
  const run = requireRun(ctx.cwd);
391
398
  if (!run) {
392
- ctx.ui.notify("No active Kingdee harness run. Use /kd-start <goal> or /kd-runs.", "error");
399
+ ctx.ui.notify("当前没有 active Kingdee Harness run。请使用 /kd-start <需求> /kd-runs", "error");
393
400
  return;
394
401
  }
395
402
 
396
403
  const note = args.trim();
397
404
  const message = [
398
- `继续 KCode Harness run ${run.id}:${run.goal ?? "unknown goal"}`,
405
+ `继续 KCode Harness run ${run.id}:${run.goal ?? "未知需求"}`,
399
406
  note ? `用户补充:${note}` : undefined,
400
407
  ]
401
408
  .filter(Boolean)
@@ -405,64 +412,64 @@ export default function (pi: ExtensionAPI) {
405
412
  });
406
413
 
407
414
  pi.registerCommand("kd-switch", {
408
- description: "Switch active Kingdee harness run: /kd-switch <run-id>",
415
+ description: "切换当前 active Kingdee Harness run:/kd-switch <run-id>",
409
416
  handler: async (args, ctx) => {
410
417
  const id = args.trim();
411
418
  if (!id) {
412
- ctx.ui.notify("Usage: /kd-switch <run-id>", "error");
419
+ ctx.ui.notify("用法:/kd-switch <run-id>", "error");
413
420
  return;
414
421
  }
415
422
 
416
423
  const run = switchActiveRun(ctx.cwd, id);
417
424
  if (!run) {
418
- ctx.ui.notify(`Kingdee harness run not found: ${id}. Use /kd-runs to list runs.`, "error");
425
+ ctx.ui.notify(`未找到 Kingdee Harness run:${id}。请用 /kd-runs 查看列表。`, "error");
419
426
  return;
420
427
  }
421
428
 
422
- ctx.ui.notify(`Switched active Kingdee harness run: ${run.id} (${run.phase})`, "info");
429
+ ctx.ui.notify(`已切换 active Kingdee Harness run:${run.id}(${run.phase})`, "info");
423
430
  },
424
431
  });
425
432
 
426
433
  pi.registerCommand("kd-finish", {
427
- description: "Mark the active Kingdee harness run as done and clear active run",
434
+ description: "标记当前 Kingdee Harness run 完成,并清除 active run",
428
435
  handler: async (_args, ctx) => {
429
436
  const run = requireRun(ctx.cwd);
430
437
  if (!run) {
431
- ctx.ui.notify("No active Kingdee harness run. Use /kd-start <goal>.", "error");
438
+ ctx.ui.notify("当前没有 active Kingdee Harness run。请使用 /kd-start <需求>。", "error");
432
439
  return;
433
440
  }
434
441
 
435
442
  const finished = finishActiveRun(ctx.cwd, run);
436
- ctx.ui.notify(`Finished Kingdee harness run: ${finished.id}. Start the next feature with /kd-start <goal>.`, "info");
443
+ ctx.ui.notify(`已完成 Kingdee Harness run:${finished.id}。下一个功能点请使用 /kd-start <需求>。`, "info");
437
444
  },
438
445
  });
439
446
 
440
447
  pi.registerCommand("kd-product", {
441
- description: "Set the active Kingdee product profile: /kd-product <flagship|cosmic|xinghan|cangqiong|enterprise> [--version version]",
448
+ description: "设置当前金蝶产品画像:/kd-product <flagship|cosmic|xinghan|cangqiong|enterprise> [--version 版本]",
442
449
  handler: async (args, ctx) => {
443
450
  const run = requireRun(ctx.cwd);
444
451
  if (!run) {
445
- ctx.ui.notify("No active Kingdee harness run. Use /kd-start <goal>.", "error");
452
+ ctx.ui.notify("当前没有 active Kingdee Harness run。请使用 /kd-start <需求>。", "error");
446
453
  return;
447
454
  }
448
455
 
449
456
  const parsed = parseProductArgs(args);
450
457
  if (!parsed) {
451
- ctx.ui.notify("Usage: /kd-product <flagship|cosmic|xinghan|cangqiong|enterprise> [--version version]", "error");
458
+ ctx.ui.notify("用法:/kd-product <flagship|cosmic|xinghan|cangqiong|enterprise> [--version 版本]", "error");
452
459
  return;
453
460
  }
454
461
 
455
462
  const updated = updateProductProfile(ctx.cwd, run, parsed.product, parsed.version);
456
- ctx.ui.notify(`Product profile: ${updated.profile?.product}/${updated.profile?.techStack}/${updated.profile?.language}`, "info");
463
+ ctx.ui.notify(`产品画像:${updated.profile?.product}/${updated.profile?.techStack}/${updated.profile?.language}`, "info");
457
464
  },
458
465
  });
459
466
 
460
467
  pi.registerCommand("kd-advance", {
461
- description: `Advance Kingdee run to the next phase or a named phase: ${PHASE_ORDER.join("|")}`,
468
+ description: `推进 Kingdee run 到下一阶段,或指定阶段:${PHASE_ORDER.join("|")}`,
462
469
  handler: async (args, ctx) => {
463
470
  const run = requireRun(ctx.cwd);
464
471
  if (!run) {
465
- ctx.ui.notify("No active Kingdee harness run. Use /kd-start <goal>.", "error");
472
+ ctx.ui.notify("当前没有 active Kingdee Harness run。请使用 /kd-start <需求>。", "error");
466
473
  return;
467
474
  }
468
475
 
@@ -470,7 +477,7 @@ export default function (pi: ExtensionAPI) {
470
477
  let requested: KdPhase | undefined;
471
478
  if (requestedText) {
472
479
  if (!isKdPhase(requestedText)) {
473
- ctx.ui.notify(`Unknown phase "${requestedText}". Valid phases: ${PHASE_ORDER.join(", ")}`, "error");
480
+ ctx.ui.notify(`未知阶段 "${requestedText}"。可用阶段:${PHASE_ORDER.join(", ")}`, "error");
474
481
  return;
475
482
  }
476
483
  requested = requestedText;
@@ -482,67 +489,67 @@ export default function (pi: ExtensionAPI) {
482
489
  });
483
490
 
484
491
  pi.registerCommand("kd-artifact", {
485
- description: "Create or update a phase artifact: /kd-artifact [phase] [content]",
492
+ description: "创建或更新阶段文档:/kd-artifact [阶段] [内容]",
486
493
  handler: async (args, ctx) => {
487
494
  const run = requireRun(ctx.cwd);
488
495
  if (!run) {
489
- ctx.ui.notify("No active Kingdee harness run. Use /kd-start <goal>.", "error");
496
+ ctx.ui.notify("当前没有 active Kingdee Harness run。请使用 /kd-start <需求>。", "error");
490
497
  return;
491
498
  }
492
499
 
493
500
  const parsed = parseArtifactArgs(args, run.phase);
494
501
  if (!parsed) {
495
- ctx.ui.notify("Usage: /kd-artifact [phase] [content]", "error");
502
+ ctx.ui.notify("用法:/kd-artifact [阶段] [内容]", "error");
496
503
  return;
497
504
  }
498
505
 
499
506
  const path = parsed.content
500
507
  ? updatePhaseArtifact(ctx.cwd, run, parsed.phase, parsed.content)
501
508
  : ensurePhaseArtifact(ctx.cwd, run, parsed.phase);
502
- ctx.ui.notify(`Artifact ready: ${path}`, "info");
509
+ ctx.ui.notify(`阶段文档已就绪:${path}`, "info");
503
510
  },
504
511
  });
505
512
 
506
513
  pi.registerCommand("kd-answer", {
507
- description: "Answer a blocking Kingdee harness question: /kd-answer Q-001 <answer>",
514
+ description: "回答一个阻塞中的 Kingdee Harness 问题:/kd-answer Q-001 <答案>",
508
515
  handler: async (args, ctx) => {
509
516
  const run = requireRun(ctx.cwd);
510
517
  if (!run) {
511
- ctx.ui.notify("No active Kingdee harness run. Use /kd-start <goal>.", "error");
518
+ ctx.ui.notify("当前没有 active Kingdee Harness run。请使用 /kd-start <需求>。", "error");
512
519
  return;
513
520
  }
514
521
  const [id, ...answerParts] = args.trim().split(/\s+/);
515
522
  const answer = answerParts.join(" ").trim();
516
523
  if (!id || !answer) {
517
- ctx.ui.notify("Usage: /kd-answer Q-001 <answer>", "error");
524
+ ctx.ui.notify("用法:/kd-answer Q-001 <答案>", "error");
518
525
  return;
519
526
  }
520
527
  const answered = answerQuestion(ctx.cwd, run, id, answer);
521
528
  if (!answered) {
522
- ctx.ui.notify(`Question not found: ${id}`, "error");
529
+ ctx.ui.notify(`未找到问题:${id}`, "error");
523
530
  return;
524
531
  }
525
- appendQuestionEventToArtifact(ctx.cwd, run, [`- Answered ${answered.id}: ${answered.answer}`]);
526
- ctx.ui.notify(`Recorded answer for ${answered.id}`, "info");
532
+ appendQuestionEventToArtifact(ctx.cwd, run, [`- 已回答 ${answered.id}:${answered.answer}`]);
533
+ ctx.ui.notify(`已记录 ${answered.id} 的答案`, "info");
527
534
  },
528
535
  });
529
536
  }
530
537
 
531
538
  function formatQuestions(run: NonNullable<ReturnType<typeof readActiveRun>>): string {
532
539
  const questions = run.questions ?? [];
533
- if (questions.length === 0) return "No harness questions recorded.";
540
+ if (questions.length === 0) return "尚未记录 Harness 问题。";
534
541
  return questions.map(formatQuestionCard).join("\n\n");
535
542
  }
536
543
 
537
544
  function formatRuns(runs: NonNullable<ReturnType<typeof readActiveRun>>[], activeId?: string): string {
538
- if (runs.length === 0) return "No Kingdee harness runs in this project. Start one with /kd-start <goal>.";
545
+ if (runs.length === 0) return "当前项目没有 Kingdee Harness run。请使用 /kd-start <需求> 创建。";
539
546
  return runs
540
547
  .map((run) =>
541
548
  [
542
549
  `${run.id === activeId ? "*" : "-"} ${run.id}`,
543
- ` Goal: ${run.goal ?? "unknown"}`,
544
- ` Status: ${run.status ?? "active"} | Phase: ${run.phase} | Product: ${run.profile?.product ?? run.product ?? "unknown"}`,
545
- ` Updated: ${run.updatedAt ?? "unknown"}`,
550
+ ` 需求:${run.goal ?? "未知"}`,
551
+ ` 状态:${run.status ?? "active"} | 阶段:${run.phase} | 产品:${run.profile?.product ?? run.product ?? "unknown"}`,
552
+ ` 更新时间:${run.updatedAt ?? "未知"}`,
546
553
  ].join("\n"),
547
554
  )
548
555
  .join("\n\n");
@@ -550,14 +557,14 @@ function formatRuns(runs: NonNullable<ReturnType<typeof readActiveRun>>[], activ
550
557
 
551
558
  function formatQuestionCard(question: NonNullable<NonNullable<ReturnType<typeof readActiveRun>>["questions"]>[number]): string {
552
559
  const lines = [
553
- `Question ${question.id} [${question.status}${question.blocking ? ", blocking" : ""}]`,
554
- `Phase: ${question.phase}`,
555
- `Question: ${question.question}`,
556
- question.reason ? `Reason: ${question.reason}` : undefined,
557
- question.choices?.length ? `Choices: ${question.choices.join(" | ")}` : undefined,
558
- question.answer ? `Answer: ${question.answer}` : undefined,
560
+ `问题 ${question.id} [${question.status}${question.blocking ? ", blocking" : ""}]`,
561
+ `阶段:${question.phase}`,
562
+ `问题:${question.question}`,
563
+ question.reason ? `原因:${question.reason}` : undefined,
564
+ question.choices?.length ? `选项:${question.choices.join(" | ")}` : undefined,
565
+ question.answer ? `答案:${question.answer}` : undefined,
559
566
  question.status === "open"
560
- ? `Answer the dialog if available, or reply in chat and record it using kd_question action=answer id=${question.id} answer=<answer>.`
567
+ ? `如果有弹窗,请直接回答;否则请在对话中回复,并用 kd_question action=answer id=${question.id} answer=<答案> 记录。`
561
568
  : undefined,
562
569
  ];
563
570
  return lines.filter(Boolean).join("\n");
@@ -589,12 +596,12 @@ async function askQuestionInteractively(
589
596
  function questionBatchProblem(question: string, choices?: string[]): string | undefined {
590
597
  const text = question.trim();
591
598
  const numberedItems = text.split(/\r?\n/).filter((line) => /^\s*(\d+[\.\)、)]|[-*]\s+)/.test(line)).length;
592
- if (numberedItems >= 2) return "kd_question rejected a batched checklist question.";
593
- if ((text.match(/[??]/g) ?? []).length > 1) return "kd_question rejected multiple questions in one prompt.";
594
- if (text.length > 220) return "kd_question rejected an overlong question. Ask only the smallest blocking question first.";
595
- if ((choices?.length ?? 0) > 3) return "kd_question rejected too many choices. Provide at most 3 concise choices.";
599
+ if (numberedItems >= 2) return "kd_question 拒绝了批量清单式问题。";
600
+ if ((text.match(/[??]/g) ?? []).length > 1) return "kd_question 拒绝了一次包含多个问题的提问。";
601
+ if (text.length > 220) return "kd_question 拒绝了过长问题。请只问当前最小的阻塞问题。";
602
+ if ((choices?.length ?? 0) > 3) return "kd_question 拒绝了过多选项。最多提供 3 个简短选项。";
596
603
  if (choices?.some((choice) => choice.length > 40 || /[\r\n??]/.test(choice))) {
597
- return "kd_question rejected complex choices. Choices must be short labels, not nested questions.";
604
+ return "kd_question 拒绝了复杂选项。选项必须是短标签,不能嵌套问题。";
598
605
  }
599
606
  return undefined;
600
607
  }
@@ -602,14 +609,14 @@ function questionBatchProblem(question: string, choices?: string[]): string | un
602
609
  function formatQuestionArtifactLines(question: NonNullable<NonNullable<ReturnType<typeof readActiveRun>>["questions"]>[number]): string[] {
603
610
  return [
604
611
  `- ${question.id} [${question.blocking ? "blocking" : "non-blocking"}] ${question.question}`,
605
- question.reason ? ` - Reason: ${question.reason}` : undefined,
606
- question.choices?.length ? ` - Choices: ${question.choices.join(" | ")}` : undefined,
607
- " - Status: open",
612
+ question.reason ? ` - 原因:${question.reason}` : undefined,
613
+ question.choices?.length ? ` - 选项:${question.choices.join(" | ")}` : undefined,
614
+ " - 状态:open",
608
615
  ].filter(Boolean) as string[];
609
616
  }
610
617
 
611
618
  function appendQuestionEventToArtifact(cwd: string, run: NonNullable<ReturnType<typeof readActiveRun>>, lines: string[]): void {
612
619
  const existing = readArtifact(cwd, run, run.phase) ?? "";
613
- const section = ["", "## Harness Questions", "", ...lines, ""].join("\n");
614
- updatePhaseArtifact(cwd, run, run.phase, existing.includes("## Harness Questions") ? `${existing.trimEnd()}\n${lines.join("\n")}\n` : `${existing.trimEnd()}${section}`);
620
+ const section = ["", "## Harness 问题", "", ...lines, ""].join("\n");
621
+ updatePhaseArtifact(cwd, run, run.phase, existing.includes("## Harness 问题") ? `${existing.trimEnd()}\n${lines.join("\n")}\n` : `${existing.trimEnd()}${section}`);
615
622
  }
@@ -32,7 +32,7 @@ function readActiveRun(cwd: string): ActiveRun | undefined {
32
32
  }
33
33
 
34
34
  function formatProduct(run: ActiveRun | undefined): string {
35
- if (!run) return "unselected";
35
+ if (!run) return "未选择";
36
36
  const product = run.profile?.product ?? run.product ?? "unknown";
37
37
  const techStack = run.profile?.techStack ?? "unknown";
38
38
  const language = run.profile?.language ?? "unknown";
@@ -40,13 +40,13 @@ function formatProduct(run: ActiveRun | undefined): string {
40
40
  }
41
41
 
42
42
  function formatPhase(phase: ActiveRun["phase"]): string {
43
- return phase ?? "idle";
43
+ return phase ?? "空闲";
44
44
  }
45
45
 
46
46
  function formatGate(run: ActiveRun | undefined): string {
47
- if (!run?.gate) return "gate: pending";
48
- if (run.gate.passed) return "gate: pass";
49
- return `gate: blocked${run.gate.reason ? ` - ${run.gate.reason}` : ""}`;
47
+ if (!run?.gate) return "门禁:待检查";
48
+ if (run.gate.passed) return "门禁:通过";
49
+ return `门禁:阻塞${run.gate.reason ? ` - ${run.gate.reason}` : ""}`;
50
50
  }
51
51
 
52
52
  function padOrTrim(text: string, width: number): string {
@@ -60,7 +60,7 @@ function logoLines(theme: Theme): string[] {
60
60
  const muted = (text: string) => theme.fg("muted", text);
61
61
 
62
62
  return [
63
- `${accent("KCode")} ${muted("Kingdee Pi Harness")}`,
63
+ `${accent("KCode")} ${muted("金蝶 Pi Harness")}`,
64
64
  `${accent("=====")} ${muted("discuss -> spec -> plan -> execute -> verify -> ship")}`,
65
65
  ];
66
66
  }
@@ -76,15 +76,15 @@ export default function (pi: ExtensionAPI) {
76
76
  const phase = formatPhase(run?.phase);
77
77
  const product = formatProduct(run);
78
78
  const gate = formatGate(run);
79
- const risk = run?.risk ?? "unknown";
80
- const runId = run?.id ?? "none";
79
+ const risk = run?.risk ?? "未知";
80
+ const runId = run?.id ?? "";
81
81
 
82
82
  const status = [
83
- theme.fg("muted", "phase: "),
83
+ theme.fg("muted", "阶段:"),
84
84
  theme.fg("accent", phase),
85
- theme.fg("muted", " | product: "),
85
+ theme.fg("muted", " | 产品:"),
86
86
  theme.fg("text", product),
87
- theme.fg("muted", " | risk: "),
87
+ theme.fg("muted", " | 风险:"),
88
88
  theme.fg(risk === "high" ? "error" : risk === "medium" ? "warning" : "success", risk),
89
89
  theme.fg("muted", " | "),
90
90
  theme.fg(run?.gate?.passed === false ? "error" : "muted", gate),
@@ -94,7 +94,7 @@ export default function (pi: ExtensionAPI) {
94
94
  "",
95
95
  ...logoLines(theme).map((line) => padOrTrim(line, width)),
96
96
  padOrTrim(status, width),
97
- padOrTrim(theme.fg("dim", `run: ${runId}`), width),
97
+ padOrTrim(theme.fg("dim", `run:${runId}`), width),
98
98
  "",
99
99
  ];
100
100
  },
@@ -104,19 +104,19 @@ export default function (pi: ExtensionAPI) {
104
104
  });
105
105
 
106
106
  pi.registerCommand("kcode-header", {
107
- description: "Restore the KCode Kingdee header",
107
+ description: "恢复 KCode 金蝶标题栏",
108
108
  handler: async (_args, ctx) => {
109
109
  if (ctx.mode !== "tui") return;
110
- ctx.ui.notify("Restart the session or reload extensions to restore the KCode header.", "info");
110
+ ctx.ui.notify("请重启会话或重新加载扩展以恢复 KCode 标题栏。", "info");
111
111
  },
112
112
  });
113
113
 
114
114
  pi.registerCommand("builtin-header", {
115
- description: "Restore Pi's built-in header",
115
+ description: "恢复 Pi 内置标题栏",
116
116
  handler: async (_args, ctx) => {
117
117
  if (ctx.mode !== "tui") return;
118
118
  ctx.ui.setHeader(undefined);
119
- ctx.ui.notify("Built-in header restored", "info");
119
+ ctx.ui.notify("已恢复 Pi 内置标题栏", "info");
120
120
  },
121
121
  });
122
122
  }