kcode-pi 0.1.6 → 0.1.8
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 +40 -5
- package/docs/DEVELOPMENT.md +2 -0
- package/extensions/kingdee-harness.ts +139 -1
- package/extensions/kingdee-tools.ts +43 -1
- package/package.json +2 -1
- package/skills/kd-check/SKILL.md +1 -2
- package/skills/kd-cosmic-dev/SKILL.md +3 -3
- package/skills/kd-cosmic-review/SKILL.md +2 -2
- package/skills/kd-cosmic-unittest/SKILL.md +2 -2
- package/skills/kd-debug/SKILL.md +1 -2
- package/skills/kd-execute/SKILL.md +2 -2
- package/skills/kd-gen/SKILL.md +2 -1
- package/skills/kd-plan/SKILL.md +3 -3
- package/src/harness/gates.ts +14 -1
- package/src/harness/state.ts +44 -1
- package/src/harness/types.ts +14 -0
- package/src/official/kingdee-skills.ts +60 -13
- package/src/rules/checker.ts +143 -0
- package/src/tools/sdk-signature.ts +309 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/SKILL.md +2 -2
- package/vendor/kingdee-skills/ok-cosmic/SKILL.md +52 -101
- package/vendor/kingdee-skills/ok-cosmic/agents/openai.yaml +4 -4
- package/vendor/kingdee-skills/ok-cosmic/manifest.json +21 -20
- package/vendor/kingdee-skills/ok-cosmic/ok-cosmic-intro.html +1 -1
- package/vendor/kingdee-skills/ok-cosmic/rules/a-layer-rules.json +1 -1
- package/vendor/kingdee-skills/ok-cosmic/rules/anti-patterns.md +2 -2
- package/vendor/kingdee-skills/ok-cosmic/rules/coding-preferences.md +4 -4
- package/vendor/kingdee-skills/ok-cosmic/rules/constraints.md +3 -3
- package/vendor/kingdee-skills/ok-cosmic/rules/decision-matrix.md +8 -8
- package/vendor/kingdee-skills/ok-cosmic/rules/intent-routing.md +1 -1
- package/vendor/kingdee-skills/ok-cosmic/rules/post-check.md +19 -18
- package/vendor/kingdee-skills/ok-ksql/SKILL.md +9 -9
- package/vendor/kingdee-skills/ok-ksql/manifest.json +2 -1
- package/vendor/kingdee-skills/ok-ksql/references/ksql-datafix.md +2 -2
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/pattern-matcher.py +0 -336
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/review-score-calculator.py +0 -121
- package/vendor/kingdee-skills/ok-cosmic/CHANGELOG.md +0 -295
- package/vendor/kingdee-skills/ok-cosmic/README.md +0 -460
- package/vendor/kingdee-skills/ok-cosmic/requirements.txt +0 -2
- package/vendor/kingdee-skills/ok-cosmic/scripts/config_loader.py +0 -204
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-api-knowledge.py +0 -910
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-basedata-query.py +0 -359
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-config-check.py +0 -181
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-extpoints-query.py +0 -389
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-form-metadata.py +0 -856
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-check.py +0 -262
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-lint.py +0 -293
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/__init__.py +0 -2
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/base.py +0 -393
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/resource_check.py +0 -176
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/scene_check.py +0 -375
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/style_check.py +0 -434
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/verify_check.py +0 -36
- package/vendor/kingdee-skills/ok-cosmic/scripts/route_client.py +0 -186
- package/vendor/kingdee-skills/ok-cosmic/scripts/script_utils.py +0 -40
- package/vendor/kingdee-skills/ok-cosmic/scripts/sqlite_cache.py +0 -142
- package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-commons.jar +0 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-features.jar +0 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/setup-mac.sh +0 -18
- package/vendor/kingdee-skills/ok-cosmic/setup/setup-windows.bat +0 -53
- package/vendor/kingdee-skills/ok-cosmic/setup/setup.jar +0 -0
- package/vendor/kingdee-skills/ok-ksql/scripts/ksql_lint.py +0 -363
package/README.md
CHANGED
|
@@ -277,6 +277,7 @@ kcode start --provider openai --model gpt-4o
|
|
|
277
277
|
/kd-gate
|
|
278
278
|
/kd-advance [phase]
|
|
279
279
|
/kd-artifact [phase] [content]
|
|
280
|
+
/kd-answer Q-001 <answer>
|
|
280
281
|
```
|
|
281
282
|
|
|
282
283
|
典型开始方式:
|
|
@@ -369,7 +370,7 @@ ship 汇总变更、验证证据、风险和后续事项
|
|
|
369
370
|
|
|
370
371
|
- Red evidence: evidence/tdd-red.md
|
|
371
372
|
- Green evidence: evidence/tdd-green.md
|
|
372
|
-
- Red/green command or tool:
|
|
373
|
+
- Red/green command or tool: kd_sdk_signature / kd_cosmic_metadata / kd_check / build
|
|
373
374
|
- Do not add third-party test jars or frameworks only for this gate.
|
|
374
375
|
```
|
|
375
376
|
|
|
@@ -387,6 +388,12 @@ ship 汇总变更、验证证据、风险和后续事项
|
|
|
387
388
|
|
|
388
389
|
如果 LLM 跳过步骤、没有记录 evidence,或只是口头声明完成,KCode 不允许进入 `verify`。
|
|
389
390
|
|
|
391
|
+
结构化问题也会进入门禁。Agent 需要确认产品、目标单据、插件位置、高风险 SQL 或方案选择时,会使用 `kd_question` 记录问题;未回答的阻断问题会阻止进入下一阶段。用户回复后,Agent 会记录答案;也可以手动执行:
|
|
392
|
+
|
|
393
|
+
```text
|
|
394
|
+
/kd-answer Q-001 采购订单
|
|
395
|
+
```
|
|
396
|
+
|
|
390
397
|
运行状态保存在当前业务项目:
|
|
391
398
|
|
|
392
399
|
```text
|
|
@@ -400,12 +407,14 @@ KCode 会向 Pi 注册这些金蝶工具:
|
|
|
400
407
|
|
|
401
408
|
```text
|
|
402
409
|
kd_plan_status 查看当前 Harness run、阶段、产物和门禁
|
|
410
|
+
kd_question 记录/回答/查看阻断问题,未回答时阻止阶段推进
|
|
403
411
|
kd_search 搜索内置金蝶 SDK 知识和代码模式
|
|
404
412
|
kd_table 查询内置金蝶表结构
|
|
405
413
|
kd_check 检查金蝶 Java/C# 插件代码
|
|
406
414
|
kd_cosmic_config 运行官方 ok-cosmic 配置预检
|
|
407
415
|
kd_cosmic_metadata 查询官方 Cosmic 表单/单据元数据
|
|
408
|
-
kd_cosmic_api
|
|
416
|
+
kd_cosmic_api 查询随包 Cosmic API 知识线索
|
|
417
|
+
kd_sdk_signature 从当前项目实际 SDK jar/dll 中读取类和方法签名
|
|
409
418
|
kd_ksql_lint 运行官方 ok-ksql SQL/KSQL lint
|
|
410
419
|
kd_build 按产品画像执行或 dry-run 构建
|
|
411
420
|
kd_debug 分析金蝶日志和堆栈
|
|
@@ -414,9 +423,35 @@ kd_debug 分析金蝶日志和堆栈
|
|
|
414
423
|
这些工具不依赖本机 Python:
|
|
415
424
|
|
|
416
425
|
- `kd_ksql_lint` 是内置 Node 静态检查器。
|
|
417
|
-
- `kd_cosmic_config` 使用 Node
|
|
418
|
-
- `kd_cosmic_metadata`
|
|
419
|
-
- `
|
|
426
|
+
- `kd_cosmic_config` 使用 Node 校验 Cosmic 官方能力配置;项目没有 `ok-cosmic.json` 时会自动使用 KCode 随包默认配置。
|
|
427
|
+
- `kd_cosmic_metadata` 使用统一路由 API 查询真实单据/表单元数据,并在当前项目 `.pi/kd/official-skills/` 下维护 JSON 缓存。
|
|
428
|
+
- `kd_sdk_signature` 优先从当前业务项目的实际 SDK jar/dll 中读取类型和方法签名。Cosmic Java 依赖 `javap`,Enterprise C# 依赖 PowerShell 读取 DLL 元数据。
|
|
429
|
+
- `kd_cosmic_api` 查询随包金蝶知识库,只作为 API 线索和兜底;精确方法签名优先使用 `kd_sdk_signature`、当前项目 SDK 或编译输出确认。
|
|
430
|
+
|
|
431
|
+
示例:
|
|
432
|
+
|
|
433
|
+
```text
|
|
434
|
+
kd_sdk_signature product=flagship query=QueryServiceHelper method=loadSingle path=lib
|
|
435
|
+
kd_sdk_signature product=enterprise query=DynamicObject method=GetDynamicObject path=bin
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
`method` 只是在已匹配的类/类型里过滤方法或属性;如果不知道类名,先用 `query` 搜类型关键词,再结合项目源码和构建文件缩小范围。
|
|
439
|
+
|
|
440
|
+
`ok-cosmic.json` 是可选的 KCode 官方能力覆盖配置,不是苍穹工程模板里的 `cosmic.json`。业务项目里的 `cosmic.json` 通常只包含开发者标识、工程标识、MC 资源地址等运行环境信息,不能替代 `ok-cosmic.json`。
|
|
441
|
+
|
|
442
|
+
只有需要指定企业统一路由 API 或覆盖知识库路径时,才需要在业务项目根目录创建 `ok-cosmic.json`:
|
|
443
|
+
|
|
444
|
+
```json
|
|
445
|
+
{
|
|
446
|
+
"graph": {
|
|
447
|
+
"dbPath": "D:\\path\\to\\ok-cosmic-docs.db"
|
|
448
|
+
},
|
|
449
|
+
"route": {
|
|
450
|
+
"apiUrl": "https://your-runtime-route.example.com/api",
|
|
451
|
+
"timeoutSeconds": 10
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
```
|
|
420
455
|
|
|
421
456
|
## 升级
|
|
422
457
|
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -32,6 +32,7 @@ npm run smoke:checker
|
|
|
32
32
|
npm run smoke:harness
|
|
33
33
|
npm run smoke:official
|
|
34
34
|
npm run smoke:build-debug
|
|
35
|
+
npm run smoke:sdk-signature
|
|
35
36
|
npm run smoke:package
|
|
36
37
|
npm run smoke:kcode-cli
|
|
37
38
|
```
|
|
@@ -45,6 +46,7 @@ npm run smoke:kcode-cli
|
|
|
45
46
|
- Harness 阶段和门禁。
|
|
46
47
|
- 官方能力 Node 适配器。
|
|
47
48
|
- 构建/调试诊断。
|
|
49
|
+
- 当前项目 SDK jar/dll 签名读取。
|
|
48
50
|
- package manifest、skills、vendor 文件完整性。
|
|
49
51
|
- `kcode` 项目级入口逻辑。
|
|
50
52
|
|
|
@@ -4,6 +4,8 @@ import { formatStatus } from "../src/harness/format.ts";
|
|
|
4
4
|
import { PHASE_ORDER, isKdPhase, type KdPhase } from "../src/harness/types.ts";
|
|
5
5
|
import {
|
|
6
6
|
advanceRun,
|
|
7
|
+
addQuestion,
|
|
8
|
+
answerQuestion,
|
|
7
9
|
createActiveRun,
|
|
8
10
|
ensurePhaseArtifact,
|
|
9
11
|
readActiveRun,
|
|
@@ -107,7 +109,7 @@ function workflowPromptForRun(cwd: string, run: NonNullable<ReturnType<typeof re
|
|
|
107
109
|
"必须先理解当前业务项目已有目录、模块、包名、基类和本地封装,再决定文件位置和实现方式。",
|
|
108
110
|
"路径规则:在 Windows 工作区内,优先使用项目相对路径;如需绝对路径必须使用 `D:\\...` 这类 Windows 路径,禁止把路径改写成 `/mnt/d/...`、`/d/...` 等 WSL/MSYS 风格路径。",
|
|
109
111
|
"execute 阶段只能写 PLAN.md 明确列出的源码文件;如果目标文件不在计划内,必须先回到 plan 更新 PLAN.md。",
|
|
110
|
-
"写生产源码前必须先有红灯证据 evidence/tdd-red.md;红绿证据可以是 API/基类/方法签名、元数据、编译、既有测试框架或外部接口最小验证,不要为了测试引入额外 jar。",
|
|
112
|
+
"写生产源码前必须先有红灯证据 evidence/tdd-red.md;红绿证据可以是 kd_sdk_signature 本地 SDK 签名、API/基类/方法签名、元数据、编译、既有测试框架或外部接口最小验证,不要为了测试引入额外 jar。",
|
|
111
113
|
].join("\n");
|
|
112
114
|
}
|
|
113
115
|
|
|
@@ -158,8 +160,86 @@ const kdPlanStatusTool = defineTool({
|
|
|
158
160
|
},
|
|
159
161
|
});
|
|
160
162
|
|
|
163
|
+
const kdQuestionTool = defineTool({
|
|
164
|
+
name: "kd_question",
|
|
165
|
+
label: "KD Question",
|
|
166
|
+
description:
|
|
167
|
+
"Create, answer, or list structured Kingdee Harness questions. Open blocking questions stop phase advancement until answered.",
|
|
168
|
+
parameters: Type.Object({
|
|
169
|
+
action: Type.Optional(Type.String({ description: "ask, answer, or list. Defaults to ask." })),
|
|
170
|
+
id: Type.Optional(Type.String({ description: "Question id when action=answer, for example Q-001." })),
|
|
171
|
+
question: Type.Optional(Type.String({ description: "Question to ask when action=ask." })),
|
|
172
|
+
answer: Type.Optional(Type.String({ description: "User answer when action=answer." })),
|
|
173
|
+
reason: Type.Optional(Type.String({ description: "Why this question blocks or matters." })),
|
|
174
|
+
choices: Type.Optional(Type.Array(Type.String(), { description: "Optional concrete choices for the user." })),
|
|
175
|
+
blocking: Type.Optional(Type.Boolean({ description: "Whether the question blocks phase advancement. Defaults to true." })),
|
|
176
|
+
}),
|
|
177
|
+
|
|
178
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
179
|
+
const run = readActiveRun(ctx.cwd);
|
|
180
|
+
if (!run) {
|
|
181
|
+
return {
|
|
182
|
+
content: [{ type: "text", text: "No active Kingdee harness run. Start one with /kd-start <goal> first." }],
|
|
183
|
+
details: { error: "no-active-run" },
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const action = (params.action ?? "ask").toLowerCase();
|
|
188
|
+
if (action === "list") {
|
|
189
|
+
const text = formatQuestions(run);
|
|
190
|
+
return { content: [{ type: "text", text }], details: { questions: run.questions ?? [] } };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (action === "answer") {
|
|
194
|
+
if (!params.id || !params.answer) {
|
|
195
|
+
return {
|
|
196
|
+
content: [{ type: "text", text: "kd_question action=answer requires id and answer." }],
|
|
197
|
+
details: { error: "missing-answer-params" },
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const answered = answerQuestion(ctx.cwd, run, params.id, params.answer);
|
|
201
|
+
if (!answered) {
|
|
202
|
+
return {
|
|
203
|
+
content: [{ type: "text", text: `Question not found: ${params.id}` }],
|
|
204
|
+
details: { error: "question-not-found", id: params.id },
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
appendQuestionEventToArtifact(ctx.cwd, run, [`- Answered ${answered.id}: ${answered.answer}`]);
|
|
208
|
+
return {
|
|
209
|
+
content: [{ type: "text", text: `Recorded answer for ${answered.id}. Gate refreshed.` }],
|
|
210
|
+
details: { question: answered, gate: readActiveRun(ctx.cwd)?.gate },
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (action !== "ask") {
|
|
215
|
+
return {
|
|
216
|
+
content: [{ type: "text", text: `Unknown kd_question action: ${params.action}. Use ask, answer, or list.` }],
|
|
217
|
+
details: { error: "unknown-action", action: params.action },
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!params.question?.trim()) {
|
|
222
|
+
return {
|
|
223
|
+
content: [{ type: "text", text: "kd_question action=ask requires question." }],
|
|
224
|
+
details: { error: "missing-question" },
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const question = addQuestion(ctx.cwd, run, {
|
|
229
|
+
question: params.question,
|
|
230
|
+
reason: params.reason,
|
|
231
|
+
choices: params.choices,
|
|
232
|
+
blocking: params.blocking,
|
|
233
|
+
});
|
|
234
|
+
appendQuestionEventToArtifact(ctx.cwd, run, formatQuestionArtifactLines(question));
|
|
235
|
+
const text = formatQuestionCard(question);
|
|
236
|
+
return { content: [{ type: "text", text }], details: { question, gate: readActiveRun(ctx.cwd)?.gate } };
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
161
240
|
export default function (pi: ExtensionAPI) {
|
|
162
241
|
pi.registerTool(kdPlanStatusTool);
|
|
242
|
+
pi.registerTool(kdQuestionTool);
|
|
163
243
|
|
|
164
244
|
pi.on("input", async (event, ctx) => {
|
|
165
245
|
if (event.source === "extension") return { action: "continue" };
|
|
@@ -299,4 +379,62 @@ export default function (pi: ExtensionAPI) {
|
|
|
299
379
|
ctx.ui.notify(`Artifact ready: ${path}`, "info");
|
|
300
380
|
},
|
|
301
381
|
});
|
|
382
|
+
|
|
383
|
+
pi.registerCommand("kd-answer", {
|
|
384
|
+
description: "Answer a blocking Kingdee harness question: /kd-answer Q-001 <answer>",
|
|
385
|
+
handler: async (args, ctx) => {
|
|
386
|
+
const run = requireRun(ctx.cwd);
|
|
387
|
+
if (!run) {
|
|
388
|
+
ctx.ui.notify("No active Kingdee harness run. Use /kd-start <goal>.", "error");
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const [id, ...answerParts] = args.trim().split(/\s+/);
|
|
392
|
+
const answer = answerParts.join(" ").trim();
|
|
393
|
+
if (!id || !answer) {
|
|
394
|
+
ctx.ui.notify("Usage: /kd-answer Q-001 <answer>", "error");
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const answered = answerQuestion(ctx.cwd, run, id, answer);
|
|
398
|
+
if (!answered) {
|
|
399
|
+
ctx.ui.notify(`Question not found: ${id}`, "error");
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
appendQuestionEventToArtifact(ctx.cwd, run, [`- Answered ${answered.id}: ${answered.answer}`]);
|
|
403
|
+
ctx.ui.notify(`Recorded answer for ${answered.id}`, "info");
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function formatQuestions(run: NonNullable<ReturnType<typeof readActiveRun>>): string {
|
|
409
|
+
const questions = run.questions ?? [];
|
|
410
|
+
if (questions.length === 0) return "No harness questions recorded.";
|
|
411
|
+
return questions.map(formatQuestionCard).join("\n\n");
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function formatQuestionCard(question: NonNullable<NonNullable<ReturnType<typeof readActiveRun>>["questions"]>[number]): string {
|
|
415
|
+
const lines = [
|
|
416
|
+
`Question ${question.id} [${question.status}${question.blocking ? ", blocking" : ""}]`,
|
|
417
|
+
`Phase: ${question.phase}`,
|
|
418
|
+
`Question: ${question.question}`,
|
|
419
|
+
question.reason ? `Reason: ${question.reason}` : undefined,
|
|
420
|
+
question.choices?.length ? `Choices: ${question.choices.join(" | ")}` : undefined,
|
|
421
|
+
question.answer ? `Answer: ${question.answer}` : undefined,
|
|
422
|
+
question.status === "open" ? `Reply with the answer, then record it using kd_question action=answer id=${question.id} answer=<answer>.` : undefined,
|
|
423
|
+
];
|
|
424
|
+
return lines.filter(Boolean).join("\n");
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function formatQuestionArtifactLines(question: NonNullable<NonNullable<ReturnType<typeof readActiveRun>>["questions"]>[number]): string[] {
|
|
428
|
+
return [
|
|
429
|
+
`- ${question.id} [${question.blocking ? "blocking" : "non-blocking"}] ${question.question}`,
|
|
430
|
+
question.reason ? ` - Reason: ${question.reason}` : undefined,
|
|
431
|
+
question.choices?.length ? ` - Choices: ${question.choices.join(" | ")}` : undefined,
|
|
432
|
+
" - Status: open",
|
|
433
|
+
].filter(Boolean) as string[];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function appendQuestionEventToArtifact(cwd: string, run: NonNullable<ReturnType<typeof readActiveRun>>, lines: string[]): void {
|
|
437
|
+
const existing = readArtifact(cwd, run, run.phase) ?? "";
|
|
438
|
+
const section = ["", "## Harness Questions", "", ...lines, ""].join("\n");
|
|
439
|
+
updatePhaseArtifact(cwd, run, run.phase, existing.includes("## Harness Questions") ? `${existing.trimEnd()}\n${lines.join("\n")}\n` : `${existing.trimEnd()}${section}`);
|
|
302
440
|
}
|
|
@@ -9,6 +9,7 @@ import type { Edition, KnowledgeScope } from "../src/knowledge/types.ts";
|
|
|
9
9
|
import { resolveProductProfile, type ProductProfile } from "../src/product/profile.ts";
|
|
10
10
|
import { checkCode, formatCheckResults, type CheckLanguage } from "../src/rules/checker.ts";
|
|
11
11
|
import { analyzeDebugText, formatDebugFindings, planBuild, readDebugInput, runBuild } from "../src/tools/build-debug.ts";
|
|
12
|
+
import { formatSdkSignatureResult, inspectSdkSignature, type SdkSignatureLanguage } from "../src/tools/sdk-signature.ts";
|
|
12
13
|
import {
|
|
13
14
|
cosmicApiCommand,
|
|
14
15
|
cosmicConfigCommand,
|
|
@@ -64,6 +65,13 @@ function languageForProfile(profile: ProductProfile, value: string | undefined):
|
|
|
64
65
|
return "java";
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
function sdkLanguageForProfile(profile: ProductProfile, value: string | undefined): SdkSignatureLanguage {
|
|
69
|
+
if (value === "csharp" || value === "cs") return "csharp";
|
|
70
|
+
if (value === "java") return "java";
|
|
71
|
+
if (profile.platform === "enterprise-csharp") return "csharp";
|
|
72
|
+
return "java";
|
|
73
|
+
}
|
|
74
|
+
|
|
67
75
|
function rejectNonCosmic(profile: ProductProfile): string | undefined {
|
|
68
76
|
if (isCosmicFamily(profile)) return undefined;
|
|
69
77
|
if (profile.product === "unknown") return "Provide a Cosmic-family product first: cangqiong, xinghan, flagship, or cosmic.";
|
|
@@ -265,7 +273,7 @@ const kdCosmicMetadataTool = defineTool({
|
|
|
265
273
|
const kdCosmicApiTool = defineTool({
|
|
266
274
|
name: "kd_cosmic_api",
|
|
267
275
|
label: "KD Cosmic API",
|
|
268
|
-
description: "Query
|
|
276
|
+
description: "Query bundled Cosmic API knowledge for class and method clues. Use kd_sdk_signature or build output for final local SDK facts.",
|
|
269
277
|
parameters: Type.Object({
|
|
270
278
|
product: Type.String({ description: "Cosmic-family product: cangqiong, xinghan, flagship, or cosmic." }),
|
|
271
279
|
mode: Type.String({ description: "search, search-method, or detail." }),
|
|
@@ -295,6 +303,39 @@ const kdCosmicApiTool = defineTool({
|
|
|
295
303
|
},
|
|
296
304
|
});
|
|
297
305
|
|
|
306
|
+
const kdSdkSignatureTool = defineTool({
|
|
307
|
+
name: "kd_sdk_signature",
|
|
308
|
+
label: "KD SDK Signature",
|
|
309
|
+
description:
|
|
310
|
+
"Inspect method/type signatures from SDK jars or dlls actually present in the current project. Prefer this over bundled knowledge for factual API signatures.",
|
|
311
|
+
parameters: Type.Object({
|
|
312
|
+
product: Type.Optional(Type.String({ description: "Kingdee product. Used to derive Java or C# when language is omitted." })),
|
|
313
|
+
language: Type.Optional(Type.String({ description: "java or csharp. Defaults from product profile." })),
|
|
314
|
+
query: Type.Optional(Type.String({ description: "Class/type keyword to search, for example QueryServiceHelper or DynamicObject." })),
|
|
315
|
+
className: Type.Optional(Type.String({ description: "Fully qualified Java/C# type name when known." })),
|
|
316
|
+
method: Type.Optional(Type.String({ description: "Optional method/property name filter within the matched class/type. Does not scan all methods globally." })),
|
|
317
|
+
path: Type.Optional(Type.String({ description: "Optional SDK lib/bin directory or specific dependency root. Defaults to current project." })),
|
|
318
|
+
limit: Type.Optional(Type.Number({ description: "Maximum jars/dlls/classes to inspect. Defaults to 20 result classes and 200 files." })),
|
|
319
|
+
}),
|
|
320
|
+
|
|
321
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
322
|
+
const profile = resolveToolProfile(params.product, undefined);
|
|
323
|
+
const language = sdkLanguageForProfile(profile, params.language);
|
|
324
|
+
const result = await inspectSdkSignature(ctx.cwd, {
|
|
325
|
+
language,
|
|
326
|
+
query: params.query,
|
|
327
|
+
className: params.className,
|
|
328
|
+
method: params.method,
|
|
329
|
+
path: params.path,
|
|
330
|
+
limit: params.limit,
|
|
331
|
+
});
|
|
332
|
+
return {
|
|
333
|
+
content: [{ type: "text", text: formatSdkSignatureResult(result) }],
|
|
334
|
+
details: { product: profile.product, ...result },
|
|
335
|
+
};
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
|
|
298
339
|
const kdKsqlLintTool = defineTool({
|
|
299
340
|
name: "kd_ksql_lint",
|
|
300
341
|
label: "KD KSQL Lint",
|
|
@@ -377,6 +418,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
377
418
|
pi.registerTool(kdCosmicConfigTool);
|
|
378
419
|
pi.registerTool(kdCosmicMetadataTool);
|
|
379
420
|
pi.registerTool(kdCosmicApiTool);
|
|
421
|
+
pi.registerTool(kdSdkSignatureTool);
|
|
380
422
|
pi.registerTool(kdKsqlLintTool);
|
|
381
423
|
pi.registerTool(kdBuildTool);
|
|
382
424
|
pi.registerTool(kdDebugTool);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kcode-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Kingdee-specific package and harness for Pi Coding Agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"smoke:harness": "tsx scripts/smoke-harness.ts",
|
|
62
62
|
"smoke:official": "tsx scripts/smoke-official-tools.ts",
|
|
63
63
|
"smoke:build-debug": "tsx scripts/smoke-build-debug.ts",
|
|
64
|
+
"smoke:sdk-signature": "tsx scripts/smoke-sdk-signature.ts",
|
|
64
65
|
"smoke:package": "tsx scripts/smoke-package.ts",
|
|
65
66
|
"smoke:kcode-cli": "tsx scripts/smoke-kcode-cli.ts",
|
|
66
67
|
"kcode": "tsx scripts/kcode.ts",
|
package/skills/kd-check/SKILL.md
CHANGED
|
@@ -12,7 +12,7 @@ Checklist:
|
|
|
12
12
|
- Magic values: status codes, operation names, field keys, table names, numbers, and business strings.
|
|
13
13
|
- Naming: Java/C# naming conventions and project-local naming.
|
|
14
14
|
- Lifecycle: correct plugin base class and lifecycle method for the target behavior.
|
|
15
|
-
- SDK usage: no invented API names; uncertain APIs must be verified through `kd_search
|
|
15
|
+
- SDK usage: no invented API names; uncertain Java/C# APIs must be verified through `kd_sdk_signature` against current project jars/dlls, or by compile/build evidence. Use `kd_search` only for guidance.
|
|
16
16
|
- Database access: no DB query or save operation inside loops unless explicitly justified.
|
|
17
17
|
- Exception handling: no empty catch blocks; errors should be logged or converted to business exceptions as appropriate.
|
|
18
18
|
- Resource cleanup: close or dispose resources where the SDK requires it.
|
|
@@ -23,4 +23,3 @@ Output expectations:
|
|
|
23
23
|
- Lead with concrete findings and file/line references when reviewing code.
|
|
24
24
|
- Separate errors, warnings, and improvement suggestions.
|
|
25
25
|
- State which checks were not possible because context or tools were missing.
|
|
26
|
-
|
|
@@ -46,7 +46,7 @@ description: 金蝶 Cosmic 体系 Java 插件开发技能,适用于苍穹、
|
|
|
46
46
|
|
|
47
47
|
3. 通过官方适配器验证产品事实。
|
|
48
48
|
- 代码引用字段、表单 ID、单据名称、枚举/下拉值、实体 ID、操作编码、SQL 字段时,使用 `kd_cosmic_metadata`。
|
|
49
|
-
- 类名、方法名、方法签名、继承关系、Override
|
|
49
|
+
- 类名、方法名、方法签名、继承关系、Override 签名不确定时,优先使用 `kd_sdk_signature` 从当前项目实际 SDK jar 中验证;`kd_cosmic_api` 只作为随包知识线索和兜底。
|
|
50
50
|
- 多个表单或字段尽量合并到一次元数据查询。
|
|
51
51
|
- 元数据查询和 API 签名查询互不依赖时并行执行。
|
|
52
52
|
|
|
@@ -63,7 +63,7 @@ description: 金蝶 Cosmic 体系 Java 插件开发技能,适用于苍穹、
|
|
|
63
63
|
|
|
64
64
|
## 硬性规则
|
|
65
65
|
|
|
66
|
-
- 不臆造字段 key、表单 ID、操作名、枚举值、表名或 SDK
|
|
66
|
+
- 不臆造字段 key、表单 ID、操作名、枚举值、表名或 SDK 方法;本地 SDK 查不到且编译未验证时,不把知识库结果当作最终签名事实。
|
|
67
67
|
- 不把 Enterprise C# 命名空间或生命周期假设用于 Cosmic Java。
|
|
68
68
|
- 星空旗舰版不允许猜目录或写 demo/sample;如果无法判断真实代码位置,先询问或更新计划,不要直接创建新目录。
|
|
69
69
|
- 不在数据绑定前的初始化阶段操作 UI 控件。
|
|
@@ -78,7 +78,7 @@ description: 金蝶 Cosmic 体系 Java 插件开发技能,适用于苍穹、
|
|
|
78
78
|
完成执行工作时说明:
|
|
79
79
|
|
|
80
80
|
- 修改了哪些文件。
|
|
81
|
-
-
|
|
81
|
+
- 使用了哪些检查:配置、元数据、本地 SDK 签名、API 知识、编译。
|
|
82
82
|
- 哪些事实仍无法验证。
|
|
83
83
|
- 验证命令和结果。
|
|
84
84
|
- `EXECUTION.md` 中记录相对 `PLAN.md` 的偏差。
|
|
@@ -17,7 +17,7 @@ description: 金蝶 Cosmic 体系 Java 插件和 KSQL 代码审查技能,按
|
|
|
17
17
|
|
|
18
18
|
- 存在 harness run 时,先用 `kd_plan_status` 查看产品画像。
|
|
19
19
|
- 用 `kd_search` 查询 Cosmic 审查清单、生命周期、平台约束、KSQL 和单测指导。
|
|
20
|
-
- SDK
|
|
20
|
+
- SDK 签名或生命周期方法不确定时,优先用 `kd_sdk_signature` 从当前项目实际 SDK jar 验证;查不到时再用 `kd_cosmic_api` 获取知识库线索,并要求编译或人工证据兜底。
|
|
21
21
|
- 变更中用到字段、操作、枚举值、表名、数据库列时,用 `kd_cosmic_metadata` 验证。
|
|
22
22
|
- 先运行 `kd_check` 做基础静态检查,再按下方清单深入审查。
|
|
23
23
|
- 对新增或修改的 SQL/KSQL 文件运行 `kd_ksql_lint`。
|
|
@@ -84,7 +84,7 @@ description: 金蝶 Cosmic 体系 Java 插件和 KSQL 代码审查技能,按
|
|
|
84
84
|
随后说明:
|
|
85
85
|
|
|
86
86
|
- 已运行和未运行的检查。
|
|
87
|
-
-
|
|
87
|
+
- 已验证的产品、元数据、本地 SDK 签名和 API 线索。
|
|
88
88
|
- 剩余风险或缺失上下文。
|
|
89
89
|
|
|
90
90
|
如果没有发现问题,明确说明未发现问题,并列出剩余测试或证据缺口。
|
|
@@ -25,7 +25,7 @@ description: 金蝶 Cosmic 体系 Java 单元测试生成和审查技能,适
|
|
|
25
25
|
- 阅读被测类和最近的测试基类。
|
|
26
26
|
- 新增测试风格前,先搜索同模块同包下已有测试。
|
|
27
27
|
- 用 `kd_search` 查询 Cosmic 单测指导和模块测试模式。
|
|
28
|
-
- 仅在测试或被测代码使用的 SDK
|
|
28
|
+
- 仅在测试或被测代码使用的 SDK 方法签名不确定时,优先使用 `kd_sdk_signature` 从当前项目实际 SDK jar 验证;查不到时再用 `kd_cosmic_api` 获取线索,并以编译结果兜底。
|
|
29
29
|
|
|
30
30
|
## 测试模式选择
|
|
31
31
|
|
|
@@ -56,7 +56,7 @@ POJO 或简单枚举场景可以简要说明后直接实现。
|
|
|
56
56
|
## 测试质量规则
|
|
57
57
|
|
|
58
58
|
- 使用项目已有 JUnit 和 Mockito 版本。
|
|
59
|
-
- 如果项目没有既有且允许使用的单测框架,不要新增 JUnit、Mockito 或额外 jar;改用 Harness 的红绿验证门禁,例如
|
|
59
|
+
- 如果项目没有既有且允许使用的单测框架,不要新增 JUnit、Mockito 或额外 jar;改用 Harness 的红绿验证门禁,例如 `kd_sdk_signature` 本地 SDK 签名、元数据、编译或最小外部接口验证。
|
|
60
60
|
- import 保持显式;项目风格允许时也不要使用宽泛静态通配导入。
|
|
61
61
|
- 每个测试方法必须有真实断言或交互验证。
|
|
62
62
|
- 避免 `assertTrue(true)`、`assertEquals(x, x)` 或只执行不验证的测试。
|
package/skills/kd-debug/SKILL.md
CHANGED
|
@@ -19,7 +19,7 @@ Analysis approach:
|
|
|
19
19
|
- permission or authorization failure
|
|
20
20
|
- null dynamic object or missing entry rows
|
|
21
21
|
- repeated DB calls in loops causing slow saves/submits
|
|
22
|
-
- Use `
|
|
22
|
+
- Use `kd_sdk_signature` for uncertain SDK types/methods against current project jars/dlls, `kd_search` for guidance, and `kd_table`/metadata tools for table assumptions.
|
|
23
23
|
|
|
24
24
|
Output expectations:
|
|
25
25
|
|
|
@@ -27,4 +27,3 @@ Output expectations:
|
|
|
27
27
|
- Provide the next concrete inspection step.
|
|
28
28
|
- Suggest a minimal fix, then verification evidence needed to prove it.
|
|
29
29
|
- If evidence is insufficient, say exactly what log, stack trace, metadata, or code file is missing.
|
|
30
|
-
|
|
@@ -10,7 +10,7 @@ Use this skill only after `PLAN.md` exists.
|
|
|
10
10
|
Goal:
|
|
11
11
|
|
|
12
12
|
- Implement only the approved plan.
|
|
13
|
-
- Use `
|
|
13
|
+
- Use `kd_sdk_signature` before relying on uncertain Java/C# SDK APIs. Use `kd_search` for guidance and `kd_table`/metadata tools for table schemas.
|
|
14
14
|
- Update `.pi/kd/runs/<run-id>/EXECUTION.md` with every planned `STEP-###`, changed files, and evidence files.
|
|
15
15
|
|
|
16
16
|
Rules:
|
|
@@ -20,7 +20,7 @@ Rules:
|
|
|
20
20
|
- For 星空旗舰版, edit only the real target path recorded in `PLAN.md` after inspecting the project. If `code/` exists, follow its actual layout; if it does not, follow the discovered source root or existing target file. Do not create demo/sample code or root-level `src/main/java` by guesswork.
|
|
21
21
|
- Do not mark work complete until verification runs.
|
|
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
|
-
- Before writing production source files, run the planned red check and record failing output in `evidence/tdd-red.md`. This can be
|
|
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
25
|
- 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.
|
|
26
26
|
- If implementation needs a plan change, update `PLAN.md` first.
|
package/skills/kd-gen/SKILL.md
CHANGED
|
@@ -13,12 +13,13 @@ Inputs to establish before coding:
|
|
|
13
13
|
- Plugin type and correct base class.
|
|
14
14
|
- Target bill/entity/form and lifecycle event.
|
|
15
15
|
- Business rule and acceptance criteria.
|
|
16
|
-
- Relevant SDK/table facts from `kd_search` or
|
|
16
|
+
- Relevant SDK/table facts from `kd_sdk_signature`, `kd_search`, `kd_table`, or product metadata tools.
|
|
17
17
|
|
|
18
18
|
Rules:
|
|
19
19
|
|
|
20
20
|
- Generate code only after the active run has a `PLAN.md`.
|
|
21
21
|
- Use the correct base class only when verified for the target product family.
|
|
22
|
+
- Verify uncertain Java/C# class names, base classes, methods, and overloads against current project SDK jars/dlls with `kd_sdk_signature`; bundled knowledge is not enough for final signature facts.
|
|
22
23
|
- For 星空旗舰版, generate or edit product code only under the real target path selected in `PLAN.md` after project inspection. Follow the existing layout, whether it uses `code/`, app modules, cloud modules, or no module split; never create demo/sample code or root-level `src/main/java` by guesswork.
|
|
23
24
|
- Use `kd.bos.*` style packages for Cosmic-family Java code and `Kingdee.BOS.*` style namespaces for enterprise C# code.
|
|
24
25
|
- Do not reuse Cosmic/Xinghan/Cangqiong APIs for enterprise C#.
|
package/skills/kd-plan/SKILL.md
CHANGED
|
@@ -13,10 +13,10 @@ 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
|
-
- List required `kd_search` and
|
|
16
|
+
- List required `kd_sdk_signature`, `kd_search`, `kd_table`, metadata, and build/compile checks.
|
|
17
17
|
- List `## Execution Steps` using `- [ ] STEP-001: ...` style IDs.
|
|
18
18
|
- List `## TDD / Red-Green Checks` with red evidence, green evidence, and the command/tool or product-specific check.
|
|
19
|
-
- Do not plan to add third-party test jars or frameworks only for red/green checks. For Kingdee plugin work, prefer
|
|
19
|
+
- 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.
|
|
20
20
|
- Define validation commands and expected evidence.
|
|
21
21
|
- Add rollback or containment notes for medium/high risk work.
|
|
22
22
|
|
|
@@ -27,4 +27,4 @@ Gate:
|
|
|
27
27
|
- A plan without validation commands is incomplete.
|
|
28
28
|
- A plan without structured `STEP-001` execution steps is incomplete.
|
|
29
29
|
- A plan without TDD red/green checks is incomplete.
|
|
30
|
-
- A plan that relies on unverified Kingdee API names is incomplete.
|
|
30
|
+
- 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.
|
package/src/harness/gates.ts
CHANGED
|
@@ -30,11 +30,13 @@ export function inspectGate(cwd: string, run: ActiveRun): GateResult {
|
|
|
30
30
|
const markerProblem = inspectMarkers(cwd, run, run.phase);
|
|
31
31
|
const stepProblem = inspectStepState(cwd, run, run.phase);
|
|
32
32
|
const evidenceProblem = inspectEvidence(cwd, run, run.phase);
|
|
33
|
+
const questionProblem = inspectOpenQuestions(run);
|
|
33
34
|
const reasonParts = [];
|
|
34
35
|
if (missing.length > 0) reasonParts.push(`缺少必需产物:${[...new Set(missing)].join(", ")}`);
|
|
35
36
|
if (markerProblem) reasonParts.push(markerProblem);
|
|
36
37
|
if (stepProblem) reasonParts.push(stepProblem);
|
|
37
38
|
if (evidenceProblem) reasonParts.push(evidenceProblem);
|
|
39
|
+
if (questionProblem) reasonParts.push(questionProblem);
|
|
38
40
|
|
|
39
41
|
return {
|
|
40
42
|
passed: reasonParts.length === 0,
|
|
@@ -72,6 +74,11 @@ export function canEnterPhase(cwd: string, run: ActiveRun, target: KdPhase): Gat
|
|
|
72
74
|
reasonParts.push(stepProblem);
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
const questionProblem = inspectOpenQuestions(run);
|
|
78
|
+
if (questionProblem) {
|
|
79
|
+
reasonParts.push(questionProblem);
|
|
80
|
+
}
|
|
81
|
+
|
|
75
82
|
missing.push(...missingEvidenceForPhase(cwd, run, target));
|
|
76
83
|
|
|
77
84
|
if (target === "ship" && !artifactExists(cwd, run, PHASE_ARTIFACTS.verify)) {
|
|
@@ -87,6 +94,12 @@ export function canEnterPhase(cwd: string, run: ActiveRun, target: KdPhase): Gat
|
|
|
87
94
|
};
|
|
88
95
|
}
|
|
89
96
|
|
|
97
|
+
function inspectOpenQuestions(run: ActiveRun): string | undefined {
|
|
98
|
+
const open = (run.questions ?? []).filter((question) => question.status === "open" && question.blocking);
|
|
99
|
+
if (open.length === 0) return undefined;
|
|
100
|
+
return `存在未回答的阻断问题:${open.map((question) => `${question.id} ${question.question}`).join(";")}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
90
103
|
function inspectStepState(cwd: string, run: ActiveRun, phase: KdPhase): string | undefined {
|
|
91
104
|
if (phase === "execute") {
|
|
92
105
|
const plan = readArtifact(cwd, run, "plan") ?? "";
|
|
@@ -154,7 +167,7 @@ function planHasMetadataRequirement(cwd: string, run: ActiveRun): boolean {
|
|
|
154
167
|
|
|
155
168
|
function planHasApiRequirement(cwd: string, run: ActiveRun): boolean {
|
|
156
169
|
const plan = readArtifact(cwd, run, "plan") ?? "";
|
|
157
|
-
return /kd_cosmic_api|cosmic-api|api signature|method signature|sdk.*签名|方法签名|接口签名/i.test(plan);
|
|
170
|
+
return /kd_sdk_signature|kd_cosmic_api|cosmic-api|api signature|method signature|sdk.*签名|方法签名|接口签名/i.test(plan);
|
|
158
171
|
}
|
|
159
172
|
|
|
160
173
|
function runHasKsqlDelivery(cwd: string, run: ActiveRun): boolean {
|
package/src/harness/state.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import type { ActiveRun, KdPhase } from "./types.ts";
|
|
2
|
+
import type { ActiveRun, KdPhase, KdQuestion } from "./types.ts";
|
|
3
3
|
import { PHASE_ARTIFACTS, isKdPhase, nextPhase } from "./types.ts";
|
|
4
4
|
import { activeRunPath, kdDir, runsDir } from "./paths.ts";
|
|
5
5
|
import { defaultArtifactContent, ensureArtifact, ensureRunDirectories, writeArtifact } from "./artifacts.ts";
|
|
@@ -14,6 +14,7 @@ export function readActiveRun(cwd: string): ActiveRun | undefined {
|
|
|
14
14
|
const parsed = JSON.parse(readFileSync(path, "utf8")) as ActiveRun;
|
|
15
15
|
if (!parsed.id || !isKdPhase(parsed.phase)) return undefined;
|
|
16
16
|
parsed.artifacts ??= {};
|
|
17
|
+
parsed.questions ??= [];
|
|
17
18
|
const legacyEdition = (parsed as ActiveRun & { edition?: string }).edition;
|
|
18
19
|
parsed.profile = parsed.profile ?? profileForProduct(parsed.product ?? resolveProductProfile(legacyEdition).product);
|
|
19
20
|
parsed.product = parsed.profile.product;
|
|
@@ -41,6 +42,7 @@ export function createActiveRun(cwd: string, goal: string, productInput?: string
|
|
|
41
42
|
profile,
|
|
42
43
|
risk: "unknown",
|
|
43
44
|
artifacts: {},
|
|
45
|
+
questions: [],
|
|
44
46
|
gate: {
|
|
45
47
|
passed: false,
|
|
46
48
|
reason: "CONTEXT.md and product profile are required before moving to spec",
|
|
@@ -55,6 +57,43 @@ export function createActiveRun(cwd: string, goal: string, productInput?: string
|
|
|
55
57
|
return run;
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
export function addQuestion(
|
|
61
|
+
cwd: string,
|
|
62
|
+
run: ActiveRun,
|
|
63
|
+
input: { question: string; reason?: string; choices?: string[]; blocking?: boolean },
|
|
64
|
+
): KdQuestion {
|
|
65
|
+
const existing = run.questions ?? [];
|
|
66
|
+
const question: KdQuestion = {
|
|
67
|
+
id: createQuestionId(existing.length + 1),
|
|
68
|
+
phase: run.phase,
|
|
69
|
+
question: input.question.trim(),
|
|
70
|
+
reason: input.reason?.trim() || undefined,
|
|
71
|
+
choices: input.choices?.map((choice) => choice.trim()).filter(Boolean),
|
|
72
|
+
blocking: input.blocking ?? true,
|
|
73
|
+
status: "open",
|
|
74
|
+
createdAt: new Date().toISOString(),
|
|
75
|
+
};
|
|
76
|
+
run.questions = [...existing, question];
|
|
77
|
+
run.gate = inspectGate(cwd, run);
|
|
78
|
+
writeActiveRun(cwd, run);
|
|
79
|
+
return question;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function answerQuestion(cwd: string, run: ActiveRun, id: string, answer: string): KdQuestion | undefined {
|
|
83
|
+
const question = (run.questions ?? []).find((item) => item.id === id);
|
|
84
|
+
if (!question) return undefined;
|
|
85
|
+
question.status = "answered";
|
|
86
|
+
question.answer = answer.trim();
|
|
87
|
+
question.answeredAt = new Date().toISOString();
|
|
88
|
+
run.gate = inspectGate(cwd, run);
|
|
89
|
+
writeActiveRun(cwd, run);
|
|
90
|
+
return question;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function openBlockingQuestions(run: ActiveRun): KdQuestion[] {
|
|
94
|
+
return (run.questions ?? []).filter((question) => question.status === "open" && question.blocking);
|
|
95
|
+
}
|
|
96
|
+
|
|
58
97
|
export function updateProductProfile(cwd: string, run: ActiveRun, productInput: string, version?: string): ActiveRun {
|
|
59
98
|
const profile = resolveProductProfile(productInput);
|
|
60
99
|
run.product = profile.product;
|
|
@@ -115,3 +154,7 @@ function createRunId(goal: string): string {
|
|
|
115
154
|
.slice(0, 40);
|
|
116
155
|
return slug ? `${stamp}-${slug}` : stamp;
|
|
117
156
|
}
|
|
157
|
+
|
|
158
|
+
function createQuestionId(sequence: number): string {
|
|
159
|
+
return `Q-${String(sequence).padStart(3, "0")}`;
|
|
160
|
+
}
|