kcode-pi 0.1.15 → 0.1.16
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 +4 -0
- package/extensions/kingdee-harness.ts +9 -2
- package/extensions/kingdee-tools.ts +35 -6
- package/package.json +1 -1
- package/prompts/kd-execute.md +1 -1
- package/prompts/kd-plan.md +1 -1
- package/src/harness/artifacts.ts +6 -1
- package/src/harness/gates.ts +19 -9
- package/src/harness/sdk-policy.ts +37 -0
package/README.md
CHANGED
|
@@ -397,6 +397,8 @@ ship 汇总变更、验证证据、风险和后续事项
|
|
|
397
397
|
- 进入 `execute` 前必须已有 `PLAN.md` 和必要证据。
|
|
398
398
|
- `PLAN.md` 必须包含 `## 执行步骤`,并用 `- [ ] STEP-001:...` 格式拆分可跟踪步骤。
|
|
399
399
|
- `PLAN.md` 必须包含 `## TDD / 红绿检查`,声明红灯证据、绿灯证据和测试/检查命令。
|
|
400
|
+
- Java/C# 产品代码进入 `execute` 前必须已有 `evidence/sdk-signature.md`。该证据由 `kd_sdk_signature` 成功查证当前项目真实 SDK jar/dll 后自动写入。
|
|
401
|
+
- LLM 禁止凭记忆、模型知识或随包知识库猜 SDK 方法签名。`kd_search`、`kd_cosmic_api` 只能作为线索;最终签名事实必须来自 `kd_sdk_signature`、当前项目构建输出、项目源码封装或官方元数据。
|
|
400
402
|
- 红绿检查不等于必须写 JUnit。金蝶项目不要为了满足门禁引入额外 jar 或测试框架。
|
|
401
403
|
- 写生产源码前必须已有 `evidence/tdd-red.md`,内容可以是 API/基类/方法签名检查、元数据检查、编译检查、既有测试框架或外部接口最小验证的失败输出。
|
|
402
404
|
- 进入 `execute` 后,只允许写入 `PLAN.md` 明确列出的源码文件;如果临时发现要改新文件,必须先回到 plan 更新 `PLAN.md`。
|
|
@@ -425,6 +427,7 @@ ship 汇总变更、验证证据、风险和后续事项
|
|
|
425
427
|
- 绿灯证据:evidence/tdd-green.md
|
|
426
428
|
- 红绿检查命令或工具:kd_sdk_signature / kd_cosmic_metadata / kd_check / build
|
|
427
429
|
- 不要为了满足门禁引入第三方测试 jar 或测试框架。
|
|
430
|
+
- Java/C# SDK 签名证据:evidence/sdk-signature.md
|
|
428
431
|
```
|
|
429
432
|
|
|
430
433
|
示例执行记录:
|
|
@@ -491,6 +494,7 @@ kd_debug 分析金蝶日志和堆栈
|
|
|
491
494
|
- `kd_cosmic_metadata` 使用统一路由 API 查询真实单据/表单元数据,并在当前项目 `.pi/kd/official-skills/` 下维护 JSON 缓存。
|
|
492
495
|
- `kd_sdk_signature` 优先从当前业务项目的实际 SDK jar/dll 中读取类型和方法签名。Cosmic Java 依赖 `javap`,Enterprise C# 依赖 PowerShell 读取 DLL 元数据。
|
|
493
496
|
- `kd_cosmic_api` 查询随包金蝶知识库,只作为 API 线索和兜底;精确方法签名优先使用 `kd_sdk_signature`、当前项目 SDK 或编译输出确认。
|
|
497
|
+
- 当前项目存在 active run 时,`kd_sdk_signature` 成功后会自动写入 `.pi/kd/runs/<run-id>/evidence/sdk-signature.md`。Java/C# 产品代码缺少这份证据时,KCode 不允许进入 `execute` 或写生产源码。
|
|
494
498
|
|
|
495
499
|
示例:
|
|
496
500
|
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "../src/harness/state.ts";
|
|
19
19
|
import { readArtifact } from "../src/harness/artifacts.ts";
|
|
20
20
|
import { flagshipWriteBlockReason, isSourceLikePath, planWriteBlockReason } from "../src/harness/path-policy.ts";
|
|
21
|
+
import { sdkSignatureProductionWriteBlockReason } from "../src/harness/sdk-policy.ts";
|
|
21
22
|
import { tddProductionWriteBlockReason } from "../src/harness/tdd-policy.ts";
|
|
22
23
|
import { readProjectContext } from "../src/context/project-context.ts";
|
|
23
24
|
import { windowsPathHint } from "../src/platform/path.ts";
|
|
@@ -123,9 +124,11 @@ function workflowPromptForRun(cwd: string, run: NonNullable<ReturnType<typeof re
|
|
|
123
124
|
"",
|
|
124
125
|
phaseGuidance[run.phase],
|
|
125
126
|
"必须先理解当前业务项目已有目录、模块、包名、基类和本地封装,再决定文件位置和实现方式。",
|
|
127
|
+
"禁止凭记忆、模型知识或随包知识库直接编写 SDK 方法调用。Java/C# 代码中出现的 SDK 类、方法、构造器、枚举和属性,必须来自 kd_sdk_signature 对当前项目 jar/dll 的成功结果、项目构建输出或官方元数据证据。",
|
|
128
|
+
"kd_search、kd_cosmic_api 和随包知识只能用于找线索;没有 evidence/sdk-signature.md 或明确构建证据时,不得进入 execute,也不得写生产源码。",
|
|
126
129
|
"路径规则:在 Windows 工作区内,优先使用项目相对路径;如需绝对路径必须使用 `D:\\...` 这类 Windows 路径,禁止把路径改写成 `/mnt/d/...`、`/d/...` 等 WSL/MSYS 风格路径。",
|
|
127
130
|
"execute 阶段只能写 PLAN.md 明确列出的源码文件;如果目标文件不在计划内,必须先回到 plan 更新 PLAN.md。",
|
|
128
|
-
"写生产源码前必须先有红灯证据 evidence/tdd-red.md
|
|
131
|
+
"写生产源码前必须先有红灯证据 evidence/tdd-red.md;Java/C# 还必须有 SDK 签名证据 evidence/sdk-signature.md。红绿证据可以是 kd_sdk_signature 本地 SDK 签名、API/基类/方法签名、元数据、编译、既有测试框架或外部接口最小验证,不要为了测试引入额外 jar。",
|
|
129
132
|
].join("\n");
|
|
130
133
|
}
|
|
131
134
|
|
|
@@ -158,7 +161,11 @@ function codeWriteBlockReason(cwd: string, path: string | undefined): string | u
|
|
|
158
161
|
return `当前 KCode 阶段是 ${run.phase},不能写产品代码。请先完成 discuss -> spec -> plan 并进入 execute。`;
|
|
159
162
|
}
|
|
160
163
|
|
|
161
|
-
return
|
|
164
|
+
return (
|
|
165
|
+
sdkSignatureProductionWriteBlockReason(cwd, run, path) ??
|
|
166
|
+
tddProductionWriteBlockReason(cwd, run, path) ??
|
|
167
|
+
planWriteBlockReason(cwd, run, path, readArtifact(cwd, run, "plan") ?? "")
|
|
168
|
+
);
|
|
162
169
|
}
|
|
163
170
|
|
|
164
171
|
const kdPlanStatusTool = defineTool({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dirname, join } from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
3
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { Type } from "@earendil-works/pi-ai";
|
|
5
5
|
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { formatSearchResults, formatTableSchema } from "../src/knowledge/format.ts";
|
|
@@ -22,6 +22,9 @@ import {
|
|
|
22
22
|
writeOfficialEvidence,
|
|
23
23
|
} from "../src/official/kingdee-skills.ts";
|
|
24
24
|
import { resolveWorkspacePath } from "../src/platform/path.ts";
|
|
25
|
+
import { readActiveRun } from "../src/harness/state.ts";
|
|
26
|
+
import { runArtifactPath } from "../src/harness/paths.ts";
|
|
27
|
+
import { SDK_SIGNATURE_EVIDENCE } from "../src/harness/sdk-policy.ts";
|
|
25
28
|
|
|
26
29
|
const extensionDir = dirname(fileURLToPath(import.meta.url));
|
|
27
30
|
const knowledgePath = join(extensionDir, "..", "knowledge");
|
|
@@ -329,9 +332,11 @@ const kdSdkSignatureTool = defineTool({
|
|
|
329
332
|
path: params.path,
|
|
330
333
|
limit: params.limit,
|
|
331
334
|
});
|
|
335
|
+
const text = formatSdkSignatureResult(result);
|
|
336
|
+
const evidencePath = result.exitCode === 0 ? writeSdkSignatureEvidence(ctx.cwd, text) : undefined;
|
|
332
337
|
return {
|
|
333
|
-
content: [{ type: "text", text:
|
|
334
|
-
details: { product: profile.product, ...result },
|
|
338
|
+
content: [{ type: "text", text: evidencePath ? `${text}\n\n已写入 SDK 签名证据:${evidencePath}` : text }],
|
|
339
|
+
details: { product: profile.product, evidencePath, ...result },
|
|
335
340
|
};
|
|
336
341
|
},
|
|
337
342
|
});
|
|
@@ -396,9 +401,9 @@ const kdDebugTool = defineTool({
|
|
|
396
401
|
return {
|
|
397
402
|
content: [
|
|
398
403
|
{
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
404
|
+
type: "text",
|
|
405
|
+
text: `来源:${input.source}\n产品:${profile.product}/${profile.platform}/${profile.techStack}\n\n${formatDebugFindings(findings)}`,
|
|
406
|
+
},
|
|
402
407
|
],
|
|
403
408
|
details: { source: input.source, product: profile.product, findings },
|
|
404
409
|
};
|
|
@@ -423,3 +428,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
423
428
|
pi.registerTool(kdBuildTool);
|
|
424
429
|
pi.registerTool(kdDebugTool);
|
|
425
430
|
}
|
|
431
|
+
|
|
432
|
+
function writeSdkSignatureEvidence(cwd: string, content: string): string | undefined {
|
|
433
|
+
const run = readActiveRun(cwd);
|
|
434
|
+
if (!run) return undefined;
|
|
435
|
+
|
|
436
|
+
const path = runArtifactPath(cwd, run, SDK_SIGNATURE_EVIDENCE);
|
|
437
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
438
|
+
writeFileSync(
|
|
439
|
+
path,
|
|
440
|
+
[
|
|
441
|
+
"# SDK 签名证据",
|
|
442
|
+
"",
|
|
443
|
+
`- 生成时间:${new Date().toISOString()}`,
|
|
444
|
+
"- 来源:kd_sdk_signature 当前项目本地 SDK jar/dll",
|
|
445
|
+
"",
|
|
446
|
+
"```text",
|
|
447
|
+
content.trim(),
|
|
448
|
+
"```",
|
|
449
|
+
"",
|
|
450
|
+
].join("\n"),
|
|
451
|
+
"utf8",
|
|
452
|
+
);
|
|
453
|
+
return path;
|
|
454
|
+
}
|
package/package.json
CHANGED
package/prompts/kd-execute.md
CHANGED
|
@@ -4,7 +4,7 @@ description: 在 Harness 门禁约束下执行当前金蝶实施计划。
|
|
|
4
4
|
|
|
5
5
|
使用 `kd-execute` skill。
|
|
6
6
|
|
|
7
|
-
编辑代码前,先使用 `kd_plan_status` 检查当前 run。如果缺少 `PLAN.md` 或门禁被阻塞,必须停止并说明缺少的文档或证据。通过后只实现 `PLAN.md` 批准的内容,并更新 `EXECUTION.md
|
|
7
|
+
编辑代码前,先使用 `kd_plan_status` 检查当前 run。如果缺少 `PLAN.md`、`evidence/sdk-signature.md` 或门禁被阻塞,必须停止并说明缺少的文档或证据。通过后只实现 `PLAN.md` 批准的内容,并更新 `EXECUTION.md`。禁止凭记忆、模型知识或随包知识库猜 SDK 方法签名。
|
|
8
8
|
|
|
9
9
|
用户补充说明:
|
|
10
10
|
|
package/prompts/kd-plan.md
CHANGED
|
@@ -4,7 +4,7 @@ description: 为当前金蝶 Harness run 编写实施计划。
|
|
|
4
4
|
|
|
5
5
|
使用 `kd-plan` skill。
|
|
6
6
|
|
|
7
|
-
读取 `CONTEXT.md` 和 `SPEC.md`,编写或更新 `PLAN.md`。必须包含已检查的项目结构、需要查看的文件、预计修改的真实路径、必须查证的金蝶 API
|
|
7
|
+
读取 `CONTEXT.md` 和 `SPEC.md`,编写或更新 `PLAN.md`。必须包含已检查的项目结构、需要查看的文件、预计修改的真实路径、必须查证的金蝶 API/元数据、SDK 签名证据、验证命令和回滚说明。Java/C# SDK 方法签名必须来自 `kd_sdk_signature` 当前项目 jar/dll、项目构建输出或官方元数据,不能凭记忆猜。
|
|
8
8
|
|
|
9
9
|
用户补充说明:
|
|
10
10
|
|
package/src/harness/artifacts.ts
CHANGED
|
@@ -83,6 +83,10 @@ export function defaultArtifactContent(phase: KdPhase, goal?: string, profile?:
|
|
|
83
83
|
"",
|
|
84
84
|
"## 必需的金蝶查证项",
|
|
85
85
|
"",
|
|
86
|
+
"- Java/C# 代码涉及 SDK 类、方法、构造器、枚举、属性时,必须先用 kd_sdk_signature 或项目构建输出确认真实签名。",
|
|
87
|
+
"- 知识库搜索、随包 Cosmic API 查询只能作为线索,不能作为最终方法签名事实。",
|
|
88
|
+
"- SDK 签名证据:evidence/sdk-signature.md",
|
|
89
|
+
"",
|
|
86
90
|
"## 执行步骤",
|
|
87
91
|
"",
|
|
88
92
|
"- [ ] STEP-001:检查现有目标文件,确认精确修改位置。",
|
|
@@ -96,9 +100,10 @@ export function defaultArtifactContent(phase: KdPhase, goal?: string, profile?:
|
|
|
96
100
|
"- 红灯证据:evidence/tdd-red.md",
|
|
97
101
|
"- 绿灯证据:evidence/tdd-green.md",
|
|
98
102
|
"- 红绿检查命令或工具:未知",
|
|
99
|
-
"-
|
|
103
|
+
"- 允许的检查:本地 SDK 签名查证、官方 API/基类/方法查证、元数据查证、kd_check、构建/编译输出、项目已有测试框架、外部接口最小验证。",
|
|
100
104
|
"- 不要为了满足门禁引入第三方测试 jar 或框架。",
|
|
101
105
|
"- 如果无法自动化测试,记录一个产品相关、实现前应失败且实现后应通过的检查。",
|
|
106
|
+
"- 禁止凭记忆或随包知识库猜 SDK 方法签名;签名事实必须来自当前项目 jar/dll、构建输出或官方元数据。",
|
|
102
107
|
"",
|
|
103
108
|
"## 验证命令",
|
|
104
109
|
"",
|
package/src/harness/gates.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import type { ActiveRun, GateResult, KdPhase } from "./types.ts";
|
|
2
4
|
import { PHASE_ARTIFACTS, PHASE_ORDER } from "./types.ts";
|
|
3
5
|
import { artifactExists, readArtifact } from "./artifacts.ts";
|
|
@@ -5,6 +7,8 @@ import { isKnownProduct } from "../product/profile.ts";
|
|
|
5
7
|
import { flagshipPlanBlockReason } from "./path-policy.ts";
|
|
6
8
|
import { executionStepsBlockReason, planStepsBlockReason } from "./plan-steps.ts";
|
|
7
9
|
import { tddPlanBlockReason, tddVerifyBlockReason } from "./tdd-policy.ts";
|
|
10
|
+
import { SDK_SIGNATURE_EVIDENCE, hasValidSdkSignatureEvidence, requiresSdkSignatureEvidence } from "./sdk-policy.ts";
|
|
11
|
+
import { runRoot } from "./paths.ts";
|
|
8
12
|
|
|
9
13
|
const REQUIRED_MARKERS: Partial<Record<KdPhase, string[]>> = {
|
|
10
14
|
plan: ["## 验证命令"],
|
|
@@ -129,7 +133,7 @@ function inspectMarkers(cwd: string, run: ActiveRun, phase: KdPhase): string | u
|
|
|
129
133
|
function inspectEvidence(cwd: string, run: ActiveRun, phase: KdPhase): string | undefined {
|
|
130
134
|
const missing = missingEvidenceForPhase(cwd, run, phase);
|
|
131
135
|
if (missing.length === 0) return undefined;
|
|
132
|
-
return
|
|
136
|
+
return `缺少必需证据:${missing.join(", ")}`;
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
function isCosmicRun(run: ActiveRun): boolean {
|
|
@@ -137,22 +141,23 @@ function isCosmicRun(run: ActiveRun): boolean {
|
|
|
137
141
|
}
|
|
138
142
|
|
|
139
143
|
function missingEvidenceForPhase(cwd: string, run: ActiveRun, phase: KdPhase): string[] {
|
|
140
|
-
return requiredEvidenceForPhase(cwd, run, phase).filter((artifact) => !
|
|
144
|
+
return requiredEvidenceForPhase(cwd, run, phase).filter((artifact) => !evidenceArtifactSatisfied(cwd, run, artifact));
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
function requiredEvidenceForPhase(cwd: string, run: ActiveRun, phase: KdPhase): string[] {
|
|
144
|
-
if (!isCosmicRun(run)) return [];
|
|
145
|
-
|
|
146
148
|
const required = new Set<string>();
|
|
147
149
|
const phaseIndex = PHASE_ORDER.indexOf(phase);
|
|
148
150
|
|
|
149
151
|
if (phaseIndex >= PHASE_ORDER.indexOf("execute")) {
|
|
150
|
-
required.add(
|
|
151
|
-
if (
|
|
152
|
-
|
|
152
|
+
if (requiresSdkSignatureEvidence(run)) required.add(SDK_SIGNATURE_EVIDENCE);
|
|
153
|
+
if (isCosmicRun(run)) {
|
|
154
|
+
required.add(COSMIC_CONFIG_EVIDENCE);
|
|
155
|
+
if (planHasMetadataRequirement(cwd, run)) required.add(COSMIC_METADATA_EVIDENCE);
|
|
156
|
+
if (planHasApiRequirement(cwd, run)) required.add(COSMIC_API_EVIDENCE);
|
|
157
|
+
}
|
|
153
158
|
}
|
|
154
159
|
|
|
155
|
-
if (phaseIndex >= PHASE_ORDER.indexOf("ship") && runHasKsqlDelivery(cwd, run)) {
|
|
160
|
+
if (isCosmicRun(run) && phaseIndex >= PHASE_ORDER.indexOf("ship") && runHasKsqlDelivery(cwd, run)) {
|
|
156
161
|
required.add(COSMIC_METADATA_EVIDENCE);
|
|
157
162
|
required.add(KSQL_LINT_EVIDENCE);
|
|
158
163
|
}
|
|
@@ -160,6 +165,11 @@ function requiredEvidenceForPhase(cwd: string, run: ActiveRun, phase: KdPhase):
|
|
|
160
165
|
return [...required];
|
|
161
166
|
}
|
|
162
167
|
|
|
168
|
+
function evidenceArtifactSatisfied(cwd: string, run: ActiveRun, artifact: string): boolean {
|
|
169
|
+
if (artifact === SDK_SIGNATURE_EVIDENCE) return hasValidSdkSignatureEvidence(cwd, run);
|
|
170
|
+
return existsSync(join(runRoot(cwd, run), artifact));
|
|
171
|
+
}
|
|
172
|
+
|
|
163
173
|
function planHasMetadataRequirement(cwd: string, run: ActiveRun): boolean {
|
|
164
174
|
const plan = readArtifact(cwd, run, "plan") ?? "";
|
|
165
175
|
return /kd_cosmic_metadata|cosmic-metadata|cosmic-metadata\.json|metadata evidence|字段元数据证据|元数据证据/i.test(plan);
|
|
@@ -167,7 +177,7 @@ function planHasMetadataRequirement(cwd: string, run: ActiveRun): boolean {
|
|
|
167
177
|
|
|
168
178
|
function planHasApiRequirement(cwd: string, run: ActiveRun): boolean {
|
|
169
179
|
const plan = readArtifact(cwd, run, "plan") ?? "";
|
|
170
|
-
return /
|
|
180
|
+
return /kd_cosmic_api|cosmic-api|cosmic-api\.txt/i.test(plan);
|
|
171
181
|
}
|
|
172
182
|
|
|
173
183
|
function runHasKsqlDelivery(cwd: string, run: ActiveRun): boolean {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
3
|
+
import type { ActiveRun } from "./types.ts";
|
|
4
|
+
import { runRoot } from "./paths.ts";
|
|
5
|
+
import { isSourceLikePath } from "./path-policy.ts";
|
|
6
|
+
|
|
7
|
+
export const SDK_SIGNATURE_EVIDENCE = "evidence/sdk-signature.md";
|
|
8
|
+
|
|
9
|
+
export function requiresSdkSignatureEvidence(run: ActiveRun): boolean {
|
|
10
|
+
const language = run.profile?.language;
|
|
11
|
+
return language === "java" || language === "csharp";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function hasValidSdkSignatureEvidence(cwd: string, run: ActiveRun): boolean {
|
|
15
|
+
if (!requiresSdkSignatureEvidence(run)) return true;
|
|
16
|
+
const path = join(runRoot(cwd, run), SDK_SIGNATURE_EVIDENCE);
|
|
17
|
+
if (!existsSync(path)) return false;
|
|
18
|
+
|
|
19
|
+
const content = readFileSync(path, "utf8");
|
|
20
|
+
return /(退出码|Exit)\s*[::]\s*0/i.test(content) && /(来源|Sources?)\s*[::]/i.test(content);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function sdkSignatureProductionWriteBlockReason(cwd: string, run: ActiveRun | undefined, path: string | undefined): string | undefined {
|
|
24
|
+
if (!run || run.phase !== "execute") return undefined;
|
|
25
|
+
if (!requiresSdkSignatureEvidence(run)) return undefined;
|
|
26
|
+
if (!path || !isSourceLikePath(path)) return undefined;
|
|
27
|
+
|
|
28
|
+
const normalized = normalizeRelativePath(cwd && isAbsolute(path) ? relative(cwd, path) : path);
|
|
29
|
+
if (normalized.startsWith(".pi/")) return undefined;
|
|
30
|
+
if (hasValidSdkSignatureEvidence(cwd, run)) return undefined;
|
|
31
|
+
|
|
32
|
+
return `不能写生产源码 ${normalized}:缺少本地 SDK 签名证据 ${SDK_SIGNATURE_EVIDENCE}。请先用 kd_sdk_signature 从当前项目真实 jar/dll 查证类、方法、构造器或属性签名;禁止凭记忆猜 SDK API。`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeRelativePath(path: string): string {
|
|
36
|
+
return path.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
37
|
+
}
|