kcode-pi 0.1.13 → 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 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`
@@ -522,7 +540,7 @@ npm install -g kcode-pi@latest
522
540
 
523
541
  ```powershell
524
542
  kcode init
525
- kcode doctor
543
+ kcode doctor --deep
526
544
  ```
527
545
 
528
546
  ## nvm 与 Node 版本切换
@@ -646,7 +664,7 @@ type .pi\settings.json
646
664
  如果 `packages` 中有多条 `kcode-pi` 路径,运行:
647
665
 
648
666
  ```powershell
649
- kcode init
667
+ kcode repair
650
668
  ```
651
669
 
652
670
  如果使用的是 `0.1.1` 或更早版本,请手工删除旧路径,或升级到最新版。
@@ -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(join(cwd, ".pi", "kd", "PROJECT_CONTEXT.md")) ? join(cwd, ".pi", "kd", "PROJECT_CONTEXT.md") : "未创建,请运行 kcode context"}`);
75
+ lines.push(`项目上下文:${existsSync(projectContextPath) ? projectContextPath : "未创建,请运行 kcode context"}`);
70
76
  if (existsSync(settingsPath)) {
71
- const settings = readSettings(settingsPath);
72
- const hasKcode = (settings.packages ?? []).includes(normalizePath(packageRoot));
73
- lines.push(`KCode package 已登记:${hasKcode ? "是" : "否"}`);
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");
@@ -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
  发布:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kcode-pi",
3
- "version": "0.1.13",
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(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");