kcode-pi 0.1.2 → 0.1.5
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 +378 -171
- package/dist/cli/kcode.d.ts +1 -0
- package/dist/cli/kcode.js +15 -1
- package/dist/context/project-context.d.ts +9 -0
- package/dist/context/project-context.js +193 -0
- package/docs/DEVELOPMENT.md +162 -0
- package/docs/KCODE_DISTRIBUTION.md +1 -1
- package/extensions/kingdee-harness.ts +122 -0
- package/extensions/kingdee-tools.ts +9 -5
- package/knowledge/enterprise-python/python-plugin.md +134 -0
- package/package.json +2 -2
- package/skills/kd-cosmic-dev/SKILL.md +2 -0
- package/skills/kd-cosmic-unittest/SKILL.md +1 -0
- package/skills/kd-enterprise-python-plugin/SKILL.md +43 -0
- package/skills/kd-execute/SKILL.md +6 -2
- package/skills/kd-gen/SKILL.md +1 -0
- package/skills/kd-plan/SKILL.md +7 -1
- package/src/cli/kcode.ts +16 -1
- package/src/context/project-context.ts +215 -0
- package/src/harness/artifacts.ts +35 -1
- package/src/harness/gates.ts +29 -0
- package/src/harness/path-policy.ts +83 -0
- package/src/harness/plan-steps.ts +79 -0
- package/src/harness/tdd-policy.ts +62 -0
- package/src/knowledge/types.ts +1 -1
- package/src/official/kingdee-skills.ts +549 -38
- package/src/platform/path.ts +38 -0
- package/src/product/profile.ts +29 -5
- package/src/rules/checker.ts +1 -1
- package/src/tools/build-debug.ts +7 -1
package/dist/cli/kcode.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface PiCliCommand {
|
|
|
10
10
|
}
|
|
11
11
|
export declare function runKcodeCli(args: string[], cwd?: string): KcodeCliResult;
|
|
12
12
|
export declare function initProject(cwd: string): KcodeCliResult;
|
|
13
|
+
export declare function context(cwd: string, args: string[]): KcodeCliResult;
|
|
13
14
|
export declare function doctor(cwd: string): KcodeCliResult;
|
|
14
15
|
export declare function start(cwd: string, piArgs: string[]): KcodeCliResult;
|
|
15
16
|
export declare function resolvePiCliCommand(piArgs?: string[]): PiCliCommand | undefined;
|
package/dist/cli/kcode.js
CHANGED
|
@@ -3,6 +3,7 @@ import { basename, dirname, join, resolve } from "node:path";
|
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
|
+
import { ensureProjectContext, writeProjectContext } from "../context/project-context.js";
|
|
6
7
|
const packageRoot = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
7
8
|
const require = createRequire(import.meta.url);
|
|
8
9
|
const packageName = readPackageName(packageRoot) ?? "kcode-pi";
|
|
@@ -11,6 +12,8 @@ export function runKcodeCli(args, cwd = process.cwd()) {
|
|
|
11
12
|
switch (command) {
|
|
12
13
|
case "init":
|
|
13
14
|
return initProject(cwd);
|
|
15
|
+
case "context":
|
|
16
|
+
return context(cwd, args.slice(1));
|
|
14
17
|
case "doctor":
|
|
15
18
|
return doctor(cwd);
|
|
16
19
|
case "start":
|
|
@@ -32,9 +35,18 @@ export function initProject(cwd) {
|
|
|
32
35
|
settings.packages = packages;
|
|
33
36
|
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
34
37
|
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
|
|
38
|
+
const projectContext = ensureProjectContext(cwd);
|
|
35
39
|
return {
|
|
36
40
|
exitCode: 0,
|
|
37
|
-
output: [`已更新项目级 Pi 配置:${settingsPath}`, `已保留当前 KCode package:${kcodePackage}`].join("\n"),
|
|
41
|
+
output: [`已更新项目级 Pi 配置:${settingsPath}`, `已保留当前 KCode package:${kcodePackage}`, `项目上下文:${projectContext.path}`].join("\n"),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function context(cwd, args) {
|
|
45
|
+
const refresh = args.includes("--refresh") || args.includes("-r");
|
|
46
|
+
const projectContext = refresh ? writeProjectContext(cwd) : ensureProjectContext(cwd);
|
|
47
|
+
return {
|
|
48
|
+
exitCode: 0,
|
|
49
|
+
output: [`项目上下文已${refresh ? "刷新" : "就绪"}:${projectContext.path}`, "", projectContext.content].join("\n"),
|
|
38
50
|
};
|
|
39
51
|
}
|
|
40
52
|
export function doctor(cwd) {
|
|
@@ -47,6 +59,7 @@ export function doctor(cwd) {
|
|
|
47
59
|
lines.push(`Pi CLI:${formatPiCliStatus(piCli, pi)}`);
|
|
48
60
|
lines.push(`KCode package:${packageRoot}`);
|
|
49
61
|
lines.push(`项目配置:${existsSync(settingsPath) ? settingsPath : "未创建,请先运行 kcode init"}`);
|
|
62
|
+
lines.push(`项目上下文:${existsSync(join(cwd, ".pi", "kd", "PROJECT_CONTEXT.md")) ? join(cwd, ".pi", "kd", "PROJECT_CONTEXT.md") : "未创建,请运行 kcode context"}`);
|
|
50
63
|
if (existsSync(settingsPath)) {
|
|
51
64
|
const settings = readSettings(settingsPath);
|
|
52
65
|
const hasKcode = (settings.packages ?? []).includes(normalizePath(packageRoot));
|
|
@@ -167,6 +180,7 @@ function helpText() {
|
|
|
167
180
|
"",
|
|
168
181
|
"用法:",
|
|
169
182
|
" kcode init 初始化当前项目的 .pi/settings.json",
|
|
183
|
+
" kcode context 生成或刷新 .pi/kd/PROJECT_CONTEXT.md",
|
|
170
184
|
" kcode doctor 检查 Node、随包 Pi CLI、KCode package 和项目级配置",
|
|
171
185
|
" kcode start 初始化项目配置后启动 KCode 工作环境",
|
|
172
186
|
].join("\n");
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ProjectContextResult {
|
|
2
|
+
path: string;
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function projectContextPath(cwd: string): string;
|
|
6
|
+
export declare function readProjectContext(cwd: string): string | undefined;
|
|
7
|
+
export declare function ensureProjectContext(cwd: string): ProjectContextResult;
|
|
8
|
+
export declare function writeProjectContext(cwd: string): ProjectContextResult;
|
|
9
|
+
export declare function generateProjectContext(cwd: string): string;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, extname, join, relative } from "node:path";
|
|
3
|
+
const IGNORED_DIRS = new Set([
|
|
4
|
+
".git",
|
|
5
|
+
".idea",
|
|
6
|
+
".pi",
|
|
7
|
+
".tmp",
|
|
8
|
+
".vscode",
|
|
9
|
+
"bin",
|
|
10
|
+
"build",
|
|
11
|
+
"dist",
|
|
12
|
+
"node_modules",
|
|
13
|
+
"out",
|
|
14
|
+
"target",
|
|
15
|
+
]);
|
|
16
|
+
const SOURCE_EXTENSIONS = new Set([".java", ".kt", ".kts", ".cs", ".py", ".xml", ".properties", ".yml", ".yaml", ".sql", ".ksql"]);
|
|
17
|
+
const BUILD_FILE_NAMES = new Set(["pom.xml", "build.gradle", "build.gradle.kts", "gradlew", "gradlew.bat", "mvnw", "mvnw.cmd", "package.json"]);
|
|
18
|
+
const MAX_DEPTH = 5;
|
|
19
|
+
const MAX_ENTRIES = 3000;
|
|
20
|
+
const MAX_SOURCE_SAMPLES = 60;
|
|
21
|
+
const MAX_MODULES = 80;
|
|
22
|
+
export function projectContextPath(cwd) {
|
|
23
|
+
return join(cwd, ".pi", "kd", "PROJECT_CONTEXT.md");
|
|
24
|
+
}
|
|
25
|
+
export function readProjectContext(cwd) {
|
|
26
|
+
const path = projectContextPath(cwd);
|
|
27
|
+
return existsSync(path) ? readFileSync(path, "utf8") : undefined;
|
|
28
|
+
}
|
|
29
|
+
export function ensureProjectContext(cwd) {
|
|
30
|
+
const path = projectContextPath(cwd);
|
|
31
|
+
if (existsSync(path))
|
|
32
|
+
return { path, content: readFileSync(path, "utf8") };
|
|
33
|
+
return writeProjectContext(cwd);
|
|
34
|
+
}
|
|
35
|
+
export function writeProjectContext(cwd) {
|
|
36
|
+
const path = projectContextPath(cwd);
|
|
37
|
+
const content = generateProjectContext(cwd);
|
|
38
|
+
mkdirSync(join(cwd, ".pi", "kd"), { recursive: true });
|
|
39
|
+
writeFileSync(path, content, "utf8");
|
|
40
|
+
return { path, content };
|
|
41
|
+
}
|
|
42
|
+
export function generateProjectContext(cwd) {
|
|
43
|
+
const scan = scanProject(cwd);
|
|
44
|
+
const codeDir = scan.directories.includes("code");
|
|
45
|
+
const likelyRoots = detectLikelySourceRoots(scan);
|
|
46
|
+
const modules = detectModules(scan);
|
|
47
|
+
const buildFiles = scan.files.filter((file) => BUILD_FILE_NAMES.has(basename(file).toLowerCase()) || isSolutionOrProject(file));
|
|
48
|
+
const sourceSamples = scan.files.filter((file) => SOURCE_EXTENSIONS.has(extname(file).toLowerCase())).slice(0, MAX_SOURCE_SAMPLES);
|
|
49
|
+
return [
|
|
50
|
+
"# KCode Project Context",
|
|
51
|
+
"",
|
|
52
|
+
`- Project root: ${cwd}`,
|
|
53
|
+
`- Project name: ${basename(cwd)}`,
|
|
54
|
+
`- Generated at: ${new Date().toISOString()}`,
|
|
55
|
+
"",
|
|
56
|
+
"## Persistent Rules",
|
|
57
|
+
"",
|
|
58
|
+
"- This file is project memory for KCode. Read it before planning or editing code.",
|
|
59
|
+
"- Do not create demo/sample/scaffold code for business requirements.",
|
|
60
|
+
"- Do not assume module layout. Follow the actual paths below and verify target files before editing.",
|
|
61
|
+
"- Use project-relative paths when calling file tools. On Windows, do not rewrite paths to /mnt/<drive>/... or /<drive>/...; use Windows paths only when an absolute path is necessary.",
|
|
62
|
+
"- If this file is stale, regenerate with `kcode context --refresh` before planning.",
|
|
63
|
+
"- Write product code only after the Harness reaches `execute` and PLAN.md names the real target path.",
|
|
64
|
+
"",
|
|
65
|
+
"## Layout Summary",
|
|
66
|
+
"",
|
|
67
|
+
`- Has code directory: ${codeDir ? "yes" : "no"}`,
|
|
68
|
+
`- Likely source roots: ${formatList(likelyRoots)}`,
|
|
69
|
+
`- Build files: ${formatList(buildFiles)}`,
|
|
70
|
+
`- Detected modules: ${formatList(modules)}`,
|
|
71
|
+
"",
|
|
72
|
+
"## Source Samples",
|
|
73
|
+
"",
|
|
74
|
+
formatBlockList(sourceSamples),
|
|
75
|
+
"",
|
|
76
|
+
"## Top-Level Directories",
|
|
77
|
+
"",
|
|
78
|
+
formatBlockList(scan.directories.filter((dir) => !dir.includes("/") && !dir.includes("\\")).sort()),
|
|
79
|
+
"",
|
|
80
|
+
].join("\n");
|
|
81
|
+
}
|
|
82
|
+
function scanProject(cwd) {
|
|
83
|
+
const files = [];
|
|
84
|
+
const directories = [];
|
|
85
|
+
const queue = [{ path: cwd, depth: 0 }];
|
|
86
|
+
let entries = 0;
|
|
87
|
+
while (queue.length > 0 && entries < MAX_ENTRIES) {
|
|
88
|
+
const current = queue.shift();
|
|
89
|
+
if (!current)
|
|
90
|
+
break;
|
|
91
|
+
let children;
|
|
92
|
+
try {
|
|
93
|
+
children = readdirSync(current.path);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
for (const child of children) {
|
|
99
|
+
if (entries >= MAX_ENTRIES)
|
|
100
|
+
break;
|
|
101
|
+
const fullPath = join(current.path, child);
|
|
102
|
+
let stat;
|
|
103
|
+
try {
|
|
104
|
+
stat = statSync(fullPath);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const rel = normalize(relative(cwd, fullPath));
|
|
110
|
+
entries++;
|
|
111
|
+
if (stat.isDirectory()) {
|
|
112
|
+
if (IGNORED_DIRS.has(child))
|
|
113
|
+
continue;
|
|
114
|
+
directories.push(rel);
|
|
115
|
+
if (current.depth < MAX_DEPTH)
|
|
116
|
+
queue.push({ path: fullPath, depth: current.depth + 1 });
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (stat.isFile())
|
|
120
|
+
files.push(rel);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { files: files.sort(), directories: directories.sort() };
|
|
124
|
+
}
|
|
125
|
+
function detectLikelySourceRoots(scan) {
|
|
126
|
+
const roots = new Set();
|
|
127
|
+
const knownRoots = [
|
|
128
|
+
"code/src/main/java",
|
|
129
|
+
"code/src/main/resources",
|
|
130
|
+
"src/main/java",
|
|
131
|
+
"src/main/resources",
|
|
132
|
+
"src",
|
|
133
|
+
"script",
|
|
134
|
+
"scripts",
|
|
135
|
+
"plugins",
|
|
136
|
+
];
|
|
137
|
+
for (const root of knownRoots) {
|
|
138
|
+
if (scan.directories.includes(root) || scan.files.some((file) => file.startsWith(`${root}/`)))
|
|
139
|
+
roots.add(root);
|
|
140
|
+
}
|
|
141
|
+
for (const file of scan.files) {
|
|
142
|
+
if (!SOURCE_EXTENSIONS.has(extname(file).toLowerCase()))
|
|
143
|
+
continue;
|
|
144
|
+
const marker = sourceRootFromFile(file);
|
|
145
|
+
if (marker)
|
|
146
|
+
roots.add(marker);
|
|
147
|
+
}
|
|
148
|
+
return [...roots].slice(0, MAX_MODULES);
|
|
149
|
+
}
|
|
150
|
+
function detectModules(scan) {
|
|
151
|
+
const modules = new Set();
|
|
152
|
+
for (const file of scan.files) {
|
|
153
|
+
const name = basename(file).toLowerCase();
|
|
154
|
+
if (!BUILD_FILE_NAMES.has(name) && !isSolutionOrProject(file))
|
|
155
|
+
continue;
|
|
156
|
+
const dir = normalize(file.slice(0, -basename(file).length).replace(/[\\/]$/, ""));
|
|
157
|
+
modules.add(dir || ".");
|
|
158
|
+
}
|
|
159
|
+
for (const sourceRoot of detectLikelySourceRoots(scan)) {
|
|
160
|
+
const parts = sourceRoot.split("/");
|
|
161
|
+
if (parts.length > 3)
|
|
162
|
+
modules.add(parts.slice(0, -3).join("/") || ".");
|
|
163
|
+
}
|
|
164
|
+
return [...modules].filter(Boolean).sort().slice(0, MAX_MODULES);
|
|
165
|
+
}
|
|
166
|
+
function sourceRootFromFile(file) {
|
|
167
|
+
const normalized = normalize(file);
|
|
168
|
+
const javaIndex = normalized.indexOf("/src/main/java/");
|
|
169
|
+
if (javaIndex >= 0)
|
|
170
|
+
return normalized.slice(0, javaIndex + "/src/main/java".length);
|
|
171
|
+
const resourcesIndex = normalized.indexOf("/src/main/resources/");
|
|
172
|
+
if (resourcesIndex >= 0)
|
|
173
|
+
return normalized.slice(0, resourcesIndex + "/src/main/resources".length);
|
|
174
|
+
const srcIndex = normalized.indexOf("/src/");
|
|
175
|
+
if (srcIndex >= 0)
|
|
176
|
+
return normalized.slice(0, srcIndex + "/src".length);
|
|
177
|
+
const first = normalized.split("/")[0];
|
|
178
|
+
return first && SOURCE_EXTENSIONS.has(extname(normalized).toLowerCase()) ? first : undefined;
|
|
179
|
+
}
|
|
180
|
+
function isSolutionOrProject(file) {
|
|
181
|
+
return [".sln", ".csproj"].includes(extname(file).toLowerCase());
|
|
182
|
+
}
|
|
183
|
+
function formatList(values) {
|
|
184
|
+
return values.length > 0 ? values.join(", ") : "none detected";
|
|
185
|
+
}
|
|
186
|
+
function formatBlockList(values) {
|
|
187
|
+
if (values.length === 0)
|
|
188
|
+
return "- none detected";
|
|
189
|
+
return values.map((value) => `- ${value}`).join("\n");
|
|
190
|
+
}
|
|
191
|
+
function normalize(path) {
|
|
192
|
+
return path.replace(/\\/g, "/");
|
|
193
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# KCode 开发与发布说明
|
|
2
|
+
|
|
3
|
+
本文档面向 KCode 维护者。用户安装和使用说明请看根目录 `README.md`。
|
|
4
|
+
|
|
5
|
+
## 源码开发
|
|
6
|
+
|
|
7
|
+
克隆仓库并安装依赖:
|
|
8
|
+
|
|
9
|
+
```powershell
|
|
10
|
+
git clone <KCodeV2仓库地址>
|
|
11
|
+
cd KCodeV2
|
|
12
|
+
npm install
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
源码仓库内可用 npm script 调试同一个入口:
|
|
16
|
+
|
|
17
|
+
```powershell
|
|
18
|
+
npm run kcode -- doctor
|
|
19
|
+
npm run kcode -- init
|
|
20
|
+
npm run kcode -- start
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 本地验证
|
|
24
|
+
|
|
25
|
+
常用检查:
|
|
26
|
+
|
|
27
|
+
```powershell
|
|
28
|
+
npm run check
|
|
29
|
+
npm run build:cli
|
|
30
|
+
npm run smoke:knowledge
|
|
31
|
+
npm run smoke:checker
|
|
32
|
+
npm run smoke:harness
|
|
33
|
+
npm run smoke:official
|
|
34
|
+
npm run smoke:build-debug
|
|
35
|
+
npm run smoke:package
|
|
36
|
+
npm run smoke:kcode-cli
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
这些 smoke 分别验证:
|
|
40
|
+
|
|
41
|
+
- TypeScript 类型检查。
|
|
42
|
+
- CLI 构建产物。
|
|
43
|
+
- 金蝶知识库搜索。
|
|
44
|
+
- `kd_check` 静态规则。
|
|
45
|
+
- Harness 阶段和门禁。
|
|
46
|
+
- 官方能力 Node 适配器。
|
|
47
|
+
- 构建/调试诊断。
|
|
48
|
+
- package manifest、skills、vendor 文件完整性。
|
|
49
|
+
- `kcode` 项目级入口逻辑。
|
|
50
|
+
|
|
51
|
+
## CLI 构建
|
|
52
|
+
|
|
53
|
+
全局 `kcode` 命令来自:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
src/cli/main.ts
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
构建输出:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
dist/cli/main.js
|
|
63
|
+
dist/cli/kcode.js
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
构建命令:
|
|
67
|
+
|
|
68
|
+
```powershell
|
|
69
|
+
npm run build:cli
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`package.json` 中的关键发布字段:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"bin": {
|
|
77
|
+
"kcode": "./dist/cli/main.js"
|
|
78
|
+
},
|
|
79
|
+
"files": [
|
|
80
|
+
"dist",
|
|
81
|
+
"src",
|
|
82
|
+
"extensions",
|
|
83
|
+
"skills",
|
|
84
|
+
"prompts",
|
|
85
|
+
"themes",
|
|
86
|
+
"knowledge",
|
|
87
|
+
"vendor",
|
|
88
|
+
"docs/KCODE_DISTRIBUTION.md",
|
|
89
|
+
"README.md"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
注意:`extensions/*.ts` 运行时会引用 `../src/**`,因此发布包必须包含 `src`。
|
|
95
|
+
|
|
96
|
+
## 发布 npm 包
|
|
97
|
+
|
|
98
|
+
发布前确认:
|
|
99
|
+
|
|
100
|
+
- `package.json.name` 是预期包名。
|
|
101
|
+
- `package.json.version` 已递增。
|
|
102
|
+
- npm registry 指向正确位置。
|
|
103
|
+
- `npm pack --dry-run` 包含 `dist`、`src`、`extensions`、`skills`、`prompts`、`themes`、`knowledge`、`vendor`。
|
|
104
|
+
|
|
105
|
+
发布检查:
|
|
106
|
+
|
|
107
|
+
```powershell
|
|
108
|
+
npm run check
|
|
109
|
+
npm run build:cli
|
|
110
|
+
npm run smoke:package
|
|
111
|
+
npm run smoke:kcode-cli
|
|
112
|
+
npm pack --dry-run
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
发布:
|
|
116
|
+
|
|
117
|
+
```powershell
|
|
118
|
+
npm publish
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
如果使用 scoped 包并发布到公网 npm:
|
|
122
|
+
|
|
123
|
+
```powershell
|
|
124
|
+
npm publish --access public
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`prepack` 会自动执行:
|
|
128
|
+
|
|
129
|
+
```powershell
|
|
130
|
+
npm run build:cli
|
|
131
|
+
npm run smoke:package
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## 发行策略
|
|
135
|
+
|
|
136
|
+
KCode 不 fork Pi,也不替换 Pi 生态。KCode 作为 Pi package 提供金蝶工具、skills、prompts、themes 和知识库;`kcode` 启动器负责项目初始化、环境检查和启动随包 Pi CLI。
|
|
137
|
+
|
|
138
|
+
详细发行边界见:
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
docs/KCODE_DISTRIBUTION.md
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 第三方 Pi package 策略
|
|
145
|
+
|
|
146
|
+
默认不内置第三方 Pi packages。
|
|
147
|
+
|
|
148
|
+
允许接入前必须检查:
|
|
149
|
+
|
|
150
|
+
- package 来源和维护状态。
|
|
151
|
+
- license 是否允许企业内部分发。
|
|
152
|
+
- 是否注册 extensions、hooks、commands、tools。
|
|
153
|
+
- 是否写全局配置或访问敏感路径。
|
|
154
|
+
- 是否与 KCode harness、权限策略、上下文策略冲突。
|
|
155
|
+
|
|
156
|
+
通过审查后优先使用项目级推荐安装,写入 `.pi/settings.json`。只有离线交付或强依赖时,才考虑 `dependencies` + `bundledDependencies` 内置。
|
|
157
|
+
|
|
158
|
+
## 当前维护事项
|
|
159
|
+
|
|
160
|
+
- 当前包名是 `kcode-pi`;如果企业侧要叫 `kcode-cli`,发布前需要调整 `package.json.name`。
|
|
161
|
+
- 更深层的 reviewer 语义规则、HTML 审查报告、自动 SQL 产物生成仍在后续计划中。
|
|
162
|
+
- 集成进度见根目录 `OFFICIAL_SKILLS_STATUS.md`。
|
|
@@ -11,6 +11,14 @@ import {
|
|
|
11
11
|
updateProductProfile,
|
|
12
12
|
updatePhaseArtifact,
|
|
13
13
|
} from "../src/harness/state.ts";
|
|
14
|
+
import { readArtifact } from "../src/harness/artifacts.ts";
|
|
15
|
+
import { flagshipWriteBlockReason, isSourceLikePath, planWriteBlockReason } from "../src/harness/path-policy.ts";
|
|
16
|
+
import { tddProductionWriteBlockReason } from "../src/harness/tdd-policy.ts";
|
|
17
|
+
import { readProjectContext } from "../src/context/project-context.ts";
|
|
18
|
+
import { windowsPathHint } from "../src/platform/path.ts";
|
|
19
|
+
|
|
20
|
+
const KINGDEE_INTENT_PATTERN =
|
|
21
|
+
/金蝶|苍穹|星瀚|星空|旗舰|企业版|单据|表单|列表|插件|操作插件|校验器|反写|转换|工作流|基础资料|动态对象|DynamicObject|BOS|Cosmic|IronPython|Python\s*插件|py\s*插件|kd_|KSQL/i;
|
|
14
22
|
|
|
15
23
|
function requireRun(cwd: string): ReturnType<typeof readActiveRun> {
|
|
16
24
|
return readActiveRun(cwd);
|
|
@@ -59,6 +67,82 @@ function parseProductArgs(args: string): { product: string; version?: string } |
|
|
|
59
67
|
return { product, version: parsed.version };
|
|
60
68
|
}
|
|
61
69
|
|
|
70
|
+
function shouldStartHarnessFromInput(text: string): boolean {
|
|
71
|
+
if (!text.trim() || text.trim().startsWith("/")) return false;
|
|
72
|
+
return KINGDEE_INTENT_PATTERN.test(text);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function workflowPromptForRun(cwd: string, run: NonNullable<ReturnType<typeof readActiveRun>>, userText: string): string {
|
|
76
|
+
const status = formatStatus(cwd, run);
|
|
77
|
+
const memory = workflowMemoryForRun(cwd, run);
|
|
78
|
+
const phaseGuidance: Record<KdPhase, string> = {
|
|
79
|
+
discuss:
|
|
80
|
+
"当前处于 discuss。先使用 kd-discuss 梳理需求、产品版本、插件类型、目标单据/表单、生命周期、边界和开放问题;不要生成 demo,不要编辑产品代码。",
|
|
81
|
+
spec:
|
|
82
|
+
"当前处于 spec。先使用 kd-spec 明确验收标准、生命周期、字段/元数据/API 假设和风险;不要编辑产品代码。",
|
|
83
|
+
plan:
|
|
84
|
+
"当前处于 plan。先使用 kd-plan 检查当前项目结构,写明实际目标路径、要检查和要修改的文件、Kingdee 查证项、验证命令和回滚说明;不要编辑产品代码。",
|
|
85
|
+
execute:
|
|
86
|
+
"当前处于 execute。只能实现 PLAN.md 中批准的内容。先读取 PLAN.md 和实际项目文件,遵循既有结构写真实可用代码;不要写 demo/sample/scaffold。",
|
|
87
|
+
verify:
|
|
88
|
+
"当前处于 verify。先使用 kd-verify 收集验证证据,不要继续扩大代码改动。",
|
|
89
|
+
ship:
|
|
90
|
+
"当前处于 ship。整理发布摘要、验证证据、风险和后续事项,不要继续扩大代码改动。",
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return [
|
|
94
|
+
"用户输入:",
|
|
95
|
+
userText,
|
|
96
|
+
"",
|
|
97
|
+
"KCode 项目常驻上下文:",
|
|
98
|
+
readProjectContext(cwd) ?? "未生成。请在终端运行 `kcode context --refresh` 后继续;在生成前不要猜测项目目录。",
|
|
99
|
+
"",
|
|
100
|
+
"KCode Harness 状态:",
|
|
101
|
+
status,
|
|
102
|
+
"",
|
|
103
|
+
"KCode 本次工作流本地文档:",
|
|
104
|
+
memory,
|
|
105
|
+
"",
|
|
106
|
+
phaseGuidance[run.phase],
|
|
107
|
+
"必须先理解当前业务项目已有目录、模块、包名、基类和本地封装,再决定文件位置和实现方式。",
|
|
108
|
+
"路径规则:在 Windows 工作区内,优先使用项目相对路径;如需绝对路径必须使用 `D:\\...` 这类 Windows 路径,禁止把路径改写成 `/mnt/d/...`、`/d/...` 等 WSL/MSYS 风格路径。",
|
|
109
|
+
"execute 阶段只能写 PLAN.md 明确列出的源码文件;如果目标文件不在计划内,必须先回到 plan 更新 PLAN.md。",
|
|
110
|
+
"写生产源码前必须先有红灯证据 evidence/tdd-red.md;红绿证据可以是 API/基类/方法签名、元数据、编译、既有测试框架或外部接口最小验证,不要为了测试引入额外 jar。",
|
|
111
|
+
].join("\n");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function workflowMemoryForRun(cwd: string, run: NonNullable<ReturnType<typeof readActiveRun>>): string {
|
|
115
|
+
const phases = PHASE_ORDER.slice(0, PHASE_ORDER.indexOf(run.phase) + 1);
|
|
116
|
+
return phases
|
|
117
|
+
.map((phase) => {
|
|
118
|
+
const content = readArtifact(cwd, run, phase);
|
|
119
|
+
if (!content) return undefined;
|
|
120
|
+
return [`## ${phase}`, trimForPrompt(content, 6000)].join("\n");
|
|
121
|
+
})
|
|
122
|
+
.filter(Boolean)
|
|
123
|
+
.join("\n\n") || "暂无阶段文档。";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function trimForPrompt(content: string, maxLength: number): string {
|
|
127
|
+
if (content.length <= maxLength) return content;
|
|
128
|
+
return `${content.slice(0, maxLength)}\n\n[...文档过长,已截断;如需完整内容必须读取本地文件...]`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function codeWriteBlockReason(cwd: string, path: string | undefined): string | undefined {
|
|
132
|
+
if (!path || !isSourceLikePath(path)) return undefined;
|
|
133
|
+
|
|
134
|
+
const run = readActiveRun(cwd);
|
|
135
|
+
if (!run) {
|
|
136
|
+
return "KCode 工作流未启动,不能直接写产品代码。请先用自然语言说明需求,KCode 会进入 discuss,或手动运行 /kd-start <需求>。";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (run.phase !== "execute") {
|
|
140
|
+
return `当前 KCode 阶段是 ${run.phase},不能写产品代码。请先完成 discuss -> spec -> plan 并进入 execute。`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return tddProductionWriteBlockReason(cwd, run, path) ?? planWriteBlockReason(cwd, run, path, readArtifact(cwd, run, "plan") ?? "");
|
|
144
|
+
}
|
|
145
|
+
|
|
62
146
|
const kdPlanStatusTool = defineTool({
|
|
63
147
|
name: "kd_plan_status",
|
|
64
148
|
label: "KD Status",
|
|
@@ -77,6 +161,44 @@ const kdPlanStatusTool = defineTool({
|
|
|
77
161
|
export default function (pi: ExtensionAPI) {
|
|
78
162
|
pi.registerTool(kdPlanStatusTool);
|
|
79
163
|
|
|
164
|
+
pi.on("input", async (event, ctx) => {
|
|
165
|
+
if (event.source === "extension") return { action: "continue" };
|
|
166
|
+
|
|
167
|
+
let run = readActiveRun(ctx.cwd);
|
|
168
|
+
if (!run && shouldStartHarnessFromInput(event.text)) {
|
|
169
|
+
run = createActiveRun(ctx.cwd, event.text);
|
|
170
|
+
if (ctx.hasUI) {
|
|
171
|
+
ctx.ui.notify(`Started Kingdee harness run: ${run.id} (${run.profile?.product}/${run.profile?.techStack})`, "info");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!run) return { action: "continue" };
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
action: "transform",
|
|
179
|
+
text: workflowPromptForRun(ctx.cwd, run, event.text),
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
pi.on("tool_call", (event, ctx) => {
|
|
184
|
+
const input = event.input as Record<string, unknown>;
|
|
185
|
+
const path = typeof input.path === "string" ? input.path : undefined;
|
|
186
|
+
const hint = path ? windowsPathHint(path) : undefined;
|
|
187
|
+
if (hint && ["read", "write", "edit"].includes(event.toolName)) {
|
|
188
|
+
const reason = `当前是 Windows 工作区,不能使用 WSL/MSYS 路径 ${path}。请改用项目相对路径,或使用 Windows 路径 ${hint}。`;
|
|
189
|
+
if (ctx.hasUI) ctx.ui.notify(reason, "warning");
|
|
190
|
+
return { block: true, reason };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (event.toolName !== "write" && event.toolName !== "edit") return undefined;
|
|
194
|
+
|
|
195
|
+
const reason = codeWriteBlockReason(ctx.cwd, path) ?? flagshipWriteBlockReason(readActiveRun(ctx.cwd), path, ctx.cwd);
|
|
196
|
+
if (!reason) return undefined;
|
|
197
|
+
|
|
198
|
+
if (ctx.hasUI) ctx.ui.notify(reason, "warning");
|
|
199
|
+
return { block: true, reason };
|
|
200
|
+
});
|
|
201
|
+
|
|
80
202
|
pi.registerCommand("kd-status", {
|
|
81
203
|
description: "Show the active Kingdee harness run and gate status",
|
|
82
204
|
handler: async (_args, ctx) => {
|
|
@@ -17,9 +17,10 @@ import {
|
|
|
17
17
|
type OfficialEvidenceFile,
|
|
18
18
|
isCosmicFamily,
|
|
19
19
|
ksqlLintCommand,
|
|
20
|
-
|
|
20
|
+
runOfficialCommand,
|
|
21
21
|
writeOfficialEvidence,
|
|
22
22
|
} from "../src/official/kingdee-skills.ts";
|
|
23
|
+
import { resolveWorkspacePath } from "../src/platform/path.ts";
|
|
23
24
|
|
|
24
25
|
const extensionDir = dirname(fileURLToPath(import.meta.url));
|
|
25
26
|
const knowledgePath = join(extensionDir, "..", "knowledge");
|
|
@@ -41,6 +42,7 @@ function editionForBundledKnowledge(profile: ProductProfile, edition: string | u
|
|
|
41
42
|
|
|
42
43
|
function scopesForProfile(profile: ProductProfile, edition: string | undefined): KnowledgeScope[] | undefined {
|
|
43
44
|
if (profile.product === "unknown") return edition ? [normalizeEdition(edition)] : undefined;
|
|
45
|
+
if (profile.techStack === "python-bos") return ["enterprise", "enterprise-python"];
|
|
44
46
|
if (profile.platform === "enterprise-csharp") return ["enterprise"];
|
|
45
47
|
if (profile.platform === "cosmic") {
|
|
46
48
|
const scopes: KnowledgeScope[] = ["cosmic"];
|
|
@@ -51,11 +53,13 @@ function scopesForProfile(profile: ProductProfile, edition: string | undefined):
|
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
function normalizeLanguage(value: string | undefined): CheckLanguage {
|
|
56
|
+
if (value === "python") return "python";
|
|
54
57
|
return value === "csharp" ? "csharp" : "java";
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
function languageForProfile(profile: ProductProfile, value: string | undefined): CheckLanguage {
|
|
58
61
|
if (value) return normalizeLanguage(value);
|
|
62
|
+
if (profile.language === "python") return "python";
|
|
59
63
|
if (profile.language === "csharp") return "csharp";
|
|
60
64
|
return "java";
|
|
61
65
|
}
|
|
@@ -80,7 +84,7 @@ async function runOrDryRun(
|
|
|
80
84
|
};
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
const result = await
|
|
87
|
+
const result = await runOfficialCommand(command);
|
|
84
88
|
const evidencePath = evidenceFile ? writeOfficialEvidence(ctx.cwd, evidenceFile, result) : undefined;
|
|
85
89
|
return {
|
|
86
90
|
content: [{ type: "text" as const, text: formatCommandResult(result) }],
|
|
@@ -176,12 +180,12 @@ const kdCheckTool = defineTool({
|
|
|
176
180
|
name: "kd_check",
|
|
177
181
|
label: "KD Check",
|
|
178
182
|
description:
|
|
179
|
-
"Check Kingdee Java/C
|
|
183
|
+
"Check Kingdee Java/C#/Python plugin code for magic values, naming issues, DB calls in loops, and empty catch blocks.",
|
|
180
184
|
parameters: Type.Object({
|
|
181
185
|
code: Type.Optional(Type.String({ description: "Source code to check. Use this or path." })),
|
|
182
186
|
path: Type.Optional(Type.String({ description: "Path to a source file to check. Use this or code." })),
|
|
183
187
|
product: Type.Optional(Type.String({ description: "Kingdee product. Used to derive Java or C# when language is omitted." })),
|
|
184
|
-
language: Type.Optional(Type.String({ description: "Language: java or
|
|
188
|
+
language: Type.Optional(Type.String({ description: "Language: java, csharp, or python. Overrides product-derived language." })),
|
|
185
189
|
}),
|
|
186
190
|
|
|
187
191
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -191,7 +195,7 @@ const kdCheckTool = defineTool({
|
|
|
191
195
|
let source = "inline";
|
|
192
196
|
|
|
193
197
|
if (!code && params.path) {
|
|
194
|
-
const filePath =
|
|
198
|
+
const filePath = resolveWorkspacePath(ctx.cwd, params.path);
|
|
195
199
|
code = readFileSync(filePath, "utf8");
|
|
196
200
|
source = params.path;
|
|
197
201
|
}
|