kcode-pi 0.1.13 → 0.1.15

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.
@@ -74,8 +74,8 @@ function sdkLanguageForProfile(profile: ProductProfile, value: string | undefine
74
74
 
75
75
  function rejectNonCosmic(profile: ProductProfile): string | undefined {
76
76
  if (isCosmicFamily(profile)) return undefined;
77
- if (profile.product === "unknown") return "Provide a Cosmic-family product first: cangqiong, xinghan, flagship, or cosmic.";
78
- return `Product ${profile.product} uses ${profile.platform}/${profile.techStack}; Cosmic official scripts are not applicable.`;
77
+ if (profile.product === "unknown") return "请先提供 Cosmic 家族产品:cangqiongxinghanflagship cosmic";
78
+ return `当前产品 ${profile.product} 使用 ${profile.platform}/${profile.techStack},不适用 Cosmic 官方能力。`;
79
79
  }
80
80
 
81
81
  async function runOrDryRun(
@@ -87,7 +87,7 @@ async function runOrDryRun(
87
87
  const command = await commandPromise;
88
88
  if (dryRun) {
89
89
  return {
90
- content: [{ type: "text" as const, text: `Dry run command:\n${command.display}` }],
90
+ content: [{ type: "text" as const, text: `仅展示命令,不执行:\n${command.display}` }],
91
91
  details: { command: command.display, dryRun: true },
92
92
  };
93
93
  }
@@ -102,13 +102,13 @@ async function runOrDryRun(
102
102
 
103
103
  const kdSearchTool = defineTool({
104
104
  name: "kd_search",
105
- label: "KD Search",
106
- description: "Search bundled Kingdee SDK knowledge, plugin lifecycle notes, code patterns, and common implementation guidance.",
105
+ label: "KD 搜索",
106
+ description: "搜索 KCode 随包金蝶知识库,包括 SDK、插件生命周期、代码模式和常见实现建议。",
107
107
  parameters: Type.Object({
108
- query: Type.String({ description: "Keyword or API/class/table/lifecycle term to search for" }),
109
- product: Type.Optional(Type.String({ description: "Kingdee product: flagship, cosmic, xinghan, cangqiong, or enterprise." })),
110
- edition: Type.Optional(Type.String({ description: "Legacy alias for product. Prefer product." })),
111
- limit: Type.Optional(Type.Number({ description: "Maximum number of results. Defaults to 5." })),
108
+ query: Type.String({ description: "要搜索的关键词、API、类名、表名或生命周期术语。" }),
109
+ product: Type.Optional(Type.String({ description: "金蝶产品:flagshipcosmicxinghancangqiong enterprise" })),
110
+ edition: Type.Optional(Type.String({ description: "旧参数,等同于 product。优先使用 product" })),
111
+ limit: Type.Optional(Type.Number({ description: "最大结果数,默认 5" })),
112
112
  }),
113
113
 
114
114
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
@@ -117,15 +117,15 @@ const kdSearchTool = defineTool({
117
117
  if (!scopes) {
118
118
  const guidance =
119
119
  profile.product === "unknown"
120
- ? "Provide product first, for example product=flagship, enterprise, cosmic, xinghan, or cangqiong."
121
- : "No knowledge scope is configured for this product profile.";
120
+ ? "请先提供 product,例如 flagshipenterprisecosmicxinghan cangqiong"
121
+ : "当前产品画像未配置可搜索知识范围。";
122
122
  return {
123
123
  content: [
124
124
  {
125
125
  type: "text",
126
126
  text: [
127
- `Product profile: ${profile.product}/${profile.techStack}/${profile.language}`,
128
- "Bundled searchable knowledge requires a known product profile.",
127
+ `产品画像:${profile.product}/${profile.techStack}/${profile.language}`,
128
+ "KCode 随包知识库搜索需要明确产品画像。",
129
129
  guidance,
130
130
  ].join("\n"),
131
131
  },
@@ -145,12 +145,12 @@ const kdSearchTool = defineTool({
145
145
 
146
146
  const kdTableTool = defineTool({
147
147
  name: "kd_table",
148
- label: "KD Table",
149
- description: "Look up a bundled Kingdee table schema by table name for the selected product profile.",
148
+ label: "KD 表结构",
149
+ description: "按表名查询 KCode 随包金蝶表结构,查询结果受产品画像约束。",
150
150
  parameters: Type.Object({
151
- table: Type.String({ description: "Table name, for example T_PUR_POORDER" }),
152
- product: Type.Optional(Type.String({ description: "Kingdee product: flagship, cosmic, xinghan, cangqiong, or enterprise." })),
153
- edition: Type.Optional(Type.String({ description: "Legacy alias for product. Prefer product." })),
151
+ table: Type.String({ description: "表名,例如 T_PUR_POORDER" }),
152
+ product: Type.Optional(Type.String({ description: "金蝶产品:flagshipcosmicxinghancangqiong enterprise" })),
153
+ edition: Type.Optional(Type.String({ description: "旧参数,等同于 product。优先使用 product" })),
154
154
  }),
155
155
 
156
156
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
@@ -159,15 +159,15 @@ const kdTableTool = defineTool({
159
159
  if (!edition) {
160
160
  const guidance =
161
161
  profile.product === "unknown"
162
- ? "Provide product first. Do not assume a table schema across Kingdee product families."
163
- : "Do not reuse flagship/enterprise table assumptions for Cosmic/Xinghan/Cangqiong without metadata verification.";
162
+ ? "请先提供 product。不要跨金蝶产品族猜测表结构。"
163
+ : "没有元数据查证前,不要把旗舰版/企业版表结构假设复用到苍穹/星瀚/Cosmic";
164
164
  return {
165
165
  content: [
166
166
  {
167
167
  type: "text",
168
168
  text: [
169
- `Product profile: ${profile.product}/${profile.techStack}/${profile.language}`,
170
- "Bundled table schemas currently cover flagship and enterprise only.",
169
+ `产品画像:${profile.product}/${profile.techStack}/${profile.language}`,
170
+ "KCode 随包表结构目前主要覆盖旗舰版和企业版。",
171
171
  guidance,
172
172
  ].join("\n"),
173
173
  },
@@ -186,14 +186,14 @@ const kdTableTool = defineTool({
186
186
 
187
187
  const kdCheckTool = defineTool({
188
188
  name: "kd_check",
189
- label: "KD Check",
189
+ label: "KD 检查",
190
190
  description:
191
- "Check Kingdee Java/C#/Python plugin code for magic values, naming issues, DB calls in loops, and empty catch blocks.",
191
+ "检查金蝶 Java/C#/Python 插件代码中的魔法值、命名问题、循环内 DB 调用和空 catch 等问题。",
192
192
  parameters: Type.Object({
193
- code: Type.Optional(Type.String({ description: "Source code to check. Use this or path." })),
194
- path: Type.Optional(Type.String({ description: "Path to a source file to check. Use this or code." })),
195
- product: Type.Optional(Type.String({ description: "Kingdee product. Used to derive Java or C# when language is omitted." })),
196
- language: Type.Optional(Type.String({ description: "Language: java, csharp, or python. Overrides product-derived language." })),
193
+ code: Type.Optional(Type.String({ description: "要检查的源码。与 path 二选一。" })),
194
+ path: Type.Optional(Type.String({ description: "要检查的源码文件路径。与 code 二选一。" })),
195
+ product: Type.Optional(Type.String({ description: "金蝶产品。未提供 language 时用于推导 Java C#。" })),
196
+ language: Type.Optional(Type.String({ description: "语言:javacsharp python。会覆盖产品推导结果。" })),
197
197
  }),
198
198
 
199
199
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -210,7 +210,7 @@ const kdCheckTool = defineTool({
210
210
 
211
211
  if (!code) {
212
212
  return {
213
- content: [{ type: "text", text: "Provide either code or path for kd_check." }],
213
+ content: [{ type: "text", text: "kd_check 需要提供 code path" }],
214
214
  details: { error: "missing-code-or-path" },
215
215
  };
216
216
  }
@@ -220,7 +220,7 @@ const kdCheckTool = defineTool({
220
220
  content: [
221
221
  {
222
222
  type: "text",
223
- text: `Source: ${source}\nProduct: ${profile.product}/${profile.techStack}\nLanguage: ${language}\n\n${formatCheckResults(results)}`,
223
+ text: `来源:${source}\n产品:${profile.product}/${profile.techStack}\n语言:${language}\n\n${formatCheckResults(results)}`,
224
224
  },
225
225
  ],
226
226
  details: { source, product: profile.product, language, issues: results },
@@ -230,12 +230,12 @@ const kdCheckTool = defineTool({
230
230
 
231
231
  const kdCosmicConfigTool = defineTool({
232
232
  name: "kd_cosmic_config",
233
- label: "KD Cosmic Config",
234
- description: "Run the official ok-cosmic configuration precheck for Cosmic-family Kingdee products.",
233
+ label: "KD Cosmic 配置",
234
+ description: "运行 Cosmic 家族金蝶产品的官方能力配置预检查。",
235
235
  parameters: Type.Object({
236
- product: Type.String({ description: "Cosmic-family product: cangqiong, xinghan, flagship, or cosmic." }),
237
- config: Type.Optional(Type.String({ description: "Optional ok-cosmic.json path. Defaults to current working directory behavior." })),
238
- dryRun: Type.Optional(Type.Boolean({ description: "Return the command without executing it." })),
236
+ product: Type.String({ description: "Cosmic 家族产品:cangqiongxinghanflagship cosmic" }),
237
+ config: Type.Optional(Type.String({ description: "可选 ok-cosmic.json 路径。默认按当前工作目录解析。" })),
238
+ dryRun: Type.Optional(Type.Boolean({ description: "只返回命令,不实际执行。" })),
239
239
  }),
240
240
 
241
241
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -248,18 +248,18 @@ const kdCosmicConfigTool = defineTool({
248
248
 
249
249
  const kdCosmicMetadataTool = defineTool({
250
250
  name: "kd_cosmic_metadata",
251
- label: "KD Cosmic Metadata",
252
- description: "Query official Cosmic form/bill metadata for fields, enum values, operations, and SQL table details.",
251
+ label: "KD Cosmic 元数据",
252
+ description: "查询官方 Cosmic 表单/单据元数据,包括字段、枚举值、操作和 SQL 表信息。",
253
253
  parameters: Type.Object({
254
- product: Type.String({ description: "Cosmic-family product: cangqiong, xinghan, flagship, or cosmic." }),
255
- form: Type.String({ description: "Form ID, bill ID, or Chinese bill name. Multiple targets can be comma-separated." }),
256
- config: Type.Optional(Type.String({ description: "Optional ok-cosmic.json path." })),
257
- fuzzy: Type.Optional(Type.String({ description: "Optional field keywords separated by spaces or commas." })),
258
- typeFilter: Type.Optional(Type.String({ description: "Optional field type regex, for example combo|check or decimal." })),
259
- sql: Type.Optional(Type.Boolean({ description: "Include database table and field information." })),
260
- op: Type.Optional(Type.Boolean({ description: "Show operations for the form/bill." })),
261
- showDetail: Type.Optional(Type.Boolean({ description: "Show detailed metadata output." })),
262
- dryRun: Type.Optional(Type.Boolean({ description: "Return the command without executing it." })),
254
+ product: Type.String({ description: "Cosmic 家族产品:cangqiongxinghanflagship cosmic" }),
255
+ form: Type.String({ description: "Form ID、单据 ID 或中文单据名;多个目标可用逗号分隔。" }),
256
+ config: Type.Optional(Type.String({ description: "可选 ok-cosmic.json 路径。" })),
257
+ fuzzy: Type.Optional(Type.String({ description: "可选字段关键词,用空格或逗号分隔。" })),
258
+ typeFilter: Type.Optional(Type.String({ description: "可选字段类型正则,例如 combo|check decimal" })),
259
+ sql: Type.Optional(Type.Boolean({ description: "是否包含数据库表和字段信息。" })),
260
+ op: Type.Optional(Type.Boolean({ description: "是否显示表单/单据操作。" })),
261
+ showDetail: Type.Optional(Type.Boolean({ description: "是否显示详细元数据输出。" })),
262
+ dryRun: Type.Optional(Type.Boolean({ description: "只返回命令,不实际执行。" })),
263
263
  }),
264
264
 
265
265
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -273,15 +273,15 @@ const kdCosmicMetadataTool = defineTool({
273
273
  const kdCosmicApiTool = defineTool({
274
274
  name: "kd_cosmic_api",
275
275
  label: "KD Cosmic API",
276
- description: "Query bundled Cosmic API knowledge for class and method clues. Use kd_sdk_signature or build output for final local SDK facts.",
276
+ description: "查询随包 Cosmic API 知识,获取类和方法线索;最终签名事实应以 kd_sdk_signature 或项目构建输出为准。",
277
277
  parameters: Type.Object({
278
- product: Type.String({ description: "Cosmic-family product: cangqiong, xinghan, flagship, or cosmic." }),
279
- mode: Type.String({ description: "search, search-method, or detail." }),
280
- query: Type.String({ description: "Class name, method name, or fully qualified class name." }),
281
- config: Type.Optional(Type.String({ description: "Optional ok-cosmic.json path." })),
282
- method: Type.Optional(Type.String({ description: "Optional method filter for detail mode." })),
283
- compact: Type.Optional(Type.Boolean({ description: "Request compact detail output when supported." })),
284
- dryRun: Type.Optional(Type.Boolean({ description: "Return the command without executing it." })),
278
+ product: Type.String({ description: "Cosmic 家族产品:cangqiongxinghanflagship cosmic" }),
279
+ mode: Type.String({ description: "查询模式:searchsearch-method detail" }),
280
+ query: Type.String({ description: "类名、方法名或完整限定类名。" }),
281
+ config: Type.Optional(Type.String({ description: "可选 ok-cosmic.json 路径。" })),
282
+ method: Type.Optional(Type.String({ description: "detail 模式下的可选方法过滤条件。" })),
283
+ compact: Type.Optional(Type.Boolean({ description: "支持时请求紧凑详情输出。" })),
284
+ dryRun: Type.Optional(Type.Boolean({ description: "只返回命令,不实际执行。" })),
285
285
  }),
286
286
 
287
287
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -289,7 +289,7 @@ const kdCosmicApiTool = defineTool({
289
289
  const rejection = rejectNonCosmic(profile);
290
290
  if (rejection) return { content: [{ type: "text", text: rejection }], details: { rejected: true, product: profile.product } };
291
291
  if (!["search", "search-method", "detail"].includes(params.mode)) {
292
- return { content: [{ type: "text", text: "mode must be one of: search, search-method, detail" }], details: { error: "invalid-mode" } };
292
+ return { content: [{ type: "text", text: "mode 必须是 searchsearch-method detail" }], details: { error: "invalid-mode" } };
293
293
  }
294
294
  return runOrDryRun(
295
295
  cosmicApiCommand(ctx.cwd, {
@@ -305,17 +305,17 @@ const kdCosmicApiTool = defineTool({
305
305
 
306
306
  const kdSdkSignatureTool = defineTool({
307
307
  name: "kd_sdk_signature",
308
- label: "KD SDK Signature",
308
+ label: "KD SDK 签名",
309
309
  description:
310
- "Inspect method/type signatures from SDK jars or dlls actually present in the current project. Prefer this over bundled knowledge for factual API signatures.",
310
+ "从当前项目真实存在的 SDK jar dll 中检查方法/类型签名。涉及 API 签名事实时,优先使用它而不是随包知识库。",
311
311
  parameters: Type.Object({
312
- product: Type.Optional(Type.String({ description: "Kingdee product. Used to derive Java or C# when language is omitted." })),
313
- language: Type.Optional(Type.String({ description: "java or csharp. Defaults from product profile." })),
314
- query: Type.Optional(Type.String({ description: "Class/type keyword to search, for example QueryServiceHelper or DynamicObject." })),
315
- className: Type.Optional(Type.String({ description: "Fully qualified Java/C# type name when known." })),
316
- method: Type.Optional(Type.String({ description: "Optional method/property name filter within the matched class/type. Does not scan all methods globally." })),
317
- path: Type.Optional(Type.String({ description: "Optional SDK lib/bin directory or specific dependency root. Defaults to current project." })),
318
- limit: Type.Optional(Type.Number({ description: "Maximum jars/dlls/classes to inspect. Defaults to 20 result classes and 200 files." })),
312
+ product: Type.Optional(Type.String({ description: "金蝶产品。未提供 language 时用于推导 Java C#。" })),
313
+ language: Type.Optional(Type.String({ description: "java csharp。默认由产品画像推导。" })),
314
+ query: Type.Optional(Type.String({ description: "要搜索的类/类型关键词,例如 QueryServiceHelper DynamicObject" })),
315
+ className: Type.Optional(Type.String({ description: "已知时提供完整限定 Java/C# 类型名。" })),
316
+ method: Type.Optional(Type.String({ description: "在匹配类/类型内过滤方法或属性;不会全局扫描所有方法。" })),
317
+ path: Type.Optional(Type.String({ description: "可选 SDK lib/bin 目录或依赖根路径。默认从当前项目查找。" })),
318
+ limit: Type.Optional(Type.Number({ description: "最大检查 jar/dll/class 数量。默认 20 个结果类、200 个文件。" })),
319
319
  }),
320
320
 
321
321
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -338,12 +338,12 @@ const kdSdkSignatureTool = defineTool({
338
338
 
339
339
  const kdKsqlLintTool = defineTool({
340
340
  name: "kd_ksql_lint",
341
- label: "KD KSQL Lint",
342
- description: "Run the official ok-ksql lint script against a generated KSQL/SQL file.",
341
+ label: "KD KSQL 检查",
342
+ description: "对生成的 KSQL/SQL 文件运行官方 ok-ksql lint 检查。",
343
343
  parameters: Type.Object({
344
- product: Type.String({ description: "Cosmic-family product: cangqiong, xinghan, flagship, or cosmic." }),
345
- path: Type.String({ description: "SQL/KSQL file path, relative to workspace or absolute." }),
346
- dryRun: Type.Optional(Type.Boolean({ description: "Return the command without executing it." })),
344
+ product: Type.String({ description: "Cosmic 家族产品:cangqiongxinghanflagship cosmic" }),
345
+ path: Type.String({ description: "SQL/KSQL 文件路径,可为工作区相对路径或绝对路径。" }),
346
+ dryRun: Type.Optional(Type.Boolean({ description: "只返回命令,不实际执行。" })),
347
347
  }),
348
348
 
349
349
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -356,12 +356,12 @@ const kdKsqlLintTool = defineTool({
356
356
 
357
357
  const kdBuildTool = defineTool({
358
358
  name: "kd_build",
359
- label: "KD Build",
360
- description: "Run or dry-run a product-aware Kingdee build command for Cosmic Java or enterprise C# projects.",
359
+ label: "KD 构建",
360
+ description: "按产品画像运行或预览金蝶构建命令,支持 Cosmic Java 和企业版 C# 项目。",
361
361
  parameters: Type.Object({
362
- product: Type.String({ description: "Kingdee product: cangqiong, xinghan, flagship, cosmic, or enterprise." }),
363
- target: Type.Optional(Type.String({ description: "Optional Gradle/Maven task for Java or .sln/.csproj path for C#." })),
364
- dryRun: Type.Optional(Type.Boolean({ description: "Return the build command without executing it." })),
362
+ product: Type.String({ description: "金蝶产品:cangqiongxinghanflagshipcosmic enterprise" }),
363
+ target: Type.Optional(Type.String({ description: "Java 可提供 Gradle/Maven task;C# 可提供 .sln/.csproj 路径。" })),
364
+ dryRun: Type.Optional(Type.Boolean({ description: "只返回构建命令,不实际执行。" })),
365
365
  }),
366
366
 
367
367
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -380,12 +380,12 @@ const kdBuildTool = defineTool({
380
380
 
381
381
  const kdDebugTool = defineTool({
382
382
  name: "kd_debug",
383
- label: "KD Debug",
384
- description: "Analyze Kingdee build/runtime logs or stack traces and map them to likely causes and next checks.",
383
+ label: "KD 调试",
384
+ description: "分析金蝶构建/运行日志或堆栈,给出可能原因和下一步检查建议。",
385
385
  parameters: Type.Object({
386
- text: Type.Optional(Type.String({ description: "Log text or stack trace. Use this or path." })),
387
- path: Type.Optional(Type.String({ description: "Path to a log file. Use this or text." })),
388
- product: Type.Optional(Type.String({ description: "Optional product hint for context." })),
386
+ text: Type.Optional(Type.String({ description: "日志文本或堆栈。与 path 二选一。" })),
387
+ path: Type.Optional(Type.String({ description: "日志文件路径。与 text 二选一。" })),
388
+ product: Type.Optional(Type.String({ description: "可选产品提示,用于补充上下文。" })),
389
389
  }),
390
390
 
391
391
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
@@ -396,9 +396,9 @@ const kdDebugTool = defineTool({
396
396
  return {
397
397
  content: [
398
398
  {
399
- type: "text",
400
- text: `Source: ${input.source}\nProduct: ${profile.product}/${profile.platform}/${profile.techStack}\n\n${formatDebugFindings(findings)}`,
401
- },
399
+ type: "text",
400
+ text: `来源:${input.source}\n产品:${profile.product}/${profile.platform}/${profile.techStack}\n\n${formatDebugFindings(findings)}`,
401
+ },
402
402
  ],
403
403
  details: { source: input.source, product: profile.product, findings },
404
404
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kcode-pi",
3
- "version": "0.1.13",
4
- "description": "Kingdee-specific package and harness for Pi Coding Agent",
3
+ "version": "0.1.15",
4
+ "description": "面向金蝶开发的 Pi Coding Agent 启动器、工具包和 Harness 工作流",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "bin": {
@@ -64,6 +64,7 @@
64
64
  "smoke:sdk-signature": "tsx scripts/smoke-sdk-signature.ts",
65
65
  "smoke:package": "tsx scripts/smoke-package.ts",
66
66
  "smoke:kcode-cli": "tsx scripts/smoke-kcode-cli.ts",
67
+ "release:check": "tsx scripts/release-check.ts",
67
68
  "kcode": "tsx scripts/kcode.ts",
68
69
  "prepack": "npm run build:cli && npm run smoke:package"
69
70
  },
@@ -1,11 +1,11 @@
1
1
  ---
2
- description: Start or refine the Kingdee Harness Engineering discussion phase.
2
+ description: 开始或继续金蝶 Harness 工作流的需求讨论阶段。
3
3
  ---
4
4
 
5
- Use the `kd-discuss` skill.
5
+ 使用 `kd-discuss` skill
6
6
 
7
- Start or update the active Kingdee harness run for this request:
7
+ 围绕本次请求开始或更新当前金蝶 Harness run
8
8
 
9
9
  {{args}}
10
10
 
11
- Capture business goal, Kingdee product/version, tech stack, plugin type, target object, out-of-scope items, and open questions in `CONTEXT.md`.
11
+ 请把业务目标、金蝶产品/版本、技术栈、插件类型、目标单据/表单/实体、非目标范围和待确认问题记录到 `CONTEXT.md`。
@@ -1,12 +1,11 @@
1
1
  ---
2
- description: Execute the active Kingdee plan under Harness Engineering gates.
2
+ description: Harness 门禁约束下执行当前金蝶实施计划。
3
3
  ---
4
4
 
5
- Use the `kd-execute` skill.
5
+ 使用 `kd-execute` skill
6
6
 
7
- Before editing code, check the active run with `kd_plan_status`. If `PLAN.md` is missing or the gate is blocked, stop and explain the missing artifact. Otherwise implement only the approved plan and update `EXECUTION.md`.
7
+ 编辑代码前,先使用 `kd_plan_status` 检查当前 run。如果缺少 `PLAN.md` 或门禁被阻塞,必须停止并说明缺少的文档或证据。通过后只实现 `PLAN.md` 批准的内容,并更新 `EXECUTION.md`。
8
8
 
9
- Extra user guidance:
9
+ 用户补充说明:
10
10
 
11
11
  {{args}}
12
-
@@ -1,12 +1,11 @@
1
1
  ---
2
- description: Draft the Kingdee implementation plan artifact for the active harness run.
2
+ description: 为当前金蝶 Harness run 编写实施计划。
3
3
  ---
4
4
 
5
- Use the `kd-plan` skill.
5
+ 使用 `kd-plan` skill
6
6
 
7
- Read `CONTEXT.md` and `SPEC.md`, then draft or update `PLAN.md`. Include files to inspect, files likely to edit, required Kingdee lookups, validation commands, and rollback notes.
7
+ 读取 `CONTEXT.md` `SPEC.md`,编写或更新 `PLAN.md`。必须包含已检查的项目结构、需要查看的文件、预计修改的真实路径、必须查证的金蝶 API/元数据、验证命令和回滚说明。
8
8
 
9
- Extra user guidance:
9
+ 用户补充说明:
10
10
 
11
11
  {{args}}
12
-
@@ -1,12 +1,11 @@
1
1
  ---
2
- description: Prepare Kingdee delivery summary after verification.
2
+ description: 验证后整理金蝶交付摘要。
3
3
  ---
4
4
 
5
- Use the `kd-ship` skill.
5
+ 使用 `kd-ship` skill
6
6
 
7
- Read the active run artifacts and produce `SHIP.md` with changed behavior, verification evidence, residual risks, and follow-up tasks. Do not ship if `VERIFY.md` is missing.
7
+ 读取当前 run 的阶段文档,生成 `SHIP.md`,说明行为变化、验证证据、残余风险和后续事项。缺少 `VERIFY.md` 时不得进入交付总结。
8
8
 
9
- Extra user guidance:
9
+ 用户补充说明:
10
10
 
11
11
  {{args}}
12
-
@@ -1,12 +1,11 @@
1
1
  ---
2
- description: Draft the Kingdee specification artifact for the active harness run.
2
+ description: 为当前金蝶 Harness run 编写需求规格文档。
3
3
  ---
4
4
 
5
- Use the `kd-spec` skill.
5
+ 使用 `kd-spec` skill
6
6
 
7
- Read the active Kingdee run context and draft or update `SPEC.md`. Include acceptance criteria, lifecycle hooks, data objects, exception behavior, performance constraints, assumptions, and risks.
7
+ 读取当前金蝶 run 的上下文,编写或更新 `SPEC.md`。必须包含验收标准、生命周期/扩展点、数据对象、异常行为、性能约束、假设和风险。
8
8
 
9
- Extra user guidance:
9
+ 用户补充说明:
10
10
 
11
11
  {{args}}
12
-
@@ -1,12 +1,11 @@
1
1
  ---
2
- description: Verify the active Kingdee implementation and collect evidence.
2
+ description: 验证当前金蝶实现并收集证据。
3
3
  ---
4
4
 
5
- Use the `kd-verify` skill.
5
+ 使用 `kd-verify` skill
6
6
 
7
- Run the planned validation commands, collect evidence, run available checks, and update `VERIFY.md`. If anything cannot run, record the exact blocker and residual risk.
7
+ 执行计划中的验证命令,收集证据,运行可用检查,并更新 `VERIFY.md`。如果某项验证无法运行,记录具体阻塞原因和残余风险。
8
8
 
9
- Extra user guidance:
9
+ 用户补充说明:
10
10
 
11
11
  {{args}}
12
-
package/src/cli/kcode.ts CHANGED
@@ -37,7 +37,9 @@ export function runKcodeCli(args: string[], cwd = process.cwd()): KcodeCliResult
37
37
  case "context":
38
38
  return context(cwd, args.slice(1));
39
39
  case "doctor":
40
- return doctor(cwd);
40
+ return doctor(cwd, args.slice(1));
41
+ case "repair":
42
+ return repair(cwd);
41
43
  case "version":
42
44
  case "--version":
43
45
  case "-v":
@@ -81,32 +83,101 @@ export function context(cwd: string, args: string[]): KcodeCliResult {
81
83
  };
82
84
  }
83
85
 
84
- export function doctor(cwd: string): KcodeCliResult {
86
+ export function doctor(cwd: string, args: string[] = []): KcodeCliResult {
87
+ const deep = args.includes("--deep");
85
88
  const lines: string[] = [];
86
89
  const node = spawnSync("node", ["--version"], { encoding: "utf8" });
87
90
  const piCli = resolvePiCliCommand(["--version"]);
88
91
  const pi = piCli ? spawnSync(piCli.command, piCli.args, { encoding: "utf8" }) : undefined;
89
92
  const settingsPath = projectSettingsPath(cwd);
93
+ const projectContextPath = join(cwd, ".pi", "kd", "PROJECT_CONTEXT.md");
94
+ let errors = 0;
95
+ let warnings = 0;
90
96
 
91
97
  lines.push(`Node:${node.status === 0 ? node.stdout.trim() : "未找到"}`);
92
98
  lines.push(`Pi CLI:${formatPiCliStatus(piCli, pi)}`);
93
99
  lines.push(`KCode version:${packageName}@${packageVersion}`);
94
100
  lines.push(`KCode package:${packageRoot}`);
95
101
  lines.push(`项目配置:${existsSync(settingsPath) ? settingsPath : "未创建,请先运行 kcode init"}`);
96
- lines.push(`项目上下文:${existsSync(join(cwd, ".pi", "kd", "PROJECT_CONTEXT.md")) ? join(cwd, ".pi", "kd", "PROJECT_CONTEXT.md") : "未创建,请运行 kcode context"}`);
102
+ lines.push(`项目上下文:${existsSync(projectContextPath) ? projectContextPath : "未创建,请运行 kcode context"}`);
97
103
 
98
104
  if (existsSync(settingsPath)) {
99
- const settings = readSettings(settingsPath);
100
- const hasKcode = (settings.packages ?? []).includes(normalizePath(packageRoot));
101
- lines.push(`KCode package 已登记:${hasKcode ? "是" : "否"}`);
105
+ const settingsResult = readSettingsSafe(settingsPath);
106
+ if (!settingsResult.ok) {
107
+ errors++;
108
+ lines.push(`[ERROR] 项目配置无法解析:${settingsResult.error}`);
109
+ } else {
110
+ const packages = settingsResult.settings.packages ?? [];
111
+ const hasKcode = packages.includes(normalizePath(packageRoot));
112
+ lines.push(`KCode package 已登记:${hasKcode ? "是" : "否"}`);
113
+ if (!hasKcode) warnings++;
114
+ if (deep) {
115
+ const kcodePackages = packages.filter((pkg) => isSameKcodePackage(pkg, normalizePath(packageRoot)));
116
+ const stalePackages = kcodePackages.filter((pkg) => normalizePath(pkg) !== normalizePath(packageRoot));
117
+ lines.push(`KCode package 条目数:${kcodePackages.length}`);
118
+ if (stalePackages.length > 0) {
119
+ warnings++;
120
+ lines.push(`[WARN] 发现旧 KCode package 路径:${stalePackages.join(" | ")}`);
121
+ lines.push(" 运行 kcode repair 可清理旧路径并刷新项目上下文。");
122
+ }
123
+ }
124
+ }
125
+ } else {
126
+ warnings++;
102
127
  }
103
128
 
129
+ if (deep) {
130
+ if (!isSupportedNodeVersion(process.version)) {
131
+ errors++;
132
+ lines.push(`[ERROR] 当前 Node ${process.version} 不满足要求:>=22.19.0`);
133
+ }
134
+ if (!existsSync(projectContextPath)) {
135
+ warnings++;
136
+ lines.push("[WARN] 项目上下文不存在,运行 kcode context --refresh 或 kcode repair。");
137
+ }
138
+ const npmPrefix = spawnSync("npm", ["prefix", "-g"], { encoding: "utf8" });
139
+ const npmRoot = spawnSync("npm", ["root", "-g"], { encoding: "utf8" });
140
+ lines.push(`npm prefix -g:${npmPrefix.status === 0 ? npmPrefix.stdout.trim() : "不可用"}`);
141
+ lines.push(`npm root -g:${npmRoot.status === 0 ? npmRoot.stdout.trim() : "不可用"}`);
142
+ lines.push(`active run:${existsSync(join(cwd, ".pi", "kd", "active-run.json")) ? "存在" : "无"}`);
143
+ }
144
+
145
+ if (deep) lines.push(`诊断汇总:errors=${errors} warnings=${warnings}`);
146
+
104
147
  return {
105
- exitCode: pi?.status === 0 ? 0 : 1,
148
+ exitCode: pi?.status === 0 && errors === 0 ? 0 : 1,
106
149
  output: lines.join("\n"),
107
150
  };
108
151
  }
109
152
 
153
+ export function repair(cwd: string): KcodeCliResult {
154
+ const settingsPath = projectSettingsPath(cwd);
155
+ const settingsResult = readSettingsSafe(settingsPath);
156
+ if (!settingsResult.ok) {
157
+ return {
158
+ exitCode: 1,
159
+ output: `项目配置无法解析,未自动覆盖:${settingsPath}\n原因:${settingsResult.error}`,
160
+ };
161
+ }
162
+
163
+ const currentPackage = normalizePath(packageRoot);
164
+ const packages = (settingsResult.settings.packages ?? []).filter((pkg) => !isSameKcodePackage(pkg, currentPackage));
165
+ packages.unshift(currentPackage);
166
+ settingsResult.settings.packages = packages;
167
+ mkdirSync(dirname(settingsPath), { recursive: true });
168
+ writeFileSync(settingsPath, `${JSON.stringify(settingsResult.settings, null, 2)}\n`, "utf8");
169
+ const projectContext = writeProjectContext(cwd);
170
+
171
+ return {
172
+ exitCode: 0,
173
+ output: [
174
+ `已修复项目级 Pi 配置:${settingsPath}`,
175
+ `已保留当前 KCode package:${currentPackage}`,
176
+ `已刷新项目上下文:${projectContext.path}`,
177
+ ].join("\n"),
178
+ };
179
+ }
180
+
110
181
  export function version(): KcodeCliResult {
111
182
  const piCli = resolvePiCliCommand(["--version"]);
112
183
  const pi = piCli ? spawnSync(piCli.command, piCli.args, { encoding: "utf8" }) : undefined;
@@ -176,6 +247,14 @@ function readSettings(path: string): PiSettings {
176
247
  return JSON.parse(readFileSync(path, "utf8")) as PiSettings;
177
248
  }
178
249
 
250
+ function readSettingsSafe(path: string): { ok: true; settings: PiSettings } | { ok: false; error: string } {
251
+ try {
252
+ return { ok: true, settings: readSettings(path) };
253
+ } catch (error) {
254
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
255
+ }
256
+ }
257
+
179
258
  function normalizePath(path: string): string {
180
259
  return resolve(path);
181
260
  }
@@ -233,6 +312,20 @@ function formatPiCliStatus(
233
312
  return `${version}(${source}:${piCli.displayPath})`;
234
313
  }
235
314
 
315
+ function isSupportedNodeVersion(version: string): boolean {
316
+ const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
317
+ if (!match) return false;
318
+ const [, majorText, minorText, patchText] = match;
319
+ const major = Number(majorText);
320
+ const minor = Number(minorText);
321
+ const patch = Number(patchText);
322
+ if (major > 22) return true;
323
+ if (major < 22) return false;
324
+ if (minor > 19) return true;
325
+ if (minor < 19) return false;
326
+ return patch >= 0;
327
+ }
328
+
236
329
  function piCliPackageVersion(piCli: PiCliCommand): string | undefined {
237
330
  if (piCli.source !== "bundled") return undefined;
238
331
 
@@ -253,6 +346,7 @@ function helpText(): string {
253
346
  " kcode init 初始化当前项目的 .pi/settings.json",
254
347
  " kcode context 生成或刷新 .pi/kd/PROJECT_CONTEXT.md",
255
348
  " kcode doctor 检查 Node、随包 Pi CLI、KCode package 和项目级配置",
349
+ " kcode repair 清理旧 KCode package 路径并刷新项目上下文",
256
350
  " kcode version 显示 KCode、随包 Pi CLI 和 Node 版本",
257
351
  " kcode start 初始化项目配置后启动 KCode 工作环境",
258
352
  ].join("\n");