scene-capability-engine 3.6.11 → 3.6.14
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/CHANGELOG.md +21 -0
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/docs/command-reference.md +23 -0
- package/docs/magicball-capability-library.md +123 -0
- package/docs/magicball-task-quality-governance.md +301 -0
- package/lib/commands/capability.js +636 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.6.14] - 2026-03-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `sce capability use --apply` to append recommended tasks to spec tasks.md.
|
|
14
|
+
|
|
15
|
+
## [3.6.13] - 2026-03-06
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- Capability library reuse commands:
|
|
19
|
+
- `sce capability catalog list/search/show`
|
|
20
|
+
- `sce capability match`
|
|
21
|
+
- `sce capability use`
|
|
22
|
+
- Magicball capability library integration guide:
|
|
23
|
+
- `docs/magicball-capability-library.md`
|
|
24
|
+
|
|
25
|
+
## [3.6.12] - 2026-03-06
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- Magicball task quality governance integration guide:
|
|
29
|
+
- `docs/magicball-task-quality-governance.md`
|
|
30
|
+
|
|
10
31
|
## [3.6.11] - 2026-03-05
|
|
11
32
|
|
|
12
33
|
### Added
|
package/README.md
CHANGED
package/README.zh.md
CHANGED
|
@@ -1888,6 +1888,29 @@ Schema references:
|
|
|
1888
1888
|
- UI contract: `docs/agent-runtime/capability-iteration-ui.schema.json`
|
|
1889
1889
|
- Ontology mapping: `docs/ontology/capability-mapping.schema.json`
|
|
1890
1890
|
|
|
1891
|
+
### Capability Library Reuse (query -> match -> use)
|
|
1892
|
+
|
|
1893
|
+
```bash
|
|
1894
|
+
# List capability templates
|
|
1895
|
+
sce capability catalog list --json
|
|
1896
|
+
|
|
1897
|
+
# Search capability templates
|
|
1898
|
+
sce capability catalog search "customer order" --json
|
|
1899
|
+
|
|
1900
|
+
# Show capability template metadata + payload
|
|
1901
|
+
sce capability catalog show customer-order-core --json
|
|
1902
|
+
|
|
1903
|
+
# Match capability templates to a spec (uses problem-domain-chain.json)
|
|
1904
|
+
sce capability match --spec 01-02-customer-order --json
|
|
1905
|
+
sce capability match --spec 01-02-customer-order --query "订单 库存" --limit 5 --json
|
|
1906
|
+
|
|
1907
|
+
# Generate a usage plan for a spec
|
|
1908
|
+
sce capability use --template customer-order-core --spec 01-02-customer-order --json
|
|
1909
|
+
|
|
1910
|
+
# Generate usage plan + append tasks to tasks.md
|
|
1911
|
+
sce capability use --template customer-order-core --spec 01-02-customer-order --apply --json
|
|
1912
|
+
```
|
|
1913
|
+
|
|
1891
1914
|
### Scene Package Batch Publish
|
|
1892
1915
|
|
|
1893
1916
|
```bash
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Magicball 能力库复用对接说明(SCE)
|
|
2
|
+
|
|
3
|
+
> 目标:在 Magicball UI 中提供“能力库检索/匹配/使用”闭环,加速场景能力落地。
|
|
4
|
+
|
|
5
|
+
## 1. 能力库复用流程
|
|
6
|
+
|
|
7
|
+
1. **查询**能力库(列表/搜索)
|
|
8
|
+
2. **匹配**当前 spec 的问题域(基于 problem-domain-chain 里的 ontology)
|
|
9
|
+
3. **使用**能力模板(生成可执行的 usage plan)
|
|
10
|
+
|
|
11
|
+
## 2. SCE CLI 支持(对 Magicball 的最小封装)
|
|
12
|
+
|
|
13
|
+
### 2.1 查询与搜索
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
sce capability catalog list --json
|
|
17
|
+
sce capability catalog search "customer order" --json
|
|
18
|
+
sce capability catalog show <template-id> --json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 2.2 匹配(基于 spec ontology)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
sce capability match --spec <spec-id> --json
|
|
25
|
+
sce capability match --spec <spec-id> --query "订单 库存" --limit 5 --json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
匹配会读取:
|
|
29
|
+
- `.sce/specs/<spec>/custom/problem-domain-chain.json`
|
|
30
|
+
|
|
31
|
+
若该文件缺失,默认仍返回结果,但 `warnings` 中会标记缺失。
|
|
32
|
+
|
|
33
|
+
### 2.3 使用(生成 usage plan)
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
sce capability use --template <template-id> --spec <spec-id> --json
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
输出:`capability-use-plan`(用于 UI 展示和后续手工应用)。
|
|
40
|
+
|
|
41
|
+
如需直接写入 spec 任务:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
sce capability use --template <template-id> --spec <spec-id> --apply --json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 3. API 封装建议(CLI -> HTTP)
|
|
48
|
+
|
|
49
|
+
建议 Magicball 后端封装为:
|
|
50
|
+
|
|
51
|
+
- `POST /api/sce/capability/catalog/list`
|
|
52
|
+
- `POST /api/sce/capability/catalog/search`
|
|
53
|
+
- `POST /api/sce/capability/catalog/show`
|
|
54
|
+
- `POST /api/sce/capability/match`
|
|
55
|
+
- `POST /api/sce/capability/use`
|
|
56
|
+
|
|
57
|
+
请求体示例:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"specId": "01-02-customer-order",
|
|
62
|
+
"query": "订单 库存",
|
|
63
|
+
"limit": 5
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 4. UI 行为建议(Magicball)
|
|
68
|
+
|
|
69
|
+
### 4.1 能力库入口
|
|
70
|
+
- 顶部图标入口
|
|
71
|
+
- Tabs:`能力库` / `匹配结果` / `使用计划`
|
|
72
|
+
|
|
73
|
+
### 4.2 能力库列表
|
|
74
|
+
- 支持筛选:category / risk / source
|
|
75
|
+
- 展示关键信息:`name` / `description` / `ontology_scope`
|
|
76
|
+
|
|
77
|
+
### 4.3 匹配结果
|
|
78
|
+
- 展示 `score` + `score_components`
|
|
79
|
+
- 支持一键生成 usage plan
|
|
80
|
+
|
|
81
|
+
### 4.4 使用计划
|
|
82
|
+
- 展示 `recommended_tasks`
|
|
83
|
+
- 可手工转为当前 spec 的任务
|
|
84
|
+
|
|
85
|
+
## 5. 输出结构(关键字段)
|
|
86
|
+
|
|
87
|
+
### capability-match
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"mode": "capability-match",
|
|
91
|
+
"spec_id": "01-02-customer-order",
|
|
92
|
+
"scene_id": "scene.customer-order",
|
|
93
|
+
"match_count": 12,
|
|
94
|
+
"matches": [
|
|
95
|
+
{
|
|
96
|
+
"template_id": "customer-order-core",
|
|
97
|
+
"score": 82,
|
|
98
|
+
"score_components": {
|
|
99
|
+
"ontology": 0.72,
|
|
100
|
+
"scenario": 1,
|
|
101
|
+
"keyword": 0.35
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### capability-use-plan
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"mode": "capability-use-plan",
|
|
112
|
+
"template": {"id": "customer-order-core", "name": "Customer Order Core"},
|
|
113
|
+
"spec_id": "01-02-customer-order",
|
|
114
|
+
"recommended_tasks": [
|
|
115
|
+
{"title": "Define order entity"},
|
|
116
|
+
{"title": "Implement order lifecycle"}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
若需要“自动落地写入 spec 任务”的强制执行模式,可以在后续版本加 `--apply` 开关。
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# Magicball 任务质量治理对接说明(SCE)
|
|
2
|
+
|
|
3
|
+
> 适用于:Magicball AI 助手任务卡 UI 的质量治理增强与 SCE Task 质量闭环对接。
|
|
4
|
+
|
|
5
|
+
## 1. 背景与目标
|
|
6
|
+
|
|
7
|
+
任务由对话生成,容易出现「多事项混杂、目标不清晰、验收缺失」等问题,导致执行偏移或兜底编程。
|
|
8
|
+
SCE 新增「任务质量治理」闭环能力,保证每个任务可执行、可验收、可追踪。
|
|
9
|
+
|
|
10
|
+
目标:
|
|
11
|
+
- 让 Magicball UI 在**同一任务卡**内完成“草案 -> 评分 -> 修正 -> Promote”的闭环
|
|
12
|
+
- 通过质量门禁(Policy)强制任务可执行、可验收
|
|
13
|
+
- 保留用户原始输入,避免信息丢失
|
|
14
|
+
|
|
15
|
+
## 2. SCE 新增能力概览
|
|
16
|
+
|
|
17
|
+
新增 CLI:
|
|
18
|
+
- `sce task draft`
|
|
19
|
+
- `sce task consolidate`
|
|
20
|
+
- `sce task score`
|
|
21
|
+
- `sce task promote`
|
|
22
|
+
|
|
23
|
+
新增策略文件:
|
|
24
|
+
- `.sce/config/task-quality-policy.json`
|
|
25
|
+
- 支持 `--policy <path>` 覆盖
|
|
26
|
+
|
|
27
|
+
默认门禁(可配置):
|
|
28
|
+
- acceptance_criteria 必须存在
|
|
29
|
+
- needs_split 必须为 false
|
|
30
|
+
- min_score >= 70
|
|
31
|
+
|
|
32
|
+
## 3. 推荐交互流程(最短闭环)
|
|
33
|
+
|
|
34
|
+
1. 用户输入 -> `sce task draft`
|
|
35
|
+
2. 多轮输入合并 -> `sce task consolidate`
|
|
36
|
+
3. 评分 -> `sce task score`
|
|
37
|
+
4. 通过门禁 -> `sce task promote`
|
|
38
|
+
5. 成功写入 `tasks.md`
|
|
39
|
+
|
|
40
|
+
## 4. 字段契约(建议 Magicball 消费)
|
|
41
|
+
|
|
42
|
+
核心字段:
|
|
43
|
+
- `task_ref`(草案阶段可为空)
|
|
44
|
+
- `title_norm`
|
|
45
|
+
- `raw_request`
|
|
46
|
+
- `goal`
|
|
47
|
+
- `sub_goals`
|
|
48
|
+
- `acceptance_criteria`
|
|
49
|
+
- `needs_split`
|
|
50
|
+
- `confidence`
|
|
51
|
+
- `next_action`
|
|
52
|
+
- `handoff`
|
|
53
|
+
- `score`
|
|
54
|
+
|
|
55
|
+
示例(草案阶段):
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"task_ref": null,
|
|
59
|
+
"title_norm": "生成客户-订单-库存演示数据流程",
|
|
60
|
+
"raw_request": "帮我做一个客户订单库存的demo",
|
|
61
|
+
"goal": "生成可运行的客户-订单-库存演示流程",
|
|
62
|
+
"sub_goals": ["定义实体关系", "生成测试数据", "配置页面展示"],
|
|
63
|
+
"acceptance_criteria": [],
|
|
64
|
+
"needs_split": true,
|
|
65
|
+
"confidence": 0.68,
|
|
66
|
+
"next_action": "split",
|
|
67
|
+
"handoff": "needs_split=true, acceptance_criteria empty"
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
评分示例:
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"score": 62,
|
|
75
|
+
"missing_items": ["acceptance_criteria", "split_required"]
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Promote 成功:
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"success": true,
|
|
83
|
+
"task_ref": "01.02.03",
|
|
84
|
+
"message": "promoted"
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Promote 失败:
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"success": false,
|
|
92
|
+
"message": "quality gate failed",
|
|
93
|
+
"reasons": ["acceptance_criteria missing", "needs_split=true"]
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 5. UI 行为规范(强制)
|
|
98
|
+
|
|
99
|
+
- 草案页:默认显示**评分卡 + 缺失项**
|
|
100
|
+
- `needs_split=true`:必须拆分或补充,**禁止 promote**
|
|
101
|
+
- `acceptance_criteria` 为空:**阻断 promote**
|
|
102
|
+
- promote 失败时提示固定文案:**“质量门禁未通过”**
|
|
103
|
+
|
|
104
|
+
## 6. 基于现有任务 UI 的最小改动建议
|
|
105
|
+
|
|
106
|
+
当前 UI:单卡片 + 事件流 + 文件变更 + 错误信息。
|
|
107
|
+
在不改整体布局的前提下,建议:
|
|
108
|
+
|
|
109
|
+
1) 任务头部
|
|
110
|
+
- 显示 `title_norm`
|
|
111
|
+
- `raw_request` 置于标题下方(可折叠)
|
|
112
|
+
- 状态徽标:Draft / Needs Split / Missing Acceptance / Ready / Failed Gate
|
|
113
|
+
|
|
114
|
+
2) 评分卡(插入到事件流上方)
|
|
115
|
+
- 展示 `score / missing_items / next_action`
|
|
116
|
+
- 点击展开查看策略阈值与建议
|
|
117
|
+
|
|
118
|
+
3) 强制阻断逻辑
|
|
119
|
+
- needs_split 或 acceptance 缺失 => promote 按钮置灰
|
|
120
|
+
- 展示原因与修复入口
|
|
121
|
+
|
|
122
|
+
4) Promote 失败提示
|
|
123
|
+
- 固定文案 “质量门禁未通过”
|
|
124
|
+
- 展示失败原因列表
|
|
125
|
+
|
|
126
|
+
5) 原有事件流保留,增强复制能力
|
|
127
|
+
- 错误日志一键复制,便于诊断
|
|
128
|
+
|
|
129
|
+
## 7. 参考命令(Magicball 封装为 API 即可)
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
sce task draft --spec <specPath> --input "<user text>"
|
|
133
|
+
sce task consolidate --spec <specPath>
|
|
134
|
+
sce task score --spec <specPath>
|
|
135
|
+
sce task promote --spec <specPath>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 8. 版本与发布说明
|
|
139
|
+
|
|
140
|
+
该能力自 **SCE v3.6.11** 开始提供,若 Magicball 需要兼容老版本,请做版本检测与能力降级处理。
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
如需进一步输出 UI 页面原型或字段映射表,可直接在此文档上增补。
|
|
145
|
+
## 9. 字段映射表(SCE -> Magicball UI)
|
|
146
|
+
|
|
147
|
+
| SCE 字段 | UI 位置 | 展示方式 | 规则 |
|
|
148
|
+
| --- | --- | --- | --- |
|
|
149
|
+
| task_ref | 任务卡标题前 | 小号标签 | 无则隐藏 |
|
|
150
|
+
| title_norm | 任务卡标题 | 主标题 | 必填 |
|
|
151
|
+
| raw_request | 标题下方 | 灰色可折叠 | 保留原文 |
|
|
152
|
+
| goal | 详情区 | 主目标段落 | 若空提示补齐 |
|
|
153
|
+
| sub_goals | 详情区 | 列表 | needs_split=true 时高亮 |
|
|
154
|
+
| acceptance_criteria | 详情区 | 验收列表 | 为空阻断 promote |
|
|
155
|
+
| needs_split | 状态徽标 | Needs Split | true 则阻断 |
|
|
156
|
+
| confidence | 评分卡 | 低/中/高 | <0.6 高亮 |
|
|
157
|
+
| score | 评分卡 | 数值 + 色阶 | <阈值红色 |
|
|
158
|
+
| missing_items | 评分卡 | 缺失列表 | 点击展开 |
|
|
159
|
+
| next_action | 评分卡 | 下一步 | 生成操作建议 |
|
|
160
|
+
| handoff | 详情区 | 灰色提示 | 展示门禁原因 |
|
|
161
|
+
| errors | 事件流 | 错误块 | 一键复制 |
|
|
162
|
+
| file_changes | 文件变更区 | 文件列表 | diff 快捷入口 |
|
|
163
|
+
|
|
164
|
+
## 10. 前端组件建议
|
|
165
|
+
|
|
166
|
+
### 10.1 TaskCard(现有卡片增强)
|
|
167
|
+
- Header:`task_ref + title_norm`,`raw_request` 折叠显示。
|
|
168
|
+
- StatusBadge:Draft / Needs Split / Missing Acceptance / Ready / Failed Gate。
|
|
169
|
+
- QuickActions:Score / Promote / Split / Fix Acceptance。
|
|
170
|
+
|
|
171
|
+
### 10.2 QualityScorePanel
|
|
172
|
+
- 固定显示 `score / missing_items / next_action`。
|
|
173
|
+
- 展开显示 policy 阈值与建议。
|
|
174
|
+
- 禁止 promote 时显示红色提示条。
|
|
175
|
+
|
|
176
|
+
### 10.3 AcceptanceEditor
|
|
177
|
+
- 为空时自动提示“请补齐验收标准”。
|
|
178
|
+
- 提供“自动补齐建议”按钮(由 SCE 生成)。
|
|
179
|
+
|
|
180
|
+
### 10.4 PromoteGuard
|
|
181
|
+
- 拦截条件:needs_split=true 或 acceptance_criteria 为空。
|
|
182
|
+
- 拦截弹窗文案固定:“质量门禁未通过”。
|
|
183
|
+
|
|
184
|
+
### 10.5 ErrorStream
|
|
185
|
+
- 事件流右侧增加复制按钮。
|
|
186
|
+
- 错误日志折叠/展开。
|
|
187
|
+
|
|
188
|
+
## 11. 建议的调用顺序(前端按钮)
|
|
189
|
+
|
|
190
|
+
- 点击“生成草案”:`sce task draft`
|
|
191
|
+
- 点击“合并输入”:`sce task consolidate`
|
|
192
|
+
- 点击“评分”:`sce task score`
|
|
193
|
+
- 点击“Promote”:`sce task promote`
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
## 12. 示例 UI 结构(JSON/DSL)
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"type": "TaskCard",
|
|
201
|
+
"props": {
|
|
202
|
+
"taskRef": "01.02.03",
|
|
203
|
+
"titleNorm": "生成客户-订单-库存演示数据流程",
|
|
204
|
+
"rawRequest": "帮我做一个客户订单库存的demo",
|
|
205
|
+
"status": "NeedsSplit",
|
|
206
|
+
"score": 62,
|
|
207
|
+
"missingItems": ["acceptance_criteria", "split_required"],
|
|
208
|
+
"nextAction": "split"
|
|
209
|
+
},
|
|
210
|
+
"children": [
|
|
211
|
+
{
|
|
212
|
+
"type": "QualityScorePanel",
|
|
213
|
+
"props": {
|
|
214
|
+
"score": 62,
|
|
215
|
+
"minScore": 70,
|
|
216
|
+
"missingItems": ["acceptance_criteria", "split_required"],
|
|
217
|
+
"nextAction": "split"
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"type": "TaskDetails",
|
|
222
|
+
"props": {
|
|
223
|
+
"goal": "生成可运行的客户-订单-库存演示流程",
|
|
224
|
+
"subGoals": ["定义实体关系", "生成测试数据", "配置页面展示"],
|
|
225
|
+
"acceptanceCriteria": [],
|
|
226
|
+
"handoff": "needs_split=true, acceptance_criteria empty"
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"type": "EventStream",
|
|
231
|
+
"props": {
|
|
232
|
+
"events": [
|
|
233
|
+
{"ts": "23:59:01", "level": "error", "message": "auto-fix blocked", "copyable": true},
|
|
234
|
+
{"ts": "23:59:02", "level": "info", "message": "Task completed"}
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"type": "FileChanges",
|
|
240
|
+
"props": {
|
|
241
|
+
"files": [
|
|
242
|
+
{"path": "src/app.ts", "diffRef": "diff:123", "reason": "adjust task acceptance"}
|
|
243
|
+
]
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## 13. 前端 API 封装建议(CLI -> HTTP)
|
|
251
|
+
|
|
252
|
+
建议 Magicball 在后端封装 SCE CLI,前端统一调用 HTTP。
|
|
253
|
+
|
|
254
|
+
### 13.1 路由定义
|
|
255
|
+
|
|
256
|
+
- `POST /api/sce/task/draft`
|
|
257
|
+
- `POST /api/sce/task/consolidate`
|
|
258
|
+
- `POST /api/sce/task/score`
|
|
259
|
+
- `POST /api/sce/task/promote`
|
|
260
|
+
|
|
261
|
+
### 13.2 请求体
|
|
262
|
+
|
|
263
|
+
```json
|
|
264
|
+
{
|
|
265
|
+
"specPath": "scenes/01/specs/02/spec.md",
|
|
266
|
+
"input": "帮我做一个客户订单库存的demo",
|
|
267
|
+
"policyPath": ".sce/config/task-quality-policy.json"
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### 13.3 响应体(统一包裹)
|
|
272
|
+
|
|
273
|
+
```json
|
|
274
|
+
{
|
|
275
|
+
"success": true,
|
|
276
|
+
"data": {
|
|
277
|
+
"task_ref": "01.02.03",
|
|
278
|
+
"title_norm": "生成客户-订单-库存演示数据流程",
|
|
279
|
+
"raw_request": "帮我做一个客户订单库存的demo",
|
|
280
|
+
"goal": "生成可运行的客户-订单-库存演示流程",
|
|
281
|
+
"sub_goals": ["定义实体关系", "生成测试数据", "配置页面展示"],
|
|
282
|
+
"acceptance_criteria": ["前端能展示客户列表"],
|
|
283
|
+
"needs_split": false,
|
|
284
|
+
"confidence": 0.81,
|
|
285
|
+
"score": 78,
|
|
286
|
+
"missing_items": [],
|
|
287
|
+
"next_action": "promote",
|
|
288
|
+
"handoff": "ready"
|
|
289
|
+
},
|
|
290
|
+
"errors": []
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### 13.4 前端调用顺序(建议)
|
|
295
|
+
|
|
296
|
+
1. `draft` -> 渲染草案 + 评分卡
|
|
297
|
+
2. `consolidate` -> 合并多轮输入(可选)
|
|
298
|
+
3. `score` -> 质量评分
|
|
299
|
+
4. `promote` -> 写入 tasks.md
|
|
300
|
+
|
|
301
|
+
---
|
|
@@ -11,7 +11,10 @@ const path = require('path');
|
|
|
11
11
|
const chalk = require('chalk');
|
|
12
12
|
const TaskClaimer = require('../task/task-claimer');
|
|
13
13
|
const { runStudioSpecGovernance } = require('../studio/spec-intake-governor');
|
|
14
|
+
const { DOMAIN_CHAIN_RELATIVE_PATH } = require('../spec/domain-modeling');
|
|
14
15
|
const { SceStateStore } = require('../state/sce-state-store');
|
|
16
|
+
const TemplateManager = require('../templates/template-manager');
|
|
17
|
+
const { TemplateError } = require('../templates/template-error');
|
|
15
18
|
const packageJson = require('../../package.json');
|
|
16
19
|
|
|
17
20
|
const DEFAULT_ITERATION_DIR = '.sce/reports/capability-iteration';
|
|
@@ -31,6 +34,20 @@ function normalizeStringArray(value) {
|
|
|
31
34
|
return value.map((item) => normalizeText(item)).filter(Boolean);
|
|
32
35
|
}
|
|
33
36
|
|
|
37
|
+
function normalizeTokenList(value) {
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
return normalizeStringArray(value).map((item) => item.toLowerCase());
|
|
40
|
+
}
|
|
41
|
+
const text = normalizeText(value);
|
|
42
|
+
if (!text) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
return text
|
|
46
|
+
.split(/[^a-zA-Z0-9._-]+/g)
|
|
47
|
+
.map((item) => item.trim().toLowerCase())
|
|
48
|
+
.filter(Boolean);
|
|
49
|
+
}
|
|
50
|
+
|
|
34
51
|
function normalizeBoolean(value, fallback = false) {
|
|
35
52
|
if (typeof value === 'boolean') {
|
|
36
53
|
return value;
|
|
@@ -71,6 +88,12 @@ function buildDefaultTemplatePath(sceneId) {
|
|
|
71
88
|
return path.join(DEFAULT_ITERATION_DIR, `${safeScene}.template.json`);
|
|
72
89
|
}
|
|
73
90
|
|
|
91
|
+
function buildDefaultUsePlanPath(specId, templateId) {
|
|
92
|
+
const safeSpec = normalizeText(specId).replace(/[^\w.-]+/g, '_') || 'spec';
|
|
93
|
+
const safeTemplate = normalizeText(templateId).replace(/[^\w.-]+/g, '_') || 'template';
|
|
94
|
+
return path.join(DEFAULT_ITERATION_DIR, 'usage', `${safeSpec}.${safeTemplate}.plan.json`);
|
|
95
|
+
}
|
|
96
|
+
|
|
74
97
|
function buildDefaultExportDir(templateId) {
|
|
75
98
|
const safeId = normalizeText(templateId).replace(/[^\w.-]+/g, '_') || 'capability';
|
|
76
99
|
return path.join(DEFAULT_EXPORT_ROOT, `capability-${safeId}`);
|
|
@@ -80,6 +103,225 @@ function buildSceneIdFromCandidate(candidate) {
|
|
|
80
103
|
return normalizeText(candidate && candidate.scene_id) || 'scene.unknown';
|
|
81
104
|
}
|
|
82
105
|
|
|
106
|
+
function parseTemplatePath(templatePath) {
|
|
107
|
+
const normalized = normalizeText(templatePath);
|
|
108
|
+
if (!normalized) {
|
|
109
|
+
return { sourceName: 'official', templateId: '' };
|
|
110
|
+
}
|
|
111
|
+
if (normalized.includes(':')) {
|
|
112
|
+
const [sourceName, templateId] = normalized.split(':', 2);
|
|
113
|
+
return { sourceName: normalizeText(sourceName) || 'official', templateId: normalizeText(templateId) };
|
|
114
|
+
}
|
|
115
|
+
return { sourceName: 'official', templateId: normalized };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function buildOntologyScopeFromChain(domainChain) {
|
|
119
|
+
const ontology = domainChain && domainChain.ontology ? domainChain.ontology : {};
|
|
120
|
+
return {
|
|
121
|
+
domains: normalizeStringArray(domainChain && domainChain.scene_id ? [domainChain.scene_id] : []),
|
|
122
|
+
entities: normalizeStringArray(ontology.entity),
|
|
123
|
+
relations: normalizeStringArray(ontology.relation),
|
|
124
|
+
business_rules: normalizeStringArray(ontology.business_rule),
|
|
125
|
+
decisions: normalizeStringArray(ontology.decision_policy)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function buildOntologyOverlap(specScope, templateScope) {
|
|
130
|
+
const fields = ['domains', 'entities', 'relations', 'business_rules', 'decisions'];
|
|
131
|
+
const details = {};
|
|
132
|
+
let weightedTotal = 0;
|
|
133
|
+
let weightedMatched = 0;
|
|
134
|
+
let bucketCount = 0;
|
|
135
|
+
|
|
136
|
+
fields.forEach((field) => {
|
|
137
|
+
const expected = normalizeTokenList(specScope && specScope[field]);
|
|
138
|
+
const provided = normalizeTokenList(templateScope && templateScope[field]);
|
|
139
|
+
const providedSet = new Set(provided);
|
|
140
|
+
const matched = expected.filter((item) => providedSet.has(item));
|
|
141
|
+
const expectedCount = expected.length;
|
|
142
|
+
const matchedCount = matched.length;
|
|
143
|
+
const coverage = expectedCount > 0 ? matchedCount / expectedCount : 0;
|
|
144
|
+
if (expectedCount > 0) {
|
|
145
|
+
weightedTotal += 1;
|
|
146
|
+
weightedMatched += coverage;
|
|
147
|
+
bucketCount += 1;
|
|
148
|
+
}
|
|
149
|
+
details[field] = {
|
|
150
|
+
expected,
|
|
151
|
+
provided,
|
|
152
|
+
matched,
|
|
153
|
+
expected_count: expectedCount,
|
|
154
|
+
matched_count: matchedCount,
|
|
155
|
+
coverage_ratio: Number(coverage.toFixed(3))
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const score = bucketCount > 0 ? weightedMatched / weightedTotal : 0;
|
|
160
|
+
return {
|
|
161
|
+
score,
|
|
162
|
+
details
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function buildKeywordScore(template, queryTokens) {
|
|
167
|
+
if (!queryTokens || queryTokens.length === 0) {
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
const haystack = [
|
|
171
|
+
template.id,
|
|
172
|
+
template.name,
|
|
173
|
+
template.description,
|
|
174
|
+
...(template.tags || []),
|
|
175
|
+
...(template.applicable_scenarios || [])
|
|
176
|
+
].map((item) => `${item || ''}`.toLowerCase());
|
|
177
|
+
const hits = queryTokens.filter((token) => haystack.some((value) => value.includes(token))).length;
|
|
178
|
+
return hits / queryTokens.length;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function collectExistingTaskRegistry(tasksContent) {
|
|
182
|
+
const taskPattern = /^-\s*\[[ x~-]\]\*?\s+(\d+(?:\.\d+)*)\s+(.+)$/;
|
|
183
|
+
const lines = String(tasksContent || '').split('\n');
|
|
184
|
+
const existingTitles = new Set();
|
|
185
|
+
let maxTaskId = 0;
|
|
186
|
+
|
|
187
|
+
for (const line of lines) {
|
|
188
|
+
const match = line.match(taskPattern);
|
|
189
|
+
if (!match) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const rawId = match[1];
|
|
194
|
+
const rawTitle = match[2];
|
|
195
|
+
const taskId = Number.parseInt(String(rawId).split('.')[0], 10);
|
|
196
|
+
|
|
197
|
+
if (Number.isFinite(taskId)) {
|
|
198
|
+
maxTaskId = Math.max(maxTaskId, taskId);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const normalizedTitle = String(rawTitle || '')
|
|
202
|
+
.replace(/\s+\[[^\]]+\]$/, '')
|
|
203
|
+
.trim()
|
|
204
|
+
.toLowerCase();
|
|
205
|
+
|
|
206
|
+
if (normalizedTitle) {
|
|
207
|
+
existingTitles.add(normalizedTitle);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
maxTaskId,
|
|
213
|
+
existingTitles
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function createCapabilityTaskLine(taskId, title, metadata = {}) {
|
|
218
|
+
const suffixParts = [];
|
|
219
|
+
if (metadata.templateId) {
|
|
220
|
+
suffixParts.push(`capability_ref=${metadata.templateId}`);
|
|
221
|
+
}
|
|
222
|
+
if (metadata.templateSource) {
|
|
223
|
+
suffixParts.push(`template_source=${metadata.templateSource}`);
|
|
224
|
+
}
|
|
225
|
+
const suffix = suffixParts.length > 0 ? ` [${suffixParts.join(' ')}]` : '';
|
|
226
|
+
return `- [ ] ${taskId} ${title}${suffix}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function appendCapabilityPlanToSpecTasks(options, plan, fileSystem = fs) {
|
|
230
|
+
const projectPath = options.projectPath || process.cwd();
|
|
231
|
+
const specId = normalizeText(options.spec || options.specId);
|
|
232
|
+
if (!specId) {
|
|
233
|
+
throw new Error('spec is required to apply capability plan');
|
|
234
|
+
}
|
|
235
|
+
const tasksPath = path.join(projectPath, '.sce', 'specs', specId, 'tasks.md');
|
|
236
|
+
const tasksExists = await fileSystem.pathExists(tasksPath);
|
|
237
|
+
if (!tasksExists) {
|
|
238
|
+
throw new Error(`target spec tasks.md not found: ${tasksPath}`);
|
|
239
|
+
}
|
|
240
|
+
const currentContent = await fileSystem.readFile(tasksPath, 'utf8');
|
|
241
|
+
const registry = collectExistingTaskRegistry(currentContent);
|
|
242
|
+
const recommended = Array.isArray(plan.recommended_tasks) ? plan.recommended_tasks : [];
|
|
243
|
+
const sectionTitle = normalizeText(options.sectionTitle)
|
|
244
|
+
|| `## Capability Template Tasks (${plan.template.id} - ${new Date().toISOString()})`;
|
|
245
|
+
|
|
246
|
+
const lines = [];
|
|
247
|
+
const addedTasks = [];
|
|
248
|
+
let nextTaskId = registry.maxTaskId + 1;
|
|
249
|
+
let duplicateCount = 0;
|
|
250
|
+
|
|
251
|
+
for (const entry of recommended) {
|
|
252
|
+
const title = normalizeText(entry && entry.title);
|
|
253
|
+
if (!title) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const titleKey = title.toLowerCase();
|
|
257
|
+
if (registry.existingTitles.has(titleKey)) {
|
|
258
|
+
duplicateCount += 1;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
registry.existingTitles.add(titleKey);
|
|
262
|
+
|
|
263
|
+
lines.push(createCapabilityTaskLine(nextTaskId, title, {
|
|
264
|
+
templateId: plan.template.id,
|
|
265
|
+
templateSource: plan.template.source
|
|
266
|
+
}));
|
|
267
|
+
|
|
268
|
+
addedTasks.push({
|
|
269
|
+
task_id: nextTaskId,
|
|
270
|
+
title
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
nextTaskId += 1;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (addedTasks.length === 0) {
|
|
277
|
+
return {
|
|
278
|
+
tasks_path: tasksPath,
|
|
279
|
+
added_count: 0,
|
|
280
|
+
skipped_duplicates: duplicateCount,
|
|
281
|
+
skipped_reason: recommended.length === 0
|
|
282
|
+
? 'no recommended tasks'
|
|
283
|
+
: 'all recommended tasks already exist in tasks.md',
|
|
284
|
+
added_tasks: []
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const prefix = currentContent.trimEnd();
|
|
289
|
+
const chunks = [
|
|
290
|
+
prefix,
|
|
291
|
+
'',
|
|
292
|
+
sectionTitle,
|
|
293
|
+
'',
|
|
294
|
+
...lines,
|
|
295
|
+
''
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
const nextContent = chunks.join('\n');
|
|
299
|
+
await fileSystem.writeFile(tasksPath, nextContent, 'utf8');
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
tasks_path: tasksPath,
|
|
303
|
+
added_count: addedTasks.length,
|
|
304
|
+
skipped_duplicates: duplicateCount,
|
|
305
|
+
first_task_id: addedTasks[0].task_id,
|
|
306
|
+
last_task_id: addedTasks[addedTasks.length - 1].task_id,
|
|
307
|
+
added_tasks: addedTasks
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function loadSpecDomainChain(projectPath, specId, fileSystem) {
|
|
312
|
+
const specPath = path.join(projectPath, '.sce', 'specs', specId);
|
|
313
|
+
const domainChainPath = path.join(specPath, DOMAIN_CHAIN_RELATIVE_PATH);
|
|
314
|
+
if (!await fileSystem.pathExists(domainChainPath)) {
|
|
315
|
+
return { exists: false, path: domainChainPath, payload: null };
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
const payload = await fileSystem.readJson(domainChainPath);
|
|
319
|
+
return { exists: true, path: domainChainPath, payload };
|
|
320
|
+
} catch (error) {
|
|
321
|
+
return { exists: true, path: domainChainPath, payload: null, error: error.message };
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
83
325
|
async function loadSceneIndexFromFile(projectPath, fileSystem) {
|
|
84
326
|
const indexPath = path.join(projectPath, '.sce', 'spec-governance', 'scene-index.json');
|
|
85
327
|
if (!await fileSystem.pathExists(indexPath)) {
|
|
@@ -537,6 +779,263 @@ async function runCapabilityRegisterCommand(options = {}, dependencies = {}) {
|
|
|
537
779
|
return result;
|
|
538
780
|
}
|
|
539
781
|
|
|
782
|
+
function displayCapabilityCatalog(templates, options = {}) {
|
|
783
|
+
const total = Array.isArray(templates) ? templates.length : 0;
|
|
784
|
+
console.log(chalk.red('🔥') + ' Capability Library');
|
|
785
|
+
if (total === 0) {
|
|
786
|
+
console.log(chalk.yellow('No capability templates found.'));
|
|
787
|
+
if (options.source) {
|
|
788
|
+
console.log(chalk.gray(`Try removing filters or run ${chalk.cyan('sce templates update')}.`));
|
|
789
|
+
}
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
templates.forEach((template) => {
|
|
793
|
+
const sourcePrefix = template.source && template.source !== 'official'
|
|
794
|
+
? chalk.gray(`[${template.source}] `)
|
|
795
|
+
: '';
|
|
796
|
+
console.log(`${sourcePrefix}${chalk.cyan(template.id)} ${chalk.gray(`(${template.category})`)}`);
|
|
797
|
+
console.log(` ${template.name}`);
|
|
798
|
+
console.log(` ${chalk.gray(template.description)}`);
|
|
799
|
+
console.log();
|
|
800
|
+
});
|
|
801
|
+
console.log(chalk.gray(`Total: ${total} capability template(s)`));
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
async function listCapabilityCatalog(options = {}) {
|
|
805
|
+
const manager = new TemplateManager();
|
|
806
|
+
const templates = await manager.listTemplates({
|
|
807
|
+
category: options.category,
|
|
808
|
+
source: options.source,
|
|
809
|
+
templateType: 'capability-template',
|
|
810
|
+
compatibleWith: options.compatibleWith,
|
|
811
|
+
riskLevel: options.risk
|
|
812
|
+
});
|
|
813
|
+
if (normalizeBoolean(options.json, false)) {
|
|
814
|
+
return {
|
|
815
|
+
mode: 'capability-catalog-list',
|
|
816
|
+
templates
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
displayCapabilityCatalog(templates, options);
|
|
820
|
+
return { templates };
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
async function searchCapabilityCatalog(keyword, options = {}) {
|
|
824
|
+
const manager = new TemplateManager();
|
|
825
|
+
const templates = await manager.searchTemplates(keyword, {
|
|
826
|
+
category: options.category,
|
|
827
|
+
source: options.source,
|
|
828
|
+
templateType: 'capability-template',
|
|
829
|
+
compatibleWith: options.compatibleWith,
|
|
830
|
+
riskLevel: options.risk
|
|
831
|
+
});
|
|
832
|
+
if (normalizeBoolean(options.json, false)) {
|
|
833
|
+
return {
|
|
834
|
+
mode: 'capability-catalog-search',
|
|
835
|
+
keyword,
|
|
836
|
+
templates
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
displayCapabilityCatalog(templates, options);
|
|
840
|
+
return { templates };
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
async function showCapabilityTemplate(templatePath, options = {}) {
|
|
844
|
+
const manager = new TemplateManager();
|
|
845
|
+
const template = await manager.showTemplate(templatePath);
|
|
846
|
+
const { sourceName, templateId } = parseTemplatePath(templatePath);
|
|
847
|
+
await manager.ensureCached(sourceName);
|
|
848
|
+
const sourcePath = manager.cacheManager.getSourceCachePath(sourceName);
|
|
849
|
+
const templateDir = path.join(sourcePath, templateId);
|
|
850
|
+
const capabilityFile = path.join(templateDir, 'capability-template.json');
|
|
851
|
+
let templatePayload = null;
|
|
852
|
+
if (await fs.pathExists(capabilityFile)) {
|
|
853
|
+
try {
|
|
854
|
+
templatePayload = await fs.readJson(capabilityFile);
|
|
855
|
+
} catch (_error) {
|
|
856
|
+
templatePayload = null;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
const result = {
|
|
860
|
+
mode: 'capability-catalog-show',
|
|
861
|
+
template,
|
|
862
|
+
template_file: await fs.pathExists(capabilityFile) ? capabilityFile : null,
|
|
863
|
+
payload: templatePayload
|
|
864
|
+
};
|
|
865
|
+
if (normalizeBoolean(options.json, false)) {
|
|
866
|
+
return result;
|
|
867
|
+
}
|
|
868
|
+
console.log(chalk.green('✅ Capability template loaded'));
|
|
869
|
+
console.log(chalk.gray(` ID: ${template.id}`));
|
|
870
|
+
console.log(chalk.gray(` Name: ${template.name}`));
|
|
871
|
+
if (templatePayload) {
|
|
872
|
+
console.log(chalk.gray(' Payload: capability-template.json loaded'));
|
|
873
|
+
}
|
|
874
|
+
return result;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
async function matchCapabilityTemplates(options = {}) {
|
|
878
|
+
const projectPath = options.projectPath || process.cwd();
|
|
879
|
+
const fileSystem = options.fileSystem || fs;
|
|
880
|
+
const specId = normalizeText(options.spec || options.specId);
|
|
881
|
+
if (!specId) {
|
|
882
|
+
throw new Error('spec is required for capability match');
|
|
883
|
+
}
|
|
884
|
+
const chain = await loadSpecDomainChain(projectPath, specId, fileSystem);
|
|
885
|
+
if (!chain.exists && normalizeBoolean(options.strict, false)) {
|
|
886
|
+
throw new Error(`problem-domain-chain missing for spec ${specId}`);
|
|
887
|
+
}
|
|
888
|
+
if (chain.error && normalizeBoolean(options.strict, false)) {
|
|
889
|
+
throw new Error(`problem-domain-chain invalid: ${chain.error}`);
|
|
890
|
+
}
|
|
891
|
+
const domainChain = chain.payload || {};
|
|
892
|
+
const specScope = buildOntologyScopeFromChain(domainChain);
|
|
893
|
+
const queryTokens = normalizeTokenList(options.query)
|
|
894
|
+
.concat(normalizeTokenList(domainChain.problem && domainChain.problem.statement))
|
|
895
|
+
.concat(normalizeTokenList(domainChain.scene_id));
|
|
896
|
+
const manager = new TemplateManager();
|
|
897
|
+
const templates = await manager.listTemplates({
|
|
898
|
+
source: options.source,
|
|
899
|
+
templateType: 'capability-template',
|
|
900
|
+
compatibleWith: options.compatibleWith,
|
|
901
|
+
riskLevel: options.risk
|
|
902
|
+
});
|
|
903
|
+
const matches = templates.map((template) => {
|
|
904
|
+
const overlap = buildOntologyOverlap(specScope, template.ontology_scope || {});
|
|
905
|
+
const scenarioScore = template.applicable_scenarios && domainChain.scene_id
|
|
906
|
+
? (template.applicable_scenarios.includes(domainChain.scene_id) ? 1 : 0)
|
|
907
|
+
: 0;
|
|
908
|
+
const keywordScore = buildKeywordScore(template, queryTokens);
|
|
909
|
+
const totalScore = (overlap.score * 0.6) + (scenarioScore * 0.2) + (keywordScore * 0.2);
|
|
910
|
+
return {
|
|
911
|
+
template_id: template.id,
|
|
912
|
+
source: template.source,
|
|
913
|
+
name: template.name,
|
|
914
|
+
description: template.description,
|
|
915
|
+
category: template.category,
|
|
916
|
+
risk_level: template.risk_level,
|
|
917
|
+
score: Math.round(totalScore * 100),
|
|
918
|
+
score_components: {
|
|
919
|
+
ontology: Number(overlap.score.toFixed(3)),
|
|
920
|
+
scenario: scenarioScore,
|
|
921
|
+
keyword: Number(keywordScore.toFixed(3))
|
|
922
|
+
},
|
|
923
|
+
overlap
|
|
924
|
+
};
|
|
925
|
+
}).sort((a, b) => b.score - a.score);
|
|
926
|
+
|
|
927
|
+
const limit = toPositiveInteger(options.limit, 10);
|
|
928
|
+
const payload = {
|
|
929
|
+
mode: 'capability-match',
|
|
930
|
+
spec_id: specId,
|
|
931
|
+
scene_id: domainChain.scene_id || null,
|
|
932
|
+
query: normalizeText(options.query) || null,
|
|
933
|
+
ontology_source: chain.exists ? chain.path : null,
|
|
934
|
+
match_count: matches.length,
|
|
935
|
+
matches: matches.slice(0, limit),
|
|
936
|
+
warnings: chain.exists ? [] : ['problem-domain-chain missing; ontology-based match unavailable']
|
|
937
|
+
};
|
|
938
|
+
if (normalizeBoolean(options.json, false)) {
|
|
939
|
+
return payload;
|
|
940
|
+
}
|
|
941
|
+
console.log(chalk.green('✅ Capability match completed'));
|
|
942
|
+
console.log(chalk.gray(` Spec: ${specId}`));
|
|
943
|
+
console.log(chalk.gray(` Matches: ${payload.matches.length}`));
|
|
944
|
+
return payload;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
async function useCapabilityTemplate(options = {}) {
|
|
948
|
+
const projectPath = options.projectPath || process.cwd();
|
|
949
|
+
const fileSystem = options.fileSystem || fs;
|
|
950
|
+
const templateId = normalizeText(options.template || options.id);
|
|
951
|
+
if (!templateId) {
|
|
952
|
+
throw new Error('template is required for capability use');
|
|
953
|
+
}
|
|
954
|
+
if (normalizeBoolean(options.apply, false) && normalizeBoolean(options.write, true) === false) {
|
|
955
|
+
throw new Error('cannot use --apply with --no-write');
|
|
956
|
+
}
|
|
957
|
+
const specId = normalizeText(options.spec || options.specId) || null;
|
|
958
|
+
const manager = new TemplateManager();
|
|
959
|
+
const template = await manager.showTemplate(templateId);
|
|
960
|
+
const { sourceName, templateId: parsedTemplateId } = parseTemplatePath(templateId);
|
|
961
|
+
await manager.ensureCached(sourceName);
|
|
962
|
+
const sourcePath = manager.cacheManager.getSourceCachePath(sourceName);
|
|
963
|
+
const templateDir = path.join(sourcePath, parsedTemplateId);
|
|
964
|
+
const capabilityFile = path.join(templateDir, 'capability-template.json');
|
|
965
|
+
let templatePayload = null;
|
|
966
|
+
if (await fileSystem.pathExists(capabilityFile)) {
|
|
967
|
+
try {
|
|
968
|
+
templatePayload = await fileSystem.readJson(capabilityFile);
|
|
969
|
+
} catch (_error) {
|
|
970
|
+
templatePayload = null;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const recommendedTasks = [];
|
|
975
|
+
if (templatePayload && templatePayload.source_candidate && Array.isArray(templatePayload.source_candidate.specs)) {
|
|
976
|
+
templatePayload.source_candidate.specs.forEach((spec) => {
|
|
977
|
+
const sample = Array.isArray(spec.task_sample) ? spec.task_sample : [];
|
|
978
|
+
sample.forEach((task) => {
|
|
979
|
+
if (task && task.title) {
|
|
980
|
+
recommendedTasks.push({
|
|
981
|
+
title: task.title,
|
|
982
|
+
source_spec_id: spec.spec_id || null,
|
|
983
|
+
source_task_id: task.id || null
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
if (recommendedTasks.length === 0) {
|
|
990
|
+
recommendedTasks.push({ title: `Implement capability scope: ${template.name || parsedTemplateId}` });
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
const plan = {
|
|
994
|
+
mode: 'capability-use-plan',
|
|
995
|
+
generated_at: new Date().toISOString(),
|
|
996
|
+
template: {
|
|
997
|
+
id: template.id,
|
|
998
|
+
name: template.name,
|
|
999
|
+
source: template.source,
|
|
1000
|
+
description: template.description,
|
|
1001
|
+
ontology_scope: template.ontology_scope || {}
|
|
1002
|
+
},
|
|
1003
|
+
spec_id: specId,
|
|
1004
|
+
recommended_tasks: recommendedTasks
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
const outputPath = normalizeText(options.out) || buildDefaultUsePlanPath(specId || 'spec', template.id);
|
|
1008
|
+
if (normalizeBoolean(options.write, true)) {
|
|
1009
|
+
await fileSystem.ensureDir(path.dirname(path.join(projectPath, outputPath)));
|
|
1010
|
+
await fileSystem.writeJson(path.join(projectPath, outputPath), plan, { spaces: 2 });
|
|
1011
|
+
plan.output_file = outputPath;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
if (normalizeBoolean(options.apply, false)) {
|
|
1015
|
+
if (!specId) {
|
|
1016
|
+
throw new Error('spec is required for --apply');
|
|
1017
|
+
}
|
|
1018
|
+
plan.apply = await appendCapabilityPlanToSpecTasks({
|
|
1019
|
+
projectPath,
|
|
1020
|
+
spec: specId,
|
|
1021
|
+
sectionTitle: options.sectionTitle
|
|
1022
|
+
}, plan, fileSystem);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
if (!normalizeBoolean(options.json, false)) {
|
|
1026
|
+
console.log(chalk.green('✅ Capability use plan generated'));
|
|
1027
|
+
console.log(chalk.gray(` Template: ${template.id}`));
|
|
1028
|
+
if (specId) {
|
|
1029
|
+
console.log(chalk.gray(` Spec: ${specId}`));
|
|
1030
|
+
}
|
|
1031
|
+
if (plan.output_file) {
|
|
1032
|
+
console.log(chalk.gray(` Output: ${plan.output_file}`));
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
return plan;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
540
1039
|
function registerCapabilityCommands(program) {
|
|
541
1040
|
const capabilityCmd = program
|
|
542
1041
|
.command('capability')
|
|
@@ -623,6 +1122,143 @@ function registerCapabilityCommands(program) {
|
|
|
623
1122
|
tags
|
|
624
1123
|
});
|
|
625
1124
|
});
|
|
1125
|
+
|
|
1126
|
+
const catalogCmd = capabilityCmd
|
|
1127
|
+
.command('catalog')
|
|
1128
|
+
.description('Browse and reuse capability templates');
|
|
1129
|
+
|
|
1130
|
+
catalogCmd
|
|
1131
|
+
.command('list')
|
|
1132
|
+
.description('List capability templates')
|
|
1133
|
+
.option('--source <name>', 'Template source name')
|
|
1134
|
+
.option('--category <name>', 'Template category filter')
|
|
1135
|
+
.option('--compatible-with <semver>', 'SCE version compatibility')
|
|
1136
|
+
.option('--risk <level>', 'Risk level filter')
|
|
1137
|
+
.option('--json', 'Output JSON to stdout')
|
|
1138
|
+
.action(async (options) => {
|
|
1139
|
+
try {
|
|
1140
|
+
const payload = await listCapabilityCatalog({
|
|
1141
|
+
source: options.source,
|
|
1142
|
+
category: options.category,
|
|
1143
|
+
compatibleWith: options.compatibleWith,
|
|
1144
|
+
risk: options.risk,
|
|
1145
|
+
json: options.json
|
|
1146
|
+
});
|
|
1147
|
+
if (options.json) {
|
|
1148
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1149
|
+
}
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
console.log();
|
|
1152
|
+
console.log(chalk.red('❌ Error:'), error.message);
|
|
1153
|
+
if (error instanceof TemplateError && error.suggestions) {
|
|
1154
|
+
console.log();
|
|
1155
|
+
console.log(chalk.yellow('💡 Suggestions:'));
|
|
1156
|
+
error.suggestions.forEach((suggestion) => console.log(` • ${suggestion}`));
|
|
1157
|
+
}
|
|
1158
|
+
process.exit(1);
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
catalogCmd
|
|
1163
|
+
.command('search <keyword>')
|
|
1164
|
+
.description('Search capability templates')
|
|
1165
|
+
.option('--source <name>', 'Template source name')
|
|
1166
|
+
.option('--category <name>', 'Template category filter')
|
|
1167
|
+
.option('--compatible-with <semver>', 'SCE version compatibility')
|
|
1168
|
+
.option('--risk <level>', 'Risk level filter')
|
|
1169
|
+
.option('--json', 'Output JSON to stdout')
|
|
1170
|
+
.action(async (keyword, options) => {
|
|
1171
|
+
try {
|
|
1172
|
+
const payload = await searchCapabilityCatalog(keyword, {
|
|
1173
|
+
source: options.source,
|
|
1174
|
+
category: options.category,
|
|
1175
|
+
compatibleWith: options.compatibleWith,
|
|
1176
|
+
risk: options.risk,
|
|
1177
|
+
json: options.json
|
|
1178
|
+
});
|
|
1179
|
+
if (options.json) {
|
|
1180
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1181
|
+
}
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
console.log();
|
|
1184
|
+
console.log(chalk.red('❌ Error:'), error.message);
|
|
1185
|
+
if (error instanceof TemplateError && error.suggestions) {
|
|
1186
|
+
console.log();
|
|
1187
|
+
console.log(chalk.yellow('💡 Suggestions:'));
|
|
1188
|
+
error.suggestions.forEach((suggestion) => console.log(` • ${suggestion}`));
|
|
1189
|
+
}
|
|
1190
|
+
process.exit(1);
|
|
1191
|
+
}
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
catalogCmd
|
|
1195
|
+
.command('show <template-id>')
|
|
1196
|
+
.description('Show capability template details')
|
|
1197
|
+
.option('--json', 'Output JSON to stdout')
|
|
1198
|
+
.action(async (templateId, options) => {
|
|
1199
|
+
try {
|
|
1200
|
+
const payload = await showCapabilityTemplate(templateId, { json: options.json });
|
|
1201
|
+
if (options.json) {
|
|
1202
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1203
|
+
}
|
|
1204
|
+
} catch (error) {
|
|
1205
|
+
console.log();
|
|
1206
|
+
console.log(chalk.red('❌ Error:'), error.message);
|
|
1207
|
+
if (error instanceof TemplateError && error.suggestions) {
|
|
1208
|
+
console.log();
|
|
1209
|
+
console.log(chalk.yellow('💡 Suggestions:'));
|
|
1210
|
+
error.suggestions.forEach((suggestion) => console.log(` • ${suggestion}`));
|
|
1211
|
+
}
|
|
1212
|
+
process.exit(1);
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
capabilityCmd
|
|
1217
|
+
.command('match')
|
|
1218
|
+
.description('Match capability templates to a spec using ontology scope')
|
|
1219
|
+
.requiredOption('--spec <spec-id>', 'Spec identifier')
|
|
1220
|
+
.option('--query <text>', 'Additional keyword query')
|
|
1221
|
+
.option('--source <name>', 'Template source name')
|
|
1222
|
+
.option('--compatible-with <semver>', 'SCE version compatibility')
|
|
1223
|
+
.option('--risk <level>', 'Risk level filter')
|
|
1224
|
+
.option('--limit <n>', 'Max match results', '10')
|
|
1225
|
+
.option('--strict', 'Fail if domain-chain missing or invalid')
|
|
1226
|
+
.option('--json', 'Output JSON to stdout')
|
|
1227
|
+
.action(async (options) => {
|
|
1228
|
+
try {
|
|
1229
|
+
const payload = await matchCapabilityTemplates(options);
|
|
1230
|
+
if (options.json) {
|
|
1231
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1232
|
+
}
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
console.log();
|
|
1235
|
+
console.log(chalk.red('❌ Error:'), error.message);
|
|
1236
|
+
process.exit(1);
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
capabilityCmd
|
|
1241
|
+
.command('use')
|
|
1242
|
+
.description('Generate a capability usage plan for a spec')
|
|
1243
|
+
.requiredOption('--template <template-id>', 'Capability template identifier')
|
|
1244
|
+
.option('--spec <spec-id>', 'Spec identifier')
|
|
1245
|
+
.option('--out <path>', 'Output JSON path')
|
|
1246
|
+
.option('--apply', 'Append recommended tasks to spec tasks.md')
|
|
1247
|
+
.option('--section-title <title>', 'Custom section title for tasks.md')
|
|
1248
|
+
.option('--no-write', 'Skip writing output file')
|
|
1249
|
+
.option('--json', 'Output JSON to stdout')
|
|
1250
|
+
.action(async (options) => {
|
|
1251
|
+
try {
|
|
1252
|
+
const payload = await useCapabilityTemplate(options);
|
|
1253
|
+
if (options.json) {
|
|
1254
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1255
|
+
}
|
|
1256
|
+
} catch (error) {
|
|
1257
|
+
console.log();
|
|
1258
|
+
console.log(chalk.red('❌ Error:'), error.message);
|
|
1259
|
+
process.exit(1);
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
626
1262
|
}
|
|
627
1263
|
|
|
628
1264
|
module.exports = {
|
package/package.json
CHANGED