kcode-pi 0.1.31 → 0.1.35
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 +10 -10
- package/dist/cli/kcode.js +3 -3
- package/dist/context/project-context.js +3 -3
- package/docs/CHANGELOG.md +28 -2
- package/docs/COMMAND_REFERENCE.md +10 -10
- package/docs/DEVELOPMENT.md +3 -3
- package/docs/EVIDENCE_AND_GATES.md +8 -8
- package/docs/HARNESS_WORKFLOW.md +12 -12
- package/docs/KCODE_DISTRIBUTION.md +7 -7
- package/docs/PRODUCT_PROFILE.md +9 -9
- package/docs/TROUBLESHOOTING.md +8 -8
- package/docs/USER_GUIDE.md +10 -10
- package/extensions/kingdee-harness.ts +61 -48
- package/extensions/kingdee-header.ts +3 -3
- package/extensions/kingdee-subagents.ts +1 -1
- package/extensions/kingdee-tools.ts +171 -43
- package/package.json +6 -2
- package/src/cli/kcode.ts +3 -3
- package/src/context/project-context.ts +3 -3
- package/src/harness/artifacts.ts +6 -7
- package/src/harness/data-source-policy.ts +302 -0
- package/src/harness/delegation.ts +23 -23
- package/src/harness/gates.ts +26 -1
- package/src/harness/messages.ts +70 -11
- package/src/harness/path-policy.ts +1 -0
- package/src/harness/plan-steps.ts +3 -3
- package/src/harness/prompt-policy.ts +196 -0
- package/src/harness/prompt.ts +8 -16
- package/src/harness/repair.ts +2 -2
- package/src/harness/state.ts +58 -1
- package/src/official/kingdee-skills.ts +4 -4
- package/src/product/profile.ts +2 -2
- package/src/rules/checker.ts +27 -27
- package/src/tools/build-debug.ts +5 -5
- package/src/tools/sdk-signature.ts +4 -4
package/src/harness/messages.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import type { KdPhase } from "./types.ts";
|
|
2
2
|
import { PHASE_ARTIFACTS } from "./types.ts";
|
|
3
|
+
import {
|
|
4
|
+
DATA_SOURCE_CONTEXT_FIELDS,
|
|
5
|
+
IMPLEMENTATION_CONTRACT_FIELDS,
|
|
6
|
+
INTEGRATION_CONTEXT_FIELDS,
|
|
7
|
+
fieldLabels,
|
|
8
|
+
questionForMissingLabel,
|
|
9
|
+
} from "./prompt-policy.ts";
|
|
3
10
|
|
|
4
11
|
export function unknownProductReason(declaration: boolean | undefined): string {
|
|
5
12
|
if (declaration === undefined) {
|
|
6
|
-
return "不能进入 execute:产品画像未知,且 PLAN.md 未明确声明是否涉及产品实现、构建、元数据或 SDK
|
|
13
|
+
return "不能进入 execute:产品画像未知,且 PLAN.md 未明确声明是否涉及产品实现、构建、元数据或 SDK 查证。下一步:根据当前需求和项目计划判断范围;涉及产品实现时执行 /kd-product <flagship|xinghan|cangqiong|enterprise>;不涉及时在 PLAN.md 的“产品实现范围”写明“不涉及”及依据。";
|
|
7
14
|
}
|
|
8
|
-
return "不能进入 execute:PLAN.md 声明涉及产品实现、构建、元数据或 SDK 查证,但产品画像未知。下一步:根据需求、计划或用户回答确认产品,然后执行 /kd-product <flagship|xinghan|cangqiong|enterprise
|
|
15
|
+
return "不能进入 execute:PLAN.md 声明涉及产品实现、构建、元数据或 SDK 查证,但产品画像未知。下一步:根据需求、计划或用户回答确认产品,然后执行 /kd-product <flagship|xinghan|cangqiong|enterprise>;无法判断时使用 kd_question 登记一个最阻塞的产品确认问题。";
|
|
9
16
|
}
|
|
10
17
|
|
|
11
18
|
export function unknownRiskReason(): string {
|
|
@@ -15,10 +22,42 @@ export function unknownRiskReason(): string {
|
|
|
15
22
|
export function openQuestionsReason(questions: Array<{ id: string; question: string }>): string {
|
|
16
23
|
return [
|
|
17
24
|
`存在未回答的阻断问题:${questions.map((question) => `${question.id} ${question.question}`).join(";")}`,
|
|
18
|
-
"
|
|
25
|
+
"下一步:获取用户答案,然后用 kd_question action=answer id=<问题编号> answer=<用户答案> 记录。",
|
|
19
26
|
].join("。");
|
|
20
27
|
}
|
|
21
28
|
|
|
29
|
+
export function repairBlockedReason(attempts: number, maxAttempts: number, evidence?: string): string {
|
|
30
|
+
const evidenceText = evidence ? `,最近失败证据:${evidence}` : "";
|
|
31
|
+
return `自动修复已停止:验证失败 ${attempts}/${maxAttempts}${evidenceText}。下一步:回到 plan 调整范围,或重新创建/回答修复问题选择“继续修复”。`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function dataSourcePlanBlockedReason(missing: string[]): string {
|
|
35
|
+
return [
|
|
36
|
+
`不能进入 execute:业务数据源上下文不完整,缺少 ${missing.join("、")}。`,
|
|
37
|
+
`下一步:停止编码,使用 kd_question 逐项确认 ${fieldLabels(DATA_SOURCE_CONTEXT_FIELDS).join("、")}。`,
|
|
38
|
+
nextQuestionInstruction(missing),
|
|
39
|
+
"API 文档只能作为调用线索,不能替代这些业务事实。",
|
|
40
|
+
].filter(Boolean).join("");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function integrationPlanBlockedReason(missing: string[]): string {
|
|
44
|
+
return [
|
|
45
|
+
`不能进入 execute:第三方对接上下文不完整,缺少 ${missing.join("、")}。`,
|
|
46
|
+
`下一步:停止编码,使用 kd_question 逐项确认 ${fieldLabels(INTEGRATION_CONTEXT_FIELDS).join("、")}。`,
|
|
47
|
+
nextQuestionInstruction(missing),
|
|
48
|
+
"缺少这些信息时禁止生成模板代码。",
|
|
49
|
+
].filter(Boolean).join("");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function implementationPlanBlockedReason(missing: string[]): string {
|
|
53
|
+
return [
|
|
54
|
+
`不能进入 execute:实现就绪信息不完整,缺少 ${missing.join("、")}。`,
|
|
55
|
+
`下一步:禁止生成模板代码;使用 kd_question 按最阻塞项逐个确认 ${fieldLabels(IMPLEMENTATION_CONTRACT_FIELDS).join("、")}。`,
|
|
56
|
+
nextQuestionInstruction(missing),
|
|
57
|
+
"这些字段是所有插件、服务和集成实现的通用输入,不依赖具体业务场景枚举。",
|
|
58
|
+
].filter(Boolean).join("");
|
|
59
|
+
}
|
|
60
|
+
|
|
22
61
|
export function missingMarkerReason(phase: KdPhase, missing: string[]): string {
|
|
23
62
|
return `${PHASE_ARTIFACTS[phase]} 缺少必需章节:${missing.join(", ")}。下一步:更新 ${PHASE_ARTIFACTS[phase]},补齐这些章节并写入真实内容。`;
|
|
24
63
|
}
|
|
@@ -35,7 +74,7 @@ export function missingForTargetReason(target: KdPhase, artifacts: string[]): st
|
|
|
35
74
|
const documents = artifacts.filter((artifact) => !artifact.startsWith("evidence/"));
|
|
36
75
|
const actions: string[] = [];
|
|
37
76
|
if (documents.length > 0) {
|
|
38
|
-
actions.push(
|
|
77
|
+
actions.push(`补齐阶段文档 ${documents.join(", ")};可使用 /kd-artifact 创建模板后填入真实分析、计划或验证内容`);
|
|
39
78
|
}
|
|
40
79
|
if (evidence.length > 0) {
|
|
41
80
|
actions.push(evidenceAction(evidence));
|
|
@@ -56,9 +95,11 @@ export function evidenceArtifactAction(artifact: string): string {
|
|
|
56
95
|
case "evidence/sdk-signature.md":
|
|
57
96
|
return "运行 kd_sdk_signature,从当前项目真实 SDK jar/dll 查证类、方法、构造器或属性签名,成功后自动写入 evidence/sdk-signature.md";
|
|
58
97
|
case "evidence/cosmic-config.txt":
|
|
59
|
-
return "运行 kd_cosmic_config 生成 evidence/cosmic-config.txt
|
|
98
|
+
return "运行 kd_cosmic_config 生成 evidence/cosmic-config.txt;项目没有 ok-cosmic.json 时使用 KCode 默认配置";
|
|
60
99
|
case "evidence/cosmic-metadata.json":
|
|
61
100
|
return "运行 kd_cosmic_metadata 查询目标表单/单据/字段元数据并生成 evidence/cosmic-metadata.json";
|
|
101
|
+
case "evidence/data-source.md":
|
|
102
|
+
return "写入真实数据源证据 evidence/data-source.md,至少包含目标 FormId/单据或表单、字段/实体标识、表或数据来源、验证来源和测试数据;禁止只引用 API 文档";
|
|
62
103
|
case "evidence/cosmic-api.txt":
|
|
63
104
|
return "运行 kd_cosmic_api 查询相关 Cosmic API 线索并生成 evidence/cosmic-api.txt,再用 kd_sdk_signature 或构建输出确认签名";
|
|
64
105
|
case "evidence/ksql-lint.txt":
|
|
@@ -69,7 +110,7 @@ export function evidenceArtifactAction(artifact: string): string {
|
|
|
69
110
|
}
|
|
70
111
|
|
|
71
112
|
export function flagshipWriteBlockedReason(path: string): string {
|
|
72
|
-
return `星空旗舰版代码必须跟随当前项目结构写入 code/ 下,不能写到 ${path}
|
|
113
|
+
return `星空旗舰版代码必须跟随当前项目结构写入 code/ 下,不能写到 ${path}。下一步:读取当前项目 code/ 下的真实模块结构,在 PLAN.md 记录目标源码路径,再把写入路径改为 code/... 下的项目相对路径。`;
|
|
73
114
|
}
|
|
74
115
|
|
|
75
116
|
export function planWriteBlockedReason(path: string): string {
|
|
@@ -77,11 +118,11 @@ export function planWriteBlockedReason(path: string): string {
|
|
|
77
118
|
}
|
|
78
119
|
|
|
79
120
|
export function flagshipPlanNeedsCodePathReason(): string {
|
|
80
|
-
return "不能进入 execute:星空旗舰版 PLAN.md
|
|
121
|
+
return "不能进入 execute:星空旗舰版 PLAN.md 必须记录当前项目 code/ 下的实际目标路径。下一步:列出 code/ 下模块,识别当前项目是按云、按应用还是不分模块组织,在 PLAN.md 写明真实目标文件。";
|
|
81
122
|
}
|
|
82
123
|
|
|
83
124
|
export function flagshipPlanNeedsSourcePathReason(): string {
|
|
84
|
-
return "不能进入 execute:PLAN.md
|
|
125
|
+
return "不能进入 execute:PLAN.md 必须记录已检查当前项目结构,并写明实际源码根或目标文件路径。下一步:读取构建文件和 src/lib/bin 等目录,确认源码根后写入 PLAN.md 的 ## 已检查的项目结构、## 目标源码根 / 路径 和 ## 允许修改的文件。";
|
|
85
126
|
}
|
|
86
127
|
|
|
87
128
|
export function tddPlanMissingReason(): string {
|
|
@@ -89,7 +130,7 @@ export function tddPlanMissingReason(): string {
|
|
|
89
130
|
}
|
|
90
131
|
|
|
91
132
|
export function tddProductionMissingRedReason(path: string, evidenceName: string): string {
|
|
92
|
-
return `不能写生产源码 ${path}:缺少红灯证据 ${evidenceName}
|
|
133
|
+
return `不能写生产源码 ${path}:缺少红灯证据 ${evidenceName}。下一步:运行一个实现前应失败的检查,例如 kd_sdk_signature 方法不存在检查、元数据/API 检查、编译检查、kd_check、项目已有测试或外部接口最小验证;把命令、非 0 Exit 或失败输出写入 evidence/tdd-red.md 后再写生产源码。`;
|
|
93
134
|
}
|
|
94
135
|
|
|
95
136
|
export function tddVerifyBlockedReason(reasons: string[]): string {
|
|
@@ -101,13 +142,31 @@ export function tddVerifyBlockedReason(reasons: string[]): string {
|
|
|
101
142
|
}
|
|
102
143
|
|
|
103
144
|
export function redEvidenceInvalidReason(evidenceName: string): string {
|
|
104
|
-
return `${evidenceName}
|
|
145
|
+
return `${evidenceName} 内容无效:必须包含真实失败输出或非 0 退出码`;
|
|
105
146
|
}
|
|
106
147
|
|
|
107
148
|
export function greenEvidenceInvalidReason(evidenceName: string): string {
|
|
108
|
-
return `${evidenceName}
|
|
149
|
+
return `${evidenceName} 内容无效:绿灯证据必须同时包含成功结论和 Exit: 0/退出码:0`;
|
|
109
150
|
}
|
|
110
151
|
|
|
111
152
|
export function sdkSignatureWriteBlockedReason(path: string, evidenceName: string): string {
|
|
112
153
|
return `不能写生产源码 ${path}:缺少本地 SDK 签名证据 ${evidenceName}。下一步:运行 kd_sdk_signature,从当前项目真实 jar/dll 查证即将使用的 SDK 类、方法、构造器或属性签名;成功生成 evidence/sdk-signature.md 后再写代码。禁止凭记忆或随包知识库猜 SDK API。`;
|
|
113
154
|
}
|
|
155
|
+
|
|
156
|
+
export function dataSourceWriteBlockedReason(path: string, evidenceName: string): string {
|
|
157
|
+
return `不能写生产源码 ${path}:缺少真实数据源/元数据证据 ${evidenceName}。下一步:确认目标 FormId、单据/表单、字段/实体、表或数据来源和测试数据;API 文档只能证明调用方式,不能证明当前业务数据源存在。`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function integrationWriteBlockedReason(path: string, missing: string[]): string {
|
|
161
|
+
return `不能写生产源码 ${path}:第三方对接上下文不完整,缺少 ${missing.join("、")}。下一步:补齐 ${fieldLabels(INTEGRATION_CONTEXT_FIELDS).join("、")}。`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function implementationWriteBlockedReason(path: string, missing: string[]): string {
|
|
165
|
+
return `不能写生产源码 ${path}:实现就绪信息不完整,缺少 ${missing.join("、")}。下一步:确认 ${fieldLabels(IMPLEMENTATION_CONTRACT_FIELDS).join("、")};信息不足时继续提问,禁止生成模板代码。`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function nextQuestionInstruction(missing: string[]): string | undefined {
|
|
169
|
+
const question = questionForMissingLabel(missing[0]);
|
|
170
|
+
if (!question) return undefined;
|
|
171
|
+
return `阻断问题命令:kd_question action=ask question="${question}" reason="${missing[0]} 缺失会阻塞实现。"。`;
|
|
172
|
+
}
|
|
@@ -22,12 +22,12 @@ export function parsePlanSteps(plan: string): PlanStep[] {
|
|
|
22
22
|
|
|
23
23
|
export function planStepsBlockReason(plan: string): string | undefined {
|
|
24
24
|
if (!/##\s*执行步骤/i.test(plan)) {
|
|
25
|
-
return "PLAN.md 缺少 ## 执行步骤。下一步:回到 plan,补充 `##
|
|
25
|
+
return "PLAN.md 缺少 ## 执行步骤。下一步:回到 plan,补充 `## 执行步骤`;使用 `- [ ] STEP-001:...` 拆分可跟踪步骤;每一步必须产生代码变更或 evidence。";
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const steps = parsePlanSteps(plan);
|
|
29
29
|
if (steps.length === 0) {
|
|
30
|
-
return "PLAN.md 没有可执行步骤。下一步:在 `## 执行步骤` 下使用 `- [ ] STEP-001:...`
|
|
30
|
+
return "PLAN.md 没有可执行步骤。下一步:在 `## 执行步骤` 下使用 `- [ ] STEP-001:...` 列出步骤;禁止用普通段落代替可勾选步骤。";
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const duplicate = firstDuplicate(steps.map((step) => step.id));
|
|
@@ -56,7 +56,7 @@ export function executionStepsBlockReason(cwd: string, run: ActiveRun, plan: str
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
if (missing.length > 0) {
|
|
59
|
-
return `不能进入 verify:EXECUTION.md 未完成计划步骤 ${missing.join(", ")}
|
|
59
|
+
return `不能进入 verify:EXECUTION.md 未完成计划步骤 ${missing.join(", ")}。下一步:执行缺失步骤;完成后在 EXECUTION.md 的 ## 步骤结果 中使用 \`- [x] STEP-###:已完成。证据:evidence/step-###.md\` 记录;未执行完成时禁止勾选。`;
|
|
60
60
|
}
|
|
61
61
|
if (missingEvidence.length > 0) {
|
|
62
62
|
return `不能进入 verify:步骤 ${missingEvidence.join(", ")} 缺少已落地的 evidence 文件。下一步:为每个步骤创建真实 evidence 文件,写入检查命令、结果、改动文件或验证输出,然后在 EXECUTION.md 对应步骤行引用该 evidence 路径。`;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type { KdPhase } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
export interface ContractField {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
aliases: string[];
|
|
7
|
+
question: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const IMPLEMENTATION_CONTRACT_FIELDS: ContractField[] = [
|
|
11
|
+
{
|
|
12
|
+
id: "trigger",
|
|
13
|
+
label: "触发入口或执行时机",
|
|
14
|
+
aliases: ["触发入口", "执行时机", "触发时机", "插件类型和事件", "入口事件"],
|
|
15
|
+
question: "该实现由哪个入口触发,执行时机是什么?",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "source",
|
|
19
|
+
label: "源对象/输入数据",
|
|
20
|
+
aliases: ["源对象", "源单", "输入数据", "数据来源", "取数来源"],
|
|
21
|
+
question: "实现读取的源对象或输入数据是什么?",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "target",
|
|
25
|
+
label: "目标对象/输出结果",
|
|
26
|
+
aliases: ["目标对象", "目标单据", "目标表单", "输出结果", "生成结果"],
|
|
27
|
+
question: "实现写入、生成或影响的目标对象是什么?",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "data-change",
|
|
31
|
+
label: "数据变化或字段映射",
|
|
32
|
+
aliases: ["数据变化", "字段映射", "字段对应", "字段改写", "赋值规则"],
|
|
33
|
+
question: "列出新增、修改、映射或保持不变的字段。",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "rules",
|
|
37
|
+
label: "业务规则和适用条件",
|
|
38
|
+
aliases: ["业务规则", "适用条件", "执行条件", "过滤条件", "前置条件"],
|
|
39
|
+
question: "该实现在哪些条件下执行,业务规则是什么?",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "failure",
|
|
43
|
+
label: "失败处理、回滚或人工处理方式",
|
|
44
|
+
aliases: ["失败处理", "异常处理", "错误处理", "回滚", "补偿", "人工处理"],
|
|
45
|
+
question: "失败、异常或重复执行时如何处理?",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "acceptance",
|
|
49
|
+
label: "验收样例或测试数据",
|
|
50
|
+
aliases: ["验收样例", "测试数据", "请求样例", "响应样例", "示例数据"],
|
|
51
|
+
question: "用于验收的样例数据和预期结果是什么?",
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
export const DATA_SOURCE_CONTEXT_FIELDS: ContractField[] = [
|
|
56
|
+
{
|
|
57
|
+
id: "target-form",
|
|
58
|
+
label: "目标 FormId/单据或表单标识",
|
|
59
|
+
aliases: ["目标 FormId", "FormId", "表单标识", "单据标识", "目标单据", "目标表单"],
|
|
60
|
+
question: "目标 FormId、单据或表单标识是什么?",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "plugin-hook",
|
|
64
|
+
label: "插件类型和触发事件",
|
|
65
|
+
aliases: ["插件类型", "触发事件", "生命周期", "挂载点", "插件类型和事件"],
|
|
66
|
+
question: "插件类型和触发事件是什么?",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "field-entity",
|
|
70
|
+
label: "字段/实体/分录标识",
|
|
71
|
+
aliases: ["字段标识", "实体标识", "分录标识", "单据体标识", "字段/实体标识"],
|
|
72
|
+
question: "涉及哪些字段、实体或分录标识?",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "data-access",
|
|
76
|
+
label: "数据读取写入方式",
|
|
77
|
+
aliases: ["数据读取写入方式", "读取方式", "写入方式", "数据访问", "取数方式"],
|
|
78
|
+
question: "数据通过表单模型、服务、SQL/KSQL 还是接口读取和写入?",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "sql-identifiers",
|
|
82
|
+
label: "SQL/KSQL 表名和数据库字段名",
|
|
83
|
+
aliases: ["SQL 表名", "KSQL 表名", "表名", "数据库字段名", "字段名"],
|
|
84
|
+
question: "SQL/KSQL 场景的表名和数据库字段名是什么?",
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
export const INTEGRATION_CONTEXT_FIELDS: ContractField[] = [
|
|
89
|
+
{
|
|
90
|
+
id: "interface-doc",
|
|
91
|
+
label: "接口文档来源/版本",
|
|
92
|
+
aliases: ["接口文档", "API 文档", "文档来源", "协议文档", "OpenAPI", "Swagger"],
|
|
93
|
+
question: "第三方接口文档来源和版本是什么?",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "direction",
|
|
97
|
+
label: "对接方向和触发时机",
|
|
98
|
+
aliases: ["对接方向", "触发时机", "同步方向", "数据流向"],
|
|
99
|
+
question: "对接方向、调用方和触发时机是什么?",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "endpoint-auth",
|
|
103
|
+
label: "接口地址、认证和密钥配置方式",
|
|
104
|
+
aliases: ["接口地址", "认证", "鉴权", "密钥", "Token", "OAuth"],
|
|
105
|
+
question: "接口地址、认证方式和密钥配置方式是什么?",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: "field-mapping",
|
|
109
|
+
label: "第三方字段到金蝶字段的映射",
|
|
110
|
+
aliases: ["字段映射", "映射关系", "字段对应", "字段对照"],
|
|
111
|
+
question: "第三方字段与金蝶字段如何对应?",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: "concurrency",
|
|
115
|
+
label: "并发/幂等策略",
|
|
116
|
+
aliases: ["并发", "幂等", "去重", "唯一键", "重复提交"],
|
|
117
|
+
question: "并发、重复提交和幂等如何处理?",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "retry",
|
|
121
|
+
label: "超时、重试和限流策略",
|
|
122
|
+
aliases: ["超时", "重试", "限流", "频率", "熔断"],
|
|
123
|
+
question: "超时、重试和限流策略是什么?",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "error",
|
|
127
|
+
label: "错误处理和失败补偿",
|
|
128
|
+
aliases: ["错误处理", "异常处理", "失败处理", "失败补偿", "告警"],
|
|
129
|
+
question: "接口失败、异常响应和补偿流程如何处理?",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: "logging",
|
|
133
|
+
label: "日志、审计和敏感信息脱敏策略",
|
|
134
|
+
aliases: ["日志", "审计", "留痕", "脱敏", "敏感信息"],
|
|
135
|
+
question: "列出日志字段、审计留痕和敏感信息脱敏规则。",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: "samples",
|
|
139
|
+
label: "请求/响应样例和验收数据",
|
|
140
|
+
aliases: ["请求样例", "响应样例", "报文样例", "验收数据", "payload"],
|
|
141
|
+
question: "请求样例、响应样例和验收数据是什么?",
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
export const PROMPT_STYLE_RULES = [
|
|
146
|
+
"使用正式、可执行的工程指令;禁止口语化、闲聊式、鼓励式表达。",
|
|
147
|
+
"事实不足时生成阻断问题;禁止输出模板代码、占位实现或基于猜测的业务标识。",
|
|
148
|
+
"每次只提出一个最阻塞问题;问题必须指向可验证事实、数据标识或验收证据。",
|
|
149
|
+
"引用顺序:当前项目文件、PLAN/SPEC、元数据 evidence、SDK 签名、验证输出。",
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
export const CORE_WORKFLOW_CONSTRAINTS = [
|
|
153
|
+
"产品代码只在 execute 阶段写入,并限于 PLAN.md 批准的文件。",
|
|
154
|
+
"写代码前必须具备通用实现契约:触发入口、源对象/输入数据、目标对象/输出结果、数据变化或字段映射、业务规则、失败处理和验收样例。",
|
|
155
|
+
"业务数据源未知时禁止编码;确认目标 FormId/单据或表单、插件类型和事件、字段/实体/分录标识、数据读取写入方式后再编码;SQL/KSQL 同步确认表名和数据库字段名。",
|
|
156
|
+
"第三方对接确认接口文档、对接方向、触发时机、认证配置、字段映射、并发/幂等、重试超时限流、错误补偿、日志脱敏和验收样例后再编码。",
|
|
157
|
+
"事实缺失时使用 kd_question 登记一个最阻塞问题;禁止用 API 文档、SDK 知识库或推测替代业务事实。",
|
|
158
|
+
"Java/C# SDK 签名以当前项目 jar/dll、构建输出或官方元数据为准。",
|
|
159
|
+
"Java/Cosmic 使用当前项目 Gradle;C#/企业版使用 dotnet build。",
|
|
160
|
+
"evidence 必须记录命令、Exit 和关键输出;命令无法运行时记录阻塞原因。",
|
|
161
|
+
"外部系统操作、BOS 注册、人工功能测试和生产环境验证不能由 LLM 代办;必须要求用户提供验证结果或可核验证据,并记录证据来源。",
|
|
162
|
+
"Windows 路径规则:项目相对路径为默认;绝对路径使用 D:\\... 形式。",
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
export const PHASE_GUIDANCE: Record<KdPhase, string> = {
|
|
166
|
+
discuss: "梳理需求来源、范围、已知事实;如缺通用实现契约、数据源或第三方接口关键事实,使用 kd_question 登记一个最阻塞问题。",
|
|
167
|
+
spec: "将需求转成验收标准、实现契约、数据对象、接口契约、异常行为、依赖和风险;数据对象和字段映射必须落到可核验标识。",
|
|
168
|
+
plan: "检查项目结构,写明目标路径、允许修改文件、通用实现契约、数据源/元数据查证项、第三方接口契约、插件挂载点、验证命令和回滚说明。",
|
|
169
|
+
execute: "按 PLAN.md 实现,记录步骤结果、变更文件和 evidence。",
|
|
170
|
+
verify: "运行计划中的验证命令,并用 kd_verify_result 记录结果;失败会回到 execute 修复,成功后更新 VERIFY.md、证据和残余风险。",
|
|
171
|
+
ship: "整理 SHIP.md,包括摘要、验证证据、风险和后续事项。",
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const PLAN_REQUIRED_CHECK_LINES = [
|
|
175
|
+
"涉及表单、单据、字段、实体、SQL/KSQL、数据读取写入或插件事件时,确认真实数据源/元数据后再编码;禁止只根据 API 文档编码。",
|
|
176
|
+
"Cosmic 家族数据源证据:evidence/cosmic-metadata.json;企业版/BOS 数据源证据:evidence/data-source.md。",
|
|
177
|
+
`所有产品代码实现必须写明通用实现契约后再编码:${IMPLEMENTATION_CONTRACT_FIELDS.map((field) => field.label).join("、")}。`,
|
|
178
|
+
`进入 execute 前必须写明:${DATA_SOURCE_CONTEXT_FIELDS.map((field) => field.label).join("、")}。`,
|
|
179
|
+
`第三方对接必须写明:${INTEGRATION_CONTEXT_FIELDS.map((field) => field.label).join("、")}。`,
|
|
180
|
+
"Java/C# 代码涉及 SDK 类、方法、构造器、枚举、属性时,使用 kd_sdk_signature 或项目构建输出确认真实签名后再编码。",
|
|
181
|
+
"知识库搜索、随包 Cosmic API 查询只能作为线索,不能作为最终方法签名事实。",
|
|
182
|
+
"SDK 签名证据:evidence/sdk-signature.md",
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
export function formatPromptLines(lines: string[]): string[] {
|
|
186
|
+
return lines.map((line) => `- ${line}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function fieldLabels(fields: ContractField[]): string[] {
|
|
190
|
+
return fields.map((field) => field.label);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function questionForMissingLabel(label: string): string | undefined {
|
|
194
|
+
const fields = [...IMPLEMENTATION_CONTRACT_FIELDS, ...DATA_SOURCE_CONTEXT_FIELDS, ...INTEGRATION_CONTEXT_FIELDS];
|
|
195
|
+
return fields.find((field) => field.label === label)?.question;
|
|
196
|
+
}
|
package/src/harness/prompt.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { delegationGuidanceForWorkflow, shouldInjectDelegationGuidance } from ".
|
|
|
4
4
|
import { formatStatus } from "./format.ts";
|
|
5
5
|
import type { ActiveRun, KdPhase } from "./types.ts";
|
|
6
6
|
import { PHASE_ORDER } from "./types.ts";
|
|
7
|
+
import { CORE_WORKFLOW_CONSTRAINTS, PHASE_GUIDANCE, PROMPT_STYLE_RULES, formatPromptLines } from "./prompt-policy.ts";
|
|
7
8
|
|
|
8
9
|
export function workflowPromptForRun(cwd: string, run: ActiveRun, userText: string): string {
|
|
9
10
|
const status = formatStatus(cwd, run);
|
|
@@ -23,17 +24,16 @@ export function workflowPromptForRun(cwd: string, run: ActiveRun, userText: stri
|
|
|
23
24
|
memory,
|
|
24
25
|
"",
|
|
25
26
|
"项目上下文:",
|
|
26
|
-
projectContext ? trimForPrompt(projectContext, 1200) : "
|
|
27
|
+
projectContext ? trimForPrompt(projectContext, 1200) : "未生成。项目结构缺失时运行 `kcode context --refresh`。",
|
|
27
28
|
"",
|
|
28
29
|
"当前阶段任务:",
|
|
29
30
|
phaseGuidance,
|
|
30
31
|
"",
|
|
32
|
+
"工程指令风格:",
|
|
33
|
+
...formatPromptLines(PROMPT_STYLE_RULES),
|
|
34
|
+
"",
|
|
31
35
|
"核心约束:",
|
|
32
|
-
|
|
33
|
-
"- Java/C# SDK 签名以当前项目 jar/dll、构建输出或官方元数据为准。",
|
|
34
|
-
"- Java/Cosmic 用当前项目 Gradle;C#/企业版用 dotnet build。",
|
|
35
|
-
"- evidence 记录命令、Exit 和关键输出;命令无法运行时记录阻塞原因。",
|
|
36
|
-
"- Windows 下优先使用项目相对路径;绝对路径使用 D:\\... 形式。",
|
|
36
|
+
...formatPromptLines(CORE_WORKFLOW_CONSTRAINTS),
|
|
37
37
|
...delegationGuidance,
|
|
38
38
|
].join("\n");
|
|
39
39
|
}
|
|
@@ -48,15 +48,7 @@ export function repairPromptForRun(run: ActiveRun): string {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function phaseGuidanceForRun(phase: KdPhase): string {
|
|
51
|
-
|
|
52
|
-
discuss: "梳理需求来源、范围、已知事实和一个最阻塞的待确认问题。",
|
|
53
|
-
spec: "把需求转成验收标准、数据对象、异常行为、依赖和风险。",
|
|
54
|
-
plan: "检查项目结构,写明目标路径、允许修改文件、查证项、验证命令和回滚说明。",
|
|
55
|
-
execute: "按 PLAN.md 实现,记录步骤结果、变更文件和 evidence。",
|
|
56
|
-
verify: "运行计划中的验证命令,并用 kd_verify_result 记录结果;失败会回到 execute 修复,成功后更新 VERIFY.md、证据和残余风险。",
|
|
57
|
-
ship: "整理 SHIP.md,包括摘要、验证证据、风险和后续事项。",
|
|
58
|
-
};
|
|
59
|
-
return guidance[phase];
|
|
51
|
+
return PHASE_GUIDANCE[phase];
|
|
60
52
|
}
|
|
61
53
|
|
|
62
54
|
function workflowMemoryForRun(cwd: string, run: ActiveRun): string {
|
|
@@ -76,5 +68,5 @@ function workflowMemoryForRun(cwd: string, run: ActiveRun): string {
|
|
|
76
68
|
|
|
77
69
|
function trimForPrompt(content: string, maxLength: number): string {
|
|
78
70
|
if (content.length <= maxLength) return content;
|
|
79
|
-
return `${content.slice(0, maxLength)}\n\n[
|
|
71
|
+
return `${content.slice(0, maxLength)}\n\n[...已截断;完整内容读取本地文件...]`;
|
|
80
72
|
}
|
package/src/harness/repair.ts
CHANGED
|
@@ -99,7 +99,7 @@ function recordVerifyFailure(cwd: string, run: ActiveRun, input: NormalizedVerif
|
|
|
99
99
|
{
|
|
100
100
|
id: `Q-${String((run.questions?.length ?? 0) + 1).padStart(3, "0")}`,
|
|
101
101
|
phase: "verify",
|
|
102
|
-
question: `验证失败已达到 ${maxAttempts}
|
|
102
|
+
question: `验证失败已达到 ${maxAttempts} 轮。选择继续修复、回到 plan 调整范围或停止。`,
|
|
103
103
|
reason: `最近失败证据:${evidence}`,
|
|
104
104
|
choices: ["继续修复", "回到 plan", "停止"],
|
|
105
105
|
blocking: true,
|
|
@@ -161,7 +161,7 @@ function appendExecuteRepairRecord(cwd: string, run: ActiveRun, evidence: string
|
|
|
161
161
|
"",
|
|
162
162
|
`- 修复轮次:${attempts}/${maxAttempts}`,
|
|
163
163
|
`- 失败证据:${evidence}`,
|
|
164
|
-
"- 下一步:读取失败证据,分析失败原因,只在 PLAN.md
|
|
164
|
+
"- 下一步:读取失败证据,分析失败原因,只在 PLAN.md 批准文件内修复;涉及未批准文件时回到 plan 更新计划。",
|
|
165
165
|
"",
|
|
166
166
|
].join("\n");
|
|
167
167
|
writeArtifact(cwd, run, "execute", `${existing.trimEnd()}\n${section}`);
|
package/src/harness/state.ts
CHANGED
|
@@ -83,7 +83,7 @@ export function createActiveRun(cwd: string, goal: string, productInput?: string
|
|
|
83
83
|
questions: [],
|
|
84
84
|
gate: {
|
|
85
85
|
passed: false,
|
|
86
|
-
reason: "进入 spec
|
|
86
|
+
reason: "进入 spec 前必须完成 CONTEXT.md 并确认产品画像",
|
|
87
87
|
checkedAt: new Date().toISOString(),
|
|
88
88
|
},
|
|
89
89
|
};
|
|
@@ -123,6 +123,7 @@ export function answerQuestion(cwd: string, run: ActiveRun, id: string, answer:
|
|
|
123
123
|
question.status = "answered";
|
|
124
124
|
question.answer = answer.trim();
|
|
125
125
|
question.answeredAt = new Date().toISOString();
|
|
126
|
+
applyRepairQuestionAnswer(cwd, run, question);
|
|
126
127
|
run.gate = inspectGate(cwd, run);
|
|
127
128
|
writeActiveRun(cwd, run);
|
|
128
129
|
return question;
|
|
@@ -216,6 +217,62 @@ function writeRunState(cwd: string, run: ActiveRun): void {
|
|
|
216
217
|
writeFileSync(runStatePath(cwd, run), `${JSON.stringify(run, null, 2)}\n`, "utf8");
|
|
217
218
|
}
|
|
218
219
|
|
|
220
|
+
function applyRepairQuestionAnswer(cwd: string, run: ActiveRun, question: KdQuestion): void {
|
|
221
|
+
if (!isRepairLimitQuestion(question)) return;
|
|
222
|
+
const answer = question.answer?.trim() ?? "";
|
|
223
|
+
const now = new Date().toISOString();
|
|
224
|
+
const attempts = run.repair?.attempts ?? 0;
|
|
225
|
+
const maxAttempts = run.repair?.maxAttempts ?? 3;
|
|
226
|
+
const extendedMaxAttempts = attempts + maxAttempts;
|
|
227
|
+
|
|
228
|
+
if (/^继续修复\b|^继续|continue/i.test(answer)) {
|
|
229
|
+
run.phase = "execute";
|
|
230
|
+
ensureArtifact(cwd, run, "execute", defaultArtifactContent("execute", run.goal, run.profile));
|
|
231
|
+
run.repair = {
|
|
232
|
+
attempts,
|
|
233
|
+
maxAttempts: extendedMaxAttempts,
|
|
234
|
+
lastFailureEvidence: run.repair?.lastFailureEvidence,
|
|
235
|
+
lastFailureSignature: run.repair?.lastFailureSignature,
|
|
236
|
+
status: "repairing",
|
|
237
|
+
updatedAt: now,
|
|
238
|
+
};
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (/^回到\s*plan\b|^回到计划|^返回\s*plan\b|^重新计划/i.test(answer)) {
|
|
243
|
+
run.phase = "plan";
|
|
244
|
+
ensureArtifact(cwd, run, "plan", defaultArtifactContent("plan", run.goal, run.profile));
|
|
245
|
+
run.repair = {
|
|
246
|
+
attempts,
|
|
247
|
+
maxAttempts: extendedMaxAttempts,
|
|
248
|
+
lastFailureEvidence: run.repair?.lastFailureEvidence,
|
|
249
|
+
lastFailureSignature: run.repair?.lastFailureSignature,
|
|
250
|
+
status: "idle",
|
|
251
|
+
updatedAt: now,
|
|
252
|
+
};
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (/^停止|^stop\b/i.test(answer)) {
|
|
257
|
+
run.repair = {
|
|
258
|
+
attempts: run.repair?.attempts ?? maxAttempts,
|
|
259
|
+
maxAttempts,
|
|
260
|
+
lastFailureEvidence: run.repair?.lastFailureEvidence,
|
|
261
|
+
lastFailureSignature: run.repair?.lastFailureSignature,
|
|
262
|
+
status: "blocked",
|
|
263
|
+
updatedAt: now,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function isRepairLimitQuestion(question: KdQuestion): boolean {
|
|
269
|
+
return (
|
|
270
|
+
question.phase === "verify" &&
|
|
271
|
+
/验证失败已达到/.test(question.question) &&
|
|
272
|
+
(Array.isArray(question.choices) ? question.choices.includes("继续修复") : true)
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
219
276
|
function hydrateRun(parsed: ActiveRun): ActiveRun | undefined {
|
|
220
277
|
if (!parsed || typeof parsed !== "object") return undefined;
|
|
221
278
|
if (typeof parsed.id !== "string" || !parsed.id.trim()) return undefined;
|
|
@@ -379,7 +379,7 @@ async function fetchMetadata(config: Record<string, unknown>, target: string): P
|
|
|
379
379
|
const route = objectValue(config.route);
|
|
380
380
|
const routeUrl = routeUrlFromConfig(route);
|
|
381
381
|
if (!routeUrl) {
|
|
382
|
-
throw new Error("未配置表单元数据查询 API
|
|
382
|
+
throw new Error("未配置表单元数据查询 API。必须在 ok-cosmic.json 的 route.apiUrl 中配置统一路由,或设置 COSMIC_ROUTE_API。");
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
const hasCjk = /[\u3400-\u9fff]/.test(target);
|
|
@@ -524,7 +524,7 @@ function runCosmicApi(cwd: string, params: { mode: "search" | "search-method" |
|
|
|
524
524
|
const results = searchKnowledge(query, { scopes: ["cosmic", "cangqiong", "xinghan", "flagship"], topK: params.compact ? 5 : 10, minScore: 1 }, knowledgePath);
|
|
525
525
|
const header = [
|
|
526
526
|
`KCode Node Cosmic API query (${params.mode})`,
|
|
527
|
-
"说明: 当前 npm 包不再调用 Python/SQLite
|
|
527
|
+
"说明: 当前 npm 包不再调用 Python/SQLite 脚本;这里查询随包金蝶知识库。精确方法签名必须结合项目 SDK/编译输出做红绿验证。",
|
|
528
528
|
"",
|
|
529
529
|
].join("\n");
|
|
530
530
|
const stdout = `${header}${formatSearchResults(query, results, knowledgePath)}\n`;
|
|
@@ -690,13 +690,13 @@ function lintStatement(stmt: KsqlStatement): KsqlFinding[] {
|
|
|
690
690
|
findings.push({ severity: "WARN", line: lineOfOffset(stmt.text, stmt.line, match.index ?? 0), message: "SQL 可读性偏好:成员关系/半连接默认使用 IN,只有 IN 改变语义时才保留 EXISTS 并说明原因。" });
|
|
691
691
|
}
|
|
692
692
|
if (/\bUPDATE\b.+\bJOIN\b/i.test(compact)) {
|
|
693
|
-
findings.push({ severity: "WARN", line: stmt.line, message: "PostgreSQL
|
|
693
|
+
findings.push({ severity: "WARN", line: stmt.line, message: "PostgreSQL 多表更新默认使用 UPDATE ... FROM ... WHERE ...,禁止使用 MySQL 风格 UPDATE ... JOIN。" });
|
|
694
694
|
}
|
|
695
695
|
if (/=\s*NULL\b|\bNULL\s*=/i.test(stmt.text)) {
|
|
696
696
|
findings.push({ severity: "ERROR", line: stmt.line, message: "NULL 判断必须使用 IS NULL / IS NOT NULL,不能使用 = NULL。" });
|
|
697
697
|
}
|
|
698
698
|
if (/<>|!=/.test(stmt.text) && hasToken(stmt.text, "NULL")) {
|
|
699
|
-
findings.push({ severity: "WARN", line: stmt.line, message: "涉及 NULL
|
|
699
|
+
findings.push({ severity: "WARN", line: stmt.line, message: "涉及 NULL 的不等比较必须确认语义;PostgreSQL 默认使用 IS DISTINCT FROM。" });
|
|
700
700
|
}
|
|
701
701
|
return findings;
|
|
702
702
|
}
|
package/src/product/profile.ts
CHANGED
|
@@ -24,7 +24,7 @@ const PROFILES: Record<KdProduct, ProductProfile> = {
|
|
|
24
24
|
language: "unknown",
|
|
25
25
|
knowledgeScope: "common",
|
|
26
26
|
requiresMetadataVerification: true,
|
|
27
|
-
notes: ["
|
|
27
|
+
notes: ["尚未选择产品;禁止假设插件技术栈、BOS/Cosmic 平台规则或 KSQL/SQL 交付规则。"],
|
|
28
28
|
},
|
|
29
29
|
flagship: {
|
|
30
30
|
product: "flagship",
|
|
@@ -36,7 +36,7 @@ const PROFILES: Record<KdProduct, ProductProfile> = {
|
|
|
36
36
|
requiresMetadataVerification: true,
|
|
37
37
|
notes: [
|
|
38
38
|
"星空旗舰版基于苍穹/Cosmic 平台。使用平台元数据、插件生命周期、SDK 和后置检查约束,但接口可能存在旗舰版差异。",
|
|
39
|
-
"
|
|
39
|
+
"当前工作区存在 code/ 目录时,产品代码必须放在 code/ 下。",
|
|
40
40
|
"创建或编辑代码前,必须检查 code/ 下真实项目结构,并跟随其实际布局;可能按云、按应用组织,也可能不分模块。",
|
|
41
41
|
],
|
|
42
42
|
},
|