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.
- package/README.md +10 -10
- package/dist/cli/kcode.js +3 -3
- package/dist/context/project-context.js +3 -3
- package/docs/CHANGELOG.md +28 -2
- package/docs/COMMAND_REFERENCE.md +10 -10
- package/docs/DEVELOPMENT.md +3 -3
- package/docs/EVIDENCE_AND_GATES.md +8 -8
- package/docs/HARNESS_WORKFLOW.md +12 -12
- package/docs/KCODE_DISTRIBUTION.md +7 -7
- package/docs/PRODUCT_PROFILE.md +9 -9
- package/docs/TROUBLESHOOTING.md +8 -8
- package/docs/USER_GUIDE.md +10 -10
- package/extensions/kingdee-harness.ts +61 -48
- package/extensions/kingdee-header.ts +3 -3
- package/extensions/kingdee-subagents.ts +1 -1
- package/extensions/kingdee-tools.ts +171 -43
- package/package.json +6 -2
- package/src/cli/kcode.ts +3 -3
- package/src/context/project-context.ts +3 -3
- package/src/harness/artifacts.ts +6 -7
- package/src/harness/data-source-policy.ts +302 -0
- package/src/harness/delegation.ts +23 -23
- package/src/harness/gates.ts +26 -1
- package/src/harness/messages.ts +70 -11
- package/src/harness/path-policy.ts +1 -0
- package/src/harness/plan-steps.ts +3 -3
- package/src/harness/prompt-policy.ts +196 -0
- package/src/harness/prompt.ts +8 -16
- package/src/harness/repair.ts +2 -2
- package/src/harness/state.ts +58 -1
- package/src/official/kingdee-skills.ts +4 -4
- package/src/product/profile.ts +2 -2
- package/src/rules/checker.ts +27 -27
- package/src/tools/build-debug.ts +5 -5
- package/src/tools/sdk-signature.ts +4 -4
|
@@ -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 "
|
|
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:
|
|
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: "
|
|
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
|
|
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
|
-
? "
|
|
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
|
|
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
|
-
? "
|
|
179
|
-
: "
|
|
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: "
|
|
210
|
-
path: Type.Optional(Type.String({ description: "
|
|
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
|
|
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: "
|
|
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: "
|
|
273
|
-
fuzzy: Type.Optional(Type.String({ description: "
|
|
274
|
-
typeFilter: Type.Optional(Type.String({ description: "
|
|
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
|
|
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: "
|
|
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
|
|
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: "
|
|
331
|
-
className: Type.Optional(Type.String({ description: "
|
|
332
|
-
method: Type.Optional(Type.String({ description: "
|
|
333
|
-
path: Type.Optional(Type.String({ description: "
|
|
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
|
|
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.
|
|
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 : "
|
|
102
|
-
lines.push(`项目上下文:${existsSync(projectContextPath) ? projectContextPath : "
|
|
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
|
|
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
|
-
"-
|
|
74
|
-
"-
|
|
72
|
+
"- 禁止假设模块结构。必须基于下方真实路径,并在编辑前确认目标文件。",
|
|
73
|
+
"- 调用文件工具时默认使用项目相对路径。在 Windows 中禁止把路径改写为 /mnt/<drive>/... 或 /<drive>/...;绝对路径只允许使用 Windows 路径。",
|
|
74
|
+
"- 本文件过期时,计划前运行 `kcode context --refresh` 重新生成。",
|
|
75
75
|
"- 只有 Harness 进入 `execute` 且 PLAN.md 写明真实目标路径后,才能写产品代码。",
|
|
76
76
|
"",
|
|
77
77
|
"## 项目结构摘要",
|
package/src/harness/artifacts.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
"## 验证命令",
|