kcode-pi 0.1.16 → 0.1.18
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 +67 -1
- package/extensions/kingdee-harness.ts +6 -3
- package/extensions/kingdee-tools.ts +1 -1
- package/package.json +1 -1
- package/prompts/kd-execute.md +2 -0
- package/prompts/kd-plan.md +6 -0
- package/prompts/kd-verify.md +6 -0
- package/skills/kd-cosmic-dev/SKILL.md +1 -1
- package/skills/kd-cosmic-unittest/SKILL.md +1 -1
- package/skills/kd-execute/SKILL.md +2 -0
- package/skills/kd-plan/SKILL.md +3 -0
- package/skills/kd-verify/SKILL.md +3 -1
- package/src/harness/artifacts.ts +4 -1
- package/src/harness/gates.ts +56 -6
- package/src/harness/path-policy.ts +6 -4
- package/src/harness/plan-steps.ts +9 -5
- package/src/harness/sdk-policy.ts +1 -1
- package/src/harness/tdd-policy.ts +50 -10
- package/src/tools/build-debug.ts +1 -12
package/README.md
CHANGED
|
@@ -403,9 +403,47 @@ ship 汇总变更、验证证据、风险和后续事项
|
|
|
403
403
|
- 写生产源码前必须已有 `evidence/tdd-red.md`,内容可以是 API/基类/方法签名检查、元数据检查、编译检查、既有测试框架或外部接口最小验证的失败输出。
|
|
404
404
|
- 进入 `execute` 后,只允许写入 `PLAN.md` 明确列出的源码文件;如果临时发现要改新文件,必须先回到 plan 更新 `PLAN.md`。
|
|
405
405
|
- 进入 `verify` 前,`EXECUTION.md` 必须逐个完成 `PLAN.md` 中的所有 `STEP-###`,并为每个步骤记录真实存在的 `evidence/...` 文件;同时必须已有 `evidence/tdd-red.md` 和 `evidence/tdd-green.md`。
|
|
406
|
+
- `evidence/tdd-green.md` 必须包含真实成功输出和 `Exit: 0` 或 `退出码:0`;不能写“需在开发环境验证”“待验证”“未执行”等不确定结论。
|
|
407
|
+
- 如果门禁提示 evidence 内容无效,不要反复改文案或补关键词;必须重新运行 `PLAN.md` 中声明的真实验证命令,并记录命令、Exit、STDOUT/STDERR 或工具输出。
|
|
408
|
+
- Java / 苍穹 / 星空旗舰版的语法和编译验证优先使用当前项目 Gradle 命令,例如 `.\gradlew.bat build`、`./gradlew build` 或 `.\gradlew.bat :模块:build`。
|
|
409
|
+
- C# / 企业版的语法和编译验证使用 `dotnet build`、`dotnet build <.sln>` 或 `dotnet build <.csproj>`。
|
|
410
|
+
- 不要写“Kingdee IDE 中编译”作为验证方式;如果构建命令无法运行,记录真实阻塞原因和残余风险,不能把它当作绿灯证据。
|
|
406
411
|
- 旗舰版项目必须先检查当前项目结构。若存在 `code/`,跟随 `code/` 下的实际组织;若不存在,必须在 `PLAN.md` 记录实际源码根或目标文件。
|
|
407
412
|
- 不允许凭空写 demo、sample、scaffold,或在不了解项目结构时新建猜测目录。
|
|
408
413
|
|
|
414
|
+
### SDK 签名门禁怎么用
|
|
415
|
+
|
|
416
|
+
当需求涉及 Java/C# 插件代码时,KCode 会强制要求先查证当前项目真实 SDK 的类、方法、构造器、枚举或属性签名。这个门禁是为了避免 LLM 根据记忆编出不存在的方法。
|
|
417
|
+
|
|
418
|
+
推荐操作顺序:
|
|
419
|
+
|
|
420
|
+
```text
|
|
421
|
+
1. discuss/spec 阶段确认需求、产品、插件类型和目标单据。
|
|
422
|
+
2. plan 阶段检查当前项目源码结构、构建文件和 SDK 依赖位置。
|
|
423
|
+
3. 用 kd_sdk_signature 查证要调用的 SDK 类型和方法。
|
|
424
|
+
4. 确认生成 .pi/kd/runs/<run-id>/evidence/sdk-signature.md。
|
|
425
|
+
5. 记录 evidence/tdd-red.md。
|
|
426
|
+
6. 进入 execute 后再写 PLAN.md 中列出的生产源码。
|
|
427
|
+
7. 写完后记录 evidence/tdd-green.md,再进入 verify。
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
示例:
|
|
431
|
+
|
|
432
|
+
```text
|
|
433
|
+
kd_sdk_signature product=flagship query=SaveServiceHelper method=save
|
|
434
|
+
kd_sdk_signature product=flagship query=OperationServiceHelper method=executeOperate
|
|
435
|
+
kd_sdk_signature product=enterprise query=DynamicObject method=GetDynamicObject
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
如果 `kd_sdk_signature` 找不到结果,不要让 Agent 直接写代码。先检查:
|
|
439
|
+
|
|
440
|
+
- 当前是否在业务项目根目录启动 `kcode start`。
|
|
441
|
+
- 项目是否能看到 SDK jar/dll,例如 `lib/`、`libs/`、`bin/`、`WebSite/bin/` 或构建缓存。
|
|
442
|
+
- `PLAN.md` 是否记录了真实源码根和 SDK 依赖位置。
|
|
443
|
+
- 是否可以通过当前项目已有封装类、编译输出或官方元数据反查签名。
|
|
444
|
+
|
|
445
|
+
只有 `kd_sdk_signature`、当前项目构建输出、项目源码封装或官方元数据能作为签名事实。`kd_search`、`kd_cosmic_api` 和 README 里的示例只用于找线索。
|
|
446
|
+
|
|
409
447
|
示例计划步骤:
|
|
410
448
|
|
|
411
449
|
```markdown
|
|
@@ -483,7 +521,7 @@ kd_cosmic_metadata 查询官方 Cosmic 表单/单据元数据
|
|
|
483
521
|
kd_cosmic_api 查询随包 Cosmic API 知识线索
|
|
484
522
|
kd_sdk_signature 从当前项目实际 SDK jar/dll 中读取类和方法签名
|
|
485
523
|
kd_ksql_lint 运行官方 ok-ksql SQL/KSQL lint
|
|
486
|
-
kd_build 按产品画像执行或 dry-run
|
|
524
|
+
kd_build 按产品画像执行或 dry-run 构建;Java 使用 Gradle,C# 使用 dotnet build
|
|
487
525
|
kd_debug 分析金蝶日志和堆栈
|
|
488
526
|
```
|
|
489
527
|
|
|
@@ -542,6 +580,34 @@ npm view kcode-pi version
|
|
|
542
580
|
npm install -g kcode-pi@latest
|
|
543
581
|
```
|
|
544
582
|
|
|
583
|
+
如果 `npm install -g` 没有更新到新版本,或提示 `EEXIST: file already exists ... kcode.cmd`,通常是全局命令 shim 或旧包目录没有被 npm 清干净。按下面顺序处理:
|
|
584
|
+
|
|
585
|
+
```powershell
|
|
586
|
+
npm uninstall -g kcode-pi
|
|
587
|
+
npm install -g kcode-pi@latest
|
|
588
|
+
kcode version
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
如果仍提示 `kcode.cmd` 已存在,先确认它属于旧的 `kcode-pi` 全局安装:
|
|
592
|
+
|
|
593
|
+
```powershell
|
|
594
|
+
npm root -g
|
|
595
|
+
where kcode
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
删除 `where kcode` 指向的旧 `kcode.cmd`、`kcode.ps1` 后再重装:
|
|
599
|
+
|
|
600
|
+
```powershell
|
|
601
|
+
npm install -g kcode-pi@latest
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
如果 npm 仍使用缓存或企业镜像没有同步新版本,先确认 registry 上真实可安装版本:
|
|
605
|
+
|
|
606
|
+
```powershell
|
|
607
|
+
npm view kcode-pi version
|
|
608
|
+
npm view kcode-pi versions --json
|
|
609
|
+
```
|
|
610
|
+
|
|
545
611
|
升级后建议在业务项目根目录重新执行:
|
|
546
612
|
|
|
547
613
|
```powershell
|
|
@@ -129,6 +129,9 @@ function workflowPromptForRun(cwd: string, run: NonNullable<ReturnType<typeof re
|
|
|
129
129
|
"路径规则:在 Windows 工作区内,优先使用项目相对路径;如需绝对路径必须使用 `D:\\...` 这类 Windows 路径,禁止把路径改写成 `/mnt/d/...`、`/d/...` 等 WSL/MSYS 风格路径。",
|
|
130
130
|
"execute 阶段只能写 PLAN.md 明确列出的源码文件;如果目标文件不在计划内,必须先回到 plan 更新 PLAN.md。",
|
|
131
131
|
"写生产源码前必须先有红灯证据 evidence/tdd-red.md;Java/C# 还必须有 SDK 签名证据 evidence/sdk-signature.md。红绿证据可以是 kd_sdk_signature 本地 SDK 签名、API/基类/方法签名、元数据、编译、既有测试框架或外部接口最小验证,不要为了测试引入额外 jar。",
|
|
132
|
+
"语法/编译验证必须使用真实项目构建命令:Java/Cosmic/苍穹/星空旗舰版使用当前项目 Gradle 命令,例如 `./gradlew build`、`.\\gradlew.bat build` 或 `:模块:build`;C#/企业版使用 `dotnet build` 或 `dotnet build <.sln/.csproj>`。",
|
|
133
|
+
"不要写“Kingdee IDE 中编译”作为验证方式或绿灯证据;命令无法运行时,记录真实阻塞原因和残余风险,不能标记为通过。",
|
|
134
|
+
"如果门禁提示 evidence 内容无效,不要通过改写结论、补关键词或反复读取文件来过关;必须重新运行 PLAN.md 中声明的真实验证命令,并记录命令、Exit、STDOUT/STDERR 或工具输出。",
|
|
132
135
|
].join("\n");
|
|
133
136
|
}
|
|
134
137
|
|
|
@@ -154,11 +157,11 @@ function codeWriteBlockReason(cwd: string, path: string | undefined): string | u
|
|
|
154
157
|
|
|
155
158
|
const run = readActiveRun(cwd);
|
|
156
159
|
if (!run) {
|
|
157
|
-
return "KCode
|
|
160
|
+
return "KCode 工作流未启动,不能直接写产品代码。下一步:先用自然语言说明需求让 KCode 自动进入 discuss,或手动运行 /kd-start <需求> 创建 run;完成 discuss -> spec -> plan -> execute 后再写生产源码。";
|
|
158
161
|
}
|
|
159
162
|
|
|
160
163
|
if (run.phase !== "execute") {
|
|
161
|
-
return `当前 KCode 阶段是 ${run.phase}
|
|
164
|
+
return `当前 KCode 阶段是 ${run.phase},不能写产品代码。下一步:先完成当前阶段文档和门禁,用 /kd-advance 推进到 execute;如果发现计划不完整,更新 PLAN.md,而不是直接写代码。`;
|
|
162
165
|
}
|
|
163
166
|
|
|
164
167
|
return (
|
|
@@ -333,7 +336,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
333
336
|
const path = typeof input.path === "string" ? input.path : undefined;
|
|
334
337
|
const hint = path ? windowsPathHint(path) : undefined;
|
|
335
338
|
if (hint && ["read", "write", "edit"].includes(event.toolName)) {
|
|
336
|
-
const reason = `当前是 Windows 工作区,不能使用 WSL/MSYS 路径 ${path}
|
|
339
|
+
const reason = `当前是 Windows 工作区,不能使用 WSL/MSYS 路径 ${path}。下一步:改用项目相对路径;如果必须使用绝对路径,使用 Windows 路径 ${hint};不要再尝试 /mnt/d 或 /d 路径。`;
|
|
337
340
|
if (ctx.hasUI) ctx.ui.notify(reason, "warning");
|
|
338
341
|
return { block: true, reason };
|
|
339
342
|
}
|
|
@@ -365,7 +365,7 @@ const kdBuildTool = defineTool({
|
|
|
365
365
|
description: "按产品画像运行或预览金蝶构建命令,支持 Cosmic Java 和企业版 C# 项目。",
|
|
366
366
|
parameters: Type.Object({
|
|
367
367
|
product: Type.String({ description: "金蝶产品:cangqiong、xinghan、flagship、cosmic 或 enterprise。" }),
|
|
368
|
-
target: Type.Optional(Type.String({ description: "Java 可提供 Gradle
|
|
368
|
+
target: Type.Optional(Type.String({ description: "Java 可提供 Gradle task;C# 可提供 .sln/.csproj 路径。" })),
|
|
369
369
|
dryRun: Type.Optional(Type.Boolean({ description: "只返回构建命令,不实际执行。" })),
|
|
370
370
|
}),
|
|
371
371
|
|
package/package.json
CHANGED
package/prompts/kd-execute.md
CHANGED
|
@@ -6,6 +6,8 @@ description: 在 Harness 门禁约束下执行当前金蝶实施计划。
|
|
|
6
6
|
|
|
7
7
|
编辑代码前,先使用 `kd_plan_status` 检查当前 run。如果缺少 `PLAN.md`、`evidence/sdk-signature.md` 或门禁被阻塞,必须停止并说明缺少的文档或证据。通过后只实现 `PLAN.md` 批准的内容,并更新 `EXECUTION.md`。禁止凭记忆、模型知识或随包知识库猜 SDK 方法签名。
|
|
8
8
|
|
|
9
|
+
实现后要按计划运行真实构建检查语法问题:Java 使用当前项目 Gradle 命令,例如 `.\gradlew.bat build` 或 `.\gradlew.bat :模块:build`;C# 使用 `dotnet build` 或 `dotnet build <.sln/.csproj>`。不能写“Kingdee IDE 中编译”作为验证结果;命令未运行就不能记录为绿灯证据。
|
|
10
|
+
|
|
9
11
|
用户补充说明:
|
|
10
12
|
|
|
11
13
|
{{args}}
|
package/prompts/kd-plan.md
CHANGED
|
@@ -6,6 +6,12 @@ description: 为当前金蝶 Harness run 编写实施计划。
|
|
|
6
6
|
|
|
7
7
|
读取 `CONTEXT.md` 和 `SPEC.md`,编写或更新 `PLAN.md`。必须包含已检查的项目结构、需要查看的文件、预计修改的真实路径、必须查证的金蝶 API/元数据、SDK 签名证据、验证命令和回滚说明。Java/C# SDK 方法签名必须来自 `kd_sdk_signature` 当前项目 jar/dll、项目构建输出或官方元数据,不能凭记忆猜。
|
|
8
8
|
|
|
9
|
+
验证命令必须贴近真实项目:
|
|
10
|
+
|
|
11
|
+
- Java / Cosmic / 苍穹 / 星空旗舰版:优先使用当前项目 Gradle 构建做语法和编译检查,例如 `./gradlew build`、`./gradlew :模块:build` 或 Windows 下的 `.\gradlew.bat build`。
|
|
12
|
+
- C# / 企业版:使用 `dotnet build` 或 `dotnet build <.sln/.csproj>` 做语法和编译检查。
|
|
13
|
+
- 不要写“Kingdee IDE 中编译”。如果当前机器缺少依赖导致命令无法运行,要记录真实阻塞原因,不能把未执行的编译当作绿灯证据。
|
|
14
|
+
|
|
9
15
|
用户补充说明:
|
|
10
16
|
|
|
11
17
|
{{args}}
|
package/prompts/kd-verify.md
CHANGED
|
@@ -6,6 +6,12 @@ description: 验证当前金蝶实现并收集证据。
|
|
|
6
6
|
|
|
7
7
|
执行计划中的验证命令,收集证据,运行可用检查,并更新 `VERIFY.md`。如果某项验证无法运行,记录具体阻塞原因和残余风险。
|
|
8
8
|
|
|
9
|
+
验证优先使用真实构建命令检查语法和编译:
|
|
10
|
+
|
|
11
|
+
- Java / Cosmic / 苍穹 / 星空旗舰版:运行当前项目 Gradle 命令,例如 `.\gradlew.bat build`、`.\gradlew.bat :模块:build` 或同等 Gradle task。
|
|
12
|
+
- C# / 企业版:运行 `dotnet build`、`dotnet build <.sln>` 或 `dotnet build <.csproj>`。
|
|
13
|
+
- 绿灯证据必须包含实际命令、`Exit: 0` 和输出摘要。不能用“Kingdee IDE 中编译”“需在开发环境验证”替代真实证据。
|
|
14
|
+
|
|
9
15
|
用户补充说明:
|
|
10
16
|
|
|
11
17
|
{{args}}
|
|
@@ -58,7 +58,7 @@ description: 金蝶 Cosmic 体系 Java 插件开发技能,适用于苍穹、
|
|
|
58
58
|
|
|
59
59
|
5. 验证结果。
|
|
60
60
|
- 对修改的 Java 代码运行 `kd_check`。
|
|
61
|
-
- 条件允许时运行 `kd_build` 或计划中的 Gradle
|
|
61
|
+
- 条件允许时运行 `kd_build` 或计划中的 Gradle 命令,优先用 `.\gradlew.bat build`、`./gradlew build` 或最窄可行的 `:模块:build` 检查语法和编译。
|
|
62
62
|
- 把验证证据写进 `EXECUTION.md`,最终验收交给 `kd-verify`。
|
|
63
63
|
|
|
64
64
|
## 硬性规则
|
|
@@ -79,7 +79,7 @@ POJO 或简单枚举场景可以简要说明后直接实现。
|
|
|
79
79
|
写完测试后:
|
|
80
80
|
|
|
81
81
|
- 使用 `kd_build` 或计划中的命令运行最窄可行 Gradle test 任务。
|
|
82
|
-
- 如果本机缺少业务 jar 或本地配置导致 Gradle
|
|
82
|
+
- 如果本机缺少业务 jar 或本地配置导致 Gradle 不能运行,明确说明真实阻塞原因,并给出应该运行的 Gradle task;不要写“在 IDE 中运行”作为验证结论。
|
|
83
83
|
- 存在 harness run 时,把测试文件和验证结果写入 `.pi/kd/runs/<run-id>/EXECUTION.md`。
|
|
84
84
|
|
|
85
85
|
## 输出要求
|
|
@@ -22,5 +22,7 @@ Rules:
|
|
|
22
22
|
- Do not skip planned steps. Every `STEP-###` in `PLAN.md` must be marked complete in `EXECUTION.md` with a real `evidence/...` file before entering verify.
|
|
23
23
|
- Before writing production source files, run the planned red check and record failing output in `evidence/tdd-red.md`. This can be `kd_sdk_signature` local SDK signature, metadata, compile/build, existing project test, or minimal external-interface evidence.
|
|
24
24
|
- Before entering verify, rerun the same check and record passing output in `evidence/tdd-green.md`.
|
|
25
|
+
- After implementation, run the planned real build command for syntax/compile validation when available: Java uses the project Gradle command such as `.\gradlew.bat build` or `.\gradlew.bat :module:build`; C# uses `dotnet build` or `dotnet build <.sln/.csproj>`.
|
|
25
26
|
- Do not add JUnit, Mockito, NUnit, xUnit, or any extra test jar/framework only to satisfy the gate. Use existing approved project test infrastructure if it already exists.
|
|
27
|
+
- Do not record "compile in Kingdee IDE" or "needs local development environment verification" as green evidence. If the command cannot run, record the blocker instead of marking verification passed.
|
|
26
28
|
- If implementation needs a plan change, update `PLAN.md` first.
|
package/skills/kd-plan/SKILL.md
CHANGED
|
@@ -14,6 +14,8 @@ Goal:
|
|
|
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
16
|
- List required `kd_sdk_signature`, `kd_search`, `kd_table`, metadata, and build/compile checks.
|
|
17
|
+
- For Java/Cosmic/Cangqiong/Flagship 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
|
+
- For C#/Enterprise projects, plan `dotnet build` or `dotnet build <.sln/.csproj>` for syntax/compile validation.
|
|
17
19
|
- List `## Execution Steps` using `- [ ] STEP-001: ...` style IDs.
|
|
18
20
|
- List `## TDD / Red-Green Checks` with red evidence, green evidence, and the command/tool or product-specific check.
|
|
19
21
|
- Do not plan to add third-party test jars or frameworks only for red/green checks. For Kingdee plugin work, prefer `kd_sdk_signature` against current project jars/dlls, metadata checks, compile/build checks, existing project tests, or minimal external-interface tests.
|
|
@@ -25,6 +27,7 @@ Gate:
|
|
|
25
27
|
- Execution must not start without `PLAN.md`.
|
|
26
28
|
- 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.
|
|
27
29
|
- A plan without validation commands is incomplete.
|
|
30
|
+
- A Java/C# plan that uses vague wording such as "compile in Kingdee IDE" instead of a real Gradle or dotnet command is incomplete.
|
|
28
31
|
- A plan without structured `STEP-001` execution steps is incomplete.
|
|
29
32
|
- A plan without TDD red/green checks is incomplete.
|
|
30
33
|
- 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,6 +10,8 @@ Use this skill after implementation.
|
|
|
10
10
|
Goal:
|
|
11
11
|
|
|
12
12
|
- Run planned validation commands.
|
|
13
|
+
- For Java/Cosmic/Cangqiong/Flagship 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
|
+
- For C#/Enterprise projects, run `dotnet build` or `dotnet build <.sln/.csproj>` to catch syntax/compile errors.
|
|
13
15
|
- Run `kd_check` when code is available.
|
|
14
16
|
- Collect evidence into `.pi/kd/runs/<run-id>/VERIFY.md`.
|
|
15
17
|
- Record failures, fixes, skipped checks, and residual risk.
|
|
@@ -18,5 +20,5 @@ Rules:
|
|
|
18
20
|
|
|
19
21
|
- Passing unit tests is not enough if acceptance criteria require workflow behavior.
|
|
20
22
|
- If validation cannot run, state the exact blocker.
|
|
23
|
+
- Do not use "Kingdee IDE" as a verification target. Evidence must show the real command, `Exit: 0`, and useful output summary.
|
|
21
24
|
- Do not ship while verification evidence is missing.
|
|
22
|
-
|
package/src/harness/artifacts.ts
CHANGED
|
@@ -100,8 +100,11 @@ export function defaultArtifactContent(phase: KdPhase, goal?: string, profile?:
|
|
|
100
100
|
"- 红灯证据:evidence/tdd-red.md",
|
|
101
101
|
"- 绿灯证据:evidence/tdd-green.md",
|
|
102
102
|
"- 红绿检查命令或工具:未知",
|
|
103
|
-
"-
|
|
103
|
+
"- Java 语法/编译检查:优先使用当前项目 Gradle 命令,例如 `./gradlew build`、`.\\gradlew.bat build` 或 `./gradlew :模块:build`。",
|
|
104
|
+
"- C# 语法/编译检查:使用 `dotnet build`、`dotnet build <.sln>` 或 `dotnet build <.csproj>`。",
|
|
105
|
+
"- 允许的检查:本地 SDK 签名查证、官方 API/基类/方法查证、元数据查证、kd_check、Gradle/dotnet 构建输出、项目已有测试框架、外部接口最小验证。",
|
|
104
106
|
"- 不要为了满足门禁引入第三方测试 jar 或框架。",
|
|
107
|
+
"- 不要写“Kingdee IDE 中编译”作为验证方式;命令无法运行时记录真实阻塞原因,不能作为绿灯证据。",
|
|
105
108
|
"- 如果无法自动化测试,记录一个产品相关、实现前应失败且实现后应通过的检查。",
|
|
106
109
|
"- 禁止凭记忆或随包知识库猜 SDK 方法签名;签名事实必须来自当前项目 jar/dll、构建输出或官方元数据。",
|
|
107
110
|
"",
|
package/src/harness/gates.ts
CHANGED
|
@@ -36,7 +36,7 @@ export function inspectGate(cwd: string, run: ActiveRun): GateResult {
|
|
|
36
36
|
const evidenceProblem = inspectEvidence(cwd, run, run.phase);
|
|
37
37
|
const questionProblem = inspectOpenQuestions(run);
|
|
38
38
|
const reasonParts = [];
|
|
39
|
-
if (missing.length > 0) reasonParts.push(
|
|
39
|
+
if (missing.length > 0) reasonParts.push(missingArtifactsReason([...new Set(missing)]));
|
|
40
40
|
if (markerProblem) reasonParts.push(markerProblem);
|
|
41
41
|
if (stepProblem) reasonParts.push(stepProblem);
|
|
42
42
|
if (evidenceProblem) reasonParts.push(evidenceProblem);
|
|
@@ -55,7 +55,9 @@ export function canEnterPhase(cwd: string, run: ActiveRun, target: KdPhase): Gat
|
|
|
55
55
|
const reasonParts: string[] = [];
|
|
56
56
|
|
|
57
57
|
if (target !== "discuss" && !isKnownProduct(run.profile?.product ?? run.product)) {
|
|
58
|
-
reasonParts.push(
|
|
58
|
+
reasonParts.push(
|
|
59
|
+
"不能离开 discuss:产品画像未知。下一步:根据需求或用户回答确认产品,然后执行 /kd-product <flagship|cosmic|xinghan|cangqiong|enterprise>;如果无法判断,先用 kd_question 只问一个最阻塞的产品确认问题。",
|
|
60
|
+
);
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
for (let i = 0; i < targetIndex; i++) {
|
|
@@ -89,7 +91,7 @@ export function canEnterPhase(cwd: string, run: ActiveRun, target: KdPhase): Gat
|
|
|
89
91
|
missing.push(PHASE_ARTIFACTS.verify);
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
if (missing.length > 0) reasonParts.push(
|
|
94
|
+
if (missing.length > 0) reasonParts.push(missingForTargetReason(target, [...new Set(missing)]));
|
|
93
95
|
const reason = reasonParts.length > 0 ? reasonParts.join("; ") : undefined;
|
|
94
96
|
return {
|
|
95
97
|
passed: !reason,
|
|
@@ -101,7 +103,10 @@ export function canEnterPhase(cwd: string, run: ActiveRun, target: KdPhase): Gat
|
|
|
101
103
|
function inspectOpenQuestions(run: ActiveRun): string | undefined {
|
|
102
104
|
const open = (run.questions ?? []).filter((question) => question.status === "open" && question.blocking);
|
|
103
105
|
if (open.length === 0) return undefined;
|
|
104
|
-
return
|
|
106
|
+
return [
|
|
107
|
+
`存在未回答的阻断问题:${open.map((question) => `${question.id} ${question.question}`).join(";")}`,
|
|
108
|
+
"下一步:先向用户等待或获取答案,然后用 kd_question action=answer id=<问题编号> answer=<用户答案> 记录;不要绕过问题推进阶段。",
|
|
109
|
+
].join("。");
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
function inspectStepState(cwd: string, run: ActiveRun, phase: KdPhase): string | undefined {
|
|
@@ -127,13 +132,13 @@ function inspectMarkers(cwd: string, run: ActiveRun, phase: KdPhase): string | u
|
|
|
127
132
|
|
|
128
133
|
const missing = markers.filter((marker) => !content.includes(marker));
|
|
129
134
|
if (missing.length === 0) return undefined;
|
|
130
|
-
return `${PHASE_ARTIFACTS[phase]} 缺少必需章节:${missing.join(", ")}
|
|
135
|
+
return `${PHASE_ARTIFACTS[phase]} 缺少必需章节:${missing.join(", ")}。下一步:更新 ${PHASE_ARTIFACTS[phase]},补齐这些章节并写入真实内容;不要只添加空标题。`;
|
|
131
136
|
}
|
|
132
137
|
|
|
133
138
|
function inspectEvidence(cwd: string, run: ActiveRun, phase: KdPhase): string | undefined {
|
|
134
139
|
const missing = missingEvidenceForPhase(cwd, run, phase);
|
|
135
140
|
if (missing.length === 0) return undefined;
|
|
136
|
-
return
|
|
141
|
+
return missingEvidenceReason(missing);
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
function isCosmicRun(run: ActiveRun): boolean {
|
|
@@ -170,6 +175,51 @@ function evidenceArtifactSatisfied(cwd: string, run: ActiveRun, artifact: string
|
|
|
170
175
|
return existsSync(join(runRoot(cwd, run), artifact));
|
|
171
176
|
}
|
|
172
177
|
|
|
178
|
+
function missingArtifactsReason(artifacts: string[]): string {
|
|
179
|
+
return [
|
|
180
|
+
`缺少必需产物:${artifacts.join(", ")}`,
|
|
181
|
+
`下一步:使用 /kd-artifact <阶段> 创建或更新阶段文档,或按当前阶段要求补写 ${artifacts.join(", ")} 的真实内容后再刷新门禁。`,
|
|
182
|
+
].join("。");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function missingForTargetReason(target: KdPhase, artifacts: string[]): string {
|
|
186
|
+
const evidence = artifacts.filter((artifact) => artifact.startsWith("evidence/"));
|
|
187
|
+
const documents = artifacts.filter((artifact) => !artifact.startsWith("evidence/"));
|
|
188
|
+
const actions: string[] = [];
|
|
189
|
+
if (documents.length > 0) {
|
|
190
|
+
actions.push(`先补齐阶段文档 ${documents.join(", ")},可用 /kd-artifact 创建模板后填入真实分析、计划或验证内容`);
|
|
191
|
+
}
|
|
192
|
+
if (evidence.length > 0) {
|
|
193
|
+
actions.push(evidenceAction(evidence));
|
|
194
|
+
}
|
|
195
|
+
return `不能进入 ${target}:缺少 ${artifacts.join(", ")}。下一步:${actions.join(";")}。`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function missingEvidenceReason(artifacts: string[]): string {
|
|
199
|
+
return `缺少必需证据:${artifacts.join(", ")}。下一步:${evidenceAction(artifacts)}。`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function evidenceAction(artifacts: string[]): string {
|
|
203
|
+
return artifacts.map(evidenceArtifactAction).join(";");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function evidenceArtifactAction(artifact: string): string {
|
|
207
|
+
switch (artifact) {
|
|
208
|
+
case SDK_SIGNATURE_EVIDENCE:
|
|
209
|
+
return "运行 kd_sdk_signature,从当前项目真实 SDK jar/dll 查证类、方法、构造器或属性签名,成功后自动写入 evidence/sdk-signature.md";
|
|
210
|
+
case COSMIC_CONFIG_EVIDENCE:
|
|
211
|
+
return "运行 kd_cosmic_config 生成 evidence/cosmic-config.txt;如果项目没有 ok-cosmic.json,先使用 KCode 默认配置,不要手写假结果";
|
|
212
|
+
case COSMIC_METADATA_EVIDENCE:
|
|
213
|
+
return "运行 kd_cosmic_metadata 查询目标表单/单据/字段元数据并生成 evidence/cosmic-metadata.json";
|
|
214
|
+
case COSMIC_API_EVIDENCE:
|
|
215
|
+
return "运行 kd_cosmic_api 查询相关 Cosmic API 线索并生成 evidence/cosmic-api.txt,再用 kd_sdk_signature 或构建输出确认签名";
|
|
216
|
+
case KSQL_LINT_EVIDENCE:
|
|
217
|
+
return "运行 kd_ksql_lint 校验 KSQL/SQL 交付内容并生成 evidence/ksql-lint.txt";
|
|
218
|
+
default:
|
|
219
|
+
return `运行 PLAN.md 中声明的验证命令,记录命令、Exit、STDOUT/STDERR 或工具输出到 ${artifact}`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
173
223
|
function planHasMetadataRequirement(cwd: string, run: ActiveRun): boolean {
|
|
174
224
|
const plan = readArtifact(cwd, run, "plan") ?? "";
|
|
175
225
|
return /kd_cosmic_metadata|cosmic-metadata|cosmic-metadata\.json|metadata evidence|字段元数据证据|元数据证据/i.test(plan);
|
|
@@ -22,7 +22,9 @@ export function flagshipWriteBlockReason(run: ActiveRun | undefined, path: strin
|
|
|
22
22
|
const normalized = normalizeRelativePath(cwd && isAbsolute(path) ? relative(cwd, path) : path);
|
|
23
23
|
if (normalized.startsWith(".pi/")) return undefined;
|
|
24
24
|
if (cwd && !hasWorkspaceCodeDir(cwd)) return undefined;
|
|
25
|
-
if (!normalized.startsWith("code/"))
|
|
25
|
+
if (!normalized.startsWith("code/")) {
|
|
26
|
+
return `星空旗舰版代码必须跟随当前项目结构写入 code/ 下,不能写到 ${path}。下一步:先读取当前项目 code/ 下的真实模块结构,在 PLAN.md 记录目标源码路径,再把写入路径改为 code/... 下的项目相对路径。`;
|
|
27
|
+
}
|
|
26
28
|
|
|
27
29
|
return undefined;
|
|
28
30
|
}
|
|
@@ -35,7 +37,7 @@ export function planWriteBlockReason(cwd: string, run: ActiveRun | undefined, pa
|
|
|
35
37
|
if (normalized.startsWith(".pi/")) return undefined;
|
|
36
38
|
if (planMentionsPath(plan, normalized)) return undefined;
|
|
37
39
|
|
|
38
|
-
return `PLAN.md 未批准写入 ${normalized}
|
|
40
|
+
return `PLAN.md 未批准写入 ${normalized}。下一步:停止写入该文件,回到 plan 阶段检查项目结构和影响范围,把该文件加入 PLAN.md 的 ## 允许修改的文件 和执行步骤;重新通过门禁后再写。`;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
export function flagshipPlanBlockReason(cwd: string, run: ActiveRun | undefined, plan: string): string | undefined {
|
|
@@ -43,11 +45,11 @@ export function flagshipPlanBlockReason(cwd: string, run: ActiveRun | undefined,
|
|
|
43
45
|
|
|
44
46
|
if (hasWorkspaceCodeDir(cwd)) {
|
|
45
47
|
if (/(?:^|[\s`"'(])code[\\/][^\s`"')]+/i.test(plan)) return undefined;
|
|
46
|
-
return "不能进入 execute:星空旗舰版 PLAN.md 必须先记录当前项目 code/
|
|
48
|
+
return "不能进入 execute:星空旗舰版 PLAN.md 必须先记录当前项目 code/ 下的实际目标路径;不要按固定模块规则猜路径。下一步:列出 code/ 下模块,识别当前项目是按云、按应用还是不分模块组织,在 PLAN.md 写明真实目标文件。";
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
if (planMentionsDiscoveredSourcePath(plan)) return undefined;
|
|
50
|
-
return "不能进入 execute:PLAN.md 必须先记录已检查当前项目结构,并写明实际源码根或目标文件路径;当前项目没有 code/
|
|
52
|
+
return "不能进入 execute:PLAN.md 必须先记录已检查当前项目结构,并写明实际源码根或目标文件路径;当前项目没有 code/ 时更不能猜路径。下一步:读取构建文件和 src/lib/bin 等目录,确认源码根后写入 PLAN.md 的 ## 已检查的项目结构、## 目标源码根 / 路径 和 ## 允许修改的文件。";
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
function hasWorkspaceCodeDir(cwd: string): boolean {
|
|
@@ -21,16 +21,16 @@ export function parsePlanSteps(plan: string): PlanStep[] {
|
|
|
21
21
|
|
|
22
22
|
export function planStepsBlockReason(plan: string): string | undefined {
|
|
23
23
|
if (!/##\s*执行步骤/i.test(plan)) {
|
|
24
|
-
return "PLAN.md 缺少 ##
|
|
24
|
+
return "PLAN.md 缺少 ## 执行步骤。下一步:回到 plan,补充 `## 执行步骤`,把工作拆成 `- [ ] STEP-001:...` 这种可跟踪步骤;每一步都应能产生代码变更或 evidence。";
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const steps = parsePlanSteps(plan);
|
|
28
28
|
if (steps.length === 0) {
|
|
29
|
-
return "PLAN.md
|
|
29
|
+
return "PLAN.md 没有可执行步骤。下一步:在 `## 执行步骤` 下使用 `- [ ] STEP-001:...` 列出步骤;不要用普通段落代替可勾选步骤。";
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const duplicate = firstDuplicate(steps.map((step) => step.id));
|
|
33
|
-
if (duplicate) return `PLAN.md 存在重复步骤编号:${duplicate}
|
|
33
|
+
if (duplicate) return `PLAN.md 存在重复步骤编号:${duplicate}。下一步:重新编号执行步骤,确保 STEP-001、STEP-002 等编号唯一且顺序稳定。`;
|
|
34
34
|
return undefined;
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -54,8 +54,12 @@ export function executionStepsBlockReason(cwd: string, run: ActiveRun, plan: str
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
if (missing.length > 0)
|
|
58
|
-
|
|
57
|
+
if (missing.length > 0) {
|
|
58
|
+
return `不能进入 verify:EXECUTION.md 未完成计划步骤 ${missing.join(", ")}。下一步:继续执行这些步骤,完成后在 EXECUTION.md 的 ## 步骤结果 中用 \`- [x] STEP-###:已完成。证据:evidence/step-###.md\` 记录;未真正执行时不要勾选。`;
|
|
59
|
+
}
|
|
60
|
+
if (missingEvidence.length > 0) {
|
|
61
|
+
return `不能进入 verify:步骤 ${missingEvidence.join(", ")} 缺少已落地的 evidence 文件。下一步:为每个步骤创建真实 evidence 文件,写入检查命令、结果、改动文件或验证输出,然后在 EXECUTION.md 对应步骤行引用该 evidence 路径。`;
|
|
62
|
+
}
|
|
59
63
|
return undefined;
|
|
60
64
|
}
|
|
61
65
|
|
|
@@ -29,7 +29,7 @@ export function sdkSignatureProductionWriteBlockReason(cwd: string, run: ActiveR
|
|
|
29
29
|
if (normalized.startsWith(".pi/")) return undefined;
|
|
30
30
|
if (hasValidSdkSignatureEvidence(cwd, run)) return undefined;
|
|
31
31
|
|
|
32
|
-
return `不能写生产源码 ${normalized}:缺少本地 SDK 签名证据 ${SDK_SIGNATURE_EVIDENCE}
|
|
32
|
+
return `不能写生产源码 ${normalized}:缺少本地 SDK 签名证据 ${SDK_SIGNATURE_EVIDENCE}。下一步:运行 kd_sdk_signature,从当前项目真实 jar/dll 查证即将使用的 SDK 类、方法、构造器或属性签名;成功生成 evidence/sdk-signature.md 后再写代码。禁止凭记忆或随包知识库猜 SDK API。`;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function normalizeRelativePath(path: string): string {
|
|
@@ -9,7 +9,7 @@ export const TDD_GREEN_EVIDENCE = "evidence/tdd-green.md";
|
|
|
9
9
|
|
|
10
10
|
export function tddPlanBlockReason(plan: string): string | undefined {
|
|
11
11
|
if (/##\s*TDD\s*\/\s*红绿检查/i.test(plan)) return undefined;
|
|
12
|
-
return "PLAN.md 缺少 ## TDD /
|
|
12
|
+
return "PLAN.md 缺少 ## TDD / 红绿检查。下一步:回到 plan 补充该章节,明确红灯证据、绿灯证据、验证命令或无法自动化时的产品验证替代方案;至少写明 evidence/tdd-red.md、evidence/tdd-green.md 和要运行的真实检查。";
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function tddProductionWriteBlockReason(cwd: string, run: ActiveRun | undefined, path: string | undefined): string | undefined {
|
|
@@ -21,25 +21,65 @@ export function tddProductionWriteBlockReason(cwd: string, run: ActiveRun | unde
|
|
|
21
21
|
if (isTestLikePath(normalized)) return undefined;
|
|
22
22
|
if (hasValidTddEvidence(cwd, run, "red")) return undefined;
|
|
23
23
|
|
|
24
|
-
return `不能写生产源码 ${normalized}:缺少红灯证据 ${TDD_RED_EVIDENCE}
|
|
24
|
+
return `不能写生产源码 ${normalized}:缺少红灯证据 ${TDD_RED_EVIDENCE}。下一步:先运行一个实现前应失败的检查,例如 kd_sdk_signature 方法不存在检查、元数据/API 检查、编译检查、kd_check、项目已有测试或外部接口最小验证;把命令、非 0 Exit 或失败输出写入 evidence/tdd-red.md 后再写生产源码。`;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function tddVerifyBlockReason(cwd: string, run: ActiveRun): string | undefined {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
const problems = [
|
|
29
|
+
validateTddEvidence(cwd, run, "red"),
|
|
30
|
+
validateTddEvidence(cwd, run, "green"),
|
|
31
|
+
].filter((result) => !result.valid);
|
|
32
|
+
if (problems.length === 0) return undefined;
|
|
33
|
+
return [
|
|
34
|
+
`不能进入 verify:${problems.map((problem) => problem.reason).join(";")}。`,
|
|
35
|
+
"修复方式:不要反复修改 evidence 文案;必须重新运行 PLAN.md 中声明的同一验证命令或等价的产品验证命令,",
|
|
36
|
+
"并把命令、Exit、STDOUT/STDERR 或明确的工具输出写入对应 evidence 文件。",
|
|
37
|
+
].join("");
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
function hasValidTddEvidence(cwd: string, run: ActiveRun, kind: "red" | "green"): boolean {
|
|
41
|
+
return validateTddEvidence(cwd, run, kind).valid;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function validateTddEvidence(cwd: string, run: ActiveRun, kind: "red" | "green"): { valid: boolean; reason?: string } {
|
|
36
45
|
const evidenceName = kind === "red" ? TDD_RED_EVIDENCE : TDD_GREEN_EVIDENCE;
|
|
37
46
|
const evidencePath = join(runRoot(cwd, run), evidenceName);
|
|
38
|
-
if (!existsSync(evidencePath)) return false;
|
|
47
|
+
if (!existsSync(evidencePath)) return { valid: false, reason: `缺少 ${evidenceName}` };
|
|
39
48
|
|
|
40
49
|
const content = readFileSync(evidencePath, "utf8");
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
const uncertainty = uncertaintyReason(content);
|
|
51
|
+
if (uncertainty) {
|
|
52
|
+
return { valid: false, reason: `${evidenceName} 内容无效:${uncertainty}` };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (kind === "red") {
|
|
56
|
+
const hasFailure = /red|fail|failed|failure|error|失败|未通过|Exit\s*[::]\s*[1-9]/i.test(content);
|
|
57
|
+
return hasFailure
|
|
58
|
+
? { valid: true }
|
|
59
|
+
: { valid: false, reason: `${evidenceName} 内容无效:必须包含真实失败输出或非 0 退出码,不能只写结论` };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const hasGreenExit = /Exit\s*[::]\s*0|退出码\s*[::]\s*0/i.test(content);
|
|
63
|
+
const hasSuccess = /green|pass|passed|success|成功|通过/i.test(content);
|
|
64
|
+
if (hasGreenExit && hasSuccess) return { valid: true };
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
valid: false,
|
|
68
|
+
reason: `${evidenceName} 内容无效:绿灯证据必须同时包含成功结论和 Exit: 0/退出码:0,不能只写“通过”或人工结论`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function uncertaintyReason(content: string): string | undefined {
|
|
73
|
+
const patterns: Array<[RegExp, string]> = [
|
|
74
|
+
[/需.*验证|需要.*验证|待.*验证|后续.*验证/i, "包含待验证措辞"],
|
|
75
|
+
[/未验证|未执行|没有执行|无法执行|无法验证/i, "声明未完成验证"],
|
|
76
|
+
[/TODO|待补充|待完善|假设|预计|理论上|应该可以/i, "包含占位或推测性结论"],
|
|
77
|
+
[/编译验证通过[^。\n]*(需|需要|待).*验证/i, "同时声明通过和仍需验证,结论矛盾"],
|
|
78
|
+
];
|
|
79
|
+
for (const [pattern, reason] of patterns) {
|
|
80
|
+
if (pattern.test(content)) return reason;
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
43
83
|
}
|
|
44
84
|
|
|
45
85
|
function isTestLikePath(path: string): boolean {
|
package/src/tools/build-debug.ts
CHANGED
|
@@ -148,8 +148,6 @@ function planJavaBuild(cwd: string, profile: ProductProfile, target?: string): B
|
|
|
148
148
|
const task = target ?? "build";
|
|
149
149
|
const gradlewBat = join(cwd, "gradlew.bat");
|
|
150
150
|
const gradlew = join(cwd, "gradlew");
|
|
151
|
-
const mvnwBat = join(cwd, "mvnw.cmd");
|
|
152
|
-
const mvnw = join(cwd, "mvnw");
|
|
153
151
|
|
|
154
152
|
if (existsSync(gradlewBat)) {
|
|
155
153
|
return buildPlan(profile, gradlewBat, [task], cwd, "检测到 Cosmic 家族 Java 项目的 Gradle wrapper。");
|
|
@@ -157,20 +155,11 @@ function planJavaBuild(cwd: string, profile: ProductProfile, target?: string): B
|
|
|
157
155
|
if (existsSync(gradlew)) {
|
|
158
156
|
return buildPlan(profile, gradlew, [task], cwd, "检测到 Cosmic 家族 Java 项目的 Gradle wrapper。");
|
|
159
157
|
}
|
|
160
|
-
if (existsSync(mvnwBat)) {
|
|
161
|
-
return buildPlan(profile, mvnwBat, [target ?? "test"], cwd, "检测到 Cosmic 家族 Java 项目的 Maven wrapper。");
|
|
162
|
-
}
|
|
163
|
-
if (existsSync(mvnw)) {
|
|
164
|
-
return buildPlan(profile, mvnw, [target ?? "test"], cwd, "检测到 Cosmic 家族 Java 项目的 Maven wrapper。");
|
|
165
|
-
}
|
|
166
158
|
if (existsSync(join(cwd, "build.gradle")) || existsSync(join(cwd, "build.gradle.kts"))) {
|
|
167
159
|
return buildPlan(profile, "gradle", [task], cwd, "检测到 Gradle 构建文件,但未找到 wrapper。");
|
|
168
160
|
}
|
|
169
|
-
if (existsSync(join(cwd, "pom.xml"))) {
|
|
170
|
-
return buildPlan(profile, "mvn", [target ?? "test"], cwd, "检测到 Maven pom.xml,但未找到 wrapper。");
|
|
171
|
-
}
|
|
172
161
|
|
|
173
|
-
throw new Error("未找到 Java 构建入口。期望在工作区根目录看到 gradlew、build.gradle
|
|
162
|
+
throw new Error("未找到 Java Gradle 构建入口。期望在工作区根目录看到 gradlew、gradlew.bat、build.gradle 或 build.gradle.kts。");
|
|
174
163
|
}
|
|
175
164
|
|
|
176
165
|
function planCsharpBuild(cwd: string, profile: ProductProfile, target?: string): BuildPlan {
|