kld-sdd 2.5.1 → 2.6.0
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 +118 -8
- package/kld-sdd-guide.html +1 -1
- package/lib/init.js +24 -5
- package/lib/tool-profiles.js +1 -1
- package/package.json +4 -2
- package/skywalk-sdd/context-client.cjs +160 -0
- package/skywalk-sdd/index.cjs +445 -36
- package/skywalk-sdd/ontology/archive-package.cjs +489 -0
- package/skywalk-sdd/ontology/artifact-observer.cjs +91 -0
- package/skywalk-sdd/ontology/artifact-parser.cjs +621 -0
- package/skywalk-sdd/ontology/change-lock.cjs +126 -0
- package/skywalk-sdd/ontology/cli.cjs +146 -0
- package/skywalk-sdd/ontology/effective-graph.cjs +158 -0
- package/skywalk-sdd/ontology/id.cjs +126 -0
- package/skywalk-sdd/ontology/identity-index.cjs +287 -0
- package/skywalk-sdd/ontology/normalizer.cjs +107 -0
- package/skywalk-sdd/ontology/runtime.cjs +466 -0
- package/skywalk-sdd/ontology/schema.cjs +139 -0
- package/skywalk-sdd/ontology/structural-identity.cjs +77 -0
- package/skywalk-sdd/ontology/traceability-validator.cjs +610 -0
- package/skywalk-sdd/ontology/working-artifacts.cjs +243 -0
- package/templates/openspec/design.md +18 -0
- package/templates/openspec/proposal.md +19 -6
- package/templates/openspec/spec.md +62 -8
- package/templates/openspec/tasks.md +28 -6
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +19 -1
- package/templates/skills/kld-sdd/opsx-archive/checklist.md +5 -1
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +18 -0
- package/templates/skills/kld-sdd/opsx-check/checklist.md +2 -0
- package/templates/skills/kld-sdd/opsx-design/SKILL.md +11 -0
- package/templates/skills/kld-sdd/opsx-propose/SKILL.md +12 -0
- package/templates/skills/kld-sdd/opsx-propose/checklist.md +2 -0
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +34 -0
- package/templates/skills/kld-sdd/opsx-spec/checklist.md +5 -0
- package/templates/skills/kld-sdd/opsx-task/SKILL.md +11 -0
package/README.md
CHANGED
|
@@ -1,12 +1,99 @@
|
|
|
1
1
|
# kld-sdd
|
|
2
2
|
|
|
3
|
-
KLD SDD OpenSpec
|
|
3
|
+
KLD SDD OpenSpec 工程增强工具:一键初始化 AI 编辑器技能、语义化文档模板和本地本体运行时,支持完整的 SDD(规格驱动开发)研发工作流。
|
|
4
4
|
|
|
5
5
|
## 这是什么?
|
|
6
6
|
|
|
7
7
|
**SDD(Specification-Driven Development)** 是一种以文档链驱动 AI 编码的研发方法:先写清楚"要做什么",再让 AI 去实现,避免 AI 乱猜、反复返工。
|
|
8
8
|
|
|
9
|
-
`kld-sdd` 帮你在项目中一键配置好这套工作流所需的全部 AI 技能,支持 **Cursor、Claude Code、CodeBuddy、Qoder、OpenCode、KunlunZhima、WorkBuddy、Codex**
|
|
9
|
+
`kld-sdd` 帮你在项目中一键配置好这套工作流所需的全部 AI 技能,支持 **Cursor、Claude Code、CodeBuddy、Qoder、OpenCode、KunlunZhima、WorkBuddy、Codex** 等编辑器。新模板和 Skills 会在产物生成时写入稳定编号与显式关系;本地运行时负责解析、对账和校验这些关系。
|
|
10
|
+
|
|
11
|
+
## 本体语义闭环
|
|
12
|
+
|
|
13
|
+
KLD-SDD 当前内置三项语义能力:
|
|
14
|
+
|
|
15
|
+
1. 定义 Change、Capability、STMT、AC、Constraint、Design、Task、Artifact、DocumentSection、OntologySnapshot 的最小本体 Schema。
|
|
16
|
+
2. 通过模板和 Skills 在产物生成时产生 `CHG/CAP/STMT/AC/CON/DES/TASK` 编号和显式引用。
|
|
17
|
+
3. 将 Proposal→Spec→Design→Task 解析为本地工作态实例,并在 Check/Archive 阶段校验和固化。
|
|
18
|
+
|
|
19
|
+
关系链示例:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
CHG contains CAP
|
|
23
|
+
CAP contains STMT
|
|
24
|
+
STMT acceptedBy AC
|
|
25
|
+
STMT constrainedBy CON
|
|
26
|
+
DES realizes STMT
|
|
27
|
+
TASK implements DES / covers STMT / dependsOn TASK
|
|
28
|
+
ART declares Entity
|
|
29
|
+
Entity sourcedFrom SEC
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
每个本体实体使用双层身份:
|
|
33
|
+
|
|
34
|
+
- `anchor`:`STMT-ORDER-005` 等人工可读编号,跨迭代保持稳定且删除后不复用。
|
|
35
|
+
- `entity-id`:全局唯一 UUIDv7,同一逻辑实体跨迭代复用。
|
|
36
|
+
- `version-id`:全局唯一 UUIDv7,每次 added/modified/removed 产生新版本;unchanged 复用历史版本。
|
|
37
|
+
- `predecessor-version`:modified/removed 指向同一实体的直接前序版本。
|
|
38
|
+
|
|
39
|
+
UUID 由本地命令生成,不依赖网络、机器号或中央 ID 服务:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
node skywalk-sdd/log.cjs semantic-identity --delta-state=added
|
|
43
|
+
node skywalk-sdd/log.cjs semantic-identity --delta-state=modified --entity-id=<uuid> --predecessor-version=<uuid>
|
|
44
|
+
node skywalk-sdd/log.cjs semantic-identity --delta-state=unchanged --entity-id=<uuid> --version-id=<uuid>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
项目级校验会同时扫描活动 Change 和 confirmed Archive:不同实体误用同一 UUID、同一版本 UUID 对应不同内容、modified/removed 前序版本断链或同 predecessor 并行分叉都会阻断 Check/Archive。Archive 只有在 manifest、正文 `facts_hash` 与 confirmed snapshot 一致时,才可成为继承来源。
|
|
48
|
+
|
|
49
|
+
跨迭代 unchanged 不复制历史原文。当前 Spec 只保存实体 UUID、版本 UUID、来源路径、来源内容哈希和需要保留的显式关系;运行时从唯一 confirmed Archive 解析这些引用,与本次差量组成 Effective Graph。modified 实体也不会自动继承前序全部关系,未显式列入 inherited relations 的历史关系不会进入新版本。
|
|
50
|
+
|
|
51
|
+
当前工作态 Schema 为 `kld-sdd-ontology/v2`。完全没有语义锚点的旧自由文本仍可读取,但已包含 CHG/CAP/STMT/AC/CON/DES/TASK 锚点的 v1 产物需要补齐 UUID 身份字段后才能通过 v2 Check/Archive,避免工具在迁移时猜测实体身份。
|
|
52
|
+
|
|
53
|
+
本地命令:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# 解析与诊断;首次运行会为 Artifact/DocumentSection 分配并持久化结构 UUIDv7 sidecar
|
|
57
|
+
node skywalk-sdd/log.cjs semantic-scan --project=. --change=<name>
|
|
58
|
+
|
|
59
|
+
# 文件与工作态本体实例全量对账
|
|
60
|
+
node skywalk-sdd/log.cjs semantic-reconcile --project=. --change=<name>
|
|
61
|
+
|
|
62
|
+
# 按 simple/full/strict 校验;通过时标记 pending
|
|
63
|
+
node skywalk-sdd/log.cjs semantic-check --project=. --change=<name> --profile=full
|
|
64
|
+
|
|
65
|
+
# 查看工作态实例
|
|
66
|
+
node skywalk-sdd/log.cjs semantic-status --project=. --change=<name>
|
|
67
|
+
|
|
68
|
+
# 可选的文件变化采集与同步
|
|
69
|
+
node skywalk-sdd/log.cjs semantic-observe --project=. --change=<name>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`propose/spec/design/task` 在文档写入完成时就必须同步工作态 JSON,不等待 Check 或 Archive。各阶段的 `stage_end` 会自动执行一次 reconcile 作为无 Hook 环境的兜底;Skill 中的显式 `semantic-reconcile` 与该兜底幂等。每份 Markdown 的结构化结果写入 `openspec/changes/<name>/artifacts/<原相对路径>.ontology.json`,`openspec/changes/<name>/artifact-index.json` 与 `working-ontology.json` 提供统一索引与全量工作态视图;这些文件随 change 目录一起提交和归档。`skywalk-sdd/state/ontology/` 仅保留 revision 指针与跨进程锁,默认不提交。
|
|
73
|
+
|
|
74
|
+
工作态 JSON 使用 `kld-sdd-working-artifact-facts/v1`,明确标记 `canonical=false` 和 `review_status=draft|pending`。它用于当前 Change 的渐进式展开、定向查询和跨文档校验,不能直接入知识库。Check 重新解析原文、校验并在全部通过时生成 pending revision;Archive 再次对账后才确认、转换为 `canonical-facts.json` 并打包。
|
|
75
|
+
|
|
76
|
+
`semantic-observe` 不是 Hook 替代品:它不能阻止 Cursor、Qoder 或人工编辑,也不能在落盘前控制内容。Observer 启动/重启后会重新对账,暂态失败和锁冲突不会推进文件指纹;Check 和 Archive 始终重新执行 `semantic-reconcile`,以处理重复、乱序或丢失的文件事件。
|
|
77
|
+
|
|
78
|
+
校验 profile:
|
|
79
|
+
|
|
80
|
+
| Profile | 阻断规则 |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `simple` | 强制 STMT→AC;Design/Task 不存在时跳过 |
|
|
83
|
+
| `full` | 强制 STMT→AC→Design→Task 全链和 Task DAG |
|
|
84
|
+
| `strict` | 在 full 基础上要求完整语义来源 |
|
|
85
|
+
|
|
86
|
+
同一 Change 的作者阶段同步、observe、reconcile、check 和 archive 共用跨进程事务锁。`working-ontology.json`、diagnostics、file index、artifact index 及所有分文件 JSON 在 change 目录内原子提交,随后切换 `skywalk-sdd/state/ontology/<change>/current.json` 指针;读取时会拒绝任一文件缺失或 revision 撕裂。
|
|
87
|
+
|
|
88
|
+
Archive 使用两阶段事务:先复制到 staging,从 staging 重新解析正文并生成本体快照、canonical facts、转换报告和 v2 manifest,所有步骤成功后才切换为正式 archive。归档完成后同时生成相邻的 `<日期>-<name>.zip`,其中 `archive-manifest.json` 的文件清单和 SHA-256 必须精确覆盖包内文件,可直接交给知识库消费。语义校验、快照、协议转换、打包或 Spec 同步失败时,活动 Change 会保留或恢复,不留下伪 confirmed 半成品。
|
|
89
|
+
|
|
90
|
+
生产端归档协议产物:
|
|
91
|
+
|
|
92
|
+
- `archive-ontology.json`:KLD-SDD 内部 confirmed 本体快照。
|
|
93
|
+
- `canonical-facts.json`:面向知识库的稳定语义事实,实体与关系均带原文来源及内容哈希。
|
|
94
|
+
- `conversion-report.json`:本体快照到 canonical facts 的转换结果、数量和归一化告警。
|
|
95
|
+
- `archive-manifest.json`:`kld-sdd-archive-manifest/v2` 身份、内容哈希及完整文件清单。
|
|
96
|
+
- `openspec/changes/archive/<日期>-<name>.zip`:知识库可直接读取的 Archive Package。
|
|
10
97
|
|
|
11
98
|
## 快速开始
|
|
12
99
|
|
|
@@ -38,9 +125,9 @@ opsx-propose → opsx-spec → opsx-design → opsx-task → opsx-check
|
|
|
38
125
|
**AI 会做什么**:
|
|
39
126
|
- 如果你没说清楚,AI 会主动问你 4 个问题(痛点/目标/影响模块/约束)
|
|
40
127
|
- 推导出变更的 kebab-case 名称,如 `add-user-auth`
|
|
41
|
-
- 生成 `
|
|
128
|
+
- 生成 `proposal.md` 并展示摘要让你确认
|
|
42
129
|
|
|
43
|
-
**产出**:`openspec/changes/<name>/
|
|
130
|
+
**产出**:`openspec/changes/<name>/proposal.md`
|
|
44
131
|
|
|
45
132
|
---
|
|
46
133
|
|
|
@@ -51,7 +138,7 @@ opsx-propose → opsx-spec → opsx-design → opsx-task → opsx-check
|
|
|
51
138
|
**这是代码生成的唯一依据,必须精确无歧义。**
|
|
52
139
|
|
|
53
140
|
**AI 会做什么**:
|
|
54
|
-
- 读取
|
|
141
|
+
- 读取 proposal.md,检查上下文是否完整
|
|
55
142
|
- 发现模糊描述时主动追问(如"高性能"→ 具体 QPS/RT 是多少?)
|
|
56
143
|
- 向你确认 API 范围、性能指标、安全要求后,生成 `spec.md`
|
|
57
144
|
|
|
@@ -98,7 +185,7 @@ opsx-propose → opsx-spec → opsx-design → opsx-task → opsx-check
|
|
|
98
185
|
- 100% 覆盖 design.md 的每个模块和接口
|
|
99
186
|
- 每个任务有明确验收标准和单测要求
|
|
100
187
|
|
|
101
|
-
|
|
188
|
+
**产出**:Full 模式为 `openspec/changes/<name>/specs/<capability>/tasks.md`,Simple 模式为根目录 `tasks.md`
|
|
102
189
|
|
|
103
190
|
---
|
|
104
191
|
|
|
@@ -114,6 +201,7 @@ opsx-propose → opsx-spec → opsx-design → opsx-task → opsx-check
|
|
|
114
201
|
| 完整性 | 4 个文档是否存在,每个文档章节是否完整 |
|
|
115
202
|
| 一致性 | spec 的 API 在 design 中是否有对应方案;各文档字段命名是否一致 |
|
|
116
203
|
| 可执行性 | task 的每个任务是否可独立执行;验收标准是否可验证 |
|
|
204
|
+
| 本体追溯 | 编号是否唯一,STMT/AC/DES/TASK 关系是否完整,Task DAG 是否成环 |
|
|
117
205
|
|
|
118
206
|
**发现问题时**,AI 会列出严重问题和警告,并提供三种处理方式供你选择。
|
|
119
207
|
|
|
@@ -175,11 +263,33 @@ kld-sdd-init --tool codex # 仅配置 Codex
|
|
|
175
263
|
|
|
176
264
|
```
|
|
177
265
|
your-project/
|
|
266
|
+
├── openspec/
|
|
267
|
+
│ └── changes/
|
|
268
|
+
│ └── <name>/
|
|
269
|
+
│ ├── proposal.md
|
|
270
|
+
│ ├── working-ontology.json
|
|
271
|
+
│ ├── artifact-index.json
|
|
272
|
+
│ ├── diagnostics.json
|
|
273
|
+
│ ├── file-index.json
|
|
274
|
+
│ ├── ontology-identities.json
|
|
275
|
+
│ └── artifacts/
|
|
276
|
+
│ ├── proposal.ontology.json
|
|
277
|
+
│ └── specs/<capability>/
|
|
278
|
+
│ ├── spec.ontology.json
|
|
279
|
+
│ ├── design.ontology.json
|
|
280
|
+
│ └── tasks.ontology.json
|
|
178
281
|
├── openspec-templates/ # openSpec 四文档参考模版
|
|
179
|
-
│ ├──
|
|
282
|
+
│ ├── proposal.md
|
|
180
283
|
│ ├── spec.md
|
|
181
284
|
│ ├── design.md
|
|
182
|
-
│ └──
|
|
285
|
+
│ └── tasks.md
|
|
286
|
+
├── skywalk-sdd/
|
|
287
|
+
│ ├── log.cjs # Telemetry + semantic-* 命令入口
|
|
288
|
+
│ ├── ontology/ # 本地本体解析、校验、观察和对账运行时
|
|
289
|
+
│ └── state/ontology/
|
|
290
|
+
│ ├── .locks/<change>.lock # observe/reconcile/check/archive 跨进程锁
|
|
291
|
+
│ └── <change>/
|
|
292
|
+
│ └── current.json # 当前 revision 指针(本体 JSON 在 change 目录)
|
|
183
293
|
├── .cursor/
|
|
184
294
|
│ └── skills/opsx-*/ # SDD skills(扁平一层)
|
|
185
295
|
├── .claude/
|
package/kld-sdd-guide.html
CHANGED
|
@@ -810,7 +810,7 @@ npx kld-sdd">复制</button>
|
|
|
810
810
|
<li>安装 CLI:<code>npm install -g @tencent-ai/codebuddy-code</code></li>
|
|
811
811
|
<li>在项目目录运行 <code>codebuddy</code>,企业环境选择 Enterprise Domain 登录</li>
|
|
812
812
|
<li>输入 <code>/opsx-propose <变更名></code> 开始 SDD;<code>/skills</code> 查看已加载技能</li>
|
|
813
|
-
<li
|
|
813
|
+
<li>SDD Hook Pack 已随初始化自动生效(lint / 测试 / 危险命令门禁),无需额外配置</li>
|
|
814
814
|
</ol>
|
|
815
815
|
<div class="cmd">/opsx-propose add-user-auth</div>
|
|
816
816
|
<p class="note">项目 Skills 在 <code>.codebuddy/skills/</code>,上下文文件为 <code>CODEBUDDY.md</code>。Windows 可用 <code>Alt+M</code> 切换权限模式;Hooks 比 Prompt 更可靠,适合 lint/test 门禁。</p>
|
package/lib/init.js
CHANGED
|
@@ -531,7 +531,7 @@ function deployProfileArtifacts(selectedTools = Object.keys(TOOL_CONFIGS), cwd =
|
|
|
531
531
|
if (!hookResult.ok) {
|
|
532
532
|
throw new Error(hookResult.error || `CodeBuddy Hook 部署失败: ${profile.configDir}/settings.json`);
|
|
533
533
|
}
|
|
534
|
-
console.log(` ✓ ${config.name}: 部署 Hook Pack
|
|
534
|
+
console.log(` ✓ ${config.name}: 部署 Hook Pack(自动生效)`);
|
|
535
535
|
}
|
|
536
536
|
|
|
537
537
|
if (profile.hookProvider === 'claude') {
|
|
@@ -830,6 +830,24 @@ function deployTelemetryDataDir() {
|
|
|
830
830
|
console.log(` ⚠️ Telemetry CLI 源文件缺失: ${telemetrySrc}`);
|
|
831
831
|
}
|
|
832
832
|
|
|
833
|
+
const contextClientSrc = path.join(pkgPath, 'skywalk-sdd', 'context-client.cjs');
|
|
834
|
+
const contextClientDst = path.join(dataDir, 'context-client.cjs');
|
|
835
|
+
if (fs.existsSync(contextClientSrc)) {
|
|
836
|
+
fs.copyFileSync(contextClientSrc, contextClientDst);
|
|
837
|
+
console.log(' ✓ 部署 skywalk-sdd/context-client.cjs(工程 Spec 上下文客户端)');
|
|
838
|
+
} else {
|
|
839
|
+
console.log(` ⚠️ 工程 Spec 上下文客户端缺失: ${contextClientSrc}`);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
const ontologySrc = path.join(pkgPath, 'skywalk-sdd', 'ontology');
|
|
843
|
+
const ontologyDst = path.join(dataDir, 'ontology');
|
|
844
|
+
if (fs.existsSync(ontologySrc)) {
|
|
845
|
+
copyDir(ontologySrc, ontologyDst);
|
|
846
|
+
console.log(' ✓ 部署 skywalk-sdd/ontology/(本地本体语义运行时)');
|
|
847
|
+
} else {
|
|
848
|
+
console.log(` ⚠️ 本体语义运行时缺失: ${ontologySrc}`);
|
|
849
|
+
}
|
|
850
|
+
|
|
833
851
|
const worktreeFinishSrc = path.join(pkgPath, 'skywalk-sdd', 'apply-worktree-finish.cjs');
|
|
834
852
|
const worktreeFinishDst = path.join(dataDir, 'apply-worktree-finish.cjs');
|
|
835
853
|
if (fs.existsSync(worktreeFinishSrc)) {
|
|
@@ -837,7 +855,8 @@ function deployTelemetryDataDir() {
|
|
|
837
855
|
console.log(' ✓ 部署 skywalk-sdd/apply-worktree-finish.cjs(Apply 收尾脚本)');
|
|
838
856
|
}
|
|
839
857
|
|
|
840
|
-
console.log(' ✓ 调用方式: node skywalk-sdd/log.cjs start|end|metrics');
|
|
858
|
+
console.log(' ✓ 调用方式: node skywalk-sdd/log.cjs start|end|metrics|semantic-identity|semantic-reconcile|semantic-check');
|
|
859
|
+
console.log(' ✓ Spec Context: node skywalk-sdd/context-client.cjs --query="<自然语言需求>" --target-stage=spec');
|
|
841
860
|
console.log(' ✓ Apply worktree: record-base 在 §1.5;收尾 apply-worktree-finish.cjs --change=<name>');
|
|
842
861
|
console.log('✅ Telemetry 已就绪(数据存储在 skywalk-sdd/,无需配置 MCP)');
|
|
843
862
|
return true;
|
|
@@ -962,7 +981,7 @@ function updateGitignore() {
|
|
|
962
981
|
.workbuddy/commands/personal-*
|
|
963
982
|
.agents/commands/personal-*
|
|
964
983
|
|
|
965
|
-
# SDD Telemetry
|
|
984
|
+
# SDD Telemetry 数据(本地度量,可选提交;本体 JSON 与报告随 openspec/changes/<change>/ 提交)
|
|
966
985
|
skywalk-sdd/events/
|
|
967
986
|
skywalk-sdd/state/
|
|
968
987
|
|
|
@@ -1111,7 +1130,7 @@ async function main() {
|
|
|
1111
1130
|
console.log(' ℹ️ KunlunZhima 通过 commands/skills 入口使用 SDD,未启用自动 Hook');
|
|
1112
1131
|
}
|
|
1113
1132
|
if (selectedTools.includes('codebuddy')) {
|
|
1114
|
-
console.log(' 🪝 .codebuddy/hooks/ # CodeBuddy SDD Hook Pack
|
|
1133
|
+
console.log(' 🪝 .codebuddy/hooks/ # CodeBuddy SDD Hook Pack(自动生效)');
|
|
1115
1134
|
}
|
|
1116
1135
|
if (selectedTools.includes('claude')) {
|
|
1117
1136
|
console.log(' 🪝 .claude/hooks/ # Claude Code SDD Hook Pack(可选增强)');
|
|
@@ -1142,7 +1161,7 @@ async function main() {
|
|
|
1142
1161
|
console.log(' 3. 激活 opsx-check 验证文档质量');
|
|
1143
1162
|
console.log(' 4. 激活 opsx-apply 申请实施,opsx-archive 归档完成');
|
|
1144
1163
|
if (selectedTools.includes('codebuddy')) {
|
|
1145
|
-
console.log(' 5. CodeBuddy
|
|
1164
|
+
console.log(' 5. CodeBuddy 用户:SDD Hook Pack 已随初始化自动部署,门禁自动生效,无需手动配置');
|
|
1146
1165
|
}
|
|
1147
1166
|
if (selectedTools.includes('kunlunzhima')) {
|
|
1148
1167
|
console.log(' 5. KunlunZhima 用户:在 Craft 模式使用 /opsx:propose 等 OPSX 命令(.kunlunzhima/commands/opsx/)');
|
package/lib/tool-profiles.js
CHANGED
|
@@ -62,7 +62,7 @@ const TOOL_PROFILES = Object.freeze({
|
|
|
62
62
|
rulesWiring: 'none',
|
|
63
63
|
templateVariables: {
|
|
64
64
|
SKILL_RUNTIME_DIR: '.codebuddy/skills',
|
|
65
|
-
HOOK_GATE_DESCRIPTION: 'CodeBuddy CLI 已部署 SDD Hook Pack(.codebuddy/hooks + settings.json
|
|
65
|
+
HOOK_GATE_DESCRIPTION: 'CodeBuddy CLI 已部署 SDD Hook Pack(.codebuddy/hooks + settings.json),初始化后自动生效。',
|
|
66
66
|
SHELL_GUIDANCE: 'CodeBuddy Hooks 在 Windows 上通过 Git Bash 执行;命令使用 node "$CODEBUDDY_PROJECT_DIR/..." 引用项目路径。',
|
|
67
67
|
},
|
|
68
68
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kld-sdd",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "KLD SDD OpenSpec 项目初始化工具 - 一键部署 SDD skills",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"kld-sdd-init": "bin/kld-sdd-init.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "node test/validate-skills-bundle.cjs && node test/tool-profiles.cjs && node test/settings-merge.cjs && node test/command-bridge.cjs && node test/codebuddy-hooks.cjs && node test/skill-content-contract.cjs && node test/init-agent-profiles.cjs"
|
|
11
|
+
"test": "node test/ontology-release-blockers.cjs && node test/ontology-semantic-core.cjs && node test/ontology-identity-versioning.cjs && node test/ontology-state-transaction.cjs && node test/ontology-process-concurrency.cjs && node test/ontology-observer-convergence.cjs && node test/ontology-working-runtime.cjs && node test/ontology-stage-materialization.cjs && node test/ontology-template-contract.cjs && node test/ontology-cli-archive.cjs && node test/archive-package-producer.cjs && node test/validate-skills-bundle.cjs && node test/tool-profiles.cjs && node test/settings-merge.cjs && node test/command-bridge.cjs && node test/codebuddy-hooks.cjs && node test/skill-content-contract.cjs && node test/init-agent-profiles.cjs"
|
|
12
12
|
},
|
|
13
13
|
"keywords": [
|
|
14
14
|
"kld",
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
"templates/",
|
|
33
33
|
"kld-sdd-guide.html",
|
|
34
34
|
"skywalk-sdd/index.cjs",
|
|
35
|
+
"skywalk-sdd/context-client.cjs",
|
|
36
|
+
"skywalk-sdd/ontology/",
|
|
35
37
|
"skywalk-sdd/apply-worktree-finish.cjs",
|
|
36
38
|
"README.md"
|
|
37
39
|
]
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const http = require('http');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const { URL } = require('url');
|
|
8
|
+
|
|
9
|
+
function parseArgs(argv) {
|
|
10
|
+
const args = {};
|
|
11
|
+
for (const item of argv.slice(2)) {
|
|
12
|
+
if (!item.startsWith('--')) continue;
|
|
13
|
+
const equal = item.indexOf('=');
|
|
14
|
+
if (equal < 0) args[item.slice(2)] = true;
|
|
15
|
+
else args[item.slice(2, equal)] = item.slice(equal + 1);
|
|
16
|
+
}
|
|
17
|
+
return args;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function queryFromArgs(args) {
|
|
21
|
+
if (typeof args.query === 'string' && args.query.trim()) {
|
|
22
|
+
return args.query.trim();
|
|
23
|
+
}
|
|
24
|
+
if (typeof args['query-file'] === 'string' && args['query-file'].trim()) {
|
|
25
|
+
return fs.readFileSync(args['query-file'], 'utf8').trim();
|
|
26
|
+
}
|
|
27
|
+
throw new Error('missing --query or --query-file');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function buildEndpoint(apiBase, spaceId) {
|
|
31
|
+
const base = String(apiBase || 'http://localhost:8090/api').replace(/\/$/, '');
|
|
32
|
+
return `${base}/v1/spaces/${encodeURIComponent(spaceId)}/context/match-requirement`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function requestJson(url, payload, token, timeoutMs) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const target = new URL(url);
|
|
38
|
+
const transport = target.protocol === 'https:' ? https : http;
|
|
39
|
+
const body = JSON.stringify(payload);
|
|
40
|
+
const headers = {
|
|
41
|
+
Accept: 'application/json',
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
'Content-Length': Buffer.byteLength(body),
|
|
44
|
+
};
|
|
45
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
46
|
+
|
|
47
|
+
const request = transport.request(
|
|
48
|
+
{
|
|
49
|
+
hostname: target.hostname,
|
|
50
|
+
port: target.port || (target.protocol === 'https:' ? 443 : 80),
|
|
51
|
+
path: target.pathname + target.search,
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers,
|
|
54
|
+
timeout: timeoutMs,
|
|
55
|
+
},
|
|
56
|
+
(response) => {
|
|
57
|
+
let responseBody = '';
|
|
58
|
+
response.on('data', (chunk) => {
|
|
59
|
+
responseBody += chunk;
|
|
60
|
+
});
|
|
61
|
+
response.on('end', () => {
|
|
62
|
+
let parsed;
|
|
63
|
+
try {
|
|
64
|
+
parsed = responseBody ? JSON.parse(responseBody) : null;
|
|
65
|
+
} catch {
|
|
66
|
+
reject(new Error(`knowledge base returned invalid JSON (HTTP ${response.statusCode})`));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (response.statusCode >= 400 || parsed?.code !== 0) {
|
|
70
|
+
reject(new Error(parsed?.message || `knowledge base HTTP ${response.statusCode}`));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
resolve(parsed.data);
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
request.on('timeout', () => request.destroy(new Error(`knowledge base timeout after ${timeoutMs}ms`)));
|
|
78
|
+
request.on('error', reject);
|
|
79
|
+
request.write(body);
|
|
80
|
+
request.end();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function compactContext(data, includeCandidates) {
|
|
85
|
+
if (includeCandidates) return data;
|
|
86
|
+
return {
|
|
87
|
+
matchType: data.matchType,
|
|
88
|
+
inheritanceAllowed: data.inheritanceAllowed,
|
|
89
|
+
reviewRequired: data.reviewRequired,
|
|
90
|
+
advisory: data.advisory,
|
|
91
|
+
degraded: data.degraded,
|
|
92
|
+
degradationReasons: data.degradationReasons,
|
|
93
|
+
targetStage: data.targetStage,
|
|
94
|
+
reuseBundles: data.reuseBundles,
|
|
95
|
+
answeredQuestions: data.answeredQuestions,
|
|
96
|
+
warnings: data.warnings,
|
|
97
|
+
clarificationQuestions: data.clarificationQuestions,
|
|
98
|
+
specGenerationContext: data.specGenerationContext,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function retrieveContext(args = parseArgs(process.argv), env = process.env) {
|
|
103
|
+
const query = queryFromArgs(args);
|
|
104
|
+
const spaceId = args['space-id'] || env.ENGINEERING_KB_SPACE_ID || '';
|
|
105
|
+
if (!spaceId) {
|
|
106
|
+
return {
|
|
107
|
+
available: false,
|
|
108
|
+
advisory: true,
|
|
109
|
+
degraded: true,
|
|
110
|
+
reason: 'engineering_kb_not_configured',
|
|
111
|
+
hint: 'set ENGINEERING_KB_SPACE_ID to enable reusable Spec context',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const apiBase = args['api-base'] || env.ENGINEERING_KB_API || 'http://localhost:8090/api';
|
|
116
|
+
const token = args.token || env.ENGINEERING_KB_TOKEN || '';
|
|
117
|
+
const timeoutMs = Number(args.timeout || env.ENGINEERING_KB_TIMEOUT_MS || 30000);
|
|
118
|
+
const data = await requestJson(
|
|
119
|
+
buildEndpoint(apiBase, spaceId),
|
|
120
|
+
{
|
|
121
|
+
query,
|
|
122
|
+
targetStage: args['target-stage'] || 'spec',
|
|
123
|
+
},
|
|
124
|
+
token,
|
|
125
|
+
timeoutMs,
|
|
126
|
+
);
|
|
127
|
+
return {
|
|
128
|
+
available: true,
|
|
129
|
+
...compactContext(data, Boolean(args['include-candidates'])),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function main() {
|
|
134
|
+
const args = parseArgs(process.argv);
|
|
135
|
+
try {
|
|
136
|
+
console.log(JSON.stringify(await retrieveContext(args), null, 2));
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.log(JSON.stringify({
|
|
139
|
+
available: false,
|
|
140
|
+
advisory: true,
|
|
141
|
+
degraded: true,
|
|
142
|
+
reason: 'engineering_kb_unavailable',
|
|
143
|
+
message: error.message,
|
|
144
|
+
}, null, 2));
|
|
145
|
+
if (args.strict) process.exitCode = 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (require.main === module) {
|
|
150
|
+
main();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
parseArgs,
|
|
155
|
+
queryFromArgs,
|
|
156
|
+
buildEndpoint,
|
|
157
|
+
requestJson,
|
|
158
|
+
compactContext,
|
|
159
|
+
retrieveContext,
|
|
160
|
+
};
|