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.
@@ -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
+ }
@@ -21,29 +21,29 @@ export interface ParsedDelegationArgs extends DelegationRequest {
21
21
  const ROLE_GUIDANCE: Record<DelegationRole, string[]> = {
22
22
  research: [
23
23
  "只读调研项目、文档、SDK 线索和现有实现。",
24
- "输出压缩结论、证据文件/代码位置、风险和建议下一步。",
25
- "不要修改文件,不要推进 Harness 状态。",
24
+ "输出压缩结论、证据文件/代码位置、风险和下一步指令。",
25
+ "禁止修改文件;禁止推进 Harness 状态。",
26
26
  ],
27
27
  doc: [
28
28
  "只写当前任务明确要求的文档或阶段产物。",
29
- "如果需要改阶段文档,保持内容和当前 Harness 阶段一致。",
30
- "不要修改产品源码,不要推进 Harness 状态。",
29
+ "修改阶段文档时,内容必须匹配当前 Harness 阶段。",
30
+ "禁止修改产品源码;禁止推进 Harness 状态。",
31
31
  ],
32
32
  code: [
33
33
  "只有当前 run 处于 execute 阶段时才允许修改产品代码。",
34
- "只修改 PLAN.md 批准的文件;发现新文件需求时停止并说明需要回到 plan。",
35
- "完成后列出变更文件、执行步骤证据和需要主 agent 验证的命令。",
34
+ "只修改 PLAN.md 批准的文件;发现新文件需求时停止并说明必须回到 plan。",
35
+ "完成后列出变更文件、执行步骤证据和主 agent 必须执行的验证命令。",
36
36
  ],
37
37
  review: [
38
38
  "只读交叉自查,不修改文件。",
39
- "优先找 bug、状态机漏洞、门禁绕过、证据缺口和测试缺口。",
39
+ "审查重点:bug、状态机漏洞、门禁绕过、证据缺口和测试缺口。",
40
40
  "输出 findings,按严重程度排序并带文件/函数引用;明确是否阻止发布。",
41
41
  ],
42
42
  verify: [
43
43
  "只读分析计划中的验证命令、失败证据和风险。",
44
- "需要实际运行验证时,把命令和原因交回主 agent 执行。",
44
+ "实际运行验证由主 agent 执行;子 agent 输出命令和原因。",
45
45
  "验证结果由主 agent 通过 kd_verify_result 记录。",
46
- "不要手工绕过 Harness 的 VERIFY/evidence 记录闭环。",
46
+ "禁止绕过 Harness 的 VERIFY/evidence 记录闭环。",
47
47
  ],
48
48
  };
49
49
 
@@ -55,12 +55,12 @@ const ROLE_ALLOWED_WRITES: Record<DelegationRole, string> = {
55
55
  verify: "evidence-through-kd_verify_result",
56
56
  };
57
57
 
58
- export const DEFAULT_REVIEW_TASK = "审查当前 run 和最近变更,重点找状态机漏洞、门禁绕过、证据缺口、提示词分散和测试缺口。";
58
+ export const DEFAULT_REVIEW_TASK = "审查当前 run 和最近变更,重点找状态机漏洞、门禁绕过、证据缺口、工程指令分散和测试缺口。";
59
59
  export const CHILD_AGENT_USER_TASK = "执行 KCode 子 agent 委派任务。";
60
60
  export const KD_SUBAGENT_TOOL_DESCRIPTION =
61
61
  "将调研、文档、代码、验证或交叉审查任务委派给隔离 Pi 子进程。主 Harness 仍负责阶段推进、证据和门禁。";
62
62
  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。";
63
+ export const KD_SUBAGENT_INVALID_PARAMS = "kd_subagent 参数要求:role=research|doc|code|review|verify task 非空,或提供 tasks/chain。";
64
64
  export const KD_SUBAGENT_PARALLEL_ROLE_ERROR = "kd_subagent 并行模式只允许 research、review、verify 这类只读角色。doc/code 必须串行执行。";
65
65
  export const KD_REVIEW_COMMAND_DESCRIPTION = "启动只读交叉自查子 agent:/kd-review [审查重点]";
66
66
  export const KD_DELEGATE_COMMAND_DESCRIPTION = "委派任务给隔离子 agent:/kd-delegate <research|doc|code|review|verify> <任务> [--dry-run]";
@@ -97,10 +97,10 @@ export function parseDelegationArgs(args: string): ParsedDelegationArgs | undefi
97
97
 
98
98
  export function delegationBlockReason(role: DelegationRole, run: ActiveRun | undefined): string | undefined {
99
99
  if (role === "code") {
100
- if (!run) return "code 子 agent 需要 active Harness run,且必须处于 execute 阶段。";
100
+ if (!run) return "code 子 agent 要求 active Harness run,且必须处于 execute 阶段。";
101
101
  if (run.phase !== "execute") return `code 子 agent 只能在 execute 阶段运行;当前阶段:${run.phase}。`;
102
102
  }
103
- if (role === "verify" && !run) return "verify 子 agent 需要 active Harness run。";
103
+ if (role === "verify" && !run) return "verify 子 agent 要求 active Harness run。";
104
104
  return undefined;
105
105
  }
106
106
 
@@ -142,7 +142,7 @@ export function subagentToolCallBlockReason(input: {
142
142
  sourceWriteBlockReason?: string;
143
143
  }): string | undefined {
144
144
  if (SHELL_TOOL_NAMES.has(input.toolName)) {
145
- return "子 agent 不允许调用 shell 类工具;需要运行命令时交回主 agent 执行。";
145
+ return "子 agent 禁止调用 shell 类工具;运行命令由主 agent 执行,子 agent 只输出命令和原因。";
146
146
  }
147
147
  if (!WRITE_TOOL_NAMES.has(input.toolName)) return undefined;
148
148
 
@@ -164,19 +164,19 @@ export function subagentToolCallBlockReason(input: {
164
164
 
165
165
  export function buildDelegationCommandPrompt(request: DelegationRequest, dryRun: boolean): string {
166
166
  return [
167
- "请调用 kd_subagent 完成以下委派任务。",
167
+ "执行 kd_subagent 委派任务。",
168
168
  `role: ${request.role}`,
169
169
  `dryRun: ${dryRun ? "true" : "false"}`,
170
170
  "task:",
171
171
  request.task,
172
172
  "",
173
- "子 agent 返回后,由主 agent 判断是否需要修改文件、记录 evidence 或推进 Harness。",
173
+ "子 agent 返回后,主 agent 负责采纳结论、修改文件、记录 evidence 或推进 Harness。",
174
174
  ].join("\n");
175
175
  }
176
176
 
177
177
  export function delegationGuidanceForWorkflow(): string {
178
178
  return [
179
- "- 当任务需要大量调研、独立交叉审查、长上下文复盘或可并行拆分时,优先考虑 kd_subagent。",
179
+ "- 任务包含大量调研、独立交叉审查、长上下文复盘或可并行拆分内容时,使用 kd_subagent。",
180
180
  "- 自动委派只做旁路工作;主 agent 仍负责采纳结论、修改文件、记录 evidence 和推进阶段。",
181
181
  "- code 委派只能在 execute 阶段且限于 PLAN.md 批准文件;review/research 默认只读。",
182
182
  ].join("\n");
@@ -192,7 +192,7 @@ export function buildChainedDelegationRequest(request: DelegationRequest, previo
192
192
  "上一子 agent 输出:",
193
193
  trimForPrompt(previousOutput, 6000),
194
194
  "",
195
- "请基于上一输出继续当前步骤,并只交付本步骤结论。",
195
+ "基于上一输出继续当前步骤;仅交付本步骤结论。",
196
196
  ].join("\n"),
197
197
  };
198
198
  }
@@ -217,8 +217,8 @@ export function buildDelegationPrompt(cwd: string, run: ActiveRun | undefined, r
217
217
  "",
218
218
  "角色规则:",
219
219
  ...roleGuidance.map((item) => `- ${item}`),
220
- "- 你不是主状态机;不要调用 /kd-advance、/kd-finish 或改变 run 生命周期。",
221
- "- 不要再创建子 agent;把结果交回主 agent 统一决策。",
220
+ "- agent 不是主状态机;禁止调用 /kd-advance、/kd-finish 或改变 run 生命周期。",
221
+ "- 禁止创建子 agent;输出结果交回主 agent 统一决策。",
222
222
  "",
223
223
  "Harness 状态:",
224
224
  status,
@@ -227,13 +227,13 @@ export function buildDelegationPrompt(cwd: string, run: ActiveRun | undefined, r
227
227
  phaseContext,
228
228
  "",
229
229
  "项目上下文:",
230
- projectContext ? trimForPrompt(projectContext, 1200) : "未生成。需要项目结构时读取本地文件或提示主 agent 运行 `kcode context --refresh`。",
230
+ projectContext ? trimForPrompt(projectContext, 1200) : "未生成。项目结构缺失时读取本地文件;无法读取时要求主 agent 运行 `kcode context --refresh`。",
231
231
  "",
232
232
  "输出格式:",
233
233
  "- 结论",
234
234
  "- 证据/引用",
235
235
  "- 风险",
236
- "- 建议给主 agent 的下一步",
236
+ "- agent 下一步指令",
237
237
  ].join("\n");
238
238
  }
239
239
 
@@ -268,7 +268,7 @@ function artifactSection(cwd: string, run: ActiveRun, phase: KdPhase): string |
268
268
 
269
269
  function trimForPrompt(content: string, maxLength: number): string {
270
270
  if (content.length <= maxLength) return content;
271
- return `${content.slice(0, maxLength)}\n\n[...已截断;需要完整内容时读取本地文件...]`;
271
+ return `${content.slice(0, maxLength)}\n\n[...已截断;完整内容读取本地文件...]`;
272
272
  }
273
273
 
274
274
  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 {
@@ -16,6 +23,7 @@ import {
16
23
  missingForTargetReason,
17
24
  missingMarkerReason,
18
25
  openQuestionsReason,
26
+ repairBlockedReason,
19
27
  unknownProductReason,
20
28
  unknownRiskReason,
21
29
  } from "./messages.ts";
@@ -27,7 +35,6 @@ const REQUIRED_MARKERS: Partial<Record<KdPhase, string[]>> = {
27
35
  };
28
36
 
29
37
  const COSMIC_CONFIG_EVIDENCE = "evidence/cosmic-config.txt";
30
- const COSMIC_METADATA_EVIDENCE = "evidence/cosmic-metadata.json";
31
38
  const COSMIC_API_EVIDENCE = "evidence/cosmic-api.txt";
32
39
  const KSQL_LINT_EVIDENCE = "evidence/ksql-lint.txt";
33
40
 
@@ -68,13 +75,18 @@ function collectGateProblems(cwd: string, run: ActiveRun, phase: KdPhase, mode:
68
75
 
69
76
  const markerProblem = mode === "inspect" ? inspectMarkers(cwd, run, phase) : undefined;
70
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;
71
79
  const stepProblem = inspectStepState(cwd, run, phase);
72
80
  const evidenceProblem = mode === "inspect" ? inspectEvidence(cwd, run, phase) : undefined;
73
81
  const questionProblem = inspectOpenQuestions(run);
82
+ const repairProblem = inspectRepairState(run);
74
83
 
75
84
  if (flagshipPathProblem) {
76
85
  reasonParts.push(flagshipPathProblem);
77
86
  }
87
+ if (dataSourceContextProblem) {
88
+ reasonParts.push(dataSourceContextProblem);
89
+ }
78
90
  if (markerProblem) {
79
91
  reasonParts.push(markerProblem);
80
92
  }
@@ -87,6 +99,9 @@ function collectGateProblems(cwd: string, run: ActiveRun, phase: KdPhase, mode:
87
99
  if (questionProblem) {
88
100
  reasonParts.push(questionProblem);
89
101
  }
102
+ if (repairProblem) {
103
+ reasonParts.push(repairProblem);
104
+ }
90
105
 
91
106
  if (mode === "enter") {
92
107
  missing.push(...missingEvidenceForPhase(cwd, run, phase));
@@ -148,6 +163,11 @@ function inspectOpenQuestions(run: ActiveRun): string | undefined {
148
163
  return openQuestionsReason(open);
149
164
  }
150
165
 
166
+ function inspectRepairState(run: ActiveRun): string | undefined {
167
+ if (run.repair?.status !== "blocked") return undefined;
168
+ return repairBlockedReason(run.repair.attempts, run.repair.maxAttempts, run.repair.lastFailureEvidence);
169
+ }
170
+
151
171
  function inspectStepState(cwd: string, run: ActiveRun, phase: KdPhase): string | undefined {
152
172
  if (phase === "execute") {
153
173
  const plan = readArtifact(cwd, run, "plan") ?? "";
@@ -194,6 +214,10 @@ function requiredEvidenceForPhase(cwd: string, run: ActiveRun, phase: KdPhase):
194
214
 
195
215
  if (phaseIndex >= PHASE_ORDER.indexOf("execute")) {
196
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
+ }
197
221
  if (isCosmicRun(run)) {
198
222
  required.add(COSMIC_CONFIG_EVIDENCE);
199
223
  if (planHasMetadataRequirement(cwd, run)) required.add(COSMIC_METADATA_EVIDENCE);
@@ -211,6 +235,7 @@ function requiredEvidenceForPhase(cwd: string, run: ActiveRun, phase: KdPhase):
211
235
 
212
236
  function evidenceArtifactSatisfied(cwd: string, run: ActiveRun, artifact: string): boolean {
213
237
  if (artifact === SDK_SIGNATURE_EVIDENCE) return hasValidSdkSignatureEvidence(cwd, run);
238
+ if (artifact === dataSourceEvidenceForRun(run)) return hasValidDataSourceEvidence(cwd, run);
214
239
  if (artifact === EVIDENCE_INDEX) return existsSync(join(runRoot(cwd, run), artifact));
215
240
  return existsSync(join(runRoot(cwd, run), artifact)) && hasSuccessfulEvidenceEntry(cwd, run, artifact);
216
241
  }