kcode-pi 0.1.14 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -25
- package/dist/context/project-context.js +20 -20
- package/extensions/kingdee-harness.ts +80 -80
- package/extensions/kingdee-header.ts +16 -16
- package/extensions/kingdee-tools.ts +82 -82
- package/package.json +2 -2
- package/prompts/kd-discuss.md +4 -4
- package/prompts/kd-execute.md +4 -5
- package/prompts/kd-plan.md +4 -5
- package/prompts/kd-ship.md +4 -5
- package/prompts/kd-spec.md +4 -5
- package/prompts/kd-verify.md +4 -5
- package/src/context/project-context.ts +20 -20
- package/src/harness/artifacts.ts +47 -47
- package/src/harness/format.ts +12 -12
- package/src/harness/gates.ts +5 -5
- package/src/harness/plan-steps.ts +3 -3
- package/src/harness/state.ts +4 -4
- package/src/harness/tdd-policy.ts +2 -2
- package/src/knowledge/format.ts +11 -12
- package/src/product/profile.ts +16 -16
- package/src/tools/build-debug.ts +35 -35
- package/src/tools/sdk-signature.ts +10 -10
package/src/harness/artifacts.ts
CHANGED
|
@@ -40,89 +40,89 @@ export function defaultArtifactContent(phase: KdPhase, goal?: string, profile?:
|
|
|
40
40
|
switch (phase) {
|
|
41
41
|
case "discuss":
|
|
42
42
|
return [
|
|
43
|
-
"#
|
|
44
|
-
"",
|
|
45
|
-
`-
|
|
46
|
-
`-
|
|
47
|
-
"-
|
|
48
|
-
`-
|
|
49
|
-
`-
|
|
50
|
-
"-
|
|
51
|
-
"-
|
|
52
|
-
"-
|
|
53
|
-
"-
|
|
54
|
-
" -
|
|
43
|
+
"# 需求讨论上下文",
|
|
44
|
+
"",
|
|
45
|
+
`- 需求目标:${goal ?? "未知"}`,
|
|
46
|
+
`- 金蝶产品:${profile?.product ?? "未知"}`,
|
|
47
|
+
"- 版本:未知",
|
|
48
|
+
`- 技术栈:${profile?.techStack ?? "未知"}`,
|
|
49
|
+
`- 语言:${profile?.language ?? "未知"}`,
|
|
50
|
+
"- 插件类型:未知",
|
|
51
|
+
"- 目标单据/实体/表单:未知",
|
|
52
|
+
"- 非目标范围:未知",
|
|
53
|
+
"- 待确认问题:",
|
|
54
|
+
" - 确认金蝶产品/版本、技术栈、插件类型和目标对象。",
|
|
55
55
|
"",
|
|
56
56
|
].join("\n");
|
|
57
57
|
case "spec":
|
|
58
58
|
return [
|
|
59
|
-
"#
|
|
59
|
+
"# 需求规格",
|
|
60
60
|
"",
|
|
61
|
-
"##
|
|
61
|
+
"## 验收标准",
|
|
62
62
|
"",
|
|
63
|
-
"##
|
|
63
|
+
"## 生命周期 / 扩展点",
|
|
64
64
|
"",
|
|
65
|
-
"##
|
|
65
|
+
"## 数据对象和字段",
|
|
66
66
|
"",
|
|
67
|
-
"##
|
|
67
|
+
"## 异常行为和性能约束",
|
|
68
68
|
"",
|
|
69
|
-
"##
|
|
69
|
+
"## 假设和风险",
|
|
70
70
|
"",
|
|
71
71
|
].join("\n");
|
|
72
72
|
case "plan":
|
|
73
73
|
return [
|
|
74
|
-
"#
|
|
74
|
+
"# 实施计划",
|
|
75
75
|
"",
|
|
76
|
-
"##
|
|
76
|
+
"## 已检查的项目结构",
|
|
77
77
|
"",
|
|
78
|
-
"##
|
|
78
|
+
"## 需要查看的文件",
|
|
79
79
|
"",
|
|
80
|
-
"##
|
|
80
|
+
"## 目标源码根 / 路径",
|
|
81
81
|
"",
|
|
82
|
-
"##
|
|
82
|
+
"## 允许修改的文件",
|
|
83
83
|
"",
|
|
84
|
-
"##
|
|
84
|
+
"## 必需的金蝶查证项",
|
|
85
85
|
"",
|
|
86
|
-
"##
|
|
86
|
+
"## 执行步骤",
|
|
87
87
|
"",
|
|
88
|
-
"- [ ] STEP-001
|
|
89
|
-
"- [ ] STEP-002
|
|
90
|
-
"- [ ] STEP-003
|
|
91
|
-
"- [ ] STEP-004
|
|
92
|
-
"- [ ] STEP-005
|
|
88
|
+
"- [ ] STEP-001:检查现有目标文件,确认精确修改位置。",
|
|
89
|
+
"- [ ] STEP-002:运行一个会失败的红灯检查,并记录红灯证据。",
|
|
90
|
+
"- [ ] STEP-003:只实现已批准的文件改动。",
|
|
91
|
+
"- [ ] STEP-004:再次运行同一检查,并记录绿灯证据。",
|
|
92
|
+
"- [ ] STEP-005:记录变更文件和步骤证据。",
|
|
93
93
|
"",
|
|
94
|
-
"## TDD /
|
|
94
|
+
"## TDD / 红绿检查",
|
|
95
95
|
"",
|
|
96
|
-
"-
|
|
97
|
-
"-
|
|
98
|
-
"-
|
|
99
|
-
"-
|
|
100
|
-
"-
|
|
101
|
-
"-
|
|
96
|
+
"- 红灯证据:evidence/tdd-red.md",
|
|
97
|
+
"- 绿灯证据:evidence/tdd-green.md",
|
|
98
|
+
"- 红绿检查命令或工具:未知",
|
|
99
|
+
"- 允许的检查:官方 API/基类/方法签名查证、元数据查证、kd_check、构建/编译输出、项目已有测试框架、外部接口最小验证。",
|
|
100
|
+
"- 不要为了满足门禁引入第三方测试 jar 或框架。",
|
|
101
|
+
"- 如果无法自动化测试,记录一个产品相关、实现前应失败且实现后应通过的检查。",
|
|
102
102
|
"",
|
|
103
|
-
"##
|
|
103
|
+
"## 验证命令",
|
|
104
104
|
"",
|
|
105
|
-
"##
|
|
105
|
+
"## 回滚 / 影响控制",
|
|
106
106
|
"",
|
|
107
107
|
].join("\n");
|
|
108
108
|
case "execute":
|
|
109
109
|
return [
|
|
110
|
-
"#
|
|
110
|
+
"# 执行记录",
|
|
111
111
|
"",
|
|
112
|
-
"##
|
|
112
|
+
"## 步骤结果",
|
|
113
113
|
"",
|
|
114
|
-
"
|
|
114
|
+
"每个计划步骤按以下格式记录:",
|
|
115
115
|
"",
|
|
116
|
-
"- [x] STEP-001
|
|
116
|
+
"- [x] STEP-001:已完成。证据:evidence/step-001.md",
|
|
117
117
|
"",
|
|
118
|
-
"##
|
|
118
|
+
"## 变更文件",
|
|
119
119
|
"",
|
|
120
|
-
"##
|
|
120
|
+
"## 偏离计划说明",
|
|
121
121
|
"",
|
|
122
122
|
].join("\n");
|
|
123
123
|
case "verify":
|
|
124
|
-
return ["#
|
|
124
|
+
return ["# 验证记录", "", "## 命令", "", "## 证据", "", "## 残余风险", ""].join("\n");
|
|
125
125
|
case "ship":
|
|
126
|
-
return ["#
|
|
126
|
+
return ["# 交付摘要", "", "## 摘要", "", "## 验证证据", "", "## 风险", "", "## 后续事项", ""].join("\n");
|
|
127
127
|
}
|
|
128
128
|
}
|
package/src/harness/format.ts
CHANGED
|
@@ -7,25 +7,25 @@ import { formatProductProfile } from "../product/profile.ts";
|
|
|
7
7
|
|
|
8
8
|
export function formatStatus(cwd: string, run: ActiveRun | undefined): string {
|
|
9
9
|
if (!run) {
|
|
10
|
-
return ["
|
|
10
|
+
return ["当前没有 active Kingdee Harness run。", "", "创建方式:", "/kd-start <需求>"].join("\n");
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const refreshed = refreshGate(cwd, run);
|
|
14
14
|
const next = nextPhase(refreshed.phase) ?? "done";
|
|
15
15
|
|
|
16
16
|
return [
|
|
17
|
-
`Run
|
|
18
|
-
refreshed.goal ?
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
refreshed.gate.reason ?
|
|
17
|
+
`Run:${refreshed.id}`,
|
|
18
|
+
refreshed.goal ? `需求:${refreshed.goal}` : undefined,
|
|
19
|
+
`状态:${refreshed.status ?? "active"}`,
|
|
20
|
+
`阶段:${refreshed.phase}`,
|
|
21
|
+
`下一阶段:${next}`,
|
|
22
|
+
`产品:${formatProductProfile(refreshed.profile)}`,
|
|
23
|
+
`版本:${refreshed.version ?? "未选择"}`,
|
|
24
|
+
`风险:${refreshed.risk ?? "未知"}`,
|
|
25
|
+
`门禁:${refreshed.gate.passed ? "通过" : "阻塞"}`,
|
|
26
|
+
refreshed.gate.reason ? `原因:${refreshed.gate.reason}` : undefined,
|
|
27
27
|
"",
|
|
28
|
-
`Run
|
|
28
|
+
`Run 目录:${join(runsDir(cwd), refreshed.id)}`,
|
|
29
29
|
]
|
|
30
30
|
.filter(Boolean)
|
|
31
31
|
.join("\n");
|
package/src/harness/gates.ts
CHANGED
|
@@ -7,9 +7,9 @@ import { executionStepsBlockReason, planStepsBlockReason } from "./plan-steps.ts
|
|
|
7
7
|
import { tddPlanBlockReason, tddVerifyBlockReason } from "./tdd-policy.ts";
|
|
8
8
|
|
|
9
9
|
const REQUIRED_MARKERS: Partial<Record<KdPhase, string[]>> = {
|
|
10
|
-
plan: ["##
|
|
11
|
-
verify: ["##
|
|
12
|
-
ship: ["##
|
|
10
|
+
plan: ["## 验证命令"],
|
|
11
|
+
verify: ["## 证据"],
|
|
12
|
+
ship: ["## 验证证据"],
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
const COSMIC_CONFIG_EVIDENCE = "evidence/cosmic-config.txt";
|
|
@@ -162,12 +162,12 @@ function requiredEvidenceForPhase(cwd: string, run: ActiveRun, phase: KdPhase):
|
|
|
162
162
|
|
|
163
163
|
function planHasMetadataRequirement(cwd: string, run: ActiveRun): boolean {
|
|
164
164
|
const plan = readArtifact(cwd, run, "plan") ?? "";
|
|
165
|
-
return /kd_cosmic_metadata|cosmic-metadata|metadata evidence
|
|
165
|
+
return /kd_cosmic_metadata|cosmic-metadata|cosmic-metadata\.json|metadata evidence|字段元数据证据|元数据证据/i.test(plan);
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
function planHasApiRequirement(cwd: string, run: ActiveRun): boolean {
|
|
169
169
|
const plan = readArtifact(cwd, run, "plan") ?? "";
|
|
170
|
-
return /kd_sdk_signature|kd_cosmic_api|cosmic-api|api signature|method signature|
|
|
170
|
+
return /kd_sdk_signature|kd_cosmic_api|cosmic-api|cosmic-api\.txt|api signature evidence|method signature evidence|SDK 签名证据|方法签名证据|接口签名证据/i.test(plan);
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
function runHasKsqlDelivery(cwd: string, run: ActiveRun): boolean {
|
|
@@ -20,13 +20,13 @@ export function parsePlanSteps(plan: string): PlanStep[] {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function planStepsBlockReason(plan: string): string | undefined {
|
|
23
|
-
if (!/##\s
|
|
24
|
-
return "PLAN.md 缺少 ##
|
|
23
|
+
if (!/##\s*执行步骤/i.test(plan)) {
|
|
24
|
+
return "PLAN.md 缺少 ## 执行步骤。必须把计划拆成 STEP-001 这种可跟踪步骤。";
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const steps = parsePlanSteps(plan);
|
|
28
28
|
if (steps.length === 0) {
|
|
29
|
-
return "PLAN.md 没有可执行步骤。请使用 `- [ ] STEP-001
|
|
29
|
+
return "PLAN.md 没有可执行步骤。请使用 `- [ ] STEP-001:...` 列出步骤。";
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const duplicate = firstDuplicate(steps.map((step) => step.id));
|
package/src/harness/state.ts
CHANGED
|
@@ -84,7 +84,7 @@ export function createActiveRun(cwd: string, goal: string, productInput?: string
|
|
|
84
84
|
questions: [],
|
|
85
85
|
gate: {
|
|
86
86
|
passed: false,
|
|
87
|
-
reason: "CONTEXT.md
|
|
87
|
+
reason: "进入 spec 前必须先完成 CONTEXT.md 并确认产品画像",
|
|
88
88
|
checkedAt: new Date().toISOString(),
|
|
89
89
|
},
|
|
90
90
|
};
|
|
@@ -161,21 +161,21 @@ export function updatePhaseArtifact(cwd: string, run: ActiveRun, phase: KdPhase,
|
|
|
161
161
|
export function advanceRun(cwd: string, run: ActiveRun, requestedPhase?: KdPhase): { run: ActiveRun; message: string } {
|
|
162
162
|
const target = requestedPhase ?? nextPhase(run.phase);
|
|
163
163
|
if (!target) {
|
|
164
|
-
return { run, message: `Run
|
|
164
|
+
return { run, message: `Run 已处于最终阶段:${run.phase}` };
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
const gate = canEnterPhase(cwd, run, target);
|
|
168
168
|
if (!gate.passed) {
|
|
169
169
|
run.gate = gate;
|
|
170
170
|
writeActiveRun(cwd, run);
|
|
171
|
-
return { run, message: gate.reason ??
|
|
171
|
+
return { run, message: gate.reason ?? `不能进入 ${target}` };
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
run.phase = target;
|
|
175
175
|
ensurePhaseArtifact(cwd, run, target);
|
|
176
176
|
run.gate = inspectGate(cwd, run);
|
|
177
177
|
writeActiveRun(cwd, run);
|
|
178
|
-
return { run, message:
|
|
178
|
+
return { run, message: `已推进 Kingdee run 到阶段:${target}` };
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
export function refreshGate(cwd: string, run: ActiveRun): ActiveRun {
|
|
@@ -8,8 +8,8 @@ export const TDD_RED_EVIDENCE = "evidence/tdd-red.md";
|
|
|
8
8
|
export const TDD_GREEN_EVIDENCE = "evidence/tdd-green.md";
|
|
9
9
|
|
|
10
10
|
export function tddPlanBlockReason(plan: string): string | undefined {
|
|
11
|
-
if (/##\s*TDD\s*\/\s
|
|
12
|
-
return "PLAN.md 缺少 ## TDD /
|
|
11
|
+
if (/##\s*TDD\s*\/\s*红绿检查/i.test(plan)) return undefined;
|
|
12
|
+
return "PLAN.md 缺少 ## TDD / 红绿检查。必须声明红灯验证、绿灯验证和无法自动化时的产品验证替代方案。";
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function tddProductionWriteBlockReason(cwd: string, run: ActiveRun | undefined, path: string | undefined): string | undefined {
|
package/src/knowledge/format.ts
CHANGED
|
@@ -3,15 +3,15 @@ import type { SearchResult, TableSchema } from "./types.ts";
|
|
|
3
3
|
|
|
4
4
|
export function formatSearchResults(query: string, results: SearchResult[], basePath: string): string {
|
|
5
5
|
if (results.length === 0) {
|
|
6
|
-
return
|
|
6
|
+
return `未找到与 "${query}" 相关的金蝶知识库结果。`;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
const lines = [`
|
|
9
|
+
const lines = [`"${query}" 的金蝶知识库结果:`, ""];
|
|
10
10
|
for (let i = 0; i < results.length; i++) {
|
|
11
11
|
const result = results[i];
|
|
12
12
|
lines.push(`[${i + 1}] ${result.section.heading}`);
|
|
13
|
-
lines.push(
|
|
14
|
-
lines.push(
|
|
13
|
+
lines.push(`来源:${relative(basePath, result.file.path)}:${result.section.lineStart + 1}`);
|
|
14
|
+
lines.push(`相关度:${result.score.toFixed(2)}`);
|
|
15
15
|
lines.push(result.highlights[0] || result.section.content.trim().split("\n").slice(0, 5).join("\n"));
|
|
16
16
|
lines.push("");
|
|
17
17
|
}
|
|
@@ -20,24 +20,24 @@ export function formatSearchResults(query: string, results: SearchResult[], base
|
|
|
20
20
|
|
|
21
21
|
export function formatTableSchema(tableName: string, schema: TableSchema | undefined): string {
|
|
22
22
|
if (!schema) {
|
|
23
|
-
return
|
|
23
|
+
return `未找到 "${tableName}" 的表结构。`;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
const lines = [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
`表:${schema.name} (${tableName.toUpperCase()})`,
|
|
28
|
+
`模块:${schema.module || "未知"}`,
|
|
29
|
+
`说明:${schema.description || "无"}`,
|
|
30
30
|
"",
|
|
31
|
-
"
|
|
31
|
+
"字段:",
|
|
32
32
|
];
|
|
33
33
|
|
|
34
34
|
for (const field of schema.fields) {
|
|
35
|
-
const nullable = field.nullable ? "
|
|
35
|
+
const nullable = field.nullable ? "可空" : "必填";
|
|
36
36
|
lines.push(`- ${field.name} ${field.type} ${nullable} - ${field.description}`);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
if (schema.relatedTables.length > 0) {
|
|
40
|
-
lines.push("", "
|
|
40
|
+
lines.push("", "相关表:");
|
|
41
41
|
for (const related of schema.relatedTables) {
|
|
42
42
|
lines.push(`- ${related.table}: ${related.relation} - ${related.description}`);
|
|
43
43
|
}
|
|
@@ -45,4 +45,3 @@ export function formatTableSchema(tableName: string, schema: TableSchema | undef
|
|
|
45
45
|
|
|
46
46
|
return lines.join("\n");
|
|
47
47
|
}
|
|
48
|
-
|
package/src/product/profile.ts
CHANGED
|
@@ -24,61 +24,61 @@ const PROFILES: Record<KdProduct, ProductProfile> = {
|
|
|
24
24
|
language: "unknown",
|
|
25
25
|
knowledgeScope: "common",
|
|
26
26
|
requiresMetadataVerification: true,
|
|
27
|
-
notes: ["
|
|
27
|
+
notes: ["尚未选择产品;不要假设 Java、C#、BOS、Cosmic 或 KSQL 规则。"],
|
|
28
28
|
},
|
|
29
29
|
flagship: {
|
|
30
30
|
product: "flagship",
|
|
31
|
-
displayName: "
|
|
31
|
+
displayName: "金蝶星空旗舰版",
|
|
32
32
|
platform: "cosmic",
|
|
33
33
|
techStack: "java-cosmic",
|
|
34
34
|
language: "java",
|
|
35
35
|
knowledgeScope: "flagship",
|
|
36
36
|
requiresMetadataVerification: true,
|
|
37
37
|
notes: [
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
38
|
+
"星空旗舰版属于 Cosmic 家族。使用 Cosmic 元数据、插件生命周期、SDK 和后置检查约束。",
|
|
39
|
+
"如果当前工作区存在 code/ 目录,产品代码应放在 code/ 下。",
|
|
40
|
+
"创建或编辑代码前,必须检查 code/ 下真实项目结构,并跟随其实际布局;可能按云、按应用组织,也可能不分模块。",
|
|
41
41
|
],
|
|
42
42
|
},
|
|
43
43
|
cosmic: {
|
|
44
44
|
product: "cosmic",
|
|
45
|
-
displayName: "
|
|
45
|
+
displayName: "金蝶 Cosmic 平台",
|
|
46
46
|
platform: "cosmic",
|
|
47
47
|
techStack: "java-cosmic",
|
|
48
48
|
language: "java",
|
|
49
49
|
knowledgeScope: "cosmic",
|
|
50
50
|
requiresMetadataVerification: true,
|
|
51
|
-
notes: ["Cosmic
|
|
51
|
+
notes: ["Cosmic 是苍穹、星瀚和星空旗舰版的共享平台基础。"],
|
|
52
52
|
},
|
|
53
53
|
xinghan: {
|
|
54
54
|
product: "xinghan",
|
|
55
|
-
displayName: "
|
|
55
|
+
displayName: "金蝶星瀚",
|
|
56
56
|
platform: "cosmic",
|
|
57
57
|
techStack: "java-cosmic",
|
|
58
58
|
language: "java",
|
|
59
59
|
knowledgeScope: "xinghan",
|
|
60
60
|
requiresMetadataVerification: true,
|
|
61
|
-
notes: ["
|
|
61
|
+
notes: ["除非存在星瀚特有规则,否则按 Cosmic 家族 Java 处理。"],
|
|
62
62
|
},
|
|
63
63
|
cangqiong: {
|
|
64
64
|
product: "cangqiong",
|
|
65
|
-
displayName: "
|
|
65
|
+
displayName: "金蝶苍穹",
|
|
66
66
|
platform: "cosmic",
|
|
67
67
|
techStack: "java-cosmic",
|
|
68
68
|
language: "java",
|
|
69
69
|
knowledgeScope: "cangqiong",
|
|
70
70
|
requiresMetadataVerification: true,
|
|
71
|
-
notes: ["
|
|
71
|
+
notes: ["使用苍穹/Cosmic 插件、SDK、元数据、KSQL 和生命周期规则。"],
|
|
72
72
|
},
|
|
73
73
|
enterprise: {
|
|
74
74
|
product: "enterprise",
|
|
75
|
-
displayName: "
|
|
75
|
+
displayName: "金蝶企业版",
|
|
76
76
|
platform: "enterprise-csharp",
|
|
77
77
|
techStack: "csharp-bos",
|
|
78
78
|
language: "csharp",
|
|
79
79
|
knowledgeScope: "enterprise",
|
|
80
80
|
requiresMetadataVerification: true,
|
|
81
|
-
notes: ["
|
|
81
|
+
notes: ["使用企业版 C# 技术栈。Java 插件规则和 Cosmic API 不适用。"],
|
|
82
82
|
},
|
|
83
83
|
};
|
|
84
84
|
|
|
@@ -101,9 +101,9 @@ function enterprisePythonProfile(): ProductProfile {
|
|
|
101
101
|
language: "python",
|
|
102
102
|
knowledgeScope: "enterprise-python",
|
|
103
103
|
notes: [
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"Python
|
|
104
|
+
"只有用户明确要求 Python 插件或 IronPython 脚本时,才使用金蝶云企业版 / BOS IronPython 插件模式。",
|
|
105
|
+
"企业版默认插件开发仍按 C# 处理,除非用户明确要求 Python 插件。",
|
|
106
|
+
"Python 插件通过 BOS 中的 IronPython 运行,并调用 Kingdee.BOS .NET 程序集;编码前必须确认插件类型、事件、FormId、字段标识和注册步骤。",
|
|
107
107
|
],
|
|
108
108
|
};
|
|
109
109
|
}
|
package/src/tools/build-debug.ts
CHANGED
|
@@ -20,17 +20,17 @@ export interface DebugFinding {
|
|
|
20
20
|
|
|
21
21
|
export function planBuild(cwd: string, profile: ProductProfile, target?: string): BuildPlan {
|
|
22
22
|
if (profile.product === "unknown") {
|
|
23
|
-
throw new Error("
|
|
23
|
+
throw new Error("产品画像未知。运行 kd_build 前请先设置 product。");
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
if (profile.platform === "cosmic") return planJavaBuild(cwd, profile, target);
|
|
27
27
|
if (profile.platform === "enterprise-csharp") return planCsharpBuild(cwd, profile, target);
|
|
28
28
|
if (profile.platform === "enterprise-python") {
|
|
29
29
|
throw new Error(
|
|
30
|
-
"
|
|
30
|
+
"企业版 Python 插件通常没有本地构建步骤。请通过 BOS 注册和功能测试验证,并在 VERIFY.md 中记录脚本路径、插件类型、FormId、字段/实体标识和测试数据。",
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
|
-
throw new Error(
|
|
33
|
+
throw new Error(`未找到 ${profile.product}/${profile.platform}/${profile.techStack} 的构建策略。`);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export async function runBuild(plan: BuildPlan, dryRun?: boolean) {
|
|
@@ -55,66 +55,66 @@ export function analyzeDebugText(text: string): DebugFinding[] {
|
|
|
55
55
|
pushIfMatch(findings, lines, /NullPointerException|Object reference not set/i, {
|
|
56
56
|
severity: "error",
|
|
57
57
|
rule: "null-pointer",
|
|
58
|
-
message: "
|
|
59
|
-
nextStep: "
|
|
58
|
+
message: "检测到空指针/空引用异常。",
|
|
59
|
+
nextStep: "检查堆栈附近的字段元数据、可空字段、DynamicObject 取值和 DataSet 行读取。",
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
pushIfMatch(findings, lines, /ClassNotFoundException|NoClassDefFoundError|FileNotFoundException.*\.jar/i, {
|
|
63
63
|
severity: "error",
|
|
64
64
|
rule: "classpath",
|
|
65
|
-
message: "
|
|
66
|
-
nextStep: "
|
|
65
|
+
message: "检测到类或 jar 依赖缺失。",
|
|
66
|
+
nextStep: "检查模块依赖、biz/cuslib 部署,以及是否使用了正确产品/版本的 SDK。",
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
pushIfMatch(findings, lines, /DataSet.*closed|ResultSet.*closed|connection.*closed/i, {
|
|
70
70
|
severity: "error",
|
|
71
71
|
rule: "dataset-scope",
|
|
72
|
-
message: "
|
|
73
|
-
nextStep: "
|
|
72
|
+
message: "检测到已关闭的 DataSet/connection 被继续使用。",
|
|
73
|
+
nextStep: "确保 DataSet 处理位于 try-with-resources 内,并处于创建它的事务/资源作用域内。",
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
pushIfMatch(findings, lines, /SQLSyntaxErrorException|PSQLException|syntax error|invalid column|column .* does not exist/i, {
|
|
77
77
|
severity: "error",
|
|
78
78
|
rule: "sql-metadata",
|
|
79
|
-
message: "
|
|
80
|
-
nextStep: "
|
|
79
|
+
message: "检测到 SQL 或元数据不匹配。",
|
|
80
|
+
nextStep: "使用 kd_cosmic_metadata 并设置 sql=true 查证表名、数据库字段、枚举值和 dbName/dbKey。",
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
pushIfMatch(findings, lines, /deadlock|lock wait timeout|could not serialize access|并发|死锁/i, {
|
|
84
84
|
severity: "error",
|
|
85
85
|
rule: "transaction-lock",
|
|
86
|
-
message: "
|
|
87
|
-
nextStep: "
|
|
86
|
+
message: "检测到事务锁或并发问题。",
|
|
87
|
+
nextStep: "检查事务范围、跨库写入、更新顺序,以及外部调用是否放在事务内。",
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
pushIfMatch(findings, lines, /OutOfMemoryError|GC overhead|Java heap space|内存溢出/i, {
|
|
91
91
|
severity: "error",
|
|
92
92
|
rule: "memory",
|
|
93
|
-
message: "
|
|
94
|
-
nextStep: "
|
|
93
|
+
message: "检测到内存压力或 OOM。",
|
|
94
|
+
nextStep: "检查全表查询、无限集合、过深字段路径,以及大 DataSet/物化加载。",
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
pushIfMatch(findings, lines, /StackOverflowError|propertyChanged/i, {
|
|
98
98
|
severity: "warning",
|
|
99
99
|
rule: "event-recursion",
|
|
100
|
-
message: "
|
|
101
|
-
nextStep: "
|
|
100
|
+
message: "可能存在事件递归触发。",
|
|
101
|
+
nextStep: "检查 propertyChanged/setValue 循环,写字段前比较新旧值。",
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
pushIfMatch(findings, lines, /Gradle|Compilation failed|javac|CS\d{4}|MSB\d{4}|BUILD FAILED|BUILD FAILURE/i, {
|
|
105
105
|
severity: "error",
|
|
106
106
|
rule: "build-error",
|
|
107
|
-
message: "
|
|
108
|
-
nextStep: "
|
|
107
|
+
message: "检测到构建或编译错误。",
|
|
108
|
+
nextStep: "使用产品对应的构建输出。Cosmic Java 需查证 SDK 签名;企业版 C# 需查证命名空间和引用。",
|
|
109
109
|
});
|
|
110
110
|
|
|
111
111
|
if (findings.length === 0) {
|
|
112
112
|
findings.push({
|
|
113
113
|
severity: "info",
|
|
114
114
|
rule: "no-known-pattern",
|
|
115
|
-
message: "
|
|
115
|
+
message: "未匹配到已知金蝶调试模式。",
|
|
116
116
|
evidence: "",
|
|
117
|
-
nextStep: "
|
|
117
|
+
nextStep: "请提供更完整的日志、堆栈、产品/版本、目标单据/表单和最近代码改动。",
|
|
118
118
|
});
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -127,8 +127,8 @@ export function formatDebugFindings(findings: DebugFinding[]): string {
|
|
|
127
127
|
[
|
|
128
128
|
`[${finding.severity}] ${finding.rule}`,
|
|
129
129
|
finding.message,
|
|
130
|
-
finding.evidence ?
|
|
131
|
-
|
|
130
|
+
finding.evidence ? `证据:${finding.evidence}` : undefined,
|
|
131
|
+
`下一步:${finding.nextStep}`,
|
|
132
132
|
]
|
|
133
133
|
.filter(Boolean)
|
|
134
134
|
.join("\n"),
|
|
@@ -138,7 +138,7 @@ export function formatDebugFindings(findings: DebugFinding[]): string {
|
|
|
138
138
|
|
|
139
139
|
export function readDebugInput(cwd: string, text?: string, path?: string): { source: string; text: string } {
|
|
140
140
|
if (text) return { source: "inline", text };
|
|
141
|
-
if (!path) throw new Error("
|
|
141
|
+
if (!path) throw new Error("kd_debug 需要提供 text 或 path。");
|
|
142
142
|
|
|
143
143
|
const fullPath = resolveWorkspacePath(cwd, path);
|
|
144
144
|
return { source: path, text: readFileSync(fullPath, "utf8") };
|
|
@@ -152,39 +152,39 @@ function planJavaBuild(cwd: string, profile: ProductProfile, target?: string): B
|
|
|
152
152
|
const mvnw = join(cwd, "mvnw");
|
|
153
153
|
|
|
154
154
|
if (existsSync(gradlewBat)) {
|
|
155
|
-
return buildPlan(profile, gradlewBat, [task], cwd, "
|
|
155
|
+
return buildPlan(profile, gradlewBat, [task], cwd, "检测到 Cosmic 家族 Java 项目的 Gradle wrapper。");
|
|
156
156
|
}
|
|
157
157
|
if (existsSync(gradlew)) {
|
|
158
|
-
return buildPlan(profile, gradlew, [task], cwd, "
|
|
158
|
+
return buildPlan(profile, gradlew, [task], cwd, "检测到 Cosmic 家族 Java 项目的 Gradle wrapper。");
|
|
159
159
|
}
|
|
160
160
|
if (existsSync(mvnwBat)) {
|
|
161
|
-
return buildPlan(profile, mvnwBat, [target ?? "test"], cwd, "
|
|
161
|
+
return buildPlan(profile, mvnwBat, [target ?? "test"], cwd, "检测到 Cosmic 家族 Java 项目的 Maven wrapper。");
|
|
162
162
|
}
|
|
163
163
|
if (existsSync(mvnw)) {
|
|
164
|
-
return buildPlan(profile, mvnw, [target ?? "test"], cwd, "
|
|
164
|
+
return buildPlan(profile, mvnw, [target ?? "test"], cwd, "检测到 Cosmic 家族 Java 项目的 Maven wrapper。");
|
|
165
165
|
}
|
|
166
166
|
if (existsSync(join(cwd, "build.gradle")) || existsSync(join(cwd, "build.gradle.kts"))) {
|
|
167
|
-
return buildPlan(profile, "gradle", [task], cwd, "
|
|
167
|
+
return buildPlan(profile, "gradle", [task], cwd, "检测到 Gradle 构建文件,但未找到 wrapper。");
|
|
168
168
|
}
|
|
169
169
|
if (existsSync(join(cwd, "pom.xml"))) {
|
|
170
|
-
return buildPlan(profile, "mvn", [target ?? "test"], cwd, "
|
|
170
|
+
return buildPlan(profile, "mvn", [target ?? "test"], cwd, "检测到 Maven pom.xml,但未找到 wrapper。");
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
throw new Error("
|
|
173
|
+
throw new Error("未找到 Java 构建入口。期望在工作区根目录看到 gradlew、build.gradle、mvnw 或 pom.xml。");
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
function planCsharpBuild(cwd: string, profile: ProductProfile, target?: string): BuildPlan {
|
|
177
177
|
const explicit = target ? resolveWorkspacePath(cwd, target) : undefined;
|
|
178
178
|
if (explicit && existsSync(explicit)) {
|
|
179
|
-
return buildPlan(profile, "dotnet", ["build", explicit], cwd, "
|
|
179
|
+
return buildPlan(profile, "dotnet", ["build", explicit], cwd, "使用显式指定的 C# 项目或解决方案。");
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
const candidates = readdirSync(cwd).filter((entry) => [".sln", ".csproj"].includes(extname(entry).toLowerCase()));
|
|
183
183
|
if (candidates.length > 0) {
|
|
184
|
-
return buildPlan(profile, "dotnet", ["build", join(cwd, candidates[0])], cwd,
|
|
184
|
+
return buildPlan(profile, "dotnet", ["build", join(cwd, candidates[0])], cwd, `检测到企业版 C# 项目文件 ${candidates[0]}。`);
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
throw new Error("
|
|
187
|
+
throw new Error("未找到 C# 构建入口。期望在工作区根目录看到 .sln 或 .csproj,或通过 target 指定路径。");
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
function buildPlan(profile: ProductProfile, executable: string, args: string[], cwd: string, reason: string): BuildPlan {
|
|
@@ -201,7 +201,7 @@ function buildPlan(profile: ProductProfile, executable: string, args: string[],
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
function formatBuildPlan(plan: BuildPlan): string {
|
|
204
|
-
return [
|
|
204
|
+
return [`产品:${plan.profile}`, `原因:${plan.reason}`, `命令:${plan.command.display}`].join("\n");
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
function formatArg(value: string): string {
|