kcode-pi 0.1.12 → 0.1.14
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 +31 -3
- package/dist/cli/kcode.d.ts +2 -1
- package/dist/cli/kcode.js +104 -7
- package/docs/DEVELOPMENT.md +2 -5
- package/extensions/kingdee-harness.ts +44 -7
- package/package.json +2 -1
- package/src/cli/kcode.ts +101 -7
package/README.md
CHANGED
|
@@ -59,6 +59,13 @@ kcode context --refresh
|
|
|
59
59
|
|
|
60
60
|
```powershell
|
|
61
61
|
kcode doctor
|
|
62
|
+
kcode doctor --deep
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
修复项目级 KCode 配置:
|
|
66
|
+
|
|
67
|
+
```powershell
|
|
68
|
+
kcode repair
|
|
62
69
|
```
|
|
63
70
|
|
|
64
71
|
查看当前 KCode 版本:
|
|
@@ -240,6 +247,17 @@ kcode context --refresh
|
|
|
240
247
|
|
|
241
248
|
```powershell
|
|
242
249
|
kcode doctor
|
|
250
|
+
kcode doctor --deep
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`--deep` 会额外检查 Node 版本、npm 全局目录、项目 `.pi/settings.json` 中的重复/旧 KCode package 路径、项目上下文和 active run。
|
|
254
|
+
|
|
255
|
+
### `kcode repair`
|
|
256
|
+
|
|
257
|
+
清理当前项目 `.pi/settings.json` 中旧 Node/nvm 版本下的 KCode package 路径,只保留当前安装路径,并刷新项目上下文。
|
|
258
|
+
|
|
259
|
+
```powershell
|
|
260
|
+
kcode repair
|
|
243
261
|
```
|
|
244
262
|
|
|
245
263
|
### `kcode version`
|
|
@@ -276,6 +294,7 @@ kcode start --provider openai --model gpt-4o
|
|
|
276
294
|
/kd-status
|
|
277
295
|
/kd-runs
|
|
278
296
|
/kd-switch <run-id>
|
|
297
|
+
/kd-resume [补充说明]
|
|
279
298
|
/kd-finish
|
|
280
299
|
/kd-gate
|
|
281
300
|
/kd-advance [phase]
|
|
@@ -291,6 +310,15 @@ kcode start --provider openai --model gpt-4o
|
|
|
291
310
|
|
|
292
311
|
`/kd-start` 会创建新的功能点 run,并立即触发 Agent 进入 `discuss` 阶段。如果未识别出产品画像,例如显示 `(unknown/unknown)`,下一步应先确认产品、版本和技术栈,而不是直接写代码。
|
|
293
312
|
|
|
313
|
+
暂停后接续:
|
|
314
|
+
|
|
315
|
+
```text
|
|
316
|
+
/kd-resume
|
|
317
|
+
/kd-resume 继续确认采购入库单即时库存检查插件
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
重新 `kcode start` 时,如果当前项目存在未完成 active run,KCode 会提示当前功能点和阶段。`/kd-resume` 会读取项目常驻上下文、active run 状态和已落盘的阶段文档,再把完整工作流上下文交给 Agent 继续。
|
|
321
|
+
|
|
294
322
|
支持的产品画像:
|
|
295
323
|
|
|
296
324
|
```text
|
|
@@ -348,7 +376,7 @@ KCode 会把金蝶开发需求纳入 Harness 工作流。你可以直接输入
|
|
|
348
376
|
.pi/kd/runs/<run-id>/evidence/
|
|
349
377
|
```
|
|
350
378
|
|
|
351
|
-
下次重新 `kcode start` 时,KCode
|
|
379
|
+
下次重新 `kcode start` 时,KCode 会提示当前项目的 active run。执行 `/kd-resume` 后,KCode 会读取项目常驻上下文、active run 状态和已生成的阶段文档,再继续当前功能点的当前阶段。已完成或暂停的功能点仍保留在 `.pi/kd/runs/<run-id>/`,可用 `/kd-runs` 查看,用 `/kd-switch <run-id>` 切回。
|
|
352
380
|
|
|
353
381
|
阶段含义:
|
|
354
382
|
|
|
@@ -512,7 +540,7 @@ npm install -g kcode-pi@latest
|
|
|
512
540
|
|
|
513
541
|
```powershell
|
|
514
542
|
kcode init
|
|
515
|
-
kcode doctor
|
|
543
|
+
kcode doctor --deep
|
|
516
544
|
```
|
|
517
545
|
|
|
518
546
|
## nvm 与 Node 版本切换
|
|
@@ -636,7 +664,7 @@ type .pi\settings.json
|
|
|
636
664
|
如果 `packages` 中有多条 `kcode-pi` 路径,运行:
|
|
637
665
|
|
|
638
666
|
```powershell
|
|
639
|
-
kcode
|
|
667
|
+
kcode repair
|
|
640
668
|
```
|
|
641
669
|
|
|
642
670
|
如果使用的是 `0.1.1` 或更早版本,请手工删除旧路径,或升级到最新版。
|
package/dist/cli/kcode.d.ts
CHANGED
|
@@ -11,7 +11,8 @@ export interface PiCliCommand {
|
|
|
11
11
|
export declare function runKcodeCli(args: string[], cwd?: string): KcodeCliResult;
|
|
12
12
|
export declare function initProject(cwd: string): KcodeCliResult;
|
|
13
13
|
export declare function context(cwd: string, args: string[]): KcodeCliResult;
|
|
14
|
-
export declare function doctor(cwd: string): KcodeCliResult;
|
|
14
|
+
export declare function doctor(cwd: string, args?: string[]): KcodeCliResult;
|
|
15
|
+
export declare function repair(cwd: string): KcodeCliResult;
|
|
15
16
|
export declare function version(): KcodeCliResult;
|
|
16
17
|
export declare function start(cwd: string, piArgs: string[]): KcodeCliResult;
|
|
17
18
|
export declare function resolvePiCliCommand(piArgs?: string[]): PiCliCommand | undefined;
|
package/dist/cli/kcode.js
CHANGED
|
@@ -17,7 +17,9 @@ export function runKcodeCli(args, cwd = process.cwd()) {
|
|
|
17
17
|
case "context":
|
|
18
18
|
return context(cwd, args.slice(1));
|
|
19
19
|
case "doctor":
|
|
20
|
-
return doctor(cwd);
|
|
20
|
+
return doctor(cwd, args.slice(1));
|
|
21
|
+
case "repair":
|
|
22
|
+
return repair(cwd);
|
|
21
23
|
case "version":
|
|
22
24
|
case "--version":
|
|
23
25
|
case "-v":
|
|
@@ -55,28 +57,96 @@ export function context(cwd, args) {
|
|
|
55
57
|
output: [`项目上下文已${refresh ? "刷新" : "就绪"}:${projectContext.path}`, "", projectContext.content].join("\n"),
|
|
56
58
|
};
|
|
57
59
|
}
|
|
58
|
-
export function doctor(cwd) {
|
|
60
|
+
export function doctor(cwd, args = []) {
|
|
61
|
+
const deep = args.includes("--deep");
|
|
59
62
|
const lines = [];
|
|
60
63
|
const node = spawnSync("node", ["--version"], { encoding: "utf8" });
|
|
61
64
|
const piCli = resolvePiCliCommand(["--version"]);
|
|
62
65
|
const pi = piCli ? spawnSync(piCli.command, piCli.args, { encoding: "utf8" }) : undefined;
|
|
63
66
|
const settingsPath = projectSettingsPath(cwd);
|
|
67
|
+
const projectContextPath = join(cwd, ".pi", "kd", "PROJECT_CONTEXT.md");
|
|
68
|
+
let errors = 0;
|
|
69
|
+
let warnings = 0;
|
|
64
70
|
lines.push(`Node:${node.status === 0 ? node.stdout.trim() : "未找到"}`);
|
|
65
71
|
lines.push(`Pi CLI:${formatPiCliStatus(piCli, pi)}`);
|
|
66
72
|
lines.push(`KCode version:${packageName}@${packageVersion}`);
|
|
67
73
|
lines.push(`KCode package:${packageRoot}`);
|
|
68
74
|
lines.push(`项目配置:${existsSync(settingsPath) ? settingsPath : "未创建,请先运行 kcode init"}`);
|
|
69
|
-
lines.push(`项目上下文:${existsSync(
|
|
75
|
+
lines.push(`项目上下文:${existsSync(projectContextPath) ? projectContextPath : "未创建,请运行 kcode context"}`);
|
|
70
76
|
if (existsSync(settingsPath)) {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
77
|
+
const settingsResult = readSettingsSafe(settingsPath);
|
|
78
|
+
if (!settingsResult.ok) {
|
|
79
|
+
errors++;
|
|
80
|
+
lines.push(`[ERROR] 项目配置无法解析:${settingsResult.error}`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const packages = settingsResult.settings.packages ?? [];
|
|
84
|
+
const hasKcode = packages.includes(normalizePath(packageRoot));
|
|
85
|
+
lines.push(`KCode package 已登记:${hasKcode ? "是" : "否"}`);
|
|
86
|
+
if (!hasKcode)
|
|
87
|
+
warnings++;
|
|
88
|
+
if (deep) {
|
|
89
|
+
const kcodePackages = packages.filter((pkg) => isSameKcodePackage(pkg, normalizePath(packageRoot)));
|
|
90
|
+
const stalePackages = kcodePackages.filter((pkg) => normalizePath(pkg) !== normalizePath(packageRoot));
|
|
91
|
+
lines.push(`KCode package 条目数:${kcodePackages.length}`);
|
|
92
|
+
if (stalePackages.length > 0) {
|
|
93
|
+
warnings++;
|
|
94
|
+
lines.push(`[WARN] 发现旧 KCode package 路径:${stalePackages.join(" | ")}`);
|
|
95
|
+
lines.push(" 运行 kcode repair 可清理旧路径并刷新项目上下文。");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
74
99
|
}
|
|
100
|
+
else {
|
|
101
|
+
warnings++;
|
|
102
|
+
}
|
|
103
|
+
if (deep) {
|
|
104
|
+
if (!isSupportedNodeVersion(process.version)) {
|
|
105
|
+
errors++;
|
|
106
|
+
lines.push(`[ERROR] 当前 Node ${process.version} 不满足要求:>=22.19.0`);
|
|
107
|
+
}
|
|
108
|
+
if (!existsSync(projectContextPath)) {
|
|
109
|
+
warnings++;
|
|
110
|
+
lines.push("[WARN] 项目上下文不存在,运行 kcode context --refresh 或 kcode repair。");
|
|
111
|
+
}
|
|
112
|
+
const npmPrefix = spawnSync("npm", ["prefix", "-g"], { encoding: "utf8" });
|
|
113
|
+
const npmRoot = spawnSync("npm", ["root", "-g"], { encoding: "utf8" });
|
|
114
|
+
lines.push(`npm prefix -g:${npmPrefix.status === 0 ? npmPrefix.stdout.trim() : "不可用"}`);
|
|
115
|
+
lines.push(`npm root -g:${npmRoot.status === 0 ? npmRoot.stdout.trim() : "不可用"}`);
|
|
116
|
+
lines.push(`active run:${existsSync(join(cwd, ".pi", "kd", "active-run.json")) ? "存在" : "无"}`);
|
|
117
|
+
}
|
|
118
|
+
if (deep)
|
|
119
|
+
lines.push(`诊断汇总:errors=${errors} warnings=${warnings}`);
|
|
75
120
|
return {
|
|
76
|
-
exitCode: pi?.status === 0 ? 0 : 1,
|
|
121
|
+
exitCode: pi?.status === 0 && errors === 0 ? 0 : 1,
|
|
77
122
|
output: lines.join("\n"),
|
|
78
123
|
};
|
|
79
124
|
}
|
|
125
|
+
export function repair(cwd) {
|
|
126
|
+
const settingsPath = projectSettingsPath(cwd);
|
|
127
|
+
const settingsResult = readSettingsSafe(settingsPath);
|
|
128
|
+
if (!settingsResult.ok) {
|
|
129
|
+
return {
|
|
130
|
+
exitCode: 1,
|
|
131
|
+
output: `项目配置无法解析,未自动覆盖:${settingsPath}\n原因:${settingsResult.error}`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const currentPackage = normalizePath(packageRoot);
|
|
135
|
+
const packages = (settingsResult.settings.packages ?? []).filter((pkg) => !isSameKcodePackage(pkg, currentPackage));
|
|
136
|
+
packages.unshift(currentPackage);
|
|
137
|
+
settingsResult.settings.packages = packages;
|
|
138
|
+
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
139
|
+
writeFileSync(settingsPath, `${JSON.stringify(settingsResult.settings, null, 2)}\n`, "utf8");
|
|
140
|
+
const projectContext = writeProjectContext(cwd);
|
|
141
|
+
return {
|
|
142
|
+
exitCode: 0,
|
|
143
|
+
output: [
|
|
144
|
+
`已修复项目级 Pi 配置:${settingsPath}`,
|
|
145
|
+
`已保留当前 KCode package:${currentPackage}`,
|
|
146
|
+
`已刷新项目上下文:${projectContext.path}`,
|
|
147
|
+
].join("\n"),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
80
150
|
export function version() {
|
|
81
151
|
const piCli = resolvePiCliCommand(["--version"]);
|
|
82
152
|
const pi = piCli ? spawnSync(piCli.command, piCli.args, { encoding: "utf8" }) : undefined;
|
|
@@ -136,6 +206,14 @@ function readSettings(path) {
|
|
|
136
206
|
return {};
|
|
137
207
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
138
208
|
}
|
|
209
|
+
function readSettingsSafe(path) {
|
|
210
|
+
try {
|
|
211
|
+
return { ok: true, settings: readSettings(path) };
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
139
217
|
function normalizePath(path) {
|
|
140
218
|
return resolve(path);
|
|
141
219
|
}
|
|
@@ -184,6 +262,24 @@ function formatPiCliStatus(piCli, result) {
|
|
|
184
262
|
const source = piCli.source === "bundled" ? "随包" : "全局";
|
|
185
263
|
return `${version}(${source}:${piCli.displayPath})`;
|
|
186
264
|
}
|
|
265
|
+
function isSupportedNodeVersion(version) {
|
|
266
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
|
|
267
|
+
if (!match)
|
|
268
|
+
return false;
|
|
269
|
+
const [, majorText, minorText, patchText] = match;
|
|
270
|
+
const major = Number(majorText);
|
|
271
|
+
const minor = Number(minorText);
|
|
272
|
+
const patch = Number(patchText);
|
|
273
|
+
if (major > 22)
|
|
274
|
+
return true;
|
|
275
|
+
if (major < 22)
|
|
276
|
+
return false;
|
|
277
|
+
if (minor > 19)
|
|
278
|
+
return true;
|
|
279
|
+
if (minor < 19)
|
|
280
|
+
return false;
|
|
281
|
+
return patch >= 0;
|
|
282
|
+
}
|
|
187
283
|
function piCliPackageVersion(piCli) {
|
|
188
284
|
if (piCli.source !== "bundled")
|
|
189
285
|
return undefined;
|
|
@@ -204,6 +300,7 @@ function helpText() {
|
|
|
204
300
|
" kcode init 初始化当前项目的 .pi/settings.json",
|
|
205
301
|
" kcode context 生成或刷新 .pi/kd/PROJECT_CONTEXT.md",
|
|
206
302
|
" kcode doctor 检查 Node、随包 Pi CLI、KCode package 和项目级配置",
|
|
303
|
+
" kcode repair 清理旧 KCode package 路径并刷新项目上下文",
|
|
207
304
|
" kcode version 显示 KCode、随包 Pi CLI 和 Node 版本",
|
|
208
305
|
" kcode start 初始化项目配置后启动 KCode 工作环境",
|
|
209
306
|
].join("\n");
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -35,6 +35,7 @@ npm run smoke:build-debug
|
|
|
35
35
|
npm run smoke:sdk-signature
|
|
36
36
|
npm run smoke:package
|
|
37
37
|
npm run smoke:kcode-cli
|
|
38
|
+
npm run release:check
|
|
38
39
|
```
|
|
39
40
|
|
|
40
41
|
这些 smoke 分别验证:
|
|
@@ -107,11 +108,7 @@ npm run build:cli
|
|
|
107
108
|
发布检查:
|
|
108
109
|
|
|
109
110
|
```powershell
|
|
110
|
-
npm run check
|
|
111
|
-
npm run build:cli
|
|
112
|
-
npm run smoke:package
|
|
113
|
-
npm run smoke:kcode-cli
|
|
114
|
-
npm pack --dry-run
|
|
111
|
+
npm run release:check
|
|
115
112
|
```
|
|
116
113
|
|
|
117
114
|
发布:
|
|
@@ -77,6 +77,16 @@ function shouldStartHarnessFromInput(text: string): boolean {
|
|
|
77
77
|
return KINGDEE_INTENT_PATTERN.test(text);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function sendWorkflowPrompt(pi: ExtensionAPI, ctx: ExtensionContext, run: NonNullable<ReturnType<typeof readActiveRun>>, userText: string): void {
|
|
81
|
+
const prompt = workflowPromptForRun(ctx.cwd, run, userText);
|
|
82
|
+
if (ctx.isIdle()) {
|
|
83
|
+
pi.sendUserMessage(prompt);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
pi.sendUserMessage(prompt, { deliverAs: "followUp" });
|
|
87
|
+
if (ctx.hasUI) ctx.ui.notify("KCode harness message queued.", "info");
|
|
88
|
+
}
|
|
89
|
+
|
|
80
90
|
function workflowPromptForRun(cwd: string, run: NonNullable<ReturnType<typeof readActiveRun>>, userText: string): string {
|
|
81
91
|
const status = formatStatus(cwd, run);
|
|
82
92
|
const memory = workflowMemoryForRun(cwd, run);
|
|
@@ -243,7 +253,7 @@ const kdQuestionTool = defineTool({
|
|
|
243
253
|
"请先问第一个必须确认的问题,例如:",
|
|
244
254
|
"kd_question action=ask question=\"采购入库单 Form ID 是否为 pur_receivebill?\" choices=[\"是\", \"不是\"]",
|
|
245
255
|
"",
|
|
246
|
-
"
|
|
256
|
+
"注意:交互模式下会弹出选择/输入对话并自动记录;非交互模式下用户在对话中回答后再用 kd_question action=answer 记录。",
|
|
247
257
|
].join("\n"),
|
|
248
258
|
},
|
|
249
259
|
],
|
|
@@ -279,6 +289,19 @@ export default function (pi: ExtensionAPI) {
|
|
|
279
289
|
pi.registerTool(kdPlanStatusTool);
|
|
280
290
|
pi.registerTool(kdQuestionTool);
|
|
281
291
|
|
|
292
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
293
|
+
const run = readActiveRun(ctx.cwd);
|
|
294
|
+
if (!run || !ctx.hasUI) return;
|
|
295
|
+
ctx.ui.notify(
|
|
296
|
+
[
|
|
297
|
+
`发现未完成 KCode run:${run.goal ?? run.id}`,
|
|
298
|
+
`阶段:${run.phase},产品:${run.profile?.product ?? run.product ?? "unknown"}`,
|
|
299
|
+
"输入 /kd-resume 可接续;输入 /kd-runs 查看其他功能点。",
|
|
300
|
+
].join("\n"),
|
|
301
|
+
"info",
|
|
302
|
+
);
|
|
303
|
+
});
|
|
304
|
+
|
|
282
305
|
pi.on("input", async (event, ctx) => {
|
|
283
306
|
if (event.source === "extension") return { action: "continue" };
|
|
284
307
|
|
|
@@ -357,13 +380,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
357
380
|
|
|
358
381
|
const run = createActiveRun(ctx.cwd, goal, parsed.product, parsed.version);
|
|
359
382
|
ctx.ui.notify(`Started Kingdee harness run: ${run.id} (${run.profile?.product}/${run.profile?.techStack})`, "info");
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
383
|
+
sendWorkflowPrompt(pi, ctx, run, `继续 KCode Harness run ${run.id}:${goal}`);
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
pi.registerCommand("kd-resume", {
|
|
388
|
+
description: "Resume the active Kingdee harness run and inject persisted context",
|
|
389
|
+
handler: async (args, ctx) => {
|
|
390
|
+
const run = requireRun(ctx.cwd);
|
|
391
|
+
if (!run) {
|
|
392
|
+
ctx.ui.notify("No active Kingdee harness run. Use /kd-start <goal> or /kd-runs.", "error");
|
|
393
|
+
return;
|
|
366
394
|
}
|
|
395
|
+
|
|
396
|
+
const note = args.trim();
|
|
397
|
+
const message = [
|
|
398
|
+
`继续 KCode Harness run ${run.id}:${run.goal ?? "unknown goal"}`,
|
|
399
|
+
note ? `用户补充:${note}` : undefined,
|
|
400
|
+
]
|
|
401
|
+
.filter(Boolean)
|
|
402
|
+
.join("\n");
|
|
403
|
+
sendWorkflowPrompt(pi, ctx, run, message);
|
|
367
404
|
},
|
|
368
405
|
});
|
|
369
406
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kcode-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "Kingdee-specific package and harness for Pi Coding Agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -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
|
},
|
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(
|
|
102
|
+
lines.push(`项目上下文:${existsSync(projectContextPath) ? projectContextPath : "未创建,请运行 kcode context"}`);
|
|
97
103
|
|
|
98
104
|
if (existsSync(settingsPath)) {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
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");
|