kcode-pi 0.1.34 → 0.1.38

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.
Files changed (43) hide show
  1. package/README.md +10 -10
  2. package/dist/cli/kcode.js +3 -3
  3. package/dist/context/project-context.js +4 -5
  4. package/dist/harness/prompt-policy.d.ts +19 -0
  5. package/dist/harness/prompt-policy.js +206 -0
  6. package/dist/harness/types.d.ts +74 -0
  7. package/dist/harness/types.js +16 -0
  8. package/dist/product/profile.d.ts +20 -0
  9. package/dist/product/profile.js +103 -0
  10. package/docs/CHANGELOG.md +90 -2
  11. package/docs/COMMAND_REFERENCE.md +27 -12
  12. package/docs/DEVELOPMENT.md +3 -3
  13. package/docs/EVIDENCE_AND_GATES.md +15 -8
  14. package/docs/HARNESS_WORKFLOW.md +32 -12
  15. package/docs/KCODE_DISTRIBUTION.md +7 -7
  16. package/docs/PRODUCT_PROFILE.md +9 -9
  17. package/docs/TROUBLESHOOTING.md +8 -8
  18. package/docs/USER_GUIDE.md +10 -10
  19. package/extensions/kingdee-harness.ts +141 -86
  20. package/extensions/kingdee-header.ts +1 -1
  21. package/extensions/kingdee-subagents.ts +1 -1
  22. package/extensions/kingdee-tools.ts +44 -44
  23. package/package.json +1 -1
  24. package/src/cli/kcode.ts +3 -3
  25. package/src/context/project-context.ts +4 -5
  26. package/src/harness/artifacts.ts +6 -7
  27. package/src/harness/data-source-policy.ts +346 -0
  28. package/src/harness/delegation.ts +28 -23
  29. package/src/harness/gates.ts +16 -1
  30. package/src/harness/messages.ts +65 -11
  31. package/src/harness/path-policy.ts +1 -0
  32. package/src/harness/plan-steps.ts +3 -3
  33. package/src/harness/prompt-policy.ts +227 -0
  34. package/src/harness/prompt.ts +12 -16
  35. package/src/harness/question-memory.ts +220 -0
  36. package/src/harness/repair.ts +18 -3
  37. package/src/harness/state.ts +93 -6
  38. package/src/harness/types.ts +19 -0
  39. package/src/official/kingdee-skills.ts +4 -4
  40. package/src/product/profile.ts +2 -2
  41. package/src/rules/checker.ts +27 -27
  42. package/src/tools/build-debug.ts +5 -5
  43. package/src/tools/sdk-signature.ts +4 -4
@@ -0,0 +1,346 @@
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, readEvidenceIndex } 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
+ import { formatAnsweredQuestionFacts } from "./question-memory.ts";
23
+
24
+ export const COSMIC_METADATA_EVIDENCE = "evidence/cosmic-metadata.json";
25
+ export const DATA_SOURCE_EVIDENCE = "evidence/data-source.md";
26
+
27
+ const DATA_SOURCE_KEYWORDS =
28
+ /数据源|元数据|表单|单据|字段|实体|分录|单据体|基础资料|物料|客户|供应商|Form\s*ID|FormId|formid|DynamicObject|DataSet|SQL|KSQL|查询|保存|校验|插件|事件|操作|列表|表名|数据库|dbName|dbKey|下推|上推|转换|生成单据/i;
29
+ const IMPLEMENTATION_KEYWORDS =
30
+ /插件|服务|处理器|二开|开发|实现|新增|修改|保存|校验|联动|下推|上推|转换|生成|同步|接口|审批|提交|审核|操作|按钮|定时|任务|脚本|SQL|KSQL/i;
31
+ const INTEGRATION_KEYWORDS =
32
+ /第三方|外部系统|外部接口|接口对接|对接|接口文档|HTTP|HTTPS|REST|SOAP|Webhook|webhook|回调|推送|拉取|同步|上报|下发|开放平台|API\s*接口/i;
33
+
34
+ const IMPLEMENTATION_FIELDS = Object.fromEntries(IMPLEMENTATION_CONTRACT_FIELDS.map((field) => [field.id, field])) as Record<string, ContractField>;
35
+ const DATA_SOURCE_FIELDS = Object.fromEntries(DATA_SOURCE_CONTEXT_FIELDS.map((field) => [field.id, field])) as Record<string, ContractField>;
36
+ const INTEGRATION_FIELDS = Object.fromEntries(INTEGRATION_CONTEXT_FIELDS.map((field) => [field.id, field])) as Record<string, ContractField>;
37
+
38
+ export function requiresDataSourceEvidence(cwd: string, run: ActiveRun): boolean {
39
+ if (!run.profile?.requiresMetadataVerification) return false;
40
+ if (run.profile.product === "unknown") return false;
41
+ return DATA_SOURCE_KEYWORDS.test(dataSourcePlanningText(cwd, run, { includePlan: false })) || planDeclaresConcreteDataSourceWork(cwd, run);
42
+ }
43
+
44
+ export function dataSourceEvidenceForRun(run: ActiveRun): string | undefined {
45
+ if (!run.profile?.requiresMetadataVerification) return undefined;
46
+ if (run.profile.platform === "cosmic") return COSMIC_METADATA_EVIDENCE;
47
+ if (run.profile.product === "enterprise") return DATA_SOURCE_EVIDENCE;
48
+ return DATA_SOURCE_EVIDENCE;
49
+ }
50
+
51
+ export function hasValidDataSourceEvidence(cwd: string, run: ActiveRun): boolean {
52
+ const evidence = dataSourceEvidenceForRun(run);
53
+ if (!evidence) return true;
54
+ const path = join(runRoot(cwd, run), evidence);
55
+ if (!existsSync(path)) return false;
56
+ if (!hasEvidenceEntry(cwd, run, evidence)) return false;
57
+ if (!hasSuccessfulEvidenceEntry(cwd, run, evidence)) return false;
58
+ const content = readFileSync(path, "utf8");
59
+ if (evidence === COSMIC_METADATA_EVIDENCE) return cosmicMetadataLooksConcrete(content);
60
+ return dataSourceEvidenceContentLooksConcrete(content);
61
+ }
62
+
63
+ export function dataSourceProductionWriteBlockReason(cwd: string, run: ActiveRun | undefined, path: string | undefined): string | undefined {
64
+ if (!run || run.phase !== "execute") return undefined;
65
+ if (!path || !isSourceLikePath(path)) return undefined;
66
+
67
+ const normalized = normalizeRelativePath(cwd && isAbsolute(path) ? relative(cwd, path) : path);
68
+ if (normalized.startsWith(".pi/")) return undefined;
69
+
70
+ const implementationMissing = missingImplementationContractItems(cwd, run);
71
+ if (implementationMissing.length > 0) return implementationWriteBlockedReason(normalized, implementationMissing);
72
+
73
+ const integrationMissing = missingIntegrationContextItems(cwd, run);
74
+ if (integrationMissing.length > 0) return integrationWriteBlockedReason(normalized, integrationMissing);
75
+
76
+ if (!requiresDataSourceEvidence(cwd, run)) return undefined;
77
+ if (hasValidDataSourceEvidence(cwd, run)) return undefined;
78
+
79
+ return dataSourceWriteBlockedReason(normalized, dataSourceEvidenceForRun(run) ?? DATA_SOURCE_EVIDENCE);
80
+ }
81
+
82
+ export function dataSourceContextBlockReason(cwd: string, run: ActiveRun): string | undefined {
83
+ const implementationMissing = missingImplementationContractItems(cwd, run);
84
+ if (implementationMissing.length > 0) return implementationPlanBlockedReason(implementationMissing);
85
+
86
+ const integrationMissing = missingIntegrationContextItems(cwd, run);
87
+ if (integrationMissing.length > 0) return integrationPlanBlockedReason(integrationMissing);
88
+
89
+ if (!requiresDataSourceEvidence(cwd, run)) return undefined;
90
+ const missing = missingDataSourceContextItems(cwd, run);
91
+ return missing.length > 0 ? dataSourcePlanBlockedReason(missing) : undefined;
92
+ }
93
+
94
+ function dataSourcePlanningText(cwd: string, run: ActiveRun, options: { includePlan?: boolean } = { includePlan: true }): string {
95
+ return [
96
+ run.goal ?? "",
97
+ readArtifact(cwd, run, "spec") ?? "",
98
+ formatAnsweredQuestionFacts(run),
99
+ options.includePlan === false ? "" : readArtifact(cwd, run, "plan") ?? "",
100
+ ].join("\n");
101
+ }
102
+
103
+ function planDeclaresConcreteDataSourceWork(cwd: string, run: ActiveRun): boolean {
104
+ const plan = readArtifact(cwd, run, "plan") ?? "";
105
+ const withoutBoilerplate = plan
106
+ .split(/\r?\n/)
107
+ .filter((line) => !/必须先确认真实数据源|数据源证据|进入 execute 前必须写明|不能只根据 API 文档|必需的金蝶查证项/.test(line))
108
+ .join("\n");
109
+ return DATA_SOURCE_KEYWORDS.test(withoutBoilerplate);
110
+ }
111
+
112
+ function missingImplementationContractItems(cwd: string, run: ActiveRun): string[] {
113
+ if (!requiresImplementationContract(cwd, run)) return [];
114
+ const text = dataSourcePlanningText(cwd, run);
115
+ const missing: string[] = [];
116
+
117
+ if (!hasTriggerOrEntry(text)) missing.push(IMPLEMENTATION_FIELDS.trigger.label);
118
+ if (!hasSourceObject(text)) missing.push(IMPLEMENTATION_FIELDS.source.label);
119
+ if (!hasTargetObject(text)) missing.push(IMPLEMENTATION_FIELDS.target.label);
120
+ if (!hasDataChangeContract(text)) missing.push(IMPLEMENTATION_FIELDS["data-change"].label);
121
+ if (!hasRuleOrCondition(text)) missing.push(IMPLEMENTATION_FIELDS.rules.label);
122
+ if (!hasFailureContract(text)) missing.push(IMPLEMENTATION_FIELDS.failure.label);
123
+ if (!hasAcceptanceSample(text)) missing.push(IMPLEMENTATION_FIELDS.acceptance.label);
124
+
125
+ return missing;
126
+ }
127
+
128
+ function requiresImplementationContract(cwd: string, run: ActiveRun): boolean {
129
+ if (!run.profile?.requiresMetadataVerification || run.profile.product === "unknown") return false;
130
+ return IMPLEMENTATION_KEYWORDS.test(dataSourcePlanningText(cwd, run, { includePlan: false })) || planDeclaresConcreteImplementationWork(cwd, run);
131
+ }
132
+
133
+ function planDeclaresConcreteImplementationWork(cwd: string, run: ActiveRun): boolean {
134
+ const plan = readArtifact(cwd, run, "plan") ?? "";
135
+ const withoutBoilerplate = plan
136
+ .split(/\r?\n/)
137
+ .filter((line) => !/实现就绪|进入 execute 前必须写明|第三方对接必须写明|必需的金蝶查证项|不要写模板代码/.test(line))
138
+ .join("\n");
139
+ return IMPLEMENTATION_KEYWORDS.test(withoutBoilerplate);
140
+ }
141
+
142
+ function missingDataSourceContextItems(cwd: string, run: ActiveRun): string[] {
143
+ const text = trustedDataSourceContextText(cwd, run);
144
+ const missing: string[] = [];
145
+
146
+ if (!hasTargetObjectIdentifier(text)) missing.push(DATA_SOURCE_FIELDS["target-form"].label);
147
+ if (!hasPluginHook(text)) missing.push(DATA_SOURCE_FIELDS["plugin-hook"].label);
148
+ if (!hasFieldOrEntityIdentifier(text)) missing.push(DATA_SOURCE_FIELDS["field-entity"].label);
149
+ if (!hasDataAccessStrategy(text)) missing.push(DATA_SOURCE_FIELDS["data-access"].label);
150
+ if (usesSql(text) && !hasSqlIdentifier(text)) missing.push(DATA_SOURCE_FIELDS["sql-identifiers"].label);
151
+
152
+ return missing;
153
+ }
154
+
155
+ function trustedDataSourceContextText(cwd: string, run: ActiveRun): string {
156
+ return [formatAnsweredQuestionFacts(run), dataSourceEvidenceContent(cwd, run)].join("\n");
157
+ }
158
+
159
+ function dataSourceEvidenceContent(cwd: string, run: ActiveRun): string {
160
+ const evidence = dataSourceEvidenceForRun(run);
161
+ if (!evidence) return "";
162
+ const path = join(runRoot(cwd, run), evidence);
163
+ return existsSync(path) ? readFileSync(path, "utf8") : "";
164
+ }
165
+
166
+ function hasTriggerOrEntry(text: string): boolean {
167
+ if (hasLabeledValue(text, IMPLEMENTATION_FIELDS.trigger)) return true;
168
+ return /(?:触发入口|执行时机|触发时机|入口|事件|生命周期|插件类型|按钮|操作|定时|任务|保存前|保存后|提交后|审核后|Before\w+|After\w+)\s*[::=]\s*[^。\n\r,,;;]{2,}|(?:保存前|保存后|提交后|审核后|定时|按钮点击|操作执行)/i.test(text);
169
+ }
170
+
171
+ function hasSourceObject(text: string): boolean {
172
+ if (hasLabeledValue(text, IMPLEMENTATION_FIELDS.source)) return true;
173
+ return /(?:源对象|源单|来源单据|来源表单|输入数据|数据来源|取数来源|源 FormId|源FormId|源表|源实体)\s*[::=]\s*[^。\n\r,,;;]{2,}|(?:从|读取|基于).{0,20}(?:单据|表单|实体|字段|DynamicObject|DataSet|SQL|KSQL)/i.test(text);
174
+ }
175
+
176
+ function hasTargetObject(text: string): boolean {
177
+ if (hasLabeledValue(text, IMPLEMENTATION_FIELDS.target)) return true;
178
+ return /(?:目标对象|目标单据|目标表单|目标 FormId|目标FormId|输出结果|生成结果|下游单据|目标表|目标实体)\s*[::=]\s*[^。\n\r,,;;]{2,}|(?:下推到|生成|写入|更新|推送到).{0,20}(?:单据|表单|实体|字段|接口|系统|表)/i.test(text);
179
+ }
180
+
181
+ function hasDataChangeContract(text: string): boolean {
182
+ if (hasLabeledValue(text, IMPLEMENTATION_FIELDS["data-change"])) return true;
183
+ return /(?:数据变化|字段映射|字段对应|字段改写|修改字段|写入字段|赋值规则|转换规则|映射关系|更新字段)\s*[::=]\s*[^。\n\r;;]{2,}|(?:字段|field).{0,20}(?:->|映射|对应|写入|修改|赋值|更新)/i.test(text);
184
+ }
185
+
186
+ function hasRuleOrCondition(text: string): boolean {
187
+ if (hasLabeledValue(text, IMPLEMENTATION_FIELDS.rules)) return true;
188
+ return /(?:业务规则|适用条件|过滤条件|执行条件|前置条件|判断条件|范围|何时执行|哪些数据)\s*[::=]\s*[^。\n\r,,;;]{2,}|(?:当|如果|仅|只处理|满足).{0,40}(?:时|则|才)/i.test(text);
189
+ }
190
+
191
+ function hasFailureContract(text: string): boolean {
192
+ if (hasLabeledValue(text, IMPLEMENTATION_FIELDS.failure)) return true;
193
+ return /(?:失败处理|异常处理|错误处理|回滚|补偿|人工处理|失败后|重复执行|幂等|日志|告警)\s*[::=]\s*[^。\n\r,,;;]{2,}/i.test(text);
194
+ }
195
+
196
+ function missingIntegrationContextItems(cwd: string, run: ActiveRun): string[] {
197
+ const text = dataSourcePlanningText(cwd, run);
198
+ if (!requiresIntegrationContext(cwd, run)) return [];
199
+
200
+ const missing: string[] = [];
201
+ if (!hasInterfaceDocument(text)) missing.push(INTEGRATION_FIELDS["interface-doc"].label);
202
+ if (!hasIntegrationDirection(text)) missing.push(INTEGRATION_FIELDS.direction.label);
203
+ if (!hasEndpointAndAuth(text)) missing.push(INTEGRATION_FIELDS["endpoint-auth"].label);
204
+ if (!hasFieldMapping(text)) missing.push(INTEGRATION_FIELDS["field-mapping"].label);
205
+ if (!hasConcurrencyStrategy(text)) missing.push(INTEGRATION_FIELDS.concurrency.label);
206
+ if (!hasRetryTimeoutStrategy(text)) missing.push(INTEGRATION_FIELDS.retry.label);
207
+ if (!hasErrorHandlingStrategy(text)) missing.push(INTEGRATION_FIELDS.error.label);
208
+ if (!hasLoggingStrategy(text)) missing.push(INTEGRATION_FIELDS.logging.label);
209
+ if (!hasAcceptanceSample(text)) missing.push(INTEGRATION_FIELDS.samples.label);
210
+ return missing;
211
+ }
212
+
213
+ function requiresIntegrationContext(cwd: string, run: ActiveRun): boolean {
214
+ return INTEGRATION_KEYWORDS.test(dataSourcePlanningText(cwd, run, { includePlan: false })) || planDeclaresConcreteIntegrationWork(cwd, run);
215
+ }
216
+
217
+ function planDeclaresConcreteIntegrationWork(cwd: string, run: ActiveRun): boolean {
218
+ const plan = readArtifact(cwd, run, "plan") ?? "";
219
+ const withoutBoilerplate = plan
220
+ .split(/\r?\n/)
221
+ .filter((line) => !/第三方对接必须写明|接口文档来源\/版本|没有这些信息只能写模板代码|必需的金蝶查证项/.test(line))
222
+ .join("\n");
223
+ return INTEGRATION_KEYWORDS.test(withoutBoilerplate);
224
+ }
225
+
226
+ function hasInterfaceDocument(text: string): boolean {
227
+ if (hasLabeledValue(text, INTEGRATION_FIELDS["interface-doc"])) return true;
228
+ return /(?:接口文档|API\s*文档|文档来源|协议文档|OpenAPI|Swagger)\s*[::=]\s*[^。\n\r,,;;]{2,}|(?:https?:\/\/|\.pdf|\.docx?|\.xlsx?|swagger\.json|openapi\.json)/i.test(text);
229
+ }
230
+
231
+ function hasIntegrationDirection(text: string): boolean {
232
+ if (hasLabeledValue(text, INTEGRATION_FIELDS.direction)) return true;
233
+ return /(?:对接方向|触发时机|同步方向|数据流向|调用方|被调用方)[^::=\n\r]{0,24}[::=]\s*[^。\n\r,,;;]{2,}|(?:金蝶调用第三方|第三方调用金蝶|入站|出站|推送|拉取|回调|定时|保存后|审核后|提交后)/i.test(text);
234
+ }
235
+
236
+ function hasEndpointAndAuth(text: string): boolean {
237
+ if (hasLabeledValue(text, INTEGRATION_FIELDS["endpoint-auth"])) return true;
238
+ return /(?:接口地址|Endpoint|URL|Base\s*URL|认证|鉴权|Token|AK|SK|AppKey|AppSecret|密钥|签名|OAuth)[^::=\n\r]{0,24}[::=]\s*[^。\n\r,,;;]{2,}/i.test(text);
239
+ }
240
+
241
+ function hasFieldMapping(text: string): boolean {
242
+ if (hasLabeledValue(text, INTEGRATION_FIELDS["field-mapping"])) return true;
243
+ return /(?:字段映射|映射关系|字段对应|字段对照|mapping)\s*[::=]\s*[^。\n\r;;]{2,}|(?:第三方字段|外部字段).*(?:金蝶字段|单据字段|字段标识)/is.test(text);
244
+ }
245
+
246
+ function hasConcurrencyStrategy(text: string): boolean {
247
+ if (hasLabeledValue(text, INTEGRATION_FIELDS.concurrency)) return true;
248
+ return /(?:并发|幂等|去重|唯一键|业务主键|重复提交|锁|乐观锁|重入)[^::=\n\r]{0,24}[::=]\s*[^。\n\r,,;;]{2,}/i.test(text);
249
+ }
250
+
251
+ function hasRetryTimeoutStrategy(text: string): boolean {
252
+ if (hasLabeledValue(text, INTEGRATION_FIELDS.retry)) return true;
253
+ return /(?:超时|timeout|重试|retry|限流|频率|rate\s*limit|熔断)[^::=\n\r]{0,24}[::=]\s*[^。\n\r,,;;]{2,}/i.test(text);
254
+ }
255
+
256
+ function hasErrorHandlingStrategy(text: string): boolean {
257
+ if (hasLabeledValue(text, INTEGRATION_FIELDS.error)) return true;
258
+ return /(?:错误处理|异常处理|失败处理|失败补偿|补偿|回滚|死信|告警|人工处理)[^::=\n\r]{0,24}[::=]\s*[^。\n\r,,;;]{2,}/i.test(text);
259
+ }
260
+
261
+ function hasLoggingStrategy(text: string): boolean {
262
+ if (hasLabeledValue(text, INTEGRATION_FIELDS.logging)) return true;
263
+ return /(?:日志|审计|留痕|脱敏|敏感信息|traceId|requestId|请求日志|响应日志)[^::=\n\r]{0,24}[::=]\s*[^。\n\r,,;;]{2,}/i.test(text);
264
+ }
265
+
266
+ function hasAcceptanceSample(text: string): boolean {
267
+ if (hasLabeledValue(text, IMPLEMENTATION_FIELDS.acceptance) || hasLabeledValue(text, INTEGRATION_FIELDS.samples)) return true;
268
+ return /(?:请求样例|响应样例|报文样例|验收样例|测试数据|示例报文|payload)\s*[::=]\s*[^。\n\r;;]{2,}/i.test(text);
269
+ }
270
+
271
+ function hasTargetObjectIdentifier(text: string): boolean {
272
+ if (hasLabeledValue(text, DATA_SOURCE_FIELDS["target-form"])) return true;
273
+ return /["']?(?:Form\s*ID|FormId|formId|formid|表单标识|单据标识|目标单据|目标表单)["']?\s*[::=]\s*["']?[A-Za-z0-9_.-]{2,}|(?:单据|表单)\s*[::=]\s*[^。\n\r,,;;]{2,}/i.test(text);
274
+ }
275
+
276
+ function hasPluginHook(text: string): boolean {
277
+ if (hasLabeledValue(text, DATA_SOURCE_FIELDS["plugin-hook"])) return true;
278
+ return /(?:插件类型|插件|事件|生命周期|触发时机|挂载点)\s*[::=]\s*[^。\n\r,,;;]{2,}|Before\w+|After\w+|Save|保存前|保存后|提交|审核|字段值改变|F7|列表/i.test(text);
279
+ }
280
+
281
+ function hasFieldOrEntityIdentifier(text: string): boolean {
282
+ if (hasLabeledValue(text, DATA_SOURCE_FIELDS["field-entity"])) return true;
283
+ return /(?:字段标识|字段|实体标识|实体|分录标识|单据体标识|Entry|Entity)\s*[::=]\s*[^。\n\r,,;;]{2,}|[A-Za-z][A-Za-z0-9_]*(?:Id|ID|No|Name|Qty|Amount)\b/.test(text);
284
+ }
285
+
286
+ function hasDataAccessStrategy(text: string): boolean {
287
+ if (hasLabeledValue(text, DATA_SOURCE_FIELDS["data-access"])) return true;
288
+ return /(?:读取方式|写入方式|数据访问|取数方式|数据源|读写策略)\s*[::=]\s*[^。\n\r,,;;]{2,}|(?:表单数据|模型数据|单据数据|DynamicObject|DataSet|QueryService|BusinessDataService|SQL|KSQL|直接读表|数据库查询)/i.test(text);
289
+ }
290
+
291
+ function usesSql(text: string): boolean {
292
+ const normalized = text.replace(/不(?:需要|使用|直接)?读表|不使用\s*(?:SQL|KSQL)|无需\s*(?:SQL|KSQL)|非\s*(?:SQL|KSQL)/gi, "");
293
+ return /\bSQL\b|\bKSQL\b|直接读表|数据库查询|表名/i.test(normalized);
294
+ }
295
+
296
+ function hasSqlIdentifier(text: string): boolean {
297
+ if (hasLabeledValue(text, DATA_SOURCE_FIELDS["sql-identifiers"])) return true;
298
+ 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);
299
+ }
300
+
301
+ function dataSourceEvidenceContentLooksConcrete(content: string): boolean {
302
+ const text = content.trim();
303
+ if (text.length < 30) return false;
304
+ if (/待确认|未知|TODO|TBD|未提供|无数据源/i.test(text)) return false;
305
+ return /Form\s*ID|FormId|formid|单据|表单|字段|实体|表名|数据源|BOS|DynamicObject|DataSet|数据库|测试数据/i.test(text);
306
+ }
307
+
308
+ function cosmicMetadataLooksConcrete(content: string): boolean {
309
+ try {
310
+ const parsed = JSON.parse(content) as unknown;
311
+ if (!parsed || (Array.isArray(parsed) && parsed.length === 0)) return false;
312
+ const text = JSON.stringify(parsed);
313
+ if (/待确认|未知|TODO|TBD|未提供/i.test(text)) return false;
314
+ return /Form\s*ID|FormId|formid|field|fields|entity|entities|字段|实体|单据体|name|key|id/i.test(text);
315
+ } catch {
316
+ return false;
317
+ }
318
+ }
319
+
320
+ function hasSuccessfulEvidenceEntry(cwd: string, run: ActiveRun, artifact: string): boolean {
321
+ const normalized = artifact.replace(/\\/g, "/").replace(/^\/+/, "").replace(/^\.\//, "");
322
+ const entry = readEvidenceIndex(cwd, run).entries.find((item) => typeof item.path === "string" && item.path.replace(/\\/g, "/") === normalized);
323
+ return Boolean(entry && entry.exitCode === 0);
324
+ }
325
+
326
+ function normalizeRelativePath(path: string): string {
327
+ return path.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
328
+ }
329
+
330
+ function hasLabeledValue(text: string, field: ContractField): boolean {
331
+ return [field.label, ...field.aliases].some((alias) => {
332
+ const escaped = escapeRegExp(alias);
333
+ const match = new RegExp(`(?:^|[\\r\\n\\-\\*\\s])${escaped}\\s*[::=]\\s*([^\\r\\n。;;,,]{2,})`, "i").exec(text);
334
+ return Boolean(match && labeledValueLooksConcrete(match[1] ?? ""));
335
+ });
336
+ }
337
+
338
+ function labeledValueLooksConcrete(value: string): boolean {
339
+ const trimmed = value.trim();
340
+ if (trimmed.length < 2) return false;
341
+ return !/^(待确认|未知|不清楚|未提供|none|unknown|todo|tbd|n\/a|按实际|按实际环境|后续确认|以后再说)$/i.test(trimmed);
342
+ }
343
+
344
+ function escapeRegExp(value: string): string {
345
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
346
+ }
@@ -4,6 +4,7 @@ import { formatStatus } from "./format.ts";
4
4
  import type { ActiveRun, KdPhase } from "./types.ts";
5
5
  import { PHASE_ORDER } from "./types.ts";
6
6
  import { isAbsolute, relative } from "node:path";
7
+ import { formatQuestionMemory } from "./question-memory.ts";
7
8
 
8
9
  export type DelegationRole = "research" | "doc" | "code" | "review" | "verify";
9
10
 
@@ -21,29 +22,29 @@ export interface ParsedDelegationArgs extends DelegationRequest {
21
22
  const ROLE_GUIDANCE: Record<DelegationRole, string[]> = {
22
23
  research: [
23
24
  "只读调研项目、文档、SDK 线索和现有实现。",
24
- "输出压缩结论、证据文件/代码位置、风险和建议下一步。",
25
- "不要修改文件,不要推进 Harness 状态。",
25
+ "输出压缩结论、证据文件/代码位置、风险和下一步指令。",
26
+ "禁止修改文件;禁止推进 Harness 状态。",
26
27
  ],
27
28
  doc: [
28
29
  "只写当前任务明确要求的文档或阶段产物。",
29
- "如果需要改阶段文档,保持内容和当前 Harness 阶段一致。",
30
- "不要修改产品源码,不要推进 Harness 状态。",
30
+ "修改阶段文档时,内容必须匹配当前 Harness 阶段。",
31
+ "禁止修改产品源码;禁止推进 Harness 状态。",
31
32
  ],
32
33
  code: [
33
34
  "只有当前 run 处于 execute 阶段时才允许修改产品代码。",
34
- "只修改 PLAN.md 批准的文件;发现新文件需求时停止并说明需要回到 plan。",
35
- "完成后列出变更文件、执行步骤证据和需要主 agent 验证的命令。",
35
+ "只修改 PLAN.md 批准的文件;发现新文件需求时停止并说明必须回到 plan。",
36
+ "完成后列出变更文件、执行步骤证据和主 agent 必须执行的验证命令。",
36
37
  ],
37
38
  review: [
38
39
  "只读交叉自查,不修改文件。",
39
- "优先找 bug、状态机漏洞、门禁绕过、证据缺口和测试缺口。",
40
+ "审查重点:bug、状态机漏洞、门禁绕过、证据缺口和测试缺口。",
40
41
  "输出 findings,按严重程度排序并带文件/函数引用;明确是否阻止发布。",
41
42
  ],
42
43
  verify: [
43
44
  "只读分析计划中的验证命令、失败证据和风险。",
44
- "需要实际运行验证时,把命令和原因交回主 agent 执行。",
45
+ "实际运行验证由主 agent 执行;子 agent 输出命令和原因。",
45
46
  "验证结果由主 agent 通过 kd_verify_result 记录。",
46
- "不要手工绕过 Harness 的 VERIFY/evidence 记录闭环。",
47
+ "禁止绕过 Harness 的 VERIFY/evidence 记录闭环。",
47
48
  ],
48
49
  };
49
50
 
@@ -55,12 +56,12 @@ const ROLE_ALLOWED_WRITES: Record<DelegationRole, string> = {
55
56
  verify: "evidence-through-kd_verify_result",
56
57
  };
57
58
 
58
- export const DEFAULT_REVIEW_TASK = "审查当前 run 和最近变更,重点找状态机漏洞、门禁绕过、证据缺口、提示词分散和测试缺口。";
59
+ export const DEFAULT_REVIEW_TASK = "审查当前 run 和最近变更,重点找状态机漏洞、门禁绕过、证据缺口、工程指令分散和测试缺口。";
59
60
  export const CHILD_AGENT_USER_TASK = "执行 KCode 子 agent 委派任务。";
60
61
  export const KD_SUBAGENT_TOOL_DESCRIPTION =
61
62
  "将调研、文档、代码、验证或交叉审查任务委派给隔离 Pi 子进程。主 Harness 仍负责阶段推进、证据和门禁。";
62
63
  export const KD_DELEGATE_USAGE = "用法:/kd-delegate <research|doc|code|review|verify> <任务> [--dry-run]";
63
- export const KD_SUBAGENT_INVALID_PARAMS = "kd_subagent 需要 role=research|doc|code|review|verify 和非空 task,或提供 tasks/chain。";
64
+ export const KD_SUBAGENT_INVALID_PARAMS = "kd_subagent 参数要求:role=research|doc|code|review|verify task 非空,或提供 tasks/chain。";
64
65
  export const KD_SUBAGENT_PARALLEL_ROLE_ERROR = "kd_subagent 并行模式只允许 research、review、verify 这类只读角色。doc/code 必须串行执行。";
65
66
  export const KD_REVIEW_COMMAND_DESCRIPTION = "启动只读交叉自查子 agent:/kd-review [审查重点]";
66
67
  export const KD_DELEGATE_COMMAND_DESCRIPTION = "委派任务给隔离子 agent:/kd-delegate <research|doc|code|review|verify> <任务> [--dry-run]";
@@ -97,10 +98,10 @@ export function parseDelegationArgs(args: string): ParsedDelegationArgs | undefi
97
98
 
98
99
  export function delegationBlockReason(role: DelegationRole, run: ActiveRun | undefined): string | undefined {
99
100
  if (role === "code") {
100
- if (!run) return "code 子 agent 需要 active Harness run,且必须处于 execute 阶段。";
101
+ if (!run) return "code 子 agent 要求 active Harness run,且必须处于 execute 阶段。";
101
102
  if (run.phase !== "execute") return `code 子 agent 只能在 execute 阶段运行;当前阶段:${run.phase}。`;
102
103
  }
103
- if (role === "verify" && !run) return "verify 子 agent 需要 active Harness run。";
104
+ if (role === "verify" && !run) return "verify 子 agent 要求 active Harness run。";
104
105
  return undefined;
105
106
  }
106
107
 
@@ -142,7 +143,7 @@ export function subagentToolCallBlockReason(input: {
142
143
  sourceWriteBlockReason?: string;
143
144
  }): string | undefined {
144
145
  if (SHELL_TOOL_NAMES.has(input.toolName)) {
145
- return "子 agent 不允许调用 shell 类工具;需要运行命令时交回主 agent 执行。";
146
+ return "子 agent 禁止调用 shell 类工具;运行命令由主 agent 执行,子 agent 只输出命令和原因。";
146
147
  }
147
148
  if (!WRITE_TOOL_NAMES.has(input.toolName)) return undefined;
148
149
 
@@ -164,19 +165,19 @@ export function subagentToolCallBlockReason(input: {
164
165
 
165
166
  export function buildDelegationCommandPrompt(request: DelegationRequest, dryRun: boolean): string {
166
167
  return [
167
- "请调用 kd_subagent 完成以下委派任务。",
168
+ "执行 kd_subagent 委派任务。",
168
169
  `role: ${request.role}`,
169
170
  `dryRun: ${dryRun ? "true" : "false"}`,
170
171
  "task:",
171
172
  request.task,
172
173
  "",
173
- "子 agent 返回后,由主 agent 判断是否需要修改文件、记录 evidence 或推进 Harness。",
174
+ "子 agent 返回后,主 agent 负责采纳结论、修改文件、记录 evidence 或推进 Harness。",
174
175
  ].join("\n");
175
176
  }
176
177
 
177
178
  export function delegationGuidanceForWorkflow(): string {
178
179
  return [
179
- "- 当任务需要大量调研、独立交叉审查、长上下文复盘或可并行拆分时,优先考虑 kd_subagent。",
180
+ "- 任务包含大量调研、独立交叉审查、长上下文复盘或可并行拆分内容时,使用 kd_subagent。",
180
181
  "- 自动委派只做旁路工作;主 agent 仍负责采纳结论、修改文件、记录 evidence 和推进阶段。",
181
182
  "- code 委派只能在 execute 阶段且限于 PLAN.md 批准文件;review/research 默认只读。",
182
183
  ].join("\n");
@@ -192,7 +193,7 @@ export function buildChainedDelegationRequest(request: DelegationRequest, previo
192
193
  "上一子 agent 输出:",
193
194
  trimForPrompt(previousOutput, 6000),
194
195
  "",
195
- "请基于上一输出继续当前步骤,并只交付本步骤结论。",
196
+ "基于上一输出继续当前步骤;仅交付本步骤结论。",
196
197
  ].join("\n"),
197
198
  };
198
199
  }
@@ -202,6 +203,7 @@ export function buildDelegationPrompt(cwd: string, run: ActiveRun | undefined, r
202
203
  const projectContext = readProjectContext(cwd);
203
204
  const status = run ? formatStatus(cwd, run) : "当前没有 active KCode Harness run。";
204
205
  const phaseContext = run ? delegationMemoryForRun(cwd, run) : "无 active run 阶段资料。";
206
+ const questionMemory = run ? formatQuestionMemory(run) : "无。";
205
207
 
206
208
  return [
207
209
  "KCode 子 agent 委派任务。",
@@ -217,8 +219,8 @@ export function buildDelegationPrompt(cwd: string, run: ActiveRun | undefined, r
217
219
  "",
218
220
  "角色规则:",
219
221
  ...roleGuidance.map((item) => `- ${item}`),
220
- "- 你不是主状态机;不要调用 /kd-advance、/kd-finish 或改变 run 生命周期。",
221
- "- 不要再创建子 agent;把结果交回主 agent 统一决策。",
222
+ "- agent 不是主状态机;禁止调用 /kd-advance、/kd-finish 或改变 run 生命周期。",
223
+ "- 禁止创建子 agent;输出结果交回主 agent 统一决策。",
222
224
  "",
223
225
  "Harness 状态:",
224
226
  status,
@@ -226,14 +228,17 @@ export function buildDelegationPrompt(cwd: string, run: ActiveRun | undefined, r
226
228
  "阶段上下文:",
227
229
  phaseContext,
228
230
  "",
231
+ "KCode 已问已答事实:",
232
+ questionMemory,
233
+ "",
229
234
  "项目上下文:",
230
- projectContext ? trimForPrompt(projectContext, 1200) : "未生成。需要项目结构时读取本地文件或提示主 agent 运行 `kcode context --refresh`。",
235
+ projectContext ? trimForPrompt(projectContext, 1200) : "未生成。项目结构缺失时读取本地文件;无法读取时要求主 agent 运行 `kcode context --refresh`。",
231
236
  "",
232
237
  "输出格式:",
233
238
  "- 结论",
234
239
  "- 证据/引用",
235
240
  "- 风险",
236
- "- 建议给主 agent 的下一步",
241
+ "- agent 下一步指令",
237
242
  ].join("\n");
238
243
  }
239
244
 
@@ -268,7 +273,7 @@ function artifactSection(cwd: string, run: ActiveRun, phase: KdPhase): string |
268
273
 
269
274
  function trimForPrompt(content: string, maxLength: number): string {
270
275
  if (content.length <= maxLength) return content;
271
- return `${content.slice(0, maxLength)}\n\n[...已截断;需要完整内容时读取本地文件...]`;
276
+ return `${content.slice(0, maxLength)}\n\n[...已截断;完整内容读取本地文件...]`;
272
277
  }
273
278
 
274
279
  function isDocWritablePath(cwd: string, run: ActiveRun | undefined, path: string | undefined): boolean {
@@ -8,6 +8,13 @@ import { flagshipPlanBlockReason } from "./path-policy.ts";
8
8
  import { executionStepsBlockReason, planStepsBlockReason } from "./plan-steps.ts";
9
9
  import { tddPlanBlockReason, tddVerifyBlockReason } from "./tdd-policy.ts";
10
10
  import { SDK_SIGNATURE_EVIDENCE, hasValidSdkSignatureEvidence, requiresSdkSignatureEvidence } from "./sdk-policy.ts";
11
+ import {
12
+ COSMIC_METADATA_EVIDENCE,
13
+ dataSourceContextBlockReason,
14
+ dataSourceEvidenceForRun,
15
+ hasValidDataSourceEvidence,
16
+ requiresDataSourceEvidence,
17
+ } from "./data-source-policy.ts";
11
18
  import { runRoot } from "./paths.ts";
12
19
  import { EVIDENCE_INDEX, readEvidenceIndex } from "./evidence.ts";
13
20
  import {
@@ -28,7 +35,6 @@ const REQUIRED_MARKERS: Partial<Record<KdPhase, string[]>> = {
28
35
  };
29
36
 
30
37
  const COSMIC_CONFIG_EVIDENCE = "evidence/cosmic-config.txt";
31
- const COSMIC_METADATA_EVIDENCE = "evidence/cosmic-metadata.json";
32
38
  const COSMIC_API_EVIDENCE = "evidence/cosmic-api.txt";
33
39
  const KSQL_LINT_EVIDENCE = "evidence/ksql-lint.txt";
34
40
 
@@ -69,6 +75,7 @@ function collectGateProblems(cwd: string, run: ActiveRun, phase: KdPhase, mode:
69
75
 
70
76
  const markerProblem = mode === "inspect" ? inspectMarkers(cwd, run, phase) : undefined;
71
77
  const flagshipPathProblem = mode === "enter" && phase === "execute" ? flagshipPlanBlockReason(cwd, run, readArtifact(cwd, run, "plan") ?? "") : undefined;
78
+ const dataSourceContextProblem = phaseIndex >= PHASE_ORDER.indexOf("execute") ? dataSourceContextBlockReason(cwd, run) : undefined;
72
79
  const stepProblem = inspectStepState(cwd, run, phase);
73
80
  const evidenceProblem = mode === "inspect" ? inspectEvidence(cwd, run, phase) : undefined;
74
81
  const questionProblem = inspectOpenQuestions(run);
@@ -77,6 +84,9 @@ function collectGateProblems(cwd: string, run: ActiveRun, phase: KdPhase, mode:
77
84
  if (flagshipPathProblem) {
78
85
  reasonParts.push(flagshipPathProblem);
79
86
  }
87
+ if (dataSourceContextProblem) {
88
+ reasonParts.push(dataSourceContextProblem);
89
+ }
80
90
  if (markerProblem) {
81
91
  reasonParts.push(markerProblem);
82
92
  }
@@ -204,6 +214,10 @@ function requiredEvidenceForPhase(cwd: string, run: ActiveRun, phase: KdPhase):
204
214
 
205
215
  if (phaseIndex >= PHASE_ORDER.indexOf("execute")) {
206
216
  if (requiresSdkSignatureEvidence(run)) required.add(SDK_SIGNATURE_EVIDENCE);
217
+ if (requiresDataSourceEvidence(cwd, run)) {
218
+ const evidence = dataSourceEvidenceForRun(run);
219
+ if (evidence) required.add(evidence);
220
+ }
207
221
  if (isCosmicRun(run)) {
208
222
  required.add(COSMIC_CONFIG_EVIDENCE);
209
223
  if (planHasMetadataRequirement(cwd, run)) required.add(COSMIC_METADATA_EVIDENCE);
@@ -221,6 +235,7 @@ function requiredEvidenceForPhase(cwd: string, run: ActiveRun, phase: KdPhase):
221
235
 
222
236
  function evidenceArtifactSatisfied(cwd: string, run: ActiveRun, artifact: string): boolean {
223
237
  if (artifact === SDK_SIGNATURE_EVIDENCE) return hasValidSdkSignatureEvidence(cwd, run);
238
+ if (artifact === dataSourceEvidenceForRun(run)) return hasValidDataSourceEvidence(cwd, run);
224
239
  if (artifact === EVIDENCE_INDEX) return existsSync(join(runRoot(cwd, run), artifact));
225
240
  return existsSync(join(runRoot(cwd, run), artifact)) && hasSuccessfulEvidenceEntry(cwd, run, artifact);
226
241
  }