kcode-pi 0.1.35 → 0.1.39

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.
@@ -1,5 +1,6 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
2
  import { basename, extname, join, relative } from "node:path";
3
+ import { PROJECT_PERSISTENT_RULES, formatPromptLines } from "../harness/prompt-policy.js";
3
4
  const IGNORED_DIRS = new Set([
4
5
  ".git",
5
6
  ".idea",
@@ -55,11 +56,9 @@ export function generateProjectContext(cwd) {
55
56
  "",
56
57
  "## 持久规则",
57
58
  "",
58
- "- 本文件是 KCode 的项目记忆,计划或编辑代码前必须读取。",
59
- "- 业务需求不得生成 demo/sample/scaffold 代码。",
59
+ ...formatPromptLines(PROJECT_PERSISTENT_RULES),
60
60
  "- 禁止假设模块结构。必须基于下方真实路径,并在编辑前确认目标文件。",
61
61
  "- 调用文件工具时默认使用项目相对路径。在 Windows 中禁止把路径改写为 /mnt/<drive>/... 或 /<drive>/...;绝对路径只允许使用 Windows 路径。",
62
- "- 本文件过期时,计划前运行 `kcode context --refresh` 重新生成。",
63
62
  "- 只有 Harness 进入 `execute` 且 PLAN.md 写明真实目标路径后,才能写产品代码。",
64
63
  "",
65
64
  "## 项目结构摘要",
@@ -0,0 +1,19 @@
1
+ import type { KdPhase } from "./types.ts";
2
+ export interface ContractField {
3
+ id: string;
4
+ label: string;
5
+ aliases: string[];
6
+ question: string;
7
+ }
8
+ export declare const IMPLEMENTATION_CONTRACT_FIELDS: ContractField[];
9
+ export declare const DATA_SOURCE_CONTEXT_FIELDS: ContractField[];
10
+ export declare const INTEGRATION_CONTEXT_FIELDS: ContractField[];
11
+ export declare const PROJECT_PERSISTENT_RULES: string[];
12
+ export declare const PROMPT_STYLE_RULES: string[];
13
+ export declare const CORE_WORKFLOW_CONSTRAINTS: string[];
14
+ export declare const PHASE_GUIDANCE: Record<KdPhase, string>;
15
+ export declare const PLAN_REQUIRED_CHECK_LINES: string[];
16
+ export declare function formatPromptLines(lines: string[]): string[];
17
+ export declare function fieldLabels(fields: ContractField[]): string[];
18
+ export declare function questionForMissingLabel(label: string): string | undefined;
19
+ export declare function canonicalFactLabel(label: string): string | undefined;
@@ -0,0 +1,209 @@
1
+ export const IMPLEMENTATION_CONTRACT_FIELDS = [
2
+ {
3
+ id: "trigger",
4
+ label: "触发入口或执行时机",
5
+ aliases: ["触发入口", "执行时机", "触发时机", "插件类型和事件", "入口事件"],
6
+ question: "该实现由哪个入口触发,执行时机是什么?",
7
+ },
8
+ {
9
+ id: "source",
10
+ label: "源对象/输入数据",
11
+ aliases: ["源对象", "源单", "输入数据", "数据来源", "取数来源"],
12
+ question: "实现读取的源对象或输入数据是什么?",
13
+ },
14
+ {
15
+ id: "target",
16
+ label: "目标对象/输出结果",
17
+ aliases: ["目标对象", "目标单据", "目标表单", "输出结果", "生成结果"],
18
+ question: "实现写入、生成或影响的目标对象是什么?",
19
+ },
20
+ {
21
+ id: "data-change",
22
+ label: "数据变化或字段映射",
23
+ aliases: ["数据变化", "字段映射", "字段对应", "字段改写", "赋值规则"],
24
+ question: "列出新增、修改、映射或保持不变的字段。",
25
+ },
26
+ {
27
+ id: "rules",
28
+ label: "业务规则和适用条件",
29
+ aliases: ["业务规则", "适用条件", "执行条件", "过滤条件", "前置条件"],
30
+ question: "该实现在哪些条件下执行,业务规则是什么?",
31
+ },
32
+ {
33
+ id: "failure",
34
+ label: "失败处理、回滚或人工处理方式",
35
+ aliases: ["失败处理", "异常处理", "错误处理", "回滚", "补偿", "人工处理"],
36
+ question: "失败、异常或重复执行时如何处理?",
37
+ },
38
+ {
39
+ id: "acceptance",
40
+ label: "验收样例或测试数据",
41
+ aliases: ["验收样例", "测试数据", "请求样例", "响应样例", "示例数据"],
42
+ question: "用于验收的样例数据和预期结果是什么?",
43
+ },
44
+ ];
45
+ export const DATA_SOURCE_CONTEXT_FIELDS = [
46
+ {
47
+ id: "target-form",
48
+ label: "目标 FormId/单据或表单标识",
49
+ aliases: ["目标 FormId", "FormId", "Form ID", "form id", "表单标识", "单据标识", "目标单据", "目标表单"],
50
+ question: "目标 FormId、单据或表单标识是什么?",
51
+ },
52
+ {
53
+ id: "plugin-hook",
54
+ label: "插件类型和触发事件",
55
+ aliases: ["插件类型", "触发事件", "生命周期", "挂载点", "插件类型和事件"],
56
+ question: "插件类型和触发事件是什么?",
57
+ },
58
+ {
59
+ id: "field-entity",
60
+ label: "字段/实体/分录标识",
61
+ aliases: ["字段标识", "实体标识", "分录标识", "单据体标识", "字段/实体标识"],
62
+ question: "涉及哪些字段、实体或分录标识?",
63
+ },
64
+ {
65
+ id: "data-access",
66
+ label: "数据读取写入方式",
67
+ aliases: ["数据读取写入方式", "读取方式", "写入方式", "数据访问", "取数方式"],
68
+ question: "数据通过表单模型、服务、SQL/KSQL 还是接口读取和写入?",
69
+ },
70
+ {
71
+ id: "sql-identifiers",
72
+ label: "SQL/KSQL 表名和数据库字段名",
73
+ aliases: ["SQL 表名", "KSQL 表名", "表名", "数据库字段名", "字段名"],
74
+ question: "SQL/KSQL 场景的表名和数据库字段名是什么?",
75
+ },
76
+ ];
77
+ export const INTEGRATION_CONTEXT_FIELDS = [
78
+ {
79
+ id: "interface-doc",
80
+ label: "接口文档来源/版本",
81
+ aliases: ["接口文档", "API 文档", "文档来源", "协议文档", "OpenAPI", "Swagger"],
82
+ question: "第三方接口文档来源和版本是什么?",
83
+ },
84
+ {
85
+ id: "direction",
86
+ label: "对接方向和触发时机",
87
+ aliases: ["对接方向", "触发时机", "同步方向", "数据流向"],
88
+ question: "对接方向、调用方和触发时机是什么?",
89
+ },
90
+ {
91
+ id: "endpoint-auth",
92
+ label: "接口地址、认证和密钥配置方式",
93
+ aliases: ["接口地址", "认证", "鉴权", "密钥", "Token", "OAuth"],
94
+ question: "接口地址、认证方式和密钥配置方式是什么?",
95
+ },
96
+ {
97
+ id: "field-mapping",
98
+ label: "第三方字段到金蝶字段的映射",
99
+ aliases: ["字段映射", "映射关系", "字段对应", "字段对照"],
100
+ question: "第三方字段与金蝶字段如何对应?",
101
+ },
102
+ {
103
+ id: "concurrency",
104
+ label: "并发/幂等策略",
105
+ aliases: ["并发", "幂等", "去重", "唯一键", "重复提交"],
106
+ question: "并发、重复提交和幂等如何处理?",
107
+ },
108
+ {
109
+ id: "retry",
110
+ label: "超时、重试和限流策略",
111
+ aliases: ["超时", "重试", "限流", "频率", "熔断"],
112
+ question: "超时、重试和限流策略是什么?",
113
+ },
114
+ {
115
+ id: "error",
116
+ label: "错误处理和失败补偿",
117
+ aliases: ["错误处理", "异常处理", "失败处理", "失败补偿", "告警"],
118
+ question: "接口失败、异常响应和补偿流程如何处理?",
119
+ },
120
+ {
121
+ id: "logging",
122
+ label: "日志、审计和敏感信息脱敏策略",
123
+ aliases: ["日志", "审计", "留痕", "脱敏", "敏感信息"],
124
+ question: "列出日志字段、审计留痕和敏感信息脱敏规则。",
125
+ },
126
+ {
127
+ id: "samples",
128
+ label: "请求/响应样例和验收数据",
129
+ aliases: ["请求样例", "响应样例", "报文样例", "验收数据", "payload"],
130
+ question: "请求样例、响应样例和验收数据是什么?",
131
+ },
132
+ ];
133
+ export const PROJECT_PERSISTENT_RULES = [
134
+ "计划或编辑代码前必须读取本文件;本文件过期时先运行 `kcode context --refresh`。",
135
+ "信息不足时禁止开始编码。必须先登记一个最阻塞的结构化问题,获得可核验答案后再继续;禁止输出 demo/sample/scaffold、模板代码或占位实现。",
136
+ "API 文档、SDK 文档和知识库只能证明技术用法,不能替代业务事实。FormId、单据/表单标识、字段/实体/分录标识、插件类型与事件、SQL/KSQL 表名和数据库字段名必须来自用户确认、项目元数据或 evidence。",
137
+ `产品代码实现前必须具备通用实现契约:${IMPLEMENTATION_CONTRACT_FIELDS.map((field) => field.label).join("、")}。`,
138
+ `涉及业务数据源时必须具备数据源上下文:${DATA_SOURCE_CONTEXT_FIELDS.map((field) => field.label).join("、")}。`,
139
+ `涉及第三方对接时必须具备接口与运行上下文:${INTEGRATION_CONTEXT_FIELDS.map((field) => field.label).join("、")}。`,
140
+ "内部插件、自动下推、字段改写、数据同步等需求不得按场景写死提示词;统一通过实现契约、数据源上下文、第三方对接上下文补齐事实。",
141
+ "PLAN 自由文本不能单独证明 FormId、字段、插件事件或读写方式;门禁只信任结构化 facts 和 evidence。",
142
+ "`run.facts` 是唯一结构化事实源;已回答 questions 仅为审计记录,禁止在读取状态时从历史 question 反推事实。",
143
+ "`factLabel` 必须使用集中定义的事实标签或别名;未知标签、占位答案、口头确认语、待确认/TODO/TBD/按实际环境等不能解除门禁。",
144
+ "用户回答 open question 后,先用 `kd_question action=answer` 写入答案;用户更正事实时用 `kd_question action=revise`,禁止重复询问已确认事实。",
145
+ "企业版 Python 插件通常没有本地构建可替代验证;BOS 注册、外部系统操作、人工功能测试和生产环境验证必须由用户提供可核验证据并记录来源。",
146
+ "提示语集中管理为正式工程指令;禁止口语化、闲聊式、鼓励式提示词进入运行时规则。",
147
+ "工具使用必须匹配当前会话实际可用工具和操作系统;Windows 默认使用 PowerShell、`rg`、`Get-ChildItem`、`Get-Content`,禁止假设 bash 可用。",
148
+ "文件定位必须使用真实搜索或目录读取结果;工具不可用时明确说明阻塞原因和需要用户执行的命令,禁止猜测路径、反复自述工具失败或用 kd_subagent 代替基础文件搜索。",
149
+ "不做旧状态迁移兼容或旧问题答案推断;坏状态只过滤,缺失事实必须重新提问确认。",
150
+ ];
151
+ export const PROMPT_STYLE_RULES = [
152
+ "使用正式、可执行的工程指令;禁止口语化、闲聊式、鼓励式表达。",
153
+ "事实不足时生成阻断问题;禁止输出模板代码、占位实现或基于猜测的业务标识。",
154
+ "每次只提出一个最阻塞问题;问题必须指向可验证事实、数据标识或验收证据。",
155
+ "引用顺序:当前项目文件、PLAN/SPEC、元数据 evidence、SDK 签名、验证输出。",
156
+ ];
157
+ export const CORE_WORKFLOW_CONSTRAINTS = [
158
+ "产品代码只在 execute 阶段写入,并限于 PLAN.md 批准的文件。",
159
+ "写代码前必须具备通用实现契约:触发入口、源对象/输入数据、目标对象/输出结果、数据变化或字段映射、业务规则、失败处理和验收样例。",
160
+ "业务数据源未知时禁止编码;确认目标 FormId/单据或表单、插件类型和事件、字段/实体/分录标识、数据读取写入方式后再编码;SQL/KSQL 同步确认表名和数据库字段名。",
161
+ "第三方对接确认接口文档、对接方向、触发时机、认证配置、字段映射、并发/幂等、重试超时限流、错误补偿、日志脱敏和验收样例后再编码。",
162
+ "事实缺失时使用 kd_question 登记一个最阻塞问题;禁止用 API 文档、SDK 知识库或推测替代业务事实。",
163
+ "用户输入是在回答 open question 时,必须先调用 kd_question action=answer 记录答案,再继续推进或登记下一个问题。",
164
+ "同一 factLabel 已有当前事实时禁止重复提问;用户明确更正时使用 kd_question action=revise 记录新事实和更正原因。",
165
+ "run.facts 是唯一结构化事实源;questions 仅作为问答审计记录,禁止从历史 question 反推门禁事实。",
166
+ "待确认、未知、按实际环境、TODO/TBD 等占位答案不能解除门禁。",
167
+ "Java/C# SDK 签名以当前项目 jar/dll、构建输出或官方元数据为准。",
168
+ "Java/Cosmic 使用当前项目 Gradle;C#/企业版使用 dotnet build。",
169
+ "evidence 必须记录命令、Exit 和关键输出;命令无法运行时记录阻塞原因。",
170
+ "外部系统操作、BOS 注册、人工功能测试和生产环境验证不能由 LLM 代办;必须要求用户提供验证结果或可核验证据,并记录证据来源。",
171
+ "Windows 路径规则:项目相对路径为默认;绝对路径使用 D:\\... 形式。",
172
+ "工具规则:按当前会话实际 shell 和工具执行;Windows 不假设 bash,可用 PowerShell/rg/Get-ChildItem/Get-Content;禁止猜路径或用 kd_subagent 代替基础搜索失败。",
173
+ ];
174
+ export const PHASE_GUIDANCE = {
175
+ discuss: "梳理需求来源、范围、已知事实;如缺通用实现契约、数据源或第三方接口关键事实,使用 kd_question 登记一个最阻塞问题。",
176
+ spec: "将需求转成验收标准、实现契约、数据对象、接口契约、异常行为、依赖和风险;数据对象和字段映射必须落到可核验标识。",
177
+ plan: "检查项目结构,写明目标路径、允许修改文件、通用实现契约、数据源/元数据查证项、第三方接口契约、插件挂载点、验证命令和回滚说明。",
178
+ execute: "按 PLAN.md 实现,记录步骤结果、变更文件和 evidence。",
179
+ verify: "运行计划中的验证命令,并用 kd_verify_result 记录结果;失败会回到 execute 修复,成功后更新 VERIFY.md、证据和残余风险。",
180
+ ship: "整理 SHIP.md,包括摘要、验证证据、风险和后续事项。",
181
+ };
182
+ export const PLAN_REQUIRED_CHECK_LINES = [
183
+ "涉及表单、单据、字段、实体、SQL/KSQL、数据读取写入或插件事件时,确认真实数据源/元数据后再编码;禁止只根据 API 文档编码。",
184
+ "Cosmic 家族数据源证据:evidence/cosmic-metadata.json;企业版/BOS 数据源证据:evidence/data-source.md。",
185
+ `所有产品代码实现必须写明通用实现契约后再编码:${IMPLEMENTATION_CONTRACT_FIELDS.map((field) => field.label).join("、")}。`,
186
+ `进入 execute 前必须写明:${DATA_SOURCE_CONTEXT_FIELDS.map((field) => field.label).join("、")}。`,
187
+ `第三方对接必须写明:${INTEGRATION_CONTEXT_FIELDS.map((field) => field.label).join("、")}。`,
188
+ "Java/C# 代码涉及 SDK 类、方法、构造器、枚举、属性时,使用 kd_sdk_signature 或项目构建输出确认真实签名后再编码。",
189
+ "知识库搜索、随包 Cosmic API 查询只能作为线索,不能作为最终方法签名事实。",
190
+ "SDK 签名证据:evidence/sdk-signature.md",
191
+ ];
192
+ export function formatPromptLines(lines) {
193
+ return lines.map((line) => `- ${line}`);
194
+ }
195
+ export function fieldLabels(fields) {
196
+ return fields.map((field) => field.label);
197
+ }
198
+ export function questionForMissingLabel(label) {
199
+ const fields = [...IMPLEMENTATION_CONTRACT_FIELDS, ...DATA_SOURCE_CONTEXT_FIELDS, ...INTEGRATION_CONTEXT_FIELDS];
200
+ return fields.find((field) => field.label === label)?.question;
201
+ }
202
+ export function canonicalFactLabel(label) {
203
+ const normalized = normalizeLabel(label);
204
+ const fields = [...IMPLEMENTATION_CONTRACT_FIELDS, ...DATA_SOURCE_CONTEXT_FIELDS, ...INTEGRATION_CONTEXT_FIELDS];
205
+ return fields.find((field) => [field.label, ...field.aliases].some((item) => normalizeLabel(item) === normalized))?.label;
206
+ }
207
+ function normalizeLabel(label) {
208
+ return label.trim().toLowerCase().replace(/\s+/g, "");
209
+ }
@@ -0,0 +1,74 @@
1
+ import type { KdProduct, ProductProfile } from "../product/profile.ts";
2
+ export type KdPhase = "discuss" | "spec" | "plan" | "execute" | "verify" | "ship";
3
+ export type KdRisk = "low" | "medium" | "high";
4
+ export type KdRunStatus = "active" | "paused" | "done";
5
+ export type KdRiskSource = "manual" | "verify" | "ship";
6
+ export interface RiskAssessment {
7
+ level: KdRisk;
8
+ reason: string;
9
+ source: KdRiskSource;
10
+ updatedAt: string;
11
+ }
12
+ export interface GateResult {
13
+ passed: boolean;
14
+ reason?: string;
15
+ checkedAt: string;
16
+ }
17
+ export interface RepairState {
18
+ attempts: number;
19
+ maxAttempts: number;
20
+ lastFailureEvidence?: string;
21
+ lastFailureSignature?: string;
22
+ status: "idle" | "repairing" | "blocked";
23
+ updatedAt: string;
24
+ }
25
+ export interface KdQuestion {
26
+ id: string;
27
+ phase: KdPhase;
28
+ question: string;
29
+ reason?: string;
30
+ factLabel?: string;
31
+ proposedFactValue?: string;
32
+ choices?: string[];
33
+ blocking: boolean;
34
+ status: "open" | "answered";
35
+ answer?: string;
36
+ createdAt: string;
37
+ answeredAt?: string;
38
+ }
39
+ export type KdFactStatus = "current" | "superseded" | "rejected";
40
+ export type KdFactSource = "question" | "manual";
41
+ export interface KdFact {
42
+ key: string;
43
+ label: string;
44
+ value: string;
45
+ status: KdFactStatus;
46
+ source: KdFactSource;
47
+ sourceQuestionId?: string;
48
+ reason?: string;
49
+ createdAt: string;
50
+ updatedAt: string;
51
+ supersededBy?: string;
52
+ }
53
+ export interface ActiveRun {
54
+ id: string;
55
+ goal?: string;
56
+ phase: KdPhase;
57
+ cwd: string;
58
+ status?: KdRunStatus;
59
+ createdAt?: string;
60
+ updatedAt?: string;
61
+ product?: KdProduct;
62
+ version?: string;
63
+ profile?: ProductProfile;
64
+ riskAssessment?: RiskAssessment;
65
+ repair?: RepairState;
66
+ artifacts: Record<string, string>;
67
+ gate: GateResult;
68
+ questions?: KdQuestion[];
69
+ facts?: KdFact[];
70
+ }
71
+ export declare const PHASE_ORDER: KdPhase[];
72
+ export declare const PHASE_ARTIFACTS: Record<KdPhase, string>;
73
+ export declare function isKdPhase(value: string): value is KdPhase;
74
+ export declare function nextPhase(phase: KdPhase): KdPhase | undefined;
@@ -0,0 +1,16 @@
1
+ export const PHASE_ORDER = ["discuss", "spec", "plan", "execute", "verify", "ship"];
2
+ export const PHASE_ARTIFACTS = {
3
+ discuss: "CONTEXT.md",
4
+ spec: "SPEC.md",
5
+ plan: "PLAN.md",
6
+ execute: "EXECUTION.md",
7
+ verify: "VERIFY.md",
8
+ ship: "SHIP.md",
9
+ };
10
+ export function isKdPhase(value) {
11
+ return PHASE_ORDER.includes(value);
12
+ }
13
+ export function nextPhase(phase) {
14
+ const index = PHASE_ORDER.indexOf(phase);
15
+ return index >= 0 ? PHASE_ORDER[index + 1] : undefined;
16
+ }
@@ -0,0 +1,20 @@
1
+ export type KdProduct = "unknown" | "flagship" | "xinghan" | "cangqiong" | "enterprise";
2
+ export type KdPlatform = "unknown" | "cosmic" | "enterprise-csharp" | "enterprise-python";
3
+ export type KdTechStack = "unknown" | "java-bos" | "java-cosmic" | "csharp-bos" | "python-bos" | "ksql";
4
+ export type KdLanguage = "unknown" | "java" | "csharp" | "python" | "sql";
5
+ export type KnowledgeScope = "common" | "flagship" | "enterprise" | "enterprise-python" | "cosmic" | "xinghan" | "cangqiong";
6
+ export interface ProductProfile {
7
+ product: KdProduct;
8
+ displayName: string;
9
+ platform: KdPlatform;
10
+ techStack: KdTechStack;
11
+ language: KdLanguage;
12
+ knowledgeScope: KnowledgeScope;
13
+ requiresMetadataVerification: boolean;
14
+ notes: string[];
15
+ }
16
+ export declare function profileForProduct(product: KdProduct | undefined): ProductProfile;
17
+ export declare function resolveProductProfile(input: string | undefined): ProductProfile;
18
+ export declare function normalizeProduct(value: string | undefined): KdProduct;
19
+ export declare function isKnownProduct(product: KdProduct | undefined): boolean;
20
+ export declare function formatProductProfile(profile: ProductProfile | undefined): string;
@@ -0,0 +1,103 @@
1
+ const PROFILES = {
2
+ unknown: {
3
+ product: "unknown",
4
+ displayName: "unknown",
5
+ platform: "unknown",
6
+ techStack: "unknown",
7
+ language: "unknown",
8
+ knowledgeScope: "common",
9
+ requiresMetadataVerification: true,
10
+ notes: ["尚未选择产品;禁止假设插件技术栈、BOS/Cosmic 平台规则或 KSQL/SQL 交付规则。"],
11
+ },
12
+ flagship: {
13
+ product: "flagship",
14
+ displayName: "金蝶星空旗舰版",
15
+ platform: "cosmic",
16
+ techStack: "java-cosmic",
17
+ language: "java",
18
+ knowledgeScope: "flagship",
19
+ requiresMetadataVerification: true,
20
+ notes: [
21
+ "星空旗舰版基于苍穹/Cosmic 平台。使用平台元数据、插件生命周期、SDK 和后置检查约束,但接口可能存在旗舰版差异。",
22
+ "当前工作区存在 code/ 目录时,产品代码必须放在 code/ 下。",
23
+ "创建或编辑代码前,必须检查 code/ 下真实项目结构,并跟随其实际布局;可能按云、按应用组织,也可能不分模块。",
24
+ ],
25
+ },
26
+ xinghan: {
27
+ product: "xinghan",
28
+ displayName: "金蝶星瀚",
29
+ platform: "cosmic",
30
+ techStack: "java-cosmic",
31
+ language: "java",
32
+ knowledgeScope: "xinghan",
33
+ requiresMetadataVerification: true,
34
+ notes: ["星瀚基于苍穹/Cosmic 平台。默认按平台 Java 插件处理,但接口可能存在星瀚差异。"],
35
+ },
36
+ cangqiong: {
37
+ product: "cangqiong",
38
+ displayName: "金蝶苍穹",
39
+ platform: "cosmic",
40
+ techStack: "java-cosmic",
41
+ language: "java",
42
+ knowledgeScope: "cangqiong",
43
+ requiresMetadataVerification: true,
44
+ notes: ["Cosmic 在 KCode 中按苍穹平台语境处理。使用苍穹/Cosmic 平台插件、SDK、元数据、KSQL/SQL 和生命周期规则。"],
45
+ },
46
+ enterprise: {
47
+ product: "enterprise",
48
+ displayName: "金蝶企业版",
49
+ platform: "enterprise-csharp",
50
+ techStack: "csharp-bos",
51
+ language: "csharp",
52
+ knowledgeScope: "enterprise",
53
+ requiresMetadataVerification: true,
54
+ notes: ["使用企业版 C# 技术栈。Java 插件规则和 Cosmic API 不适用。"],
55
+ },
56
+ };
57
+ const PRODUCT_ALIASES = [
58
+ [/企业版|enterprise|csharp|c#|\.net/i, "enterprise"],
59
+ [/星瀚|xinghan/i, "xinghan"],
60
+ [/苍穹|cangqiong/i, "cangqiong"],
61
+ [/cosmic|云苍穹/i, "cangqiong"],
62
+ [/星空旗舰版|星空旗舰|旗舰版|旗舰|flagship/i, "flagship"],
63
+ ];
64
+ const ENTERPRISE_PYTHON_PATTERN = /(?:python|py)\s*(?:插件|plugin)|(?:python|py).*?(?:插件|plugin)|ironpython|(?:企业版|enterprise|星空|金蝶云星空|BOS).*?python\s*脚本|python\s*脚本.*?(?:企业版|enterprise|星空|金蝶云星空|BOS)/i;
65
+ function enterprisePythonProfile() {
66
+ return {
67
+ ...PROFILES.enterprise,
68
+ platform: "enterprise-python",
69
+ techStack: "python-bos",
70
+ language: "python",
71
+ knowledgeScope: "enterprise-python",
72
+ notes: [
73
+ "只有用户明确要求 Python 插件或 IronPython 脚本时,才使用金蝶云企业版 / BOS IronPython 插件模式。",
74
+ "企业版默认插件开发仍按 C# 处理,除非用户明确要求 Python 插件。",
75
+ "Python 插件通过 BOS 中的 IronPython 运行,并调用 Kingdee.BOS .NET 程序集;编码前必须确认插件类型、事件、FormId、字段标识和注册步骤。",
76
+ ],
77
+ };
78
+ }
79
+ export function profileForProduct(product) {
80
+ return PROFILES[product ?? "unknown"] ?? PROFILES.unknown;
81
+ }
82
+ export function resolveProductProfile(input) {
83
+ const text = input?.trim();
84
+ if (!text)
85
+ return profileForProduct("unknown");
86
+ if (ENTERPRISE_PYTHON_PATTERN.test(text))
87
+ return enterprisePythonProfile();
88
+ for (const [pattern, product] of PRODUCT_ALIASES) {
89
+ if (pattern.test(text))
90
+ return profileForProduct(product);
91
+ }
92
+ return profileForProduct("unknown");
93
+ }
94
+ export function normalizeProduct(value) {
95
+ return resolveProductProfile(value).product;
96
+ }
97
+ export function isKnownProduct(product) {
98
+ return Boolean(product && product !== "unknown");
99
+ }
100
+ export function formatProductProfile(profile) {
101
+ const resolved = profile ?? profileForProduct("unknown");
102
+ return `${resolved.product}/${resolved.platform}/${resolved.techStack}/${resolved.language}`;
103
+ }
package/docs/CHANGELOG.md CHANGED
@@ -6,6 +6,84 @@
6
6
 
7
7
  - 暂无。
8
8
 
9
+ ## 0.1.39 - 2026-06-07
10
+
11
+ ### 修复
12
+
13
+ - 持久规则和核心约束新增工具环境规则:Windows 默认使用 PowerShell、`rg`、`Get-ChildItem`、`Get-Content`,禁止假设 bash 可用。
14
+ - 明确文件定位必须基于真实搜索或目录读取结果;工具不可用时记录阻塞原因,禁止猜测路径或反复自述工具失败。
15
+ - 强化 `kd_subagent` 委派边界:子 agent 不能作为 shell/read/grep 失败的替代方案,不能用来绕过基础文件搜索。
16
+
17
+ ### 验证
18
+
19
+ - `npm run check`
20
+ - `npm run smoke:harness`
21
+ - `npm run smoke:kcode-command`
22
+ - `npm run build:cli`
23
+ - `git diff --check`
24
+
25
+ ## 0.1.38 - 2026-06-07
26
+
27
+ ### 修复
28
+
29
+ - 将用户要求的“足够事实后再编码”规则集中写入运行时策略,并自动生成到 `.pi/kd/PROJECT_CONTEXT.md` 的持久规则中,确保新会话继承约束。
30
+ - 明确 API/SDK 文档不能替代 FormId、字段/实体、插件事件、SQL/KSQL 表名、字段映射、并发、日志和验收数据等业务事实。
31
+ - `run.facts` 固化为唯一结构化事实源;已回答 questions 仅作为审计记录,读取状态时不再从历史问题答案反推门禁事实。
32
+ - 状态读取时过滤未知 `factLabel` 和无效事实值,规范化可识别事实标签,避免自造标签或占位答案污染门禁。
33
+
34
+ ### 文档
35
+
36
+ - 在 Harness 工作流和证据门禁文档中补充项目持久规则、业务事实门禁和外部/BOS 人工验证边界。
37
+
38
+ ### 验证
39
+
40
+ - `npm run check`
41
+ - `npm run smoke:harness`
42
+ - `npm run smoke:kcode-command`
43
+ - `npm run smoke:package`
44
+ - `npm run smoke:official`
45
+ - `npm run build:cli`
46
+ - `git diff --check`
47
+
48
+ ## 0.1.37 - 2026-06-07
49
+
50
+ ### 修复
51
+
52
+ - `factLabel` 限制为集中定义的事实标签或别名,禁止 LLM 自造标签写入门禁事实。
53
+ - 数据源上下文门禁只信任结构化 facts 和 evidence,不再仅凭 PLAN 自由文本证明 FormId、字段、插件事件或读写方式。
54
+ - 开放事实问题拒绝“可以、好的、是”等口头确认语;确认题只接受明确肯定或明确否定。
55
+ - 重复 open 问题不会再次写入阶段文档,`/kd-answer` 会区分已回答、无效答案和问题不存在。
56
+
57
+ ### 验证
58
+
59
+ - `npm run check`
60
+ - `npm run smoke:harness`
61
+ - `npm run smoke:package`
62
+ - `npm run smoke:official`
63
+ - `npm run build:cli`
64
+ - `git diff --check`
65
+
66
+ ## 0.1.36 - 2026-06-07
67
+
68
+ ### 修复
69
+
70
+ - `kd_question` 改为结构化事实账本:带 `factLabel` 的答案会写入 current fact,否定确认题不会成为事实。
71
+ - 同一 `factLabel` 已有 open 问题或当前事实时禁止重复提问;事实更正必须使用 `action=revise`,旧值会标记为 superseded。
72
+ - 移除 `kd_question` 的交互输入/自定义输入路径;当前只有一个 open blocking 问题时,用户普通短答会自动记录为答案。
73
+ - 占位答案如“待确认、未知、按实际环境、TODO/TBD”不能解除门禁。
74
+ - 子 agent 上下文显式注入已问已答事实和未回答问题,避免委派任务丢失关键约束。
75
+ - Cosmic metadata 数据源证据要求 evidence index `exitCode=0` 且 JSON 内容可解析,避免失败或空证据通过门禁。
76
+ - `kd_verify_result` 拒绝空命令、`unknown` 和占位命令,避免坏 payload 写入验证证据。
77
+
78
+ ### 验证
79
+
80
+ - `npm run check`
81
+ - `npm run smoke:harness`
82
+ - `npm run smoke:package`
83
+ - `npm run smoke:official`
84
+ - `npm run build:cli`
85
+ - `git diff --check`
86
+
9
87
  ## 0.1.35 - 2026-06-07
10
88
 
11
89
  ### 新增
@@ -303,12 +303,27 @@ kd_plan_status
303
303
  记录、回答或列出结构化问题:
304
304
 
305
305
  ```text
306
- kd_question action=ask question="采购入库单 Form ID 是否为 pur_receivebill?" choices=["","不是"]
306
+ kd_question action=ask factLabel="目标 FormId/单据或表单标识" question="目标 FormId、单据或表单标识是什么?"
307
+ kd_question action=ask factLabel="目标 FormId/单据或表单标识" proposedFactValue="pur_receivebill" question="采购入库单 Form ID 是否为 pur_receivebill?" choices=["是","不是"]
307
308
  kd_question action=answer id=Q-001 answer=是
309
+ kd_question action=revise factLabel="目标 FormId/单据或表单标识" answer="SAL_SaleOrder" reason="用户更正先前答案"
308
310
  kd_question action=list
309
311
  ```
310
312
 
311
- 一次只能登记一个当前最阻塞的问题,最多 3 个简短选项。
313
+ 一次只能登记一个当前最阻塞的问题,最多 3 个简短选项。`action=ask` 只登记 open 问题,不弹出输入框、不在工具调用内继续追问。
314
+
315
+ 结构化事实参数:
316
+
317
+ ```text
318
+ factLabel 可选,该答案要补齐的门禁事实标签
319
+ proposedFactValue 可选,确认题候选事实值;用户回答 是/yes 时写入 factLabel
320
+ ```
321
+
322
+ 门禁只消费当前结构化事实。没有 `factLabel` 的问答只作为上下文记录,不直接解除数据源、字段、接口等事实缺口。
323
+
324
+ `factLabel` 必须使用 KCode 集中定义的实现契约、数据源上下文或第三方对接事实标签,允许使用这些标签的别名并自动规范为正式标签。同一 `factLabel` 已有 open 问题时禁止重复提问;已有当前事实时禁止再次提问,必须使用 `action=revise` 显式修订。`待确认`、`未知`、`按实际环境`、`TODO/TBD`、单独“是/否”和“可以/好的”等口头确认不会解除开放事实问题。带 `proposedFactValue` 的确认题只接受明确肯定或明确否定。
325
+
326
+ 当前只有一个 open blocking 问题时,用户下一条普通短答会自动记录为该问题答案;命令、疑问句和明显的新任务不会自动记录。
312
327
 
313
328
  ### kd_subagent
314
329
 
@@ -38,6 +38,13 @@ KCode Harness 的门禁用于避免跳阶段、猜 SDK、伪造验证结果或
38
38
 
39
39
  `execute` 阶段只能写 `PLAN.md` 明确列出的源码文件。
40
40
 
41
+ 业务事实门禁:
42
+
43
+ - 通用实现契约必须覆盖触发入口、源对象、目标对象、数据变化或字段映射、业务规则、失败处理和验收样例。
44
+ - 数据源上下文必须覆盖目标 FormId/单据或表单标识、插件类型和触发事件、字段/实体/分录标识、数据读取写入方式;SQL/KSQL 场景还必须覆盖表名和数据库字段名。
45
+ - 第三方对接必须覆盖接口文档、对接方向、触发时机、接口地址、认证配置、字段映射、并发/幂等、超时重试限流、错误补偿、日志脱敏和请求/响应样例。
46
+ - API 文档、SDK 知识库和 PLAN 自由文本不能替代结构化 facts 或 evidence。
47
+
41
48
  ## SDK 签名证据
42
49
 
43
50
  涉及 Java/C# 产品代码时,进入 `execute` 前必须已有:
@@ -107,6 +107,11 @@ evidence/index.json
107
107
  原则:
108
108
 
109
109
  - 一次只问一个当前最阻塞的问题。
110
+ - 问题登记后保持 open;用户回答后必须用 `/kd-answer` 或 `kd_question action=answer` 记录。
111
+ - 需要解除门禁事实缺口的问题必须带结构化 `factLabel`;确认题必须用 `proposedFactValue` 表示候选事实值。
112
+ - 同一 `factLabel` 已有 open 问题或当前事实时禁止重复提问;用户更正事实时使用 `kd_question action=revise`,旧事实会标记为 superseded。
113
+ - 当前只有一个 open blocking 问题时,用户下一条普通短答会自动记录;占位答案不会解除门禁。
114
+ - 数据源 FormId、字段/实体、插件事件和读写方式必须来自结构化 facts 或 evidence;PLAN 自由文本不能单独证明这些事实。
110
115
  - 得到答案后再继续问下一个必要问题。
111
116
 
112
117
  ## 多个需求
@@ -143,6 +148,23 @@ KCode 会阻止过早写入 Java/XML/SQL/C# 等产品代码:
143
148
  - `execute` 只能写 `PLAN.md` 中批准的真实源码文件。
144
149
  - 临时发现要改新文件,回到 `plan` 更新计划。
145
150
  - 必须理解当前业务项目已有目录、模块、包名、基类和本地封装后再编码。
151
+ - API 文档、SDK 文档和知识库只能作为技术用法证据,不能替代 FormId、字段/实体、插件事件、SQL/KSQL 表名、数据库字段名、字段映射、并发/幂等、日志脱敏和验收数据等业务事实。
152
+ - 信息不足时登记一个最阻塞的结构化问题;禁止生成模板代码或占位实现。
153
+ - 内部插件、自动下推、第三方对接、字段改写和数据同步统一按实现契约、数据源上下文和对接上下文补齐事实,不为单个业务场景写死提示词。
154
+ - 工具使用必须匹配当前环境。Windows 项目默认使用 PowerShell、`rg`、`Get-ChildItem`、`Get-Content`;禁止假设 bash 可用,禁止猜测路径。
155
+
156
+ ## 项目持久规则
157
+
158
+ `kcode init` 和 `kcode context --refresh` 会生成 `.pi/kd/PROJECT_CONTEXT.md`。新会话必须先读取该文件,再计划或编辑代码。
159
+
160
+ 持久规则由 `src/harness/prompt-policy.ts` 集中管理,并自动写入项目上下文。规则要求:
161
+
162
+ - `run.facts` 是唯一结构化事实源,已回答 questions 只作为审计记录。
163
+ - `factLabel` 必须使用集中定义的事实标签或别名。
164
+ - PLAN 自由文本不能单独证明 FormId、字段、插件事件或读写方式。
165
+ - 外部系统操作、BOS 注册、人工功能测试和生产验证必须由用户提供可核验证据。
166
+ - 工具不可用时必须说明阻塞原因和需要用户执行的命令;禁止反复自述工具失败或用 `kd_subagent` 代替基础文件搜索。
167
+ - 不做旧状态迁移兼容或旧问题答案推断;坏状态只过滤,缺失事实重新提问确认。
146
168
 
147
169
  证据和门禁细节见 [证据和门禁](EVIDENCE_AND_GATES.md)。
148
170
 
@@ -169,6 +191,7 @@ KCode 支持把局部任务委派给隔离子 agent,用来降低长上下文
169
191
  - `doc` 只写明确指定的文档或阶段产物。
170
192
  - `code` 只能在 `execute` 阶段运行,并且只能修改 `PLAN.md` 批准文件。
171
193
  - `verify` 只读分析验证命令、失败证据和风险;实际命令和结果记录仍由主 agent 执行。
194
+ - 子 agent 不是 shell、read、grep 失败的替代方案;主 agent 必须先使用当前环境可用工具定位文件。
172
195
 
173
196
  `--dry-run` 会预览发送给子 agent 的上下文包,用来检查上下文是否过长、是否包含不该交给子 agent 的信息。
174
197