kcode-pi 0.1.34 → 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 +52 -49
- package/extensions/kingdee-header.ts +1 -1
- package/extensions/kingdee-subagents.ts +1 -1
- package/extensions/kingdee-tools.ts +44 -44
- package/package.json +1 -1
- 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 +16 -1
- package/src/harness/messages.ts +65 -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 +1 -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
|
@@ -77,7 +77,7 @@ function sdkLanguageForProfile(profile: ProductProfile, value: string | undefine
|
|
|
77
77
|
|
|
78
78
|
function rejectNonCosmic(profile: ProductProfile): string | undefined {
|
|
79
79
|
if (isCosmicFamily(profile)) return undefined;
|
|
80
|
-
if (profile.product === "unknown") return "
|
|
80
|
+
if (profile.product === "unknown") return "必须提供支持 Cosmic 平台能力的产品:cangqiong、xinghan 或 flagship。";
|
|
81
81
|
return `当前产品 ${profile.product} 使用 ${profile.platform}/${profile.techStack},不适用 Cosmic 官方能力。`;
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -90,7 +90,7 @@ async function runOrDryRun(
|
|
|
90
90
|
const command = await commandPromise;
|
|
91
91
|
if (dryRun) {
|
|
92
92
|
return {
|
|
93
|
-
content: [{ type: "text" as const, text:
|
|
93
|
+
content: [{ type: "text" as const, text: `输出命令预览;不执行:\n${command.display}` }],
|
|
94
94
|
details: { command: command.display, dryRun: true },
|
|
95
95
|
};
|
|
96
96
|
}
|
|
@@ -119,11 +119,11 @@ function autoAdvanceAfterEvidence(cwd: string): { text: string; details: ReturnT
|
|
|
119
119
|
const kdSearchTool = defineTool({
|
|
120
120
|
name: "kd_search",
|
|
121
121
|
label: "KD 搜索",
|
|
122
|
-
description: "搜索 KCode 随包金蝶知识库,包括 SDK
|
|
122
|
+
description: "搜索 KCode 随包金蝶知识库,包括 SDK、插件生命周期、代码模式和实现约束。",
|
|
123
123
|
parameters: Type.Object({
|
|
124
|
-
query: Type.String({ description: "
|
|
124
|
+
query: Type.String({ description: "搜索关键词、API、类名、表名或生命周期术语。" }),
|
|
125
125
|
product: Type.Optional(Type.String({ description: "金蝶产品:flagship、xinghan、cangqiong 或 enterprise。" })),
|
|
126
|
-
edition: Type.Optional(Type.String({ description: "旧参数,等同于 product
|
|
126
|
+
edition: Type.Optional(Type.String({ description: "旧参数,等同于 product;product 覆盖 edition。" })),
|
|
127
127
|
limit: Type.Optional(Type.Number({ description: "最大结果数,默认 5。" })),
|
|
128
128
|
}),
|
|
129
129
|
|
|
@@ -133,7 +133,7 @@ const kdSearchTool = defineTool({
|
|
|
133
133
|
if (!scopes) {
|
|
134
134
|
const guidance =
|
|
135
135
|
profile.product === "unknown"
|
|
136
|
-
? "
|
|
136
|
+
? "必须提供 product,例如 flagship、enterprise、xinghan 或 cangqiong。"
|
|
137
137
|
: "当前产品画像未配置可搜索知识范围。";
|
|
138
138
|
return {
|
|
139
139
|
content: [
|
|
@@ -141,7 +141,7 @@ const kdSearchTool = defineTool({
|
|
|
141
141
|
type: "text",
|
|
142
142
|
text: [
|
|
143
143
|
`产品画像:${profile.product}/${profile.techStack}/${profile.language}`,
|
|
144
|
-
"KCode
|
|
144
|
+
"KCode 随包知识库搜索必须明确产品画像。",
|
|
145
145
|
guidance,
|
|
146
146
|
].join("\n"),
|
|
147
147
|
},
|
|
@@ -166,7 +166,7 @@ const kdTableTool = defineTool({
|
|
|
166
166
|
parameters: Type.Object({
|
|
167
167
|
table: Type.String({ description: "表名,例如 T_PUR_POORDER。" }),
|
|
168
168
|
product: Type.Optional(Type.String({ description: "金蝶产品:flagship、xinghan、cangqiong 或 enterprise。" })),
|
|
169
|
-
edition: Type.Optional(Type.String({ description: "旧参数,等同于 product
|
|
169
|
+
edition: Type.Optional(Type.String({ description: "旧参数,等同于 product;product 覆盖 edition。" })),
|
|
170
170
|
}),
|
|
171
171
|
|
|
172
172
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
@@ -175,8 +175,8 @@ const kdTableTool = defineTool({
|
|
|
175
175
|
if (!edition) {
|
|
176
176
|
const guidance =
|
|
177
177
|
profile.product === "unknown"
|
|
178
|
-
? "
|
|
179
|
-
: "
|
|
178
|
+
? "必须提供 product。禁止跨金蝶产品族猜测表结构。"
|
|
179
|
+
: "没有元数据查证前,禁止把旗舰版/企业版表结构假设复用到苍穹/星瀚/Cosmic。";
|
|
180
180
|
return {
|
|
181
181
|
content: [
|
|
182
182
|
{
|
|
@@ -204,12 +204,12 @@ const kdCheckTool = defineTool({
|
|
|
204
204
|
name: "kd_check",
|
|
205
205
|
label: "KD 检查",
|
|
206
206
|
description:
|
|
207
|
-
"检查金蝶 Java/C#/Python 插件代码中的魔法值、命名问题、循环内 DB 调用和空 catch
|
|
207
|
+
"检查金蝶 Java/C#/Python 插件代码中的魔法值、命名问题、循环内 DB 调用和空 catch。",
|
|
208
208
|
parameters: Type.Object({
|
|
209
|
-
code: Type.Optional(Type.String({ description: "
|
|
210
|
-
path: Type.Optional(Type.String({ description: "
|
|
209
|
+
code: Type.Optional(Type.String({ description: "待检查源码。与 path 二选一。" })),
|
|
210
|
+
path: Type.Optional(Type.String({ description: "待检查源码文件路径。与 code 二选一。" })),
|
|
211
211
|
product: Type.Optional(Type.String({ description: "金蝶产品。未提供 language 时用于推导 Java 或 C#。" })),
|
|
212
|
-
language: Type.Optional(Type.String({ description: "语言:java、csharp 或 python
|
|
212
|
+
language: Type.Optional(Type.String({ description: "语言:java、csharp 或 python。覆盖产品推导结果。" })),
|
|
213
213
|
}),
|
|
214
214
|
|
|
215
215
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -226,7 +226,7 @@ const kdCheckTool = defineTool({
|
|
|
226
226
|
|
|
227
227
|
if (!code) {
|
|
228
228
|
return {
|
|
229
|
-
content: [{ type: "text", text: "kd_check
|
|
229
|
+
content: [{ type: "text", text: "kd_check 必须提供 code 或 path。" }],
|
|
230
230
|
details: { error: "missing-code-or-path" },
|
|
231
231
|
};
|
|
232
232
|
}
|
|
@@ -250,8 +250,8 @@ const kdCosmicConfigTool = defineTool({
|
|
|
250
250
|
description: "运行 Cosmic 家族金蝶产品的官方能力配置预检查。",
|
|
251
251
|
parameters: Type.Object({
|
|
252
252
|
product: Type.String({ description: "支持 Cosmic 平台能力的产品:cangqiong、xinghan 或 flagship。" }),
|
|
253
|
-
config: Type.Optional(Type.String({ description: "
|
|
254
|
-
dryRun: Type.Optional(Type.Boolean({ description: "
|
|
253
|
+
config: Type.Optional(Type.String({ description: "ok-cosmic.json 路径;省略时按当前工作目录解析。" })),
|
|
254
|
+
dryRun: Type.Optional(Type.Boolean({ description: "输出命令预览;不实际执行。" })),
|
|
255
255
|
}),
|
|
256
256
|
|
|
257
257
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -268,14 +268,14 @@ const kdCosmicMetadataTool = defineTool({
|
|
|
268
268
|
description: "查询官方 Cosmic 表单/单据元数据,包括字段、枚举值、操作和 SQL 表信息。",
|
|
269
269
|
parameters: Type.Object({
|
|
270
270
|
product: Type.String({ description: "支持 Cosmic 平台能力的产品:cangqiong、xinghan 或 flagship。" }),
|
|
271
|
-
form: Type.String({ description: "Form ID、单据 ID
|
|
272
|
-
config: Type.Optional(Type.String({ description: "
|
|
273
|
-
fuzzy: Type.Optional(Type.String({ description: "
|
|
274
|
-
typeFilter: Type.Optional(Type.String({ description: "
|
|
275
|
-
sql: Type.Optional(Type.Boolean({ description: "
|
|
276
|
-
op: Type.Optional(Type.Boolean({ description: "
|
|
277
|
-
showDetail: Type.Optional(Type.Boolean({ description: "
|
|
278
|
-
dryRun: Type.Optional(Type.Boolean({ description: "
|
|
271
|
+
form: Type.String({ description: "Form ID、单据 ID 或中文单据名;多个目标使用逗号分隔。" }),
|
|
272
|
+
config: Type.Optional(Type.String({ description: "ok-cosmic.json 路径;省略时使用默认配置。" })),
|
|
273
|
+
fuzzy: Type.Optional(Type.String({ description: "字段关键词,使用空格或逗号分隔。" })),
|
|
274
|
+
typeFilter: Type.Optional(Type.String({ description: "字段类型正则,例如 combo|check 或 decimal。" })),
|
|
275
|
+
sql: Type.Optional(Type.Boolean({ description: "包含数据库表和字段信息。" })),
|
|
276
|
+
op: Type.Optional(Type.Boolean({ description: "显示表单/单据操作。" })),
|
|
277
|
+
showDetail: Type.Optional(Type.Boolean({ description: "显示详细元数据输出。" })),
|
|
278
|
+
dryRun: Type.Optional(Type.Boolean({ description: "输出命令预览;不实际执行。" })),
|
|
279
279
|
}),
|
|
280
280
|
|
|
281
281
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -289,15 +289,15 @@ const kdCosmicMetadataTool = defineTool({
|
|
|
289
289
|
const kdCosmicApiTool = defineTool({
|
|
290
290
|
name: "kd_cosmic_api",
|
|
291
291
|
label: "KD Cosmic API",
|
|
292
|
-
description: "查询随包 Cosmic API
|
|
292
|
+
description: "查询随包 Cosmic API 知识,输出类和方法线索;最终签名事实必须以 kd_sdk_signature 或项目构建输出为准。",
|
|
293
293
|
parameters: Type.Object({
|
|
294
294
|
product: Type.String({ description: "支持 Cosmic 平台能力的产品:cangqiong、xinghan 或 flagship。" }),
|
|
295
295
|
mode: Type.String({ description: "查询模式:search、search-method 或 detail。" }),
|
|
296
296
|
query: Type.String({ description: "类名、方法名或完整限定类名。" }),
|
|
297
|
-
config: Type.Optional(Type.String({ description: "
|
|
298
|
-
method: Type.Optional(Type.String({ description: "detail
|
|
299
|
-
compact: Type.Optional(Type.Boolean({ description: "
|
|
300
|
-
dryRun: Type.Optional(Type.Boolean({ description: "
|
|
297
|
+
config: Type.Optional(Type.String({ description: "ok-cosmic.json 路径;省略时使用默认配置。" })),
|
|
298
|
+
method: Type.Optional(Type.String({ description: "detail 模式下的方法过滤条件。" })),
|
|
299
|
+
compact: Type.Optional(Type.Boolean({ description: "启用紧凑详情输出;命令支持时生效。" })),
|
|
300
|
+
dryRun: Type.Optional(Type.Boolean({ description: "输出命令预览;不实际执行。" })),
|
|
301
301
|
}),
|
|
302
302
|
|
|
303
303
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -323,14 +323,14 @@ const kdSdkSignatureTool = defineTool({
|
|
|
323
323
|
name: "kd_sdk_signature",
|
|
324
324
|
label: "KD SDK 签名",
|
|
325
325
|
description:
|
|
326
|
-
"从当前项目真实存在的 SDK jar 或 dll
|
|
326
|
+
"从当前项目真实存在的 SDK jar 或 dll 中检查方法/类型签名。API 签名事实必须使用此工具查证,不以随包知识库替代。",
|
|
327
327
|
parameters: Type.Object({
|
|
328
328
|
product: Type.Optional(Type.String({ description: "金蝶产品。未提供 language 时用于推导 Java 或 C#。" })),
|
|
329
329
|
language: Type.Optional(Type.String({ description: "java 或 csharp。默认由产品画像推导。" })),
|
|
330
|
-
query: Type.Optional(Type.String({ description: "
|
|
331
|
-
className: Type.Optional(Type.String({ description: "
|
|
332
|
-
method: Type.Optional(Type.String({ description: "
|
|
333
|
-
path: Type.Optional(Type.String({ description: "
|
|
330
|
+
query: Type.Optional(Type.String({ description: "类/类型搜索关键词,例如 QueryServiceHelper 或 DynamicObject。" })),
|
|
331
|
+
className: Type.Optional(Type.String({ description: "完整限定 Java/C# 类型名。" })),
|
|
332
|
+
method: Type.Optional(Type.String({ description: "在匹配类/类型内过滤方法或属性;禁止全局扫描所有方法。" })),
|
|
333
|
+
path: Type.Optional(Type.String({ description: "SDK lib/bin 目录或依赖根路径;省略时从当前项目查找。" })),
|
|
334
334
|
limit: Type.Optional(Type.Number({ description: "最大检查 jar/dll/class 数量。默认 20 个结果类、200 个文件。" })),
|
|
335
335
|
}),
|
|
336
336
|
|
|
@@ -361,8 +361,8 @@ const kdKsqlLintTool = defineTool({
|
|
|
361
361
|
description: "对生成的 KSQL/SQL 文件运行官方 ok-ksql lint 检查。",
|
|
362
362
|
parameters: Type.Object({
|
|
363
363
|
product: Type.String({ description: "支持 Cosmic 平台能力的产品:cangqiong、xinghan 或 flagship。" }),
|
|
364
|
-
path: Type.String({ description: "SQL/KSQL
|
|
365
|
-
dryRun: Type.Optional(Type.Boolean({ description: "
|
|
364
|
+
path: Type.String({ description: "SQL/KSQL 文件路径;支持工作区相对路径或绝对路径。" }),
|
|
365
|
+
dryRun: Type.Optional(Type.Boolean({ description: "输出命令预览;不实际执行。" })),
|
|
366
366
|
}),
|
|
367
367
|
|
|
368
368
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -379,8 +379,8 @@ const kdBuildTool = defineTool({
|
|
|
379
379
|
description: "按产品画像运行或预览金蝶构建命令,支持 Cosmic Java 和企业版 C# 项目。",
|
|
380
380
|
parameters: Type.Object({
|
|
381
381
|
product: Type.String({ description: "金蝶产品:cangqiong、xinghan、flagship 或 enterprise。" }),
|
|
382
|
-
target: Type.Optional(Type.String({ description: "Java
|
|
383
|
-
dryRun: Type.Optional(Type.Boolean({ description: "
|
|
382
|
+
target: Type.Optional(Type.String({ description: "Java 使用 Gradle task;C# 使用 .sln/.csproj 路径。" })),
|
|
383
|
+
dryRun: Type.Optional(Type.Boolean({ description: "输出构建命令预览;不实际执行。" })),
|
|
384
384
|
}),
|
|
385
385
|
|
|
386
386
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -400,11 +400,11 @@ const kdBuildTool = defineTool({
|
|
|
400
400
|
const kdDebugTool = defineTool({
|
|
401
401
|
name: "kd_debug",
|
|
402
402
|
label: "KD 调试",
|
|
403
|
-
description: "
|
|
403
|
+
description: "分析金蝶构建/运行日志或堆栈,输出推断原因和检查指令。",
|
|
404
404
|
parameters: Type.Object({
|
|
405
405
|
text: Type.Optional(Type.String({ description: "日志文本或堆栈。与 path 二选一。" })),
|
|
406
406
|
path: Type.Optional(Type.String({ description: "日志文件路径。与 text 二选一。" })),
|
|
407
|
-
product: Type.Optional(Type.String({ description: "
|
|
407
|
+
product: Type.Optional(Type.String({ description: "产品上下文。" })),
|
|
408
408
|
}),
|
|
409
409
|
|
|
410
410
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -434,10 +434,10 @@ const kdDocReadTool = defineTool({
|
|
|
434
434
|
name: "kd_doc_read",
|
|
435
435
|
label: "KD 文档读取",
|
|
436
436
|
description:
|
|
437
|
-
"读取 PDF、Word (.docx/.doc)、Excel (.xlsx/.xls) 或 CSV 文件并提取文本内容。对于 .pdf、.docx、.doc、.xlsx、.xls、.csv 文件,必须使用此工具而非 read 工具,因为 read 无法解析这些二进制格式。PDF
|
|
437
|
+
"读取 PDF、Word (.docx/.doc)、Excel (.xlsx/.xls) 或 CSV 文件并提取文本内容。对于 .pdf、.docx、.doc、.xlsx、.xls、.csv 文件,必须使用此工具而非 read 工具,因为 read 无法解析这些二进制格式。PDF 目前仅支持可复制文本抽取;扫描型 PDF 必须另行提供图片或 OCR 结果。",
|
|
438
438
|
parameters: Type.Object({
|
|
439
439
|
path: Type.String({ description: "文档文件路径,支持 .pdf、.docx、.doc、.xlsx、.xls、.csv。" }),
|
|
440
|
-
sheet: Type.Optional(Type.String({ description: "Excel 工作表名或序号(从 1
|
|
440
|
+
sheet: Type.Optional(Type.String({ description: "Excel 工作表名或序号(从 1 开始)。默认第一个工作表。" })),
|
|
441
441
|
maxRows: Type.Optional(Type.Number({ description: "Excel/CSV 最大返回行数。默认 200。" })),
|
|
442
442
|
maxPages: Type.Optional(Type.Number({ description: "PDF 最大提取页数。默认 50。" })),
|
|
443
443
|
}),
|
|
@@ -516,7 +516,7 @@ function extractXlsx(filePath: string, sheetNameOrIndex?: string, maxRows?: numb
|
|
|
516
516
|
} else if (sheetNames.includes(sheetNameOrIndex)) {
|
|
517
517
|
sheetName = sheetNameOrIndex;
|
|
518
518
|
} else {
|
|
519
|
-
return `[Excel] 工作表 "${sheetNameOrIndex}"
|
|
519
|
+
return `[Excel] 工作表 "${sheetNameOrIndex}" 不存在。有效工作表:${sheetNames.join(", ")}`;
|
|
520
520
|
}
|
|
521
521
|
} else {
|
|
522
522
|
sheetName = sheetNames[0];
|
package/package.json
CHANGED
package/src/cli/kcode.ts
CHANGED
|
@@ -98,8 +98,8 @@ export function doctor(cwd: string, args: string[] = []): KcodeCliResult {
|
|
|
98
98
|
lines.push(`Pi CLI:${formatPiCliStatus(piCli, pi)}`);
|
|
99
99
|
lines.push(`KCode version:${packageName}@${packageVersion}`);
|
|
100
100
|
lines.push(`KCode package:${packageRoot}`);
|
|
101
|
-
lines.push(`项目配置:${existsSync(settingsPath) ? settingsPath : "
|
|
102
|
-
lines.push(`项目上下文:${existsSync(projectContextPath) ? projectContextPath : "
|
|
101
|
+
lines.push(`项目配置:${existsSync(settingsPath) ? settingsPath : "未创建,运行 kcode init"}`);
|
|
102
|
+
lines.push(`项目上下文:${existsSync(projectContextPath) ? projectContextPath : "未创建,运行 kcode context"}`);
|
|
103
103
|
|
|
104
104
|
if (existsSync(settingsPath)) {
|
|
105
105
|
const settingsResult = readSettingsSafe(settingsPath);
|
|
@@ -199,7 +199,7 @@ export function start(cwd: string, piArgs: string[]): KcodeCliResult {
|
|
|
199
199
|
if (!piCli) {
|
|
200
200
|
return {
|
|
201
201
|
exitCode: 1,
|
|
202
|
-
output: `${init.output}\n未找到随包 Pi CLI 或全局 pi
|
|
202
|
+
output: `${init.output}\n未找到随包 Pi CLI 或全局 pi 命令。重新安装 kcode-pi 后再运行 kcode start。`,
|
|
203
203
|
};
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -69,9 +69,9 @@ export function generateProjectContext(cwd: string): string {
|
|
|
69
69
|
"",
|
|
70
70
|
"- 本文件是 KCode 的项目记忆,计划或编辑代码前必须读取。",
|
|
71
71
|
"- 业务需求不得生成 demo/sample/scaffold 代码。",
|
|
72
|
-
"-
|
|
73
|
-
"-
|
|
74
|
-
"-
|
|
72
|
+
"- 禁止假设模块结构。必须基于下方真实路径,并在编辑前确认目标文件。",
|
|
73
|
+
"- 调用文件工具时默认使用项目相对路径。在 Windows 中禁止把路径改写为 /mnt/<drive>/... 或 /<drive>/...;绝对路径只允许使用 Windows 路径。",
|
|
74
|
+
"- 本文件过期时,计划前运行 `kcode context --refresh` 重新生成。",
|
|
75
75
|
"- 只有 Harness 进入 `execute` 且 PLAN.md 写明真实目标路径后,才能写产品代码。",
|
|
76
76
|
"",
|
|
77
77
|
"## 项目结构摘要",
|
package/src/harness/artifacts.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { ActiveRun, KdPhase } from "./types.ts";
|
|
|
3
3
|
import { PHASE_ARTIFACTS } from "./types.ts";
|
|
4
4
|
import { runArtifactPath, runRoot } from "./paths.ts";
|
|
5
5
|
import type { ProductProfile } from "../product/profile.ts";
|
|
6
|
+
import { PLAN_REQUIRED_CHECK_LINES, formatPromptLines } from "./prompt-policy.ts";
|
|
6
7
|
|
|
7
8
|
export function ensureRunDirectories(cwd: string, run: ActiveRun): void {
|
|
8
9
|
mkdirSync(runRoot(cwd, run), { recursive: true });
|
|
@@ -86,7 +87,7 @@ export function defaultArtifactContent(phase: KdPhase, goal?: string, profile?:
|
|
|
86
87
|
"",
|
|
87
88
|
"## 已检查的项目结构",
|
|
88
89
|
"",
|
|
89
|
-
"##
|
|
90
|
+
"## 待读取文件",
|
|
90
91
|
"",
|
|
91
92
|
"## 目标源码根 / 路径",
|
|
92
93
|
"",
|
|
@@ -99,9 +100,7 @@ export function defaultArtifactContent(phase: KdPhase, goal?: string, profile?:
|
|
|
99
100
|
"",
|
|
100
101
|
"## 必需的金蝶查证项",
|
|
101
102
|
"",
|
|
102
|
-
|
|
103
|
-
"- 知识库搜索、随包 Cosmic API 查询只能作为线索,不能作为最终方法签名事实。",
|
|
104
|
-
"- SDK 签名证据:evidence/sdk-signature.md",
|
|
103
|
+
...formatPromptLines(PLAN_REQUIRED_CHECK_LINES),
|
|
105
104
|
"",
|
|
106
105
|
"## 需求条目 / 依赖 / 批次",
|
|
107
106
|
"",
|
|
@@ -120,12 +119,12 @@ export function defaultArtifactContent(phase: KdPhase, goal?: string, profile?:
|
|
|
120
119
|
"- 红灯证据:evidence/tdd-red.md",
|
|
121
120
|
"- 绿灯证据:evidence/tdd-green.md",
|
|
122
121
|
"- 红绿检查命令或工具:未知",
|
|
123
|
-
"- Java
|
|
122
|
+
"- Java 语法/编译检查:使用当前项目 Gradle 命令,例如 `./gradlew build`、`.\\gradlew.bat build` 或 `./gradlew :模块:build`。",
|
|
124
123
|
"- C# 语法/编译检查:使用 `dotnet build`、`dotnet build <.sln>` 或 `dotnet build <.csproj>`。",
|
|
125
124
|
"- 允许的检查:本地 SDK 签名查证、官方 API/基类/方法查证、元数据查证、kd_check、Gradle/dotnet 构建输出、项目已有测试框架、外部接口最小验证。",
|
|
126
|
-
"-
|
|
125
|
+
"- 测试框架:使用项目已有测试基础设施。",
|
|
127
126
|
"- 命令无法运行时记录真实阻塞原因和残余风险,不能作为绿灯证据。",
|
|
128
|
-
"-
|
|
127
|
+
"- 自动化测试不可执行时,记录一个产品相关、实现前应失败且实现后应通过的检查。",
|
|
129
128
|
"- SDK 方法签名事实必须来自当前项目 jar/dll、构建输出或官方元数据。",
|
|
130
129
|
"",
|
|
131
130
|
"## 验证命令",
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
3
|
+
import type { ActiveRun } from "./types.ts";
|
|
4
|
+
import { readArtifact } from "./artifacts.ts";
|
|
5
|
+
import { hasEvidenceEntry } from "./evidence.ts";
|
|
6
|
+
import { runRoot } from "./paths.ts";
|
|
7
|
+
import { isSourceLikePath } from "./path-policy.ts";
|
|
8
|
+
import {
|
|
9
|
+
dataSourcePlanBlockedReason,
|
|
10
|
+
dataSourceWriteBlockedReason,
|
|
11
|
+
implementationPlanBlockedReason,
|
|
12
|
+
implementationWriteBlockedReason,
|
|
13
|
+
integrationPlanBlockedReason,
|
|
14
|
+
integrationWriteBlockedReason,
|
|
15
|
+
} from "./messages.ts";
|
|
16
|
+
import {
|
|
17
|
+
DATA_SOURCE_CONTEXT_FIELDS,
|
|
18
|
+
IMPLEMENTATION_CONTRACT_FIELDS,
|
|
19
|
+
INTEGRATION_CONTEXT_FIELDS,
|
|
20
|
+
type ContractField,
|
|
21
|
+
} from "./prompt-policy.ts";
|
|
22
|
+
|
|
23
|
+
export const COSMIC_METADATA_EVIDENCE = "evidence/cosmic-metadata.json";
|
|
24
|
+
export const DATA_SOURCE_EVIDENCE = "evidence/data-source.md";
|
|
25
|
+
|
|
26
|
+
const DATA_SOURCE_KEYWORDS =
|
|
27
|
+
/数据源|元数据|表单|单据|字段|实体|分录|单据体|基础资料|物料|客户|供应商|Form\s*ID|FormId|formid|DynamicObject|DataSet|SQL|KSQL|查询|保存|校验|插件|事件|操作|列表|表名|数据库|dbName|dbKey|下推|上推|转换|生成单据/i;
|
|
28
|
+
const IMPLEMENTATION_KEYWORDS =
|
|
29
|
+
/插件|服务|处理器|二开|开发|实现|新增|修改|保存|校验|联动|下推|上推|转换|生成|同步|接口|审批|提交|审核|操作|按钮|定时|任务|脚本|SQL|KSQL/i;
|
|
30
|
+
const INTEGRATION_KEYWORDS =
|
|
31
|
+
/第三方|外部系统|外部接口|接口对接|对接|接口文档|HTTP|HTTPS|REST|SOAP|Webhook|webhook|回调|推送|拉取|同步|上报|下发|开放平台|API\s*接口/i;
|
|
32
|
+
|
|
33
|
+
const IMPLEMENTATION_FIELDS = Object.fromEntries(IMPLEMENTATION_CONTRACT_FIELDS.map((field) => [field.id, field])) as Record<string, ContractField>;
|
|
34
|
+
const DATA_SOURCE_FIELDS = Object.fromEntries(DATA_SOURCE_CONTEXT_FIELDS.map((field) => [field.id, field])) as Record<string, ContractField>;
|
|
35
|
+
const INTEGRATION_FIELDS = Object.fromEntries(INTEGRATION_CONTEXT_FIELDS.map((field) => [field.id, field])) as Record<string, ContractField>;
|
|
36
|
+
|
|
37
|
+
export function requiresDataSourceEvidence(cwd: string, run: ActiveRun): boolean {
|
|
38
|
+
if (!run.profile?.requiresMetadataVerification) return false;
|
|
39
|
+
if (run.profile.product === "unknown") return false;
|
|
40
|
+
return DATA_SOURCE_KEYWORDS.test(dataSourcePlanningText(cwd, run, { includePlan: false })) || planDeclaresConcreteDataSourceWork(cwd, run);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function dataSourceEvidenceForRun(run: ActiveRun): string | undefined {
|
|
44
|
+
if (!run.profile?.requiresMetadataVerification) return undefined;
|
|
45
|
+
if (run.profile.platform === "cosmic") return COSMIC_METADATA_EVIDENCE;
|
|
46
|
+
if (run.profile.product === "enterprise") return DATA_SOURCE_EVIDENCE;
|
|
47
|
+
return DATA_SOURCE_EVIDENCE;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function hasValidDataSourceEvidence(cwd: string, run: ActiveRun): boolean {
|
|
51
|
+
const evidence = dataSourceEvidenceForRun(run);
|
|
52
|
+
if (!evidence) return true;
|
|
53
|
+
const path = join(runRoot(cwd, run), evidence);
|
|
54
|
+
if (!existsSync(path)) return false;
|
|
55
|
+
if (!hasEvidenceEntry(cwd, run, evidence)) return false;
|
|
56
|
+
if (evidence === COSMIC_METADATA_EVIDENCE) return true;
|
|
57
|
+
return dataSourceEvidenceContentLooksConcrete(readFileSync(path, "utf8"));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function dataSourceProductionWriteBlockReason(cwd: string, run: ActiveRun | undefined, path: string | undefined): string | undefined {
|
|
61
|
+
if (!run || run.phase !== "execute") return undefined;
|
|
62
|
+
if (!path || !isSourceLikePath(path)) return undefined;
|
|
63
|
+
|
|
64
|
+
const normalized = normalizeRelativePath(cwd && isAbsolute(path) ? relative(cwd, path) : path);
|
|
65
|
+
if (normalized.startsWith(".pi/")) return undefined;
|
|
66
|
+
|
|
67
|
+
const implementationMissing = missingImplementationContractItems(cwd, run);
|
|
68
|
+
if (implementationMissing.length > 0) return implementationWriteBlockedReason(normalized, implementationMissing);
|
|
69
|
+
|
|
70
|
+
const integrationMissing = missingIntegrationContextItems(cwd, run);
|
|
71
|
+
if (integrationMissing.length > 0) return integrationWriteBlockedReason(normalized, integrationMissing);
|
|
72
|
+
|
|
73
|
+
if (!requiresDataSourceEvidence(cwd, run)) return undefined;
|
|
74
|
+
if (hasValidDataSourceEvidence(cwd, run)) return undefined;
|
|
75
|
+
|
|
76
|
+
return dataSourceWriteBlockedReason(normalized, dataSourceEvidenceForRun(run) ?? DATA_SOURCE_EVIDENCE);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function dataSourceContextBlockReason(cwd: string, run: ActiveRun): string | undefined {
|
|
80
|
+
const implementationMissing = missingImplementationContractItems(cwd, run);
|
|
81
|
+
if (implementationMissing.length > 0) return implementationPlanBlockedReason(implementationMissing);
|
|
82
|
+
|
|
83
|
+
const integrationMissing = missingIntegrationContextItems(cwd, run);
|
|
84
|
+
if (integrationMissing.length > 0) return integrationPlanBlockedReason(integrationMissing);
|
|
85
|
+
|
|
86
|
+
if (!requiresDataSourceEvidence(cwd, run)) return undefined;
|
|
87
|
+
const missing = missingDataSourceContextItems(cwd, run);
|
|
88
|
+
return missing.length > 0 ? dataSourcePlanBlockedReason(missing) : undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function dataSourcePlanningText(cwd: string, run: ActiveRun, options: { includePlan?: boolean } = { includePlan: true }): string {
|
|
92
|
+
return [run.goal ?? "", readArtifact(cwd, run, "spec") ?? "", options.includePlan === false ? "" : readArtifact(cwd, run, "plan") ?? ""].join("\n");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function planDeclaresConcreteDataSourceWork(cwd: string, run: ActiveRun): boolean {
|
|
96
|
+
const plan = readArtifact(cwd, run, "plan") ?? "";
|
|
97
|
+
const withoutBoilerplate = plan
|
|
98
|
+
.split(/\r?\n/)
|
|
99
|
+
.filter((line) => !/必须先确认真实数据源|数据源证据|进入 execute 前必须写明|不能只根据 API 文档|必需的金蝶查证项/.test(line))
|
|
100
|
+
.join("\n");
|
|
101
|
+
return DATA_SOURCE_KEYWORDS.test(withoutBoilerplate);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function missingImplementationContractItems(cwd: string, run: ActiveRun): string[] {
|
|
105
|
+
if (!requiresImplementationContract(cwd, run)) return [];
|
|
106
|
+
const text = dataSourcePlanningText(cwd, run);
|
|
107
|
+
const missing: string[] = [];
|
|
108
|
+
|
|
109
|
+
if (!hasTriggerOrEntry(text)) missing.push(IMPLEMENTATION_FIELDS.trigger.label);
|
|
110
|
+
if (!hasSourceObject(text)) missing.push(IMPLEMENTATION_FIELDS.source.label);
|
|
111
|
+
if (!hasTargetObject(text)) missing.push(IMPLEMENTATION_FIELDS.target.label);
|
|
112
|
+
if (!hasDataChangeContract(text)) missing.push(IMPLEMENTATION_FIELDS["data-change"].label);
|
|
113
|
+
if (!hasRuleOrCondition(text)) missing.push(IMPLEMENTATION_FIELDS.rules.label);
|
|
114
|
+
if (!hasFailureContract(text)) missing.push(IMPLEMENTATION_FIELDS.failure.label);
|
|
115
|
+
if (!hasAcceptanceSample(text)) missing.push(IMPLEMENTATION_FIELDS.acceptance.label);
|
|
116
|
+
|
|
117
|
+
return missing;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function requiresImplementationContract(cwd: string, run: ActiveRun): boolean {
|
|
121
|
+
if (!run.profile?.requiresMetadataVerification || run.profile.product === "unknown") return false;
|
|
122
|
+
return IMPLEMENTATION_KEYWORDS.test(dataSourcePlanningText(cwd, run, { includePlan: false })) || planDeclaresConcreteImplementationWork(cwd, run);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function planDeclaresConcreteImplementationWork(cwd: string, run: ActiveRun): boolean {
|
|
126
|
+
const plan = readArtifact(cwd, run, "plan") ?? "";
|
|
127
|
+
const withoutBoilerplate = plan
|
|
128
|
+
.split(/\r?\n/)
|
|
129
|
+
.filter((line) => !/实现就绪|进入 execute 前必须写明|第三方对接必须写明|必需的金蝶查证项|不要写模板代码/.test(line))
|
|
130
|
+
.join("\n");
|
|
131
|
+
return IMPLEMENTATION_KEYWORDS.test(withoutBoilerplate);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function missingDataSourceContextItems(cwd: string, run: ActiveRun): string[] {
|
|
135
|
+
const text = dataSourcePlanningText(cwd, run);
|
|
136
|
+
const missing: string[] = [];
|
|
137
|
+
|
|
138
|
+
if (!hasTargetObjectIdentifier(text)) missing.push(DATA_SOURCE_FIELDS["target-form"].label);
|
|
139
|
+
if (!hasPluginHook(text)) missing.push(DATA_SOURCE_FIELDS["plugin-hook"].label);
|
|
140
|
+
if (!hasFieldOrEntityIdentifier(text)) missing.push(DATA_SOURCE_FIELDS["field-entity"].label);
|
|
141
|
+
if (!hasDataAccessStrategy(text)) missing.push(DATA_SOURCE_FIELDS["data-access"].label);
|
|
142
|
+
if (usesSql(text) && !hasSqlIdentifier(text)) missing.push(DATA_SOURCE_FIELDS["sql-identifiers"].label);
|
|
143
|
+
|
|
144
|
+
return missing;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function hasTriggerOrEntry(text: string): boolean {
|
|
148
|
+
if (hasLabeledValue(text, IMPLEMENTATION_FIELDS.trigger)) return true;
|
|
149
|
+
return /(?:触发入口|执行时机|触发时机|入口|事件|生命周期|插件类型|按钮|操作|定时|任务|保存前|保存后|提交后|审核后|Before\w+|After\w+)\s*[::=]\s*[^。\n\r,,;;]{2,}|(?:保存前|保存后|提交后|审核后|定时|按钮点击|操作执行)/i.test(text);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function hasSourceObject(text: string): boolean {
|
|
153
|
+
if (hasLabeledValue(text, IMPLEMENTATION_FIELDS.source)) return true;
|
|
154
|
+
return /(?:源对象|源单|来源单据|来源表单|输入数据|数据来源|取数来源|源 FormId|源FormId|源表|源实体)\s*[::=]\s*[^。\n\r,,;;]{2,}|(?:从|读取|基于).{0,20}(?:单据|表单|实体|字段|DynamicObject|DataSet|SQL|KSQL)/i.test(text);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function hasTargetObject(text: string): boolean {
|
|
158
|
+
if (hasLabeledValue(text, IMPLEMENTATION_FIELDS.target)) return true;
|
|
159
|
+
return /(?:目标对象|目标单据|目标表单|目标 FormId|目标FormId|输出结果|生成结果|下游单据|目标表|目标实体)\s*[::=]\s*[^。\n\r,,;;]{2,}|(?:下推到|生成|写入|更新|推送到).{0,20}(?:单据|表单|实体|字段|接口|系统|表)/i.test(text);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function hasDataChangeContract(text: string): boolean {
|
|
163
|
+
if (hasLabeledValue(text, IMPLEMENTATION_FIELDS["data-change"])) return true;
|
|
164
|
+
return /(?:数据变化|字段映射|字段对应|字段改写|修改字段|写入字段|赋值规则|转换规则|映射关系|更新字段)\s*[::=]\s*[^。\n\r;;]{2,}|(?:字段|field).{0,20}(?:->|映射|对应|写入|修改|赋值|更新)/i.test(text);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function hasRuleOrCondition(text: string): boolean {
|
|
168
|
+
if (hasLabeledValue(text, IMPLEMENTATION_FIELDS.rules)) return true;
|
|
169
|
+
return /(?:业务规则|适用条件|过滤条件|执行条件|前置条件|判断条件|范围|何时执行|哪些数据)\s*[::=]\s*[^。\n\r,,;;]{2,}|(?:当|如果|仅|只处理|满足).{0,40}(?:时|则|才)/i.test(text);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function hasFailureContract(text: string): boolean {
|
|
173
|
+
if (hasLabeledValue(text, IMPLEMENTATION_FIELDS.failure)) return true;
|
|
174
|
+
return /(?:失败处理|异常处理|错误处理|回滚|补偿|人工处理|失败后|重复执行|幂等|日志|告警)\s*[::=]\s*[^。\n\r,,;;]{2,}/i.test(text);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function missingIntegrationContextItems(cwd: string, run: ActiveRun): string[] {
|
|
178
|
+
const text = dataSourcePlanningText(cwd, run);
|
|
179
|
+
if (!requiresIntegrationContext(cwd, run)) return [];
|
|
180
|
+
|
|
181
|
+
const missing: string[] = [];
|
|
182
|
+
if (!hasInterfaceDocument(text)) missing.push(INTEGRATION_FIELDS["interface-doc"].label);
|
|
183
|
+
if (!hasIntegrationDirection(text)) missing.push(INTEGRATION_FIELDS.direction.label);
|
|
184
|
+
if (!hasEndpointAndAuth(text)) missing.push(INTEGRATION_FIELDS["endpoint-auth"].label);
|
|
185
|
+
if (!hasFieldMapping(text)) missing.push(INTEGRATION_FIELDS["field-mapping"].label);
|
|
186
|
+
if (!hasConcurrencyStrategy(text)) missing.push(INTEGRATION_FIELDS.concurrency.label);
|
|
187
|
+
if (!hasRetryTimeoutStrategy(text)) missing.push(INTEGRATION_FIELDS.retry.label);
|
|
188
|
+
if (!hasErrorHandlingStrategy(text)) missing.push(INTEGRATION_FIELDS.error.label);
|
|
189
|
+
if (!hasLoggingStrategy(text)) missing.push(INTEGRATION_FIELDS.logging.label);
|
|
190
|
+
if (!hasAcceptanceSample(text)) missing.push(INTEGRATION_FIELDS.samples.label);
|
|
191
|
+
return missing;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function requiresIntegrationContext(cwd: string, run: ActiveRun): boolean {
|
|
195
|
+
return INTEGRATION_KEYWORDS.test(dataSourcePlanningText(cwd, run, { includePlan: false })) || planDeclaresConcreteIntegrationWork(cwd, run);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function planDeclaresConcreteIntegrationWork(cwd: string, run: ActiveRun): boolean {
|
|
199
|
+
const plan = readArtifact(cwd, run, "plan") ?? "";
|
|
200
|
+
const withoutBoilerplate = plan
|
|
201
|
+
.split(/\r?\n/)
|
|
202
|
+
.filter((line) => !/第三方对接必须写明|接口文档来源\/版本|没有这些信息只能写模板代码|必需的金蝶查证项/.test(line))
|
|
203
|
+
.join("\n");
|
|
204
|
+
return INTEGRATION_KEYWORDS.test(withoutBoilerplate);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function hasInterfaceDocument(text: string): boolean {
|
|
208
|
+
if (hasLabeledValue(text, INTEGRATION_FIELDS["interface-doc"])) return true;
|
|
209
|
+
return /(?:接口文档|API\s*文档|文档来源|协议文档|OpenAPI|Swagger)\s*[::=]\s*[^。\n\r,,;;]{2,}|(?:https?:\/\/|\.pdf|\.docx?|\.xlsx?|swagger\.json|openapi\.json)/i.test(text);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function hasIntegrationDirection(text: string): boolean {
|
|
213
|
+
if (hasLabeledValue(text, INTEGRATION_FIELDS.direction)) return true;
|
|
214
|
+
return /(?:对接方向|触发时机|同步方向|数据流向|调用方|被调用方)[^::=\n\r]{0,24}[::=]\s*[^。\n\r,,;;]{2,}|(?:金蝶调用第三方|第三方调用金蝶|入站|出站|推送|拉取|回调|定时|保存后|审核后|提交后)/i.test(text);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function hasEndpointAndAuth(text: string): boolean {
|
|
218
|
+
if (hasLabeledValue(text, INTEGRATION_FIELDS["endpoint-auth"])) return true;
|
|
219
|
+
return /(?:接口地址|Endpoint|URL|Base\s*URL|认证|鉴权|Token|AK|SK|AppKey|AppSecret|密钥|签名|OAuth)[^::=\n\r]{0,24}[::=]\s*[^。\n\r,,;;]{2,}/i.test(text);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function hasFieldMapping(text: string): boolean {
|
|
223
|
+
if (hasLabeledValue(text, INTEGRATION_FIELDS["field-mapping"])) return true;
|
|
224
|
+
return /(?:字段映射|映射关系|字段对应|字段对照|mapping)\s*[::=]\s*[^。\n\r;;]{2,}|(?:第三方字段|外部字段).*(?:金蝶字段|单据字段|字段标识)/is.test(text);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function hasConcurrencyStrategy(text: string): boolean {
|
|
228
|
+
if (hasLabeledValue(text, INTEGRATION_FIELDS.concurrency)) return true;
|
|
229
|
+
return /(?:并发|幂等|去重|唯一键|业务主键|重复提交|锁|乐观锁|重入)[^::=\n\r]{0,24}[::=]\s*[^。\n\r,,;;]{2,}/i.test(text);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function hasRetryTimeoutStrategy(text: string): boolean {
|
|
233
|
+
if (hasLabeledValue(text, INTEGRATION_FIELDS.retry)) return true;
|
|
234
|
+
return /(?:超时|timeout|重试|retry|限流|频率|rate\s*limit|熔断)[^::=\n\r]{0,24}[::=]\s*[^。\n\r,,;;]{2,}/i.test(text);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function hasErrorHandlingStrategy(text: string): boolean {
|
|
238
|
+
if (hasLabeledValue(text, INTEGRATION_FIELDS.error)) return true;
|
|
239
|
+
return /(?:错误处理|异常处理|失败处理|失败补偿|补偿|回滚|死信|告警|人工处理)[^::=\n\r]{0,24}[::=]\s*[^。\n\r,,;;]{2,}/i.test(text);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function hasLoggingStrategy(text: string): boolean {
|
|
243
|
+
if (hasLabeledValue(text, INTEGRATION_FIELDS.logging)) return true;
|
|
244
|
+
return /(?:日志|审计|留痕|脱敏|敏感信息|traceId|requestId|请求日志|响应日志)[^::=\n\r]{0,24}[::=]\s*[^。\n\r,,;;]{2,}/i.test(text);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function hasAcceptanceSample(text: string): boolean {
|
|
248
|
+
if (hasLabeledValue(text, IMPLEMENTATION_FIELDS.acceptance) || hasLabeledValue(text, INTEGRATION_FIELDS.samples)) return true;
|
|
249
|
+
return /(?:请求样例|响应样例|报文样例|验收样例|测试数据|示例报文|payload)\s*[::=]\s*[^。\n\r;;]{2,}/i.test(text);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function hasTargetObjectIdentifier(text: string): boolean {
|
|
253
|
+
if (hasLabeledValue(text, DATA_SOURCE_FIELDS["target-form"])) return true;
|
|
254
|
+
return /(?:Form\s*ID|FormId|formid|表单标识|单据标识|目标单据|目标表单)\s*[::=]\s*[A-Za-z0-9_.-]{2,}|(?:单据|表单)\s*[::=]\s*[^。\n\r,,;;]{2,}/i.test(text);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function hasPluginHook(text: string): boolean {
|
|
258
|
+
if (hasLabeledValue(text, DATA_SOURCE_FIELDS["plugin-hook"])) return true;
|
|
259
|
+
return /(?:插件类型|插件|事件|生命周期|触发时机|挂载点)\s*[::=]\s*[^。\n\r,,;;]{2,}|Before\w+|After\w+|Save|保存前|保存后|提交|审核|字段值改变|F7|列表/i.test(text);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function hasFieldOrEntityIdentifier(text: string): boolean {
|
|
263
|
+
if (hasLabeledValue(text, DATA_SOURCE_FIELDS["field-entity"])) return true;
|
|
264
|
+
return /(?:字段标识|字段|实体标识|实体|分录标识|单据体标识|Entry|Entity)\s*[::=]\s*[^。\n\r,,;;]{2,}|[A-Za-z][A-Za-z0-9_]*(?:Id|ID|No|Name|Qty|Amount)\b/.test(text);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function hasDataAccessStrategy(text: string): boolean {
|
|
268
|
+
if (hasLabeledValue(text, DATA_SOURCE_FIELDS["data-access"])) return true;
|
|
269
|
+
return /(?:读取方式|写入方式|数据访问|取数方式|数据源|读写策略)\s*[::=]\s*[^。\n\r,,;;]{2,}|(?:表单数据|模型数据|单据数据|DynamicObject|DataSet|QueryService|BusinessDataService|SQL|KSQL|直接读表|数据库查询)/i.test(text);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function usesSql(text: string): boolean {
|
|
273
|
+
const normalized = text.replace(/不(?:需要|使用|直接)?读表|不使用\s*(?:SQL|KSQL)|无需\s*(?:SQL|KSQL)|非\s*(?:SQL|KSQL)/gi, "");
|
|
274
|
+
return /\bSQL\b|\bKSQL\b|直接读表|数据库查询|表名/i.test(normalized);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function hasSqlIdentifier(text: string): boolean {
|
|
278
|
+
if (hasLabeledValue(text, DATA_SOURCE_FIELDS["sql-identifiers"])) return true;
|
|
279
|
+
return /(?:表名|数据库表|SQL\s*表|KSQL\s*表)\s*[::=]\s*[A-Za-z0-9_.$-]{2,}/i.test(text) && /(?:数据库字段|SQL\s*字段|字段名|列名)\s*[::=]\s*[A-Za-z0-9_.$,\s-]{2,}/i.test(text);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function dataSourceEvidenceContentLooksConcrete(content: string): boolean {
|
|
283
|
+
const text = content.trim();
|
|
284
|
+
if (text.length < 30) return false;
|
|
285
|
+
if (/待确认|未知|TODO|TBD|未提供|无数据源/i.test(text)) return false;
|
|
286
|
+
return /Form\s*ID|FormId|formid|单据|表单|字段|实体|表名|数据源|BOS|DynamicObject|DataSet|数据库|测试数据/i.test(text);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function normalizeRelativePath(path: string): string {
|
|
290
|
+
return path.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function hasLabeledValue(text: string, field: ContractField): boolean {
|
|
294
|
+
return field.aliases.some((alias) => {
|
|
295
|
+
const escaped = escapeRegExp(alias);
|
|
296
|
+
return new RegExp(`(?:^|[\\r\\n\\-\\*\\s])${escaped}\\s*[::=]\\s*[^\\r\\n。;;,,]{2,}`, "i").test(text);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function escapeRegExp(value: string): string {
|
|
301
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
302
|
+
}
|