kcode-pi 0.1.20 → 0.1.24
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 +68 -658
- package/dist/cli/kcode.js +2 -2
- package/docs/CHANGELOG.md +76 -0
- package/docs/COMMAND_REFERENCE.md +512 -0
- package/docs/DEVELOPMENT.md +4 -4
- package/docs/EVIDENCE_AND_GATES.md +167 -0
- package/docs/HARNESS_WORKFLOW.md +147 -0
- package/docs/KCODE_DISTRIBUTION.md +8 -8
- package/docs/PRODUCT_PROFILE.md +108 -0
- package/docs/TROUBLESHOOTING.md +135 -0
- package/docs/USER_GUIDE.md +209 -0
- package/extensions/kingdee-harness.ts +28 -83
- package/extensions/kingdee-tools.ts +9 -9
- package/package.json +2 -2
- package/prompts/kd-discuss.md +6 -1
- package/prompts/kd-execute.md +2 -2
- package/prompts/kd-plan.md +2 -6
- package/prompts/kd-verify.md +1 -7
- package/skills/kd-cosmic-dev/SKILL.md +5 -6
- package/skills/kd-cosmic-review/SKILL.md +2 -2
- package/skills/kd-cosmic-unittest/SKILL.md +1 -1
- package/skills/kd-execute/SKILL.md +1 -1
- package/skills/kd-plan/SKILL.md +3 -2
- package/skills/kd-verify/SKILL.md +2 -2
- package/src/cli/kcode.ts +2 -2
- package/src/harness/artifacts.ts +20 -8
- package/src/harness/gates.ts +25 -58
- package/src/harness/messages.ts +113 -0
- package/src/harness/path-policy.ts +10 -4
- package/src/harness/prompt.ts +68 -0
- package/src/harness/sdk-policy.ts +2 -1
- package/src/harness/state.ts +2 -2
- package/src/harness/tdd-policy.ts +12 -9
- package/src/product/profile.ts +6 -16
package/skills/kd-plan/SKILL.md
CHANGED
|
@@ -13,8 +13,9 @@ Goal:
|
|
|
13
13
|
- List files to inspect before editing.
|
|
14
14
|
- List the inspected project layout and the exact target source root or file path before editing.
|
|
15
15
|
- List expected files to modify.
|
|
16
|
+
- State whether the plan involves product implementation, build, metadata, or SDK verification, with a brief basis for the decision.
|
|
16
17
|
- List required `kd_sdk_signature`, `kd_search`, `kd_table`, metadata, and build/compile checks.
|
|
17
|
-
- For
|
|
18
|
+
- For Cangqiong/Xinghan/Flagship Java projects, plan a Gradle build check for syntax/compile validation, using the current project command such as `.\gradlew.bat build`, `./gradlew build`, or `.\gradlew.bat :module:build`.
|
|
18
19
|
- For C#/Enterprise projects, plan `dotnet build` or `dotnet build <.sln/.csproj>` for syntax/compile validation.
|
|
19
20
|
- List `## Execution Steps` using `- [ ] STEP-001: ...` style IDs.
|
|
20
21
|
- List `## TDD / Red-Green Checks` with red evidence, green evidence, and the command/tool or product-specific check.
|
|
@@ -27,7 +28,7 @@ Gate:
|
|
|
27
28
|
- Execution must not start without `PLAN.md`.
|
|
28
29
|
- A plan for 星空旗舰版 is incomplete unless it records the existing project layout and exact target path to edit. If `code/` exists, follow its actual structure; if it does not, record the discovered source root or existing target file.
|
|
29
30
|
- A plan without validation commands is incomplete.
|
|
30
|
-
- A Java/C# plan
|
|
31
|
+
- A Java/C# plan without a concrete Gradle or dotnet validation command is incomplete.
|
|
31
32
|
- A plan without structured `STEP-001` execution steps is incomplete.
|
|
32
33
|
- A plan without TDD red/green checks is incomplete.
|
|
33
34
|
- A plan that relies on unverified Kingdee API names is incomplete; bundled knowledge alone is not enough when local SDK jars/dlls or compile evidence are available.
|
|
@@ -10,7 +10,7 @@ Use this skill after implementation.
|
|
|
10
10
|
Goal:
|
|
11
11
|
|
|
12
12
|
- Run planned validation commands.
|
|
13
|
-
- For
|
|
13
|
+
- For Cangqiong/Xinghan/Flagship Java projects, run the planned Gradle command to catch syntax/compile errors, for example `.\gradlew.bat build`, `./gradlew build`, or a narrow `:module:build` task.
|
|
14
14
|
- For C#/Enterprise projects, run `dotnet build` or `dotnet build <.sln/.csproj>` to catch syntax/compile errors.
|
|
15
15
|
- Run `kd_check` when code is available.
|
|
16
16
|
- Collect evidence into `.pi/kd/runs/<run-id>/VERIFY.md`.
|
|
@@ -20,5 +20,5 @@ Rules:
|
|
|
20
20
|
|
|
21
21
|
- Passing unit tests is not enough if acceptance criteria require workflow behavior.
|
|
22
22
|
- If validation cannot run, state the exact blocker.
|
|
23
|
-
-
|
|
23
|
+
- Passing evidence must show the real command, `Exit: 0`, and useful output summary.
|
|
24
24
|
- Do not ship while verification evidence is missing.
|
package/src/cli/kcode.ts
CHANGED
|
@@ -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 命令。请重新安装 kcode-
|
|
202
|
+
output: `${init.output}\n未找到随包 Pi CLI 或全局 pi 命令。请重新安装 kcode-pi 后再运行 kcode start。`,
|
|
203
203
|
};
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -340,7 +340,7 @@ function piCliPackageVersion(piCli: PiCliCommand): string | undefined {
|
|
|
340
340
|
|
|
341
341
|
function helpText(): string {
|
|
342
342
|
return [
|
|
343
|
-
"KCode
|
|
343
|
+
"KCode 金蝶开发入口",
|
|
344
344
|
"",
|
|
345
345
|
"用法:",
|
|
346
346
|
" kcode init 初始化当前项目的 .pi/settings.json",
|
package/src/harness/artifacts.ts
CHANGED
|
@@ -47,11 +47,12 @@ export function defaultArtifactContent(phase: KdPhase, goal?: string, profile?:
|
|
|
47
47
|
"- 版本:未知",
|
|
48
48
|
`- 技术栈:${profile?.techStack ?? "未知"}`,
|
|
49
49
|
`- 语言:${profile?.language ?? "未知"}`,
|
|
50
|
-
"-
|
|
51
|
-
"-
|
|
50
|
+
"- 需求条目:未知",
|
|
51
|
+
"- 目标对象:未知",
|
|
52
|
+
"- 依赖和批次:未知",
|
|
52
53
|
"- 非目标范围:未知",
|
|
53
54
|
"- 待确认问题:",
|
|
54
|
-
" -
|
|
55
|
+
" - 从需求来源中提取可确认事实;只把无法判断且阻塞推进的关键点列为待确认。",
|
|
55
56
|
"",
|
|
56
57
|
].join("\n");
|
|
57
58
|
case "spec":
|
|
@@ -60,9 +61,11 @@ export function defaultArtifactContent(phase: KdPhase, goal?: string, profile?:
|
|
|
60
61
|
"",
|
|
61
62
|
"## 验收标准",
|
|
62
63
|
"",
|
|
63
|
-
"##
|
|
64
|
+
"## 需求条目",
|
|
64
65
|
"",
|
|
65
|
-
"##
|
|
66
|
+
"## 数据对象、字段或影响范围",
|
|
67
|
+
"",
|
|
68
|
+
"## 依赖和批次",
|
|
66
69
|
"",
|
|
67
70
|
"## 异常行为和性能约束",
|
|
68
71
|
"",
|
|
@@ -81,12 +84,21 @@ export function defaultArtifactContent(phase: KdPhase, goal?: string, profile?:
|
|
|
81
84
|
"",
|
|
82
85
|
"## 允许修改的文件",
|
|
83
86
|
"",
|
|
87
|
+
"## 产品实现范围",
|
|
88
|
+
"",
|
|
89
|
+
"- 是否涉及产品实现、构建、元数据或 SDK 查证:待确认",
|
|
90
|
+
"- 判断依据:待确认",
|
|
91
|
+
"",
|
|
84
92
|
"## 必需的金蝶查证项",
|
|
85
93
|
"",
|
|
86
94
|
"- Java/C# 代码涉及 SDK 类、方法、构造器、枚举、属性时,必须先用 kd_sdk_signature 或项目构建输出确认真实签名。",
|
|
87
95
|
"- 知识库搜索、随包 Cosmic API 查询只能作为线索,不能作为最终方法签名事实。",
|
|
88
96
|
"- SDK 签名证据:evidence/sdk-signature.md",
|
|
89
97
|
"",
|
|
98
|
+
"## 需求条目 / 依赖 / 批次",
|
|
99
|
+
"",
|
|
100
|
+
"## 验收与验证对应关系",
|
|
101
|
+
"",
|
|
90
102
|
"## 执行步骤",
|
|
91
103
|
"",
|
|
92
104
|
"- [ ] STEP-001:检查现有目标文件,确认精确修改位置。",
|
|
@@ -103,10 +115,10 @@ export function defaultArtifactContent(phase: KdPhase, goal?: string, profile?:
|
|
|
103
115
|
"- Java 语法/编译检查:优先使用当前项目 Gradle 命令,例如 `./gradlew build`、`.\\gradlew.bat build` 或 `./gradlew :模块:build`。",
|
|
104
116
|
"- C# 语法/编译检查:使用 `dotnet build`、`dotnet build <.sln>` 或 `dotnet build <.csproj>`。",
|
|
105
117
|
"- 允许的检查:本地 SDK 签名查证、官方 API/基类/方法查证、元数据查证、kd_check、Gradle/dotnet 构建输出、项目已有测试框架、外部接口最小验证。",
|
|
106
|
-
"-
|
|
107
|
-
"-
|
|
118
|
+
"- 测试框架:优先使用项目已有测试基础设施。",
|
|
119
|
+
"- 命令无法运行时记录真实阻塞原因和残余风险,不能作为绿灯证据。",
|
|
108
120
|
"- 如果无法自动化测试,记录一个产品相关、实现前应失败且实现后应通过的检查。",
|
|
109
|
-
"-
|
|
121
|
+
"- SDK 方法签名事实必须来自当前项目 jar/dll、构建输出或官方元数据。",
|
|
110
122
|
"",
|
|
111
123
|
"## 验证命令",
|
|
112
124
|
"",
|
package/src/harness/gates.ts
CHANGED
|
@@ -10,6 +10,15 @@ import { tddPlanBlockReason, tddVerifyBlockReason } from "./tdd-policy.ts";
|
|
|
10
10
|
import { SDK_SIGNATURE_EVIDENCE, hasValidSdkSignatureEvidence, requiresSdkSignatureEvidence } from "./sdk-policy.ts";
|
|
11
11
|
import { runRoot } from "./paths.ts";
|
|
12
12
|
import { EVIDENCE_INDEX, hasEvidenceEntry } from "./evidence.ts";
|
|
13
|
+
import {
|
|
14
|
+
missingArtifactsReason,
|
|
15
|
+
missingEvidenceReason,
|
|
16
|
+
missingForTargetReason,
|
|
17
|
+
missingMarkerReason,
|
|
18
|
+
openQuestionsReason,
|
|
19
|
+
unknownProductReason,
|
|
20
|
+
unknownRiskReason,
|
|
21
|
+
} from "./messages.ts";
|
|
13
22
|
|
|
14
23
|
const REQUIRED_MARKERS: Partial<Record<KdPhase, string[]>> = {
|
|
15
24
|
plan: ["## 验证命令"],
|
|
@@ -37,8 +46,9 @@ function collectGateProblems(cwd: string, run: ActiveRun, phase: KdPhase, mode:
|
|
|
37
46
|
const missing: string[] = [];
|
|
38
47
|
const reasonParts: string[] = [];
|
|
39
48
|
|
|
40
|
-
if (
|
|
41
|
-
|
|
49
|
+
if (phaseIndex >= PHASE_ORDER.indexOf("execute") && !isKnownProduct(run.profile?.product ?? run.product)) {
|
|
50
|
+
const declaration = productImplementationDeclaration(cwd, run);
|
|
51
|
+
if (declaration !== false) reasonParts.push(unknownProductReason(declaration));
|
|
42
52
|
}
|
|
43
53
|
|
|
44
54
|
if (phaseIndex >= PHASE_ORDER.indexOf("ship") && !hasRiskAssessment(cwd, run)) {
|
|
@@ -103,8 +113,17 @@ function gateResult(reasonParts: string[]): GateResult {
|
|
|
103
113
|
};
|
|
104
114
|
}
|
|
105
115
|
|
|
106
|
-
function
|
|
107
|
-
|
|
116
|
+
function productImplementationDeclaration(cwd: string, run: ActiveRun): boolean | undefined {
|
|
117
|
+
const plan = readArtifact(cwd, run, "plan") ?? "";
|
|
118
|
+
const line = plan
|
|
119
|
+
.split(/\r?\n/)
|
|
120
|
+
.map((item) => item.trim())
|
|
121
|
+
.find((item) => item.includes("是否涉及产品实现、构建、元数据或 SDK 查证"));
|
|
122
|
+
if (!line) return undefined;
|
|
123
|
+
const value = line.replace(/^[-*]\s*/, "").split(/[::]/).slice(1).join(":").trim();
|
|
124
|
+
if (/^(是|涉及|需要|yes|true)(\s|。|,|,|;|;|$)/i.test(value)) return true;
|
|
125
|
+
if (/^(否|不涉及|不需要|无|no|false)(\s|。|,|,|;|;|$)/i.test(value)) return false;
|
|
126
|
+
return undefined;
|
|
108
127
|
}
|
|
109
128
|
|
|
110
129
|
function hasRiskAssessment(cwd: string, run: ActiveRun): boolean {
|
|
@@ -114,10 +133,6 @@ function hasRiskAssessment(cwd: string, run: ActiveRun): boolean {
|
|
|
114
133
|
return riskSectionHasContent(readArtifact(cwd, run, "verify") ?? "") || riskSectionHasContent(readArtifact(cwd, run, "ship") ?? "");
|
|
115
134
|
}
|
|
116
135
|
|
|
117
|
-
function unknownRiskReason(): string {
|
|
118
|
-
return "不能进入 ship:风险等级或风险原因未知。下一步:根据 VERIFY.md 和 SHIP.md 的残余风险执行 /kd-risk <low|medium|high> <原因>,或在风险章节写入真实风险说明后再刷新门禁。";
|
|
119
|
-
}
|
|
120
|
-
|
|
121
136
|
function riskSectionHasContent(content: string): boolean {
|
|
122
137
|
const match = content.match(/##\s*(残余风险|风险)\s*\r?\n([\s\S]*?)(?=\r?\n##\s+|$)/);
|
|
123
138
|
if (!match) return false;
|
|
@@ -130,10 +145,7 @@ function riskSectionHasContent(content: string): boolean {
|
|
|
130
145
|
function inspectOpenQuestions(run: ActiveRun): string | undefined {
|
|
131
146
|
const open = (run.questions ?? []).filter((question) => question.status === "open" && question.blocking);
|
|
132
147
|
if (open.length === 0) return undefined;
|
|
133
|
-
return
|
|
134
|
-
`存在未回答的阻断问题:${open.map((question) => `${question.id} ${question.question}`).join(";")}`,
|
|
135
|
-
"下一步:先向用户等待或获取答案,然后用 kd_question action=answer id=<问题编号> answer=<用户答案> 记录;不要绕过问题推进阶段。",
|
|
136
|
-
].join("。");
|
|
148
|
+
return openQuestionsReason(open);
|
|
137
149
|
}
|
|
138
150
|
|
|
139
151
|
function inspectStepState(cwd: string, run: ActiveRun, phase: KdPhase): string | undefined {
|
|
@@ -159,7 +171,7 @@ function inspectMarkers(cwd: string, run: ActiveRun, phase: KdPhase): string | u
|
|
|
159
171
|
|
|
160
172
|
const missing = markers.filter((marker) => !content.includes(marker));
|
|
161
173
|
if (missing.length === 0) return undefined;
|
|
162
|
-
return
|
|
174
|
+
return missingMarkerReason(phase, missing);
|
|
163
175
|
}
|
|
164
176
|
|
|
165
177
|
function inspectEvidence(cwd: string, run: ActiveRun, phase: KdPhase): string | undefined {
|
|
@@ -203,51 +215,6 @@ function evidenceArtifactSatisfied(cwd: string, run: ActiveRun, artifact: string
|
|
|
203
215
|
return existsSync(join(runRoot(cwd, run), artifact)) && hasEvidenceEntry(cwd, run, artifact);
|
|
204
216
|
}
|
|
205
217
|
|
|
206
|
-
function missingArtifactsReason(artifacts: string[]): string {
|
|
207
|
-
return [
|
|
208
|
-
`缺少必需产物:${artifacts.join(", ")}`,
|
|
209
|
-
`下一步:使用 /kd-artifact <阶段> 创建或更新阶段文档,或按当前阶段要求补写 ${artifacts.join(", ")} 的真实内容后再刷新门禁。`,
|
|
210
|
-
].join("。");
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function missingForTargetReason(target: KdPhase, artifacts: string[]): string {
|
|
214
|
-
const evidence = artifacts.filter((artifact) => artifact.startsWith("evidence/"));
|
|
215
|
-
const documents = artifacts.filter((artifact) => !artifact.startsWith("evidence/"));
|
|
216
|
-
const actions: string[] = [];
|
|
217
|
-
if (documents.length > 0) {
|
|
218
|
-
actions.push(`先补齐阶段文档 ${documents.join(", ")},可用 /kd-artifact 创建模板后填入真实分析、计划或验证内容`);
|
|
219
|
-
}
|
|
220
|
-
if (evidence.length > 0) {
|
|
221
|
-
actions.push(evidenceAction(evidence));
|
|
222
|
-
}
|
|
223
|
-
return `不能进入 ${target}:缺少 ${artifacts.join(", ")}。下一步:${actions.join(";")}。`;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function missingEvidenceReason(artifacts: string[]): string {
|
|
227
|
-
return `缺少必需证据:${artifacts.join(", ")}。下一步:${evidenceAction(artifacts)}。`;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function evidenceAction(artifacts: string[]): string {
|
|
231
|
-
return artifacts.map(evidenceArtifactAction).join(";");
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function evidenceArtifactAction(artifact: string): string {
|
|
235
|
-
switch (artifact) {
|
|
236
|
-
case SDK_SIGNATURE_EVIDENCE:
|
|
237
|
-
return "运行 kd_sdk_signature,从当前项目真实 SDK jar/dll 查证类、方法、构造器或属性签名,成功后自动写入 evidence/sdk-signature.md";
|
|
238
|
-
case COSMIC_CONFIG_EVIDENCE:
|
|
239
|
-
return "运行 kd_cosmic_config 生成 evidence/cosmic-config.txt;如果项目没有 ok-cosmic.json,先使用 KCode 默认配置,不要手写假结果";
|
|
240
|
-
case COSMIC_METADATA_EVIDENCE:
|
|
241
|
-
return "运行 kd_cosmic_metadata 查询目标表单/单据/字段元数据并生成 evidence/cosmic-metadata.json";
|
|
242
|
-
case COSMIC_API_EVIDENCE:
|
|
243
|
-
return "运行 kd_cosmic_api 查询相关 Cosmic API 线索并生成 evidence/cosmic-api.txt,再用 kd_sdk_signature 或构建输出确认签名";
|
|
244
|
-
case KSQL_LINT_EVIDENCE:
|
|
245
|
-
return "运行 kd_ksql_lint 校验 KSQL/SQL 交付内容并生成 evidence/ksql-lint.txt";
|
|
246
|
-
default:
|
|
247
|
-
return `运行 PLAN.md 中声明的验证命令,记录命令、Exit、STDOUT/STDERR 或工具输出到 ${artifact}`;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
218
|
function planHasMetadataRequirement(cwd: string, run: ActiveRun): boolean {
|
|
252
219
|
const plan = readArtifact(cwd, run, "plan") ?? "";
|
|
253
220
|
return /kd_cosmic_metadata|cosmic-metadata|cosmic-metadata\.json|metadata evidence|字段元数据证据|元数据证据/i.test(plan);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { KdPhase } from "./types.ts";
|
|
2
|
+
import { PHASE_ARTIFACTS } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
export function unknownProductReason(declaration: boolean | undefined): string {
|
|
5
|
+
if (declaration === undefined) {
|
|
6
|
+
return "不能进入 execute:产品画像未知,且 PLAN.md 未明确声明是否涉及产品实现、构建、元数据或 SDK 查证。下一步:先由当前需求和项目计划判断该范围;如果涉及产品实现,执行 /kd-product <flagship|xinghan|cangqiong|enterprise>;如果不涉及,在 PLAN.md 的“产品实现范围”写明“不涉及”及依据。";
|
|
7
|
+
}
|
|
8
|
+
return "不能进入 execute:PLAN.md 声明涉及产品实现、构建、元数据或 SDK 查证,但产品画像未知。下一步:根据需求、计划或用户回答确认产品,然后执行 /kd-product <flagship|xinghan|cangqiong|enterprise>;如果无法判断,先用 kd_question 只问一个最阻塞的产品确认问题。";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function unknownRiskReason(): string {
|
|
12
|
+
return "不能进入 ship:风险等级或风险原因未知。下一步:根据 VERIFY.md 和 SHIP.md 的残余风险执行 /kd-risk <low|medium|high> <原因>,或在风险章节写入真实风险说明后再刷新门禁。";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function openQuestionsReason(questions: Array<{ id: string; question: string }>): string {
|
|
16
|
+
return [
|
|
17
|
+
`存在未回答的阻断问题:${questions.map((question) => `${question.id} ${question.question}`).join(";")}`,
|
|
18
|
+
"下一步:先获取用户答案,然后用 kd_question action=answer id=<问题编号> answer=<用户答案> 记录。",
|
|
19
|
+
].join("。");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function missingMarkerReason(phase: KdPhase, missing: string[]): string {
|
|
23
|
+
return `${PHASE_ARTIFACTS[phase]} 缺少必需章节:${missing.join(", ")}。下一步:更新 ${PHASE_ARTIFACTS[phase]},补齐这些章节并写入真实内容。`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function missingArtifactsReason(artifacts: string[]): string {
|
|
27
|
+
return [
|
|
28
|
+
`缺少必需产物:${artifacts.join(", ")}`,
|
|
29
|
+
`下一步:使用 /kd-artifact <阶段> 创建或更新阶段文档,或按当前阶段要求补写 ${artifacts.join(", ")} 的真实内容后再刷新门禁。`,
|
|
30
|
+
].join("。");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function missingForTargetReason(target: KdPhase, artifacts: string[]): string {
|
|
34
|
+
const evidence = artifacts.filter((artifact) => artifact.startsWith("evidence/"));
|
|
35
|
+
const documents = artifacts.filter((artifact) => !artifact.startsWith("evidence/"));
|
|
36
|
+
const actions: string[] = [];
|
|
37
|
+
if (documents.length > 0) {
|
|
38
|
+
actions.push(`先补齐阶段文档 ${documents.join(", ")},可用 /kd-artifact 创建模板后填入真实分析、计划或验证内容`);
|
|
39
|
+
}
|
|
40
|
+
if (evidence.length > 0) {
|
|
41
|
+
actions.push(evidenceAction(evidence));
|
|
42
|
+
}
|
|
43
|
+
return `不能进入 ${target}:缺少 ${artifacts.join(", ")}。下一步:${actions.join(";")}。`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function missingEvidenceReason(artifacts: string[]): string {
|
|
47
|
+
return `缺少必需证据:${artifacts.join(", ")}。下一步:${evidenceAction(artifacts)}。`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function evidenceAction(artifacts: string[]): string {
|
|
51
|
+
return artifacts.map(evidenceArtifactAction).join(";");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function evidenceArtifactAction(artifact: string): string {
|
|
55
|
+
switch (artifact) {
|
|
56
|
+
case "evidence/sdk-signature.md":
|
|
57
|
+
return "运行 kd_sdk_signature,从当前项目真实 SDK jar/dll 查证类、方法、构造器或属性签名,成功后自动写入 evidence/sdk-signature.md";
|
|
58
|
+
case "evidence/cosmic-config.txt":
|
|
59
|
+
return "运行 kd_cosmic_config 生成 evidence/cosmic-config.txt;如果项目没有 ok-cosmic.json,先使用 KCode 默认配置";
|
|
60
|
+
case "evidence/cosmic-metadata.json":
|
|
61
|
+
return "运行 kd_cosmic_metadata 查询目标表单/单据/字段元数据并生成 evidence/cosmic-metadata.json";
|
|
62
|
+
case "evidence/cosmic-api.txt":
|
|
63
|
+
return "运行 kd_cosmic_api 查询相关 Cosmic API 线索并生成 evidence/cosmic-api.txt,再用 kd_sdk_signature 或构建输出确认签名";
|
|
64
|
+
case "evidence/ksql-lint.txt":
|
|
65
|
+
return "运行 kd_ksql_lint 校验 KSQL/SQL 交付内容并生成 evidence/ksql-lint.txt";
|
|
66
|
+
default:
|
|
67
|
+
return `运行 PLAN.md 中声明的验证命令,记录命令、Exit、STDOUT/STDERR 或工具输出到 ${artifact}`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function flagshipWriteBlockedReason(path: string): string {
|
|
72
|
+
return `星空旗舰版代码必须跟随当前项目结构写入 code/ 下,不能写到 ${path}。下一步:先读取当前项目 code/ 下的真实模块结构,在 PLAN.md 记录目标源码路径,再把写入路径改为 code/... 下的项目相对路径。`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function planWriteBlockedReason(path: string): string {
|
|
76
|
+
return `PLAN.md 未批准写入 ${path}。下一步:停止写入该文件,回到 plan 阶段检查项目结构和影响范围,把该文件加入 PLAN.md 的 ## 允许修改的文件 和执行步骤;重新通过门禁后再写。`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function flagshipPlanNeedsCodePathReason(): string {
|
|
80
|
+
return "不能进入 execute:星空旗舰版 PLAN.md 需要先记录当前项目 code/ 下的实际目标路径。下一步:列出 code/ 下模块,识别当前项目是按云、按应用还是不分模块组织,在 PLAN.md 写明真实目标文件。";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function flagshipPlanNeedsSourcePathReason(): string {
|
|
84
|
+
return "不能进入 execute:PLAN.md 需要先记录已检查当前项目结构,并写明实际源码根或目标文件路径。下一步:读取构建文件和 src/lib/bin 等目录,确认源码根后写入 PLAN.md 的 ## 已检查的项目结构、## 目标源码根 / 路径 和 ## 允许修改的文件。";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function tddPlanMissingReason(): string {
|
|
88
|
+
return "PLAN.md 缺少 ## TDD / 红绿检查。下一步:回到 plan 补充该章节,明确红灯证据、绿灯证据、验证命令或无法自动化时的产品验证替代方案;至少写明 evidence/tdd-red.md、evidence/tdd-green.md 和要运行的真实检查。";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function tddProductionMissingRedReason(path: string, evidenceName: string): string {
|
|
92
|
+
return `不能写生产源码 ${path}:缺少红灯证据 ${evidenceName}。下一步:先运行一个实现前应失败的检查,例如 kd_sdk_signature 方法不存在检查、元数据/API 检查、编译检查、kd_check、项目已有测试或外部接口最小验证;把命令、非 0 Exit 或失败输出写入 evidence/tdd-red.md 后再写生产源码。`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function tddVerifyBlockedReason(reasons: string[]): string {
|
|
96
|
+
return [
|
|
97
|
+
`不能进入 verify:${reasons.join(";")}。`,
|
|
98
|
+
"修复方式:重新运行 PLAN.md 中声明的同一验证命令或等价的产品验证命令,",
|
|
99
|
+
"并把命令、Exit、STDOUT/STDERR 或明确的工具输出写入对应 evidence 文件。",
|
|
100
|
+
].join("");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function redEvidenceInvalidReason(evidenceName: string): string {
|
|
104
|
+
return `${evidenceName} 内容无效:需要包含真实失败输出或非 0 退出码`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function greenEvidenceInvalidReason(evidenceName: string): string {
|
|
108
|
+
return `${evidenceName} 内容无效:绿灯证据需要同时包含成功结论和 Exit: 0/退出码:0`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function sdkSignatureWriteBlockedReason(path: string, evidenceName: string): string {
|
|
112
|
+
return `不能写生产源码 ${path}:缺少本地 SDK 签名证据 ${evidenceName}。下一步:运行 kd_sdk_signature,从当前项目真实 jar/dll 查证即将使用的 SDK 类、方法、构造器或属性签名;成功生成 evidence/sdk-signature.md 后再写代码。禁止凭记忆或随包知识库猜 SDK API。`;
|
|
113
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { ActiveRun } from "./types.ts";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { isAbsolute, join, relative } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
flagshipPlanNeedsCodePathReason,
|
|
6
|
+
flagshipPlanNeedsSourcePathReason,
|
|
7
|
+
flagshipWriteBlockedReason,
|
|
8
|
+
planWriteBlockedReason,
|
|
9
|
+
} from "./messages.ts";
|
|
4
10
|
|
|
5
11
|
const SOURCE_EXTENSIONS = new Set([
|
|
6
12
|
".java",
|
|
@@ -23,7 +29,7 @@ export function flagshipWriteBlockReason(run: ActiveRun | undefined, path: strin
|
|
|
23
29
|
if (normalized.startsWith(".pi/")) return undefined;
|
|
24
30
|
if (cwd && !hasWorkspaceCodeDir(cwd)) return undefined;
|
|
25
31
|
if (!normalized.startsWith("code/")) {
|
|
26
|
-
return
|
|
32
|
+
return flagshipWriteBlockedReason(path);
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
return undefined;
|
|
@@ -37,7 +43,7 @@ export function planWriteBlockReason(cwd: string, run: ActiveRun | undefined, pa
|
|
|
37
43
|
if (normalized.startsWith(".pi/")) return undefined;
|
|
38
44
|
if (planMentionsPath(plan, normalized)) return undefined;
|
|
39
45
|
|
|
40
|
-
return
|
|
46
|
+
return planWriteBlockedReason(normalized);
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
export function flagshipPlanBlockReason(cwd: string, run: ActiveRun | undefined, plan: string): string | undefined {
|
|
@@ -45,11 +51,11 @@ export function flagshipPlanBlockReason(cwd: string, run: ActiveRun | undefined,
|
|
|
45
51
|
|
|
46
52
|
if (hasWorkspaceCodeDir(cwd)) {
|
|
47
53
|
if (/(?:^|[\s`"'(])code[\\/][^\s`"')]+/i.test(plan)) return undefined;
|
|
48
|
-
return
|
|
54
|
+
return flagshipPlanNeedsCodePathReason();
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
if (planMentionsDiscoveredSourcePath(plan)) return undefined;
|
|
52
|
-
return
|
|
58
|
+
return flagshipPlanNeedsSourcePathReason();
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
function hasWorkspaceCodeDir(cwd: string): boolean {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { readProjectContext } from "../context/project-context.ts";
|
|
2
|
+
import { readArtifact } from "./artifacts.ts";
|
|
3
|
+
import { formatStatus } from "./format.ts";
|
|
4
|
+
import type { ActiveRun, KdPhase } from "./types.ts";
|
|
5
|
+
import { PHASE_ORDER } from "./types.ts";
|
|
6
|
+
|
|
7
|
+
export function workflowPromptForRun(cwd: string, run: ActiveRun, userText: string): string {
|
|
8
|
+
const status = formatStatus(cwd, run);
|
|
9
|
+
const memory = workflowMemoryForRun(cwd, run);
|
|
10
|
+
const phaseGuidance = phaseGuidanceForRun(run.phase);
|
|
11
|
+
const projectContext = readProjectContext(cwd);
|
|
12
|
+
|
|
13
|
+
return [
|
|
14
|
+
"用户输入:",
|
|
15
|
+
userText,
|
|
16
|
+
"",
|
|
17
|
+
"KCode Harness 状态:",
|
|
18
|
+
status,
|
|
19
|
+
"",
|
|
20
|
+
"KCode 阶段资料:",
|
|
21
|
+
memory,
|
|
22
|
+
"",
|
|
23
|
+
"项目上下文:",
|
|
24
|
+
projectContext ? trimForPrompt(projectContext, 1200) : "未生成。需要项目结构时先运行或提示用户运行 `kcode context --refresh`。",
|
|
25
|
+
"",
|
|
26
|
+
"当前阶段任务:",
|
|
27
|
+
phaseGuidance,
|
|
28
|
+
"",
|
|
29
|
+
"核心约束:",
|
|
30
|
+
"- 产品代码只在 execute 阶段写入,并限于 PLAN.md 批准的文件。",
|
|
31
|
+
"- Java/C# SDK 签名以当前项目 jar/dll、构建输出或官方元数据为准。",
|
|
32
|
+
"- Java/Cosmic 用当前项目 Gradle;C#/企业版用 dotnet build。",
|
|
33
|
+
"- evidence 记录命令、Exit 和关键输出;命令无法运行时记录阻塞原因。",
|
|
34
|
+
"- Windows 下优先使用项目相对路径;绝对路径使用 D:\\... 形式。",
|
|
35
|
+
].join("\n");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function phaseGuidanceForRun(phase: KdPhase): string {
|
|
39
|
+
const guidance: Record<KdPhase, string> = {
|
|
40
|
+
discuss: "梳理需求来源、范围、已知事实和一个最阻塞的待确认问题。",
|
|
41
|
+
spec: "把需求转成验收标准、数据对象、异常行为、依赖和风险。",
|
|
42
|
+
plan: "检查项目结构,写明目标路径、允许修改文件、查证项、验证命令和回滚说明。",
|
|
43
|
+
execute: "按 PLAN.md 实现,记录步骤结果、变更文件和 evidence。",
|
|
44
|
+
verify: "运行计划中的验证命令,更新 VERIFY.md、证据和残余风险。",
|
|
45
|
+
ship: "整理 SHIP.md,包括摘要、验证证据、风险和后续事项。",
|
|
46
|
+
};
|
|
47
|
+
return guidance[phase];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function workflowMemoryForRun(cwd: string, run: ActiveRun): string {
|
|
51
|
+
const currentIndex = PHASE_ORDER.indexOf(run.phase);
|
|
52
|
+
const phases = PHASE_ORDER.slice(Math.max(0, currentIndex - 1), currentIndex + 1);
|
|
53
|
+
return (
|
|
54
|
+
phases
|
|
55
|
+
.map((phase) => {
|
|
56
|
+
const content = readArtifact(cwd, run, phase);
|
|
57
|
+
if (!content) return undefined;
|
|
58
|
+
return [`## ${phase}`, trimForPrompt(content, 1500)].join("\n");
|
|
59
|
+
})
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.join("\n\n") || `阶段文档路径:.pi/kd/runs/${run.id}/`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function trimForPrompt(content: string, maxLength: number): string {
|
|
66
|
+
if (content.length <= maxLength) return content;
|
|
67
|
+
return `${content.slice(0, maxLength)}\n\n[...已截断;需要完整内容时读取本地文件...]`;
|
|
68
|
+
}
|
|
@@ -4,6 +4,7 @@ import type { ActiveRun } from "./types.ts";
|
|
|
4
4
|
import { runRoot } from "./paths.ts";
|
|
5
5
|
import { isSourceLikePath } from "./path-policy.ts";
|
|
6
6
|
import { hasEvidenceEntry } from "./evidence.ts";
|
|
7
|
+
import { sdkSignatureWriteBlockedReason } from "./messages.ts";
|
|
7
8
|
|
|
8
9
|
export const SDK_SIGNATURE_EVIDENCE = "evidence/sdk-signature.md";
|
|
9
10
|
|
|
@@ -31,7 +32,7 @@ export function sdkSignatureProductionWriteBlockReason(cwd: string, run: ActiveR
|
|
|
31
32
|
if (normalized.startsWith(".pi/")) return undefined;
|
|
32
33
|
if (hasValidSdkSignatureEvidence(cwd, run)) return undefined;
|
|
33
34
|
|
|
34
|
-
return
|
|
35
|
+
return sdkSignatureWriteBlockedReason(normalized, SDK_SIGNATURE_EVIDENCE);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
function normalizeRelativePath(path: string): string {
|
package/src/harness/state.ts
CHANGED
|
@@ -66,7 +66,7 @@ export function finishActiveRun(cwd: string, run: ActiveRun): ActiveRun {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export function createActiveRun(cwd: string, goal: string, productInput?: string, version?: string): ActiveRun {
|
|
69
|
-
const profile = resolveProductProfile(productInput
|
|
69
|
+
const profile = resolveProductProfile(productInput);
|
|
70
70
|
const now = new Date().toISOString();
|
|
71
71
|
const run: ActiveRun = {
|
|
72
72
|
id: createRunId(goal),
|
|
@@ -155,7 +155,7 @@ export function updateRisk(cwd: string, run: ActiveRun, risk: KdRisk, reason: st
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
export function ensurePhaseArtifact(cwd: string, run: ActiveRun, phase: KdPhase): string {
|
|
158
|
-
const path = ensureArtifact(cwd, run, phase, defaultArtifactContent(phase));
|
|
158
|
+
const path = ensureArtifact(cwd, run, phase, defaultArtifactContent(phase, run.goal, run.profile));
|
|
159
159
|
run.artifacts[phase] = PHASE_ARTIFACTS[phase];
|
|
160
160
|
run.gate = inspectGate(cwd, run);
|
|
161
161
|
writeActiveRun(cwd, run);
|
|
@@ -4,13 +4,20 @@ import type { ActiveRun } from "./types.ts";
|
|
|
4
4
|
import { runRoot } from "./paths.ts";
|
|
5
5
|
import { isSourceLikePath } from "./path-policy.ts";
|
|
6
6
|
import { hasEvidenceEntry } from "./evidence.ts";
|
|
7
|
+
import {
|
|
8
|
+
greenEvidenceInvalidReason,
|
|
9
|
+
redEvidenceInvalidReason,
|
|
10
|
+
tddPlanMissingReason,
|
|
11
|
+
tddProductionMissingRedReason,
|
|
12
|
+
tddVerifyBlockedReason,
|
|
13
|
+
} from "./messages.ts";
|
|
7
14
|
|
|
8
15
|
export const TDD_RED_EVIDENCE = "evidence/tdd-red.md";
|
|
9
16
|
export const TDD_GREEN_EVIDENCE = "evidence/tdd-green.md";
|
|
10
17
|
|
|
11
18
|
export function tddPlanBlockReason(plan: string): string | undefined {
|
|
12
19
|
if (/##\s*TDD\s*\/\s*红绿检查/i.test(plan)) return undefined;
|
|
13
|
-
return
|
|
20
|
+
return tddPlanMissingReason();
|
|
14
21
|
}
|
|
15
22
|
|
|
16
23
|
export function tddProductionWriteBlockReason(cwd: string, run: ActiveRun | undefined, path: string | undefined): string | undefined {
|
|
@@ -22,7 +29,7 @@ export function tddProductionWriteBlockReason(cwd: string, run: ActiveRun | unde
|
|
|
22
29
|
if (isTestLikePath(normalized)) return undefined;
|
|
23
30
|
if (hasValidTddEvidence(cwd, run, "red")) return undefined;
|
|
24
31
|
|
|
25
|
-
return
|
|
32
|
+
return tddProductionMissingRedReason(normalized, TDD_RED_EVIDENCE);
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
export function tddVerifyBlockReason(cwd: string, run: ActiveRun): string | undefined {
|
|
@@ -31,11 +38,7 @@ export function tddVerifyBlockReason(cwd: string, run: ActiveRun): string | unde
|
|
|
31
38
|
validateTddEvidence(cwd, run, "green"),
|
|
32
39
|
].filter((result) => !result.valid);
|
|
33
40
|
if (problems.length === 0) return undefined;
|
|
34
|
-
return
|
|
35
|
-
`不能进入 verify:${problems.map((problem) => problem.reason).join(";")}。`,
|
|
36
|
-
"修复方式:不要反复修改 evidence 文案;必须重新运行 PLAN.md 中声明的同一验证命令或等价的产品验证命令,",
|
|
37
|
-
"并把命令、Exit、STDOUT/STDERR 或明确的工具输出写入对应 evidence 文件。",
|
|
38
|
-
].join("");
|
|
41
|
+
return tddVerifyBlockedReason(problems.map((problem) => problem.reason ?? "未知 TDD evidence 问题"));
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
function hasValidTddEvidence(cwd: string, run: ActiveRun, kind: "red" | "green"): boolean {
|
|
@@ -58,7 +61,7 @@ function validateTddEvidence(cwd: string, run: ActiveRun, kind: "red" | "green")
|
|
|
58
61
|
const hasFailure = /red|fail|failed|failure|error|失败|未通过|Exit\s*[::]\s*[1-9]/i.test(content);
|
|
59
62
|
return hasFailure
|
|
60
63
|
? { valid: true }
|
|
61
|
-
: { valid: false, reason:
|
|
64
|
+
: { valid: false, reason: redEvidenceInvalidReason(evidenceName) };
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
const hasGreenExit = /Exit\s*[::]\s*0|退出码\s*[::]\s*0/i.test(content);
|
|
@@ -67,7 +70,7 @@ function validateTddEvidence(cwd: string, run: ActiveRun, kind: "red" | "green")
|
|
|
67
70
|
|
|
68
71
|
return {
|
|
69
72
|
valid: false,
|
|
70
|
-
reason:
|
|
73
|
+
reason: greenEvidenceInvalidReason(evidenceName),
|
|
71
74
|
};
|
|
72
75
|
}
|
|
73
76
|
|
package/src/product/profile.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type KdProduct = "unknown" | "flagship" | "
|
|
1
|
+
export type KdProduct = "unknown" | "flagship" | "xinghan" | "cangqiong" | "enterprise";
|
|
2
2
|
export type KdPlatform = "unknown" | "cosmic" | "enterprise-csharp" | "enterprise-python";
|
|
3
3
|
export type KdTechStack = "unknown" | "java-bos" | "java-cosmic" | "csharp-bos" | "python-bos" | "ksql";
|
|
4
4
|
export type KdLanguage = "unknown" | "java" | "csharp" | "python" | "sql";
|
|
@@ -24,7 +24,7 @@ const PROFILES: Record<KdProduct, ProductProfile> = {
|
|
|
24
24
|
language: "unknown",
|
|
25
25
|
knowledgeScope: "common",
|
|
26
26
|
requiresMetadataVerification: true,
|
|
27
|
-
notes: ["
|
|
27
|
+
notes: ["尚未选择产品;不要假设插件技术栈、BOS/Cosmic 平台规则或 KSQL/SQL 交付规则。"],
|
|
28
28
|
},
|
|
29
29
|
flagship: {
|
|
30
30
|
product: "flagship",
|
|
@@ -35,21 +35,11 @@ const PROFILES: Record<KdProduct, ProductProfile> = {
|
|
|
35
35
|
knowledgeScope: "flagship",
|
|
36
36
|
requiresMetadataVerification: true,
|
|
37
37
|
notes: [
|
|
38
|
-
"
|
|
38
|
+
"星空旗舰版基于苍穹/Cosmic 平台。使用平台元数据、插件生命周期、SDK 和后置检查约束,但接口可能存在旗舰版差异。",
|
|
39
39
|
"如果当前工作区存在 code/ 目录,产品代码应放在 code/ 下。",
|
|
40
40
|
"创建或编辑代码前,必须检查 code/ 下真实项目结构,并跟随其实际布局;可能按云、按应用组织,也可能不分模块。",
|
|
41
41
|
],
|
|
42
42
|
},
|
|
43
|
-
cosmic: {
|
|
44
|
-
product: "cosmic",
|
|
45
|
-
displayName: "金蝶 Cosmic 平台",
|
|
46
|
-
platform: "cosmic",
|
|
47
|
-
techStack: "java-cosmic",
|
|
48
|
-
language: "java",
|
|
49
|
-
knowledgeScope: "cosmic",
|
|
50
|
-
requiresMetadataVerification: true,
|
|
51
|
-
notes: ["Cosmic 是苍穹、星瀚和星空旗舰版的共享平台基础。"],
|
|
52
|
-
},
|
|
53
43
|
xinghan: {
|
|
54
44
|
product: "xinghan",
|
|
55
45
|
displayName: "金蝶星瀚",
|
|
@@ -58,7 +48,7 @@ const PROFILES: Record<KdProduct, ProductProfile> = {
|
|
|
58
48
|
language: "java",
|
|
59
49
|
knowledgeScope: "xinghan",
|
|
60
50
|
requiresMetadataVerification: true,
|
|
61
|
-
notes: ["
|
|
51
|
+
notes: ["星瀚基于苍穹/Cosmic 平台。默认按平台 Java 插件处理,但接口可能存在星瀚差异。"],
|
|
62
52
|
},
|
|
63
53
|
cangqiong: {
|
|
64
54
|
product: "cangqiong",
|
|
@@ -68,7 +58,7 @@ const PROFILES: Record<KdProduct, ProductProfile> = {
|
|
|
68
58
|
language: "java",
|
|
69
59
|
knowledgeScope: "cangqiong",
|
|
70
60
|
requiresMetadataVerification: true,
|
|
71
|
-
notes: ["
|
|
61
|
+
notes: ["Cosmic 在 KCode 中按苍穹平台语境处理。使用苍穹/Cosmic 平台插件、SDK、元数据、KSQL/SQL 和生命周期规则。"],
|
|
72
62
|
},
|
|
73
63
|
enterprise: {
|
|
74
64
|
product: "enterprise",
|
|
@@ -86,7 +76,7 @@ const PRODUCT_ALIASES: Array<[RegExp, KdProduct]> = [
|
|
|
86
76
|
[/企业版|enterprise|csharp|c#|\.net/i, "enterprise"],
|
|
87
77
|
[/星瀚|xinghan/i, "xinghan"],
|
|
88
78
|
[/苍穹|cangqiong/i, "cangqiong"],
|
|
89
|
-
[/cosmic|云苍穹/i, "
|
|
79
|
+
[/cosmic|云苍穹/i, "cangqiong"],
|
|
90
80
|
[/星空旗舰版|星空旗舰|旗舰版|旗舰|flagship/i, "flagship"],
|
|
91
81
|
];
|
|
92
82
|
|