kcode-pi 0.1.31 → 0.1.35

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.
@@ -1,6 +1,6 @@
1
- import { dirname, join } from "node:path";
1
+ import { dirname, join, extname } from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
- import { readFileSync } from "node:fs";
3
+ import { readFileSync, readFile as fsReadFile } from "node:fs";
4
4
  import { Type } from "@earendil-works/pi-ai";
5
5
  import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
6
  import { formatSearchResults, formatTableSchema } from "../src/knowledge/format.ts";
@@ -77,7 +77,7 @@ function sdkLanguageForProfile(profile: ProductProfile, value: string | undefine
77
77
 
78
78
  function rejectNonCosmic(profile: ProductProfile): string | undefined {
79
79
  if (isCosmicFamily(profile)) return undefined;
80
- if (profile.product === "unknown") return "请先提供支持 Cosmic 平台能力的产品:cangqiong、xinghan 或 flagship。";
80
+ if (profile.product === "unknown") return "必须提供支持 Cosmic 平台能力的产品:cangqiong、xinghan 或 flagship。";
81
81
  return `当前产品 ${profile.product} 使用 ${profile.platform}/${profile.techStack},不适用 Cosmic 官方能力。`;
82
82
  }
83
83
 
@@ -90,7 +90,7 @@ async function runOrDryRun(
90
90
  const command = await commandPromise;
91
91
  if (dryRun) {
92
92
  return {
93
- content: [{ type: "text" as const, text: `仅展示命令,不执行:\n${command.display}` }],
93
+ content: [{ type: "text" as const, text: `输出命令预览;不执行:\n${command.display}` }],
94
94
  details: { command: command.display, dryRun: true },
95
95
  };
96
96
  }
@@ -119,11 +119,11 @@ function autoAdvanceAfterEvidence(cwd: string): { text: string; details: ReturnT
119
119
  const kdSearchTool = defineTool({
120
120
  name: "kd_search",
121
121
  label: "KD 搜索",
122
- description: "搜索 KCode 随包金蝶知识库,包括 SDK、插件生命周期、代码模式和常见实现建议。",
122
+ description: "搜索 KCode 随包金蝶知识库,包括 SDK、插件生命周期、代码模式和实现约束。",
123
123
  parameters: Type.Object({
124
- query: Type.String({ description: "要搜索的关键词、API、类名、表名或生命周期术语。" }),
124
+ query: Type.String({ description: "搜索关键词、API、类名、表名或生命周期术语。" }),
125
125
  product: Type.Optional(Type.String({ description: "金蝶产品:flagship、xinghan、cangqiong 或 enterprise。" })),
126
- edition: Type.Optional(Type.String({ description: "旧参数,等同于 product。优先使用 product。" })),
126
+ edition: Type.Optional(Type.String({ description: "旧参数,等同于 product;product 覆盖 edition。" })),
127
127
  limit: Type.Optional(Type.Number({ description: "最大结果数,默认 5。" })),
128
128
  }),
129
129
 
@@ -133,7 +133,7 @@ const kdSearchTool = defineTool({
133
133
  if (!scopes) {
134
134
  const guidance =
135
135
  profile.product === "unknown"
136
- ? "请先提供 product,例如 flagship、enterprise、xinghan 或 cangqiong。"
136
+ ? "必须提供 product,例如 flagship、enterprise、xinghan 或 cangqiong。"
137
137
  : "当前产品画像未配置可搜索知识范围。";
138
138
  return {
139
139
  content: [
@@ -141,7 +141,7 @@ const kdSearchTool = defineTool({
141
141
  type: "text",
142
142
  text: [
143
143
  `产品画像:${profile.product}/${profile.techStack}/${profile.language}`,
144
- "KCode 随包知识库搜索需要明确产品画像。",
144
+ "KCode 随包知识库搜索必须明确产品画像。",
145
145
  guidance,
146
146
  ].join("\n"),
147
147
  },
@@ -166,7 +166,7 @@ const kdTableTool = defineTool({
166
166
  parameters: Type.Object({
167
167
  table: Type.String({ description: "表名,例如 T_PUR_POORDER。" }),
168
168
  product: Type.Optional(Type.String({ description: "金蝶产品:flagship、xinghan、cangqiong 或 enterprise。" })),
169
- edition: Type.Optional(Type.String({ description: "旧参数,等同于 product。优先使用 product。" })),
169
+ edition: Type.Optional(Type.String({ description: "旧参数,等同于 product;product 覆盖 edition。" })),
170
170
  }),
171
171
 
172
172
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
@@ -175,8 +175,8 @@ const kdTableTool = defineTool({
175
175
  if (!edition) {
176
176
  const guidance =
177
177
  profile.product === "unknown"
178
- ? "请先提供 product。不要跨金蝶产品族猜测表结构。"
179
- : "没有元数据查证前,不要把旗舰版/企业版表结构假设复用到苍穹/星瀚/Cosmic。";
178
+ ? "必须提供 product。禁止跨金蝶产品族猜测表结构。"
179
+ : "没有元数据查证前,禁止把旗舰版/企业版表结构假设复用到苍穹/星瀚/Cosmic。";
180
180
  return {
181
181
  content: [
182
182
  {
@@ -204,12 +204,12 @@ const kdCheckTool = defineTool({
204
204
  name: "kd_check",
205
205
  label: "KD 检查",
206
206
  description:
207
- "检查金蝶 Java/C#/Python 插件代码中的魔法值、命名问题、循环内 DB 调用和空 catch 等问题。",
207
+ "检查金蝶 Java/C#/Python 插件代码中的魔法值、命名问题、循环内 DB 调用和空 catch",
208
208
  parameters: Type.Object({
209
- code: Type.Optional(Type.String({ description: "要检查的源码。与 path 二选一。" })),
210
- path: Type.Optional(Type.String({ description: "要检查的源码文件路径。与 code 二选一。" })),
209
+ code: Type.Optional(Type.String({ description: "待检查源码。与 path 二选一。" })),
210
+ path: Type.Optional(Type.String({ description: "待检查源码文件路径。与 code 二选一。" })),
211
211
  product: Type.Optional(Type.String({ description: "金蝶产品。未提供 language 时用于推导 Java 或 C#。" })),
212
- language: Type.Optional(Type.String({ description: "语言:java、csharp 或 python。会覆盖产品推导结果。" })),
212
+ language: Type.Optional(Type.String({ description: "语言:java、csharp 或 python。覆盖产品推导结果。" })),
213
213
  }),
214
214
 
215
215
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -226,7 +226,7 @@ const kdCheckTool = defineTool({
226
226
 
227
227
  if (!code) {
228
228
  return {
229
- content: [{ type: "text", text: "kd_check 需要提供 code 或 path。" }],
229
+ content: [{ type: "text", text: "kd_check 必须提供 code 或 path。" }],
230
230
  details: { error: "missing-code-or-path" },
231
231
  };
232
232
  }
@@ -250,8 +250,8 @@ const kdCosmicConfigTool = defineTool({
250
250
  description: "运行 Cosmic 家族金蝶产品的官方能力配置预检查。",
251
251
  parameters: Type.Object({
252
252
  product: Type.String({ description: "支持 Cosmic 平台能力的产品:cangqiong、xinghan 或 flagship。" }),
253
- config: Type.Optional(Type.String({ description: "可选 ok-cosmic.json 路径。默认按当前工作目录解析。" })),
254
- dryRun: Type.Optional(Type.Boolean({ description: "只返回命令,不实际执行。" })),
253
+ config: Type.Optional(Type.String({ description: "ok-cosmic.json 路径;省略时按当前工作目录解析。" })),
254
+ dryRun: Type.Optional(Type.Boolean({ description: "输出命令预览;不实际执行。" })),
255
255
  }),
256
256
 
257
257
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -268,14 +268,14 @@ const kdCosmicMetadataTool = defineTool({
268
268
  description: "查询官方 Cosmic 表单/单据元数据,包括字段、枚举值、操作和 SQL 表信息。",
269
269
  parameters: Type.Object({
270
270
  product: Type.String({ description: "支持 Cosmic 平台能力的产品:cangqiong、xinghan 或 flagship。" }),
271
- form: Type.String({ description: "Form ID、单据 ID 或中文单据名;多个目标可用逗号分隔。" }),
272
- config: Type.Optional(Type.String({ description: "可选 ok-cosmic.json 路径。" })),
273
- fuzzy: Type.Optional(Type.String({ description: "可选字段关键词,用空格或逗号分隔。" })),
274
- typeFilter: Type.Optional(Type.String({ description: "可选字段类型正则,例如 combo|check 或 decimal。" })),
275
- sql: Type.Optional(Type.Boolean({ description: "是否包含数据库表和字段信息。" })),
276
- op: Type.Optional(Type.Boolean({ description: "是否显示表单/单据操作。" })),
277
- showDetail: Type.Optional(Type.Boolean({ description: "是否显示详细元数据输出。" })),
278
- dryRun: Type.Optional(Type.Boolean({ description: "只返回命令,不实际执行。" })),
271
+ form: Type.String({ description: "Form ID、单据 ID 或中文单据名;多个目标使用逗号分隔。" }),
272
+ config: Type.Optional(Type.String({ description: "ok-cosmic.json 路径;省略时使用默认配置。" })),
273
+ fuzzy: Type.Optional(Type.String({ description: "字段关键词,使用空格或逗号分隔。" })),
274
+ typeFilter: Type.Optional(Type.String({ description: "字段类型正则,例如 combo|check 或 decimal。" })),
275
+ sql: Type.Optional(Type.Boolean({ description: "包含数据库表和字段信息。" })),
276
+ op: Type.Optional(Type.Boolean({ description: "显示表单/单据操作。" })),
277
+ showDetail: Type.Optional(Type.Boolean({ description: "显示详细元数据输出。" })),
278
+ dryRun: Type.Optional(Type.Boolean({ description: "输出命令预览;不实际执行。" })),
279
279
  }),
280
280
 
281
281
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -289,15 +289,15 @@ const kdCosmicMetadataTool = defineTool({
289
289
  const kdCosmicApiTool = defineTool({
290
290
  name: "kd_cosmic_api",
291
291
  label: "KD Cosmic API",
292
- description: "查询随包 Cosmic API 知识,获取类和方法线索;最终签名事实应以 kd_sdk_signature 或项目构建输出为准。",
292
+ description: "查询随包 Cosmic API 知识,输出类和方法线索;最终签名事实必须以 kd_sdk_signature 或项目构建输出为准。",
293
293
  parameters: Type.Object({
294
294
  product: Type.String({ description: "支持 Cosmic 平台能力的产品:cangqiong、xinghan 或 flagship。" }),
295
295
  mode: Type.String({ description: "查询模式:search、search-method 或 detail。" }),
296
296
  query: Type.String({ description: "类名、方法名或完整限定类名。" }),
297
- config: Type.Optional(Type.String({ description: "可选 ok-cosmic.json 路径。" })),
298
- method: Type.Optional(Type.String({ description: "detail 模式下的可选方法过滤条件。" })),
299
- compact: Type.Optional(Type.Boolean({ description: "支持时请求紧凑详情输出。" })),
300
- dryRun: Type.Optional(Type.Boolean({ description: "只返回命令,不实际执行。" })),
297
+ config: Type.Optional(Type.String({ description: "ok-cosmic.json 路径;省略时使用默认配置。" })),
298
+ method: Type.Optional(Type.String({ description: "detail 模式下的方法过滤条件。" })),
299
+ compact: Type.Optional(Type.Boolean({ description: "启用紧凑详情输出;命令支持时生效。" })),
300
+ dryRun: Type.Optional(Type.Boolean({ description: "输出命令预览;不实际执行。" })),
301
301
  }),
302
302
 
303
303
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -323,14 +323,14 @@ const kdSdkSignatureTool = defineTool({
323
323
  name: "kd_sdk_signature",
324
324
  label: "KD SDK 签名",
325
325
  description:
326
- "从当前项目真实存在的 SDK jar 或 dll 中检查方法/类型签名。涉及 API 签名事实时,优先使用它而不是随包知识库。",
326
+ "从当前项目真实存在的 SDK jar 或 dll 中检查方法/类型签名。API 签名事实必须使用此工具查证,不以随包知识库替代。",
327
327
  parameters: Type.Object({
328
328
  product: Type.Optional(Type.String({ description: "金蝶产品。未提供 language 时用于推导 Java 或 C#。" })),
329
329
  language: Type.Optional(Type.String({ description: "java 或 csharp。默认由产品画像推导。" })),
330
- query: Type.Optional(Type.String({ description: "要搜索的类/类型关键词,例如 QueryServiceHelper 或 DynamicObject。" })),
331
- className: Type.Optional(Type.String({ description: "已知时提供完整限定 Java/C# 类型名。" })),
332
- method: Type.Optional(Type.String({ description: "在匹配类/类型内过滤方法或属性;不会全局扫描所有方法。" })),
333
- path: Type.Optional(Type.String({ description: "可选 SDK lib/bin 目录或依赖根路径。默认从当前项目查找。" })),
330
+ query: Type.Optional(Type.String({ description: "类/类型搜索关键词,例如 QueryServiceHelper 或 DynamicObject。" })),
331
+ className: Type.Optional(Type.String({ description: "完整限定 Java/C# 类型名。" })),
332
+ method: Type.Optional(Type.String({ description: "在匹配类/类型内过滤方法或属性;禁止全局扫描所有方法。" })),
333
+ path: Type.Optional(Type.String({ description: "SDK lib/bin 目录或依赖根路径;省略时从当前项目查找。" })),
334
334
  limit: Type.Optional(Type.Number({ description: "最大检查 jar/dll/class 数量。默认 20 个结果类、200 个文件。" })),
335
335
  }),
336
336
 
@@ -361,8 +361,8 @@ const kdKsqlLintTool = defineTool({
361
361
  description: "对生成的 KSQL/SQL 文件运行官方 ok-ksql lint 检查。",
362
362
  parameters: Type.Object({
363
363
  product: Type.String({ description: "支持 Cosmic 平台能力的产品:cangqiong、xinghan 或 flagship。" }),
364
- path: Type.String({ description: "SQL/KSQL 文件路径,可为工作区相对路径或绝对路径。" }),
365
- dryRun: Type.Optional(Type.Boolean({ description: "只返回命令,不实际执行。" })),
364
+ path: Type.String({ description: "SQL/KSQL 文件路径;支持工作区相对路径或绝对路径。" }),
365
+ dryRun: Type.Optional(Type.Boolean({ description: "输出命令预览;不实际执行。" })),
366
366
  }),
367
367
 
368
368
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -379,8 +379,8 @@ const kdBuildTool = defineTool({
379
379
  description: "按产品画像运行或预览金蝶构建命令,支持 Cosmic Java 和企业版 C# 项目。",
380
380
  parameters: Type.Object({
381
381
  product: Type.String({ description: "金蝶产品:cangqiong、xinghan、flagship 或 enterprise。" }),
382
- target: Type.Optional(Type.String({ description: "Java 可提供 Gradle task;C# 可提供 .sln/.csproj 路径。" })),
383
- dryRun: Type.Optional(Type.Boolean({ description: "只返回构建命令,不实际执行。" })),
382
+ target: Type.Optional(Type.String({ description: "Java 使用 Gradle task;C# 使用 .sln/.csproj 路径。" })),
383
+ dryRun: Type.Optional(Type.Boolean({ description: "输出构建命令预览;不实际执行。" })),
384
384
  }),
385
385
 
386
386
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -400,11 +400,11 @@ const kdBuildTool = defineTool({
400
400
  const kdDebugTool = defineTool({
401
401
  name: "kd_debug",
402
402
  label: "KD 调试",
403
- description: "分析金蝶构建/运行日志或堆栈,给出可能原因和下一步检查建议。",
403
+ description: "分析金蝶构建/运行日志或堆栈,输出推断原因和检查指令。",
404
404
  parameters: Type.Object({
405
405
  text: Type.Optional(Type.String({ description: "日志文本或堆栈。与 path 二选一。" })),
406
406
  path: Type.Optional(Type.String({ description: "日志文件路径。与 text 二选一。" })),
407
- product: Type.Optional(Type.String({ description: "可选产品提示,用于补充上下文。" })),
407
+ product: Type.Optional(Type.String({ description: "产品上下文。" })),
408
408
  }),
409
409
 
410
410
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -430,6 +430,133 @@ const kdDebugTool = defineTool({
430
430
  },
431
431
  });
432
432
 
433
+ const kdDocReadTool = defineTool({
434
+ name: "kd_doc_read",
435
+ label: "KD 文档读取",
436
+ description:
437
+ "读取 PDF、Word (.docx/.doc)、Excel (.xlsx/.xls) 或 CSV 文件并提取文本内容。对于 .pdf、.docx、.doc、.xlsx、.xls、.csv 文件,必须使用此工具而非 read 工具,因为 read 无法解析这些二进制格式。PDF 目前仅支持可复制文本抽取;扫描型 PDF 必须另行提供图片或 OCR 结果。",
438
+ parameters: Type.Object({
439
+ path: Type.String({ description: "文档文件路径,支持 .pdf、.docx、.doc、.xlsx、.xls、.csv。" }),
440
+ sheet: Type.Optional(Type.String({ description: "Excel 工作表名或序号(从 1 开始)。默认第一个工作表。" })),
441
+ maxRows: Type.Optional(Type.Number({ description: "Excel/CSV 最大返回行数。默认 200。" })),
442
+ maxPages: Type.Optional(Type.Number({ description: "PDF 最大提取页数。默认 50。" })),
443
+ }),
444
+
445
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
446
+ const filePath = resolveWorkspacePath(ctx.cwd, params.path);
447
+ const ext = extname(filePath).toLowerCase();
448
+
449
+ try {
450
+ let text: string;
451
+
452
+ if (ext === ".pdf") {
453
+ text = await extractPdf(filePath, params.maxPages ?? 50);
454
+ } else if (ext === ".docx") {
455
+ text = await extractDocx(filePath);
456
+ } else if (ext === ".doc") {
457
+ text = await extractDoc(filePath);
458
+ } else if (ext === ".xlsx" || ext === ".xls") {
459
+ text = extractXlsx(filePath, params.sheet, params.maxRows ?? 200);
460
+ } else if (ext === ".csv") {
461
+ text = extractCsv(filePath, params.maxRows ?? 200);
462
+ } else {
463
+ return {
464
+ content: [{ type: "text", text: `不支持的文件格式:${ext}。支持 .pdf、.docx、.doc、.xlsx、.xls、.csv。` }],
465
+ details: { error: "unsupported-format", ext },
466
+ };
467
+ }
468
+
469
+ return {
470
+ content: [{ type: "text", text }],
471
+ details: { path: params.path, format: ext },
472
+ };
473
+ } catch (error) {
474
+ return {
475
+ content: [{ type: "text", text: `读取文档失败:${error instanceof Error ? error.message : String(error)}` }],
476
+ details: { error: "doc-read-failed", path: params.path },
477
+ };
478
+ }
479
+ },
480
+ });
481
+
482
+ async function extractPdf(filePath: string, maxPages: number): Promise<string> {
483
+ const { PDFParse } = await import("pdf-parse");
484
+ const buffer = readFileSync(filePath);
485
+ const parser = new PDFParse({ data: new Uint8Array(buffer) });
486
+ const result = await parser.getText({ last: maxPages });
487
+ const pages = result.total;
488
+ const text = result.text.trim();
489
+ return `[PDF] ${pages} 页\n\n${text}`;
490
+ }
491
+
492
+ async function extractDocx(filePath: string): Promise<string> {
493
+ const mammoth = await import("mammoth");
494
+ const buffer = readFileSync(filePath);
495
+ const result = await mammoth.extractRawText({ buffer });
496
+ return `[Word .docx]\n\n${result.value.trim()}`;
497
+ }
498
+
499
+ async function extractDoc(filePath: string): Promise<string> {
500
+ const word = await import("word");
501
+ const doc = await word.readFile(filePath);
502
+ const text = word.to_text(doc);
503
+ return `[Word .doc]\n\n${text.trim()}`;
504
+ }
505
+
506
+ function extractXlsx(filePath: string, sheetNameOrIndex?: string, maxRows?: number): string {
507
+ const XLSX = require("xlsx");
508
+ const workbook = XLSX.readFile(filePath);
509
+ const sheetNames = workbook.SheetNames;
510
+
511
+ let sheetName: string;
512
+ if (sheetNameOrIndex) {
513
+ const idx = Number(sheetNameOrIndex);
514
+ if (!isNaN(idx) && idx >= 1 && idx <= sheetNames.length) {
515
+ sheetName = sheetNames[idx - 1];
516
+ } else if (sheetNames.includes(sheetNameOrIndex)) {
517
+ sheetName = sheetNameOrIndex;
518
+ } else {
519
+ return `[Excel] 工作表 "${sheetNameOrIndex}" 不存在。有效工作表:${sheetNames.join(", ")}`;
520
+ }
521
+ } else {
522
+ sheetName = sheetNames[0];
523
+ }
524
+
525
+ const sheet = workbook.Sheets[sheetName];
526
+ const rows: Record<string, unknown>[] = XLSX.utils.sheet_to_json(sheet, { defval: "" });
527
+ const limit = Math.min(rows.length, maxRows ?? 200);
528
+
529
+ if (rows.length === 0) return `[Excel] 工作表 "${sheetName}" 为空。`;
530
+
531
+ const headers = Object.keys(rows[0]);
532
+ const lines: string[] = [
533
+ `[Excel] 工作表:${sheetName}(${sheetNames.length} 个:${sheetNames.join(", ")})`,
534
+ `行数:${rows.length}${rows.length > limit ? `(显示前 ${limit} 行)` : ""}`,
535
+ "",
536
+ "| " + headers.join(" | ") + " |",
537
+ "| " + headers.map(() => "---").join(" | ") + " |",
538
+ ];
539
+
540
+ for (let i = 0; i < limit; i++) {
541
+ const row = rows[i];
542
+ lines.push("| " + headers.map((h) => String(row[h] ?? "")).join(" | ") + " |");
543
+ }
544
+
545
+ return lines.join("\n");
546
+ }
547
+
548
+ function extractCsv(filePath: string, maxRows: number): string {
549
+ const XLSX = require("xlsx");
550
+ const workbook = XLSX.readFile(filePath, { raw: true });
551
+ const sheetName = workbook.SheetNames[0];
552
+ const sheet = workbook.Sheets[sheetName];
553
+ const csv = XLSX.utils.sheet_to_csv(sheet);
554
+ const lines = csv.split("\n");
555
+ const limit = Math.min(lines.length, maxRows + 1); // +1 for header
556
+ const truncated = lines.length > limit;
557
+ return `[CSV] ${lines.length - 1} 行${truncated ? `(显示前 ${limit - 1} 行)` : ""}\n\n${lines.slice(0, limit).join("\n")}`;
558
+ }
559
+
433
560
  export default function (pi: ExtensionAPI) {
434
561
  pi.registerTool(kdSearchTool);
435
562
  pi.registerTool(kdTableTool);
@@ -441,6 +568,7 @@ export default function (pi: ExtensionAPI) {
441
568
  pi.registerTool(kdKsqlLintTool);
442
569
  pi.registerTool(kdBuildTool);
443
570
  pi.registerTool(kdDebugTool);
571
+ pi.registerTool(kdDocReadTool);
444
572
  }
445
573
 
446
574
  function writeSdkSignatureEvidence(cwd: string, content: string): string | undefined {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kcode-pi",
3
- "version": "0.1.31",
3
+ "version": "0.1.35",
4
4
  "description": "面向金蝶开发的 Pi Coding Agent 启动器、工具包和 Harness 工作流",
5
5
  "type": "module",
6
6
  "private": false,
@@ -47,7 +47,11 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@earendil-works/pi-ai": "^0.78.1",
50
- "@earendil-works/pi-coding-agent": "^0.78.1"
50
+ "@earendil-works/pi-coding-agent": "^0.78.1",
51
+ "mammoth": "^1.12.0",
52
+ "pdf-parse": "^2.4.5",
53
+ "word": "^0.4.0",
54
+ "xlsx": "^0.18.5"
51
55
  },
52
56
  "devDependencies": {
53
57
  "@types/node": "^25.9.2",
package/src/cli/kcode.ts CHANGED
@@ -98,8 +98,8 @@ export function doctor(cwd: string, args: string[] = []): KcodeCliResult {
98
98
  lines.push(`Pi CLI:${formatPiCliStatus(piCli, pi)}`);
99
99
  lines.push(`KCode version:${packageName}@${packageVersion}`);
100
100
  lines.push(`KCode package:${packageRoot}`);
101
- lines.push(`项目配置:${existsSync(settingsPath) ? settingsPath : "未创建,请先运行 kcode init"}`);
102
- lines.push(`项目上下文:${existsSync(projectContextPath) ? projectContextPath : "未创建,请运行 kcode context"}`);
101
+ lines.push(`项目配置:${existsSync(settingsPath) ? settingsPath : "未创建,运行 kcode init"}`);
102
+ lines.push(`项目上下文:${existsSync(projectContextPath) ? projectContextPath : "未创建,运行 kcode context"}`);
103
103
 
104
104
  if (existsSync(settingsPath)) {
105
105
  const settingsResult = readSettingsSafe(settingsPath);
@@ -199,7 +199,7 @@ export function start(cwd: string, piArgs: string[]): KcodeCliResult {
199
199
  if (!piCli) {
200
200
  return {
201
201
  exitCode: 1,
202
- output: `${init.output}\n未找到随包 Pi CLI 或全局 pi 命令。请重新安装 kcode-pi 后再运行 kcode start。`,
202
+ output: `${init.output}\n未找到随包 Pi CLI 或全局 pi 命令。重新安装 kcode-pi 后再运行 kcode start。`,
203
203
  };
204
204
  }
205
205
 
@@ -69,9 +69,9 @@ export function generateProjectContext(cwd: string): string {
69
69
  "",
70
70
  "- 本文件是 KCode 的项目记忆,计划或编辑代码前必须读取。",
71
71
  "- 业务需求不得生成 demo/sample/scaffold 代码。",
72
- "- 不要假设模块结构。必须基于下方真实路径,并在编辑前确认目标文件。",
73
- "- 调用文件工具时优先使用项目相对路径。在 Windows 中,不要把路径改写为 /mnt/<drive>/... 或 /<drive>/...;只有确需绝对路径时才使用 Windows 路径。",
74
- "- 如果本文件过期,计划前先运行 `kcode context --refresh` 重新生成。",
72
+ "- 禁止假设模块结构。必须基于下方真实路径,并在编辑前确认目标文件。",
73
+ "- 调用文件工具时默认使用项目相对路径。在 Windows 中禁止把路径改写为 /mnt/<drive>/... 或 /<drive>/...;绝对路径只允许使用 Windows 路径。",
74
+ "- 本文件过期时,计划前运行 `kcode context --refresh` 重新生成。",
75
75
  "- 只有 Harness 进入 `execute` 且 PLAN.md 写明真实目标路径后,才能写产品代码。",
76
76
  "",
77
77
  "## 项目结构摘要",
@@ -3,6 +3,7 @@ import type { ActiveRun, KdPhase } from "./types.ts";
3
3
  import { PHASE_ARTIFACTS } from "./types.ts";
4
4
  import { runArtifactPath, runRoot } from "./paths.ts";
5
5
  import type { ProductProfile } from "../product/profile.ts";
6
+ import { PLAN_REQUIRED_CHECK_LINES, formatPromptLines } from "./prompt-policy.ts";
6
7
 
7
8
  export function ensureRunDirectories(cwd: string, run: ActiveRun): void {
8
9
  mkdirSync(runRoot(cwd, run), { recursive: true });
@@ -86,7 +87,7 @@ export function defaultArtifactContent(phase: KdPhase, goal?: string, profile?:
86
87
  "",
87
88
  "## 已检查的项目结构",
88
89
  "",
89
- "## 需要查看的文件",
90
+ "## 待读取文件",
90
91
  "",
91
92
  "## 目标源码根 / 路径",
92
93
  "",
@@ -99,9 +100,7 @@ export function defaultArtifactContent(phase: KdPhase, goal?: string, profile?:
99
100
  "",
100
101
  "## 必需的金蝶查证项",
101
102
  "",
102
- "- Java/C# 代码涉及 SDK 类、方法、构造器、枚举、属性时,必须先用 kd_sdk_signature 或项目构建输出确认真实签名。",
103
- "- 知识库搜索、随包 Cosmic API 查询只能作为线索,不能作为最终方法签名事实。",
104
- "- SDK 签名证据:evidence/sdk-signature.md",
103
+ ...formatPromptLines(PLAN_REQUIRED_CHECK_LINES),
105
104
  "",
106
105
  "## 需求条目 / 依赖 / 批次",
107
106
  "",
@@ -120,12 +119,12 @@ export function defaultArtifactContent(phase: KdPhase, goal?: string, profile?:
120
119
  "- 红灯证据:evidence/tdd-red.md",
121
120
  "- 绿灯证据:evidence/tdd-green.md",
122
121
  "- 红绿检查命令或工具:未知",
123
- "- Java 语法/编译检查:优先使用当前项目 Gradle 命令,例如 `./gradlew build`、`.\\gradlew.bat build` 或 `./gradlew :模块:build`。",
122
+ "- Java 语法/编译检查:使用当前项目 Gradle 命令,例如 `./gradlew build`、`.\\gradlew.bat build` 或 `./gradlew :模块:build`。",
124
123
  "- C# 语法/编译检查:使用 `dotnet build`、`dotnet build <.sln>` 或 `dotnet build <.csproj>`。",
125
124
  "- 允许的检查:本地 SDK 签名查证、官方 API/基类/方法查证、元数据查证、kd_check、Gradle/dotnet 构建输出、项目已有测试框架、外部接口最小验证。",
126
- "- 测试框架:优先使用项目已有测试基础设施。",
125
+ "- 测试框架:使用项目已有测试基础设施。",
127
126
  "- 命令无法运行时记录真实阻塞原因和残余风险,不能作为绿灯证据。",
128
- "- 如果无法自动化测试,记录一个产品相关、实现前应失败且实现后应通过的检查。",
127
+ "- 自动化测试不可执行时,记录一个产品相关、实现前应失败且实现后应通过的检查。",
129
128
  "- SDK 方法签名事实必须来自当前项目 jar/dll、构建输出或官方元数据。",
130
129
  "",
131
130
  "## 验证命令",