scene-capability-engine 3.6.9 → 3.6.10

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 CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.6.10] - 2026-03-05
11
+
12
+ ### Added
13
+ - Capability iteration CLI for scene/spec/task history:
14
+ - `sce capability extract`
15
+ - `sce capability score`
16
+ - `sce capability map`
17
+ - `sce capability register`
18
+ - Extracted capability candidates now include task summaries and scene/spec provenance for UI governance workflows.
19
+ - Added schema references for capability iteration UI contract and ontology mapping.
20
+ - Magicball capability iteration docs:
21
+ - `docs/magicball-capability-iteration-ui.md`
22
+ - `docs/magicball-capability-iteration-api.md`
23
+
10
24
  ## [3.6.9] - 2026-03-05
11
25
 
12
26
  ### Added
package/README.md CHANGED
@@ -218,5 +218,5 @@ MIT. See [LICENSE](LICENSE).
218
218
 
219
219
  ---
220
220
 
221
- **Version**: 3.6.9
221
+ **Version**: 3.6.10
222
222
  **Last Updated**: 2026-03-05
package/README.zh.md CHANGED
@@ -218,5 +218,5 @@ MIT,见 [LICENSE](LICENSE)。
218
218
 
219
219
  ---
220
220
 
221
- **版本**:3.6.9
221
+ **版本**:3.6.10
222
222
  **最后更新**:2026-03-05
@@ -26,6 +26,7 @@ const { registerValueCommands } = require('../lib/commands/value');
26
26
  const { registerTaskCommands } = require('../lib/commands/task');
27
27
  const { registerAuthCommands } = require('../lib/commands/auth');
28
28
  const { registerStateCommands } = require('../lib/commands/state');
29
+ const { registerCapabilityCommands } = require('../lib/commands/capability');
29
30
  const VersionChecker = require('../lib/version/version-checker');
30
31
  const {
31
32
  findLegacyKiroDirectories,
@@ -976,6 +977,7 @@ registerValueCommands(program);
976
977
  registerTaskCommands(program);
977
978
  registerAuthCommands(program);
978
979
  registerStateCommands(program);
980
+ registerCapabilityCommands(program);
979
981
 
980
982
  // Template management commands
981
983
  const templatesCommand = require('../lib/commands/templates');
@@ -0,0 +1,226 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://scene-capability-engine.dev/contracts/capability-iteration-ui.schema.json",
4
+ "title": "Capability Iteration UI Contract",
5
+ "type": "object",
6
+ "required": [
7
+ "candidate",
8
+ "score",
9
+ "mapping",
10
+ "registry"
11
+ ],
12
+ "properties": {
13
+ "candidate": {
14
+ "$ref": "#/definitions/candidate"
15
+ },
16
+ "score": {
17
+ "$ref": "#/definitions/score"
18
+ },
19
+ "mapping": {
20
+ "$ref": "#/definitions/mapping"
21
+ },
22
+ "registry": {
23
+ "$ref": "#/definitions/registry"
24
+ }
25
+ },
26
+ "definitions": {
27
+ "candidate": {
28
+ "type": "object",
29
+ "required": [
30
+ "mode",
31
+ "scene_id",
32
+ "generated_at",
33
+ "source",
34
+ "specs",
35
+ "summary"
36
+ ],
37
+ "properties": {
38
+ "mode": {
39
+ "const": "capability-extract"
40
+ },
41
+ "scene_id": { "type": "string" },
42
+ "generated_at": { "type": "string" },
43
+ "output_file": { "type": "string" },
44
+ "source": {
45
+ "type": "object",
46
+ "required": ["scene_index_source", "spec_count"],
47
+ "properties": {
48
+ "scene_index_source": { "type": "string" },
49
+ "spec_count": { "type": "integer", "minimum": 0 }
50
+ },
51
+ "additionalProperties": false
52
+ },
53
+ "specs": {
54
+ "type": "array",
55
+ "items": {
56
+ "type": "object",
57
+ "required": ["spec_id", "tasks_path", "task_summary"],
58
+ "properties": {
59
+ "spec_id": { "type": "string" },
60
+ "tasks_path": { "type": "string" },
61
+ "task_error": { "type": "string" },
62
+ "task_summary": {
63
+ "type": "object",
64
+ "required": [
65
+ "total",
66
+ "completed",
67
+ "in_progress",
68
+ "queued",
69
+ "not_started",
70
+ "unknown"
71
+ ],
72
+ "properties": {
73
+ "total": { "type": "integer", "minimum": 0 },
74
+ "completed": { "type": "integer", "minimum": 0 },
75
+ "in_progress": { "type": "integer", "minimum": 0 },
76
+ "queued": { "type": "integer", "minimum": 0 },
77
+ "not_started": { "type": "integer", "minimum": 0 },
78
+ "unknown": { "type": "integer", "minimum": 0 }
79
+ },
80
+ "additionalProperties": false
81
+ },
82
+ "task_sample": {
83
+ "type": "array",
84
+ "items": {
85
+ "type": "object",
86
+ "required": ["id", "title", "status"],
87
+ "properties": {
88
+ "id": { "type": "string" },
89
+ "title": { "type": "string" },
90
+ "status": { "type": "string" }
91
+ },
92
+ "additionalProperties": false
93
+ }
94
+ }
95
+ },
96
+ "additionalProperties": false
97
+ }
98
+ },
99
+ "summary": {
100
+ "type": "object",
101
+ "required": ["spec_count", "task_total", "task_completed", "task_pending"],
102
+ "properties": {
103
+ "spec_count": { "type": "integer", "minimum": 0 },
104
+ "task_total": { "type": "integer", "minimum": 0 },
105
+ "task_completed": { "type": "integer", "minimum": 0 },
106
+ "task_pending": { "type": "integer", "minimum": 0 }
107
+ },
108
+ "additionalProperties": false
109
+ }
110
+ },
111
+ "additionalProperties": false
112
+ },
113
+ "score": {
114
+ "type": "object",
115
+ "required": [
116
+ "mode",
117
+ "scene_id",
118
+ "generated_at",
119
+ "input",
120
+ "scores"
121
+ ],
122
+ "properties": {
123
+ "mode": { "const": "capability-score" },
124
+ "scene_id": { "type": "string" },
125
+ "generated_at": { "type": "string" },
126
+ "input": { "type": "string" },
127
+ "output_file": { "type": "string" },
128
+ "scores": {
129
+ "type": "object",
130
+ "required": [
131
+ "completion_rate",
132
+ "reuse_score",
133
+ "stability_score",
134
+ "risk_score",
135
+ "value_score"
136
+ ],
137
+ "properties": {
138
+ "completion_rate": { "type": "number", "minimum": 0, "maximum": 1 },
139
+ "reuse_score": { "type": "integer", "minimum": 0, "maximum": 100 },
140
+ "stability_score": { "type": "integer", "minimum": 0, "maximum": 100 },
141
+ "risk_score": { "type": "integer", "minimum": 0, "maximum": 100 },
142
+ "value_score": { "type": "integer", "minimum": 0, "maximum": 100 }
143
+ },
144
+ "additionalProperties": false
145
+ },
146
+ "summary": { "type": ["object", "null"] }
147
+ },
148
+ "additionalProperties": false
149
+ },
150
+ "mapping": {
151
+ "type": "object",
152
+ "required": [
153
+ "mode",
154
+ "scene_id",
155
+ "generated_at",
156
+ "input",
157
+ "template"
158
+ ],
159
+ "properties": {
160
+ "mode": { "const": "capability-map" },
161
+ "scene_id": { "type": "string" },
162
+ "generated_at": { "type": "string" },
163
+ "input": { "type": "string" },
164
+ "mapping": { "type": ["string", "null"] },
165
+ "output_file": { "type": "string" },
166
+ "template": {
167
+ "type": "object",
168
+ "required": [
169
+ "mode",
170
+ "template_id",
171
+ "name",
172
+ "description",
173
+ "category",
174
+ "template_type",
175
+ "scene_id",
176
+ "ontology_scope",
177
+ "created_at"
178
+ ],
179
+ "properties": {
180
+ "mode": { "const": "capability-template" },
181
+ "template_id": { "type": "string" },
182
+ "name": { "type": "string" },
183
+ "description": { "type": "string" },
184
+ "category": { "type": "string" },
185
+ "template_type": { "const": "capability-template" },
186
+ "scene_id": { "type": "string" },
187
+ "tags": { "type": "array", "items": { "type": "string" } },
188
+ "ontology_scope": {
189
+ "type": "object",
190
+ "required": ["domains", "entities", "relations", "business_rules", "decisions"],
191
+ "properties": {
192
+ "domains": { "type": "array", "items": { "type": "string" } },
193
+ "entities": { "type": "array", "items": { "type": "string" } },
194
+ "relations": { "type": "array", "items": { "type": "string" } },
195
+ "business_rules": { "type": "array", "items": { "type": "string" } },
196
+ "decisions": { "type": "array", "items": { "type": "string" } }
197
+ },
198
+ "additionalProperties": false
199
+ },
200
+ "created_at": { "type": "string" },
201
+ "source_candidate": { "type": ["object", "null"] }
202
+ },
203
+ "additionalProperties": false
204
+ }
205
+ },
206
+ "additionalProperties": false
207
+ },
208
+ "registry": {
209
+ "type": "object",
210
+ "required": [
211
+ "mode",
212
+ "template_id",
213
+ "output_dir",
214
+ "files"
215
+ ],
216
+ "properties": {
217
+ "mode": { "const": "capability-register" },
218
+ "template_id": { "type": "string" },
219
+ "output_dir": { "type": "string" },
220
+ "files": { "type": "array", "items": { "type": "string" } }
221
+ },
222
+ "additionalProperties": false
223
+ }
224
+ },
225
+ "additionalProperties": false
226
+ }
@@ -1847,6 +1847,27 @@ sce scene template-render --package <name> --values <json-or-path> --out <dir>
1847
1847
  sce scene template-render --package scene-erp --values '{"entity_name":"Order"}' --out ./output --json
1848
1848
  ```
1849
1849
 
1850
+ ### Capability Iteration (scene -> template -> ontology)
1851
+
1852
+ ```bash
1853
+ # 1) Extract capability candidate from scene history
1854
+ sce capability extract --scene scene.customer-order --json
1855
+
1856
+ # 2) Score candidate value/reuse/risk
1857
+ sce capability score --input .sce/reports/capability-iteration/scene.customer-order.candidate.json --json
1858
+
1859
+ # 3) Attach ontology mapping
1860
+ sce capability map --input .sce/reports/capability-iteration/scene.customer-order.candidate.json \
1861
+ --mapping .sce/ontology/capability-mapping.json --json
1862
+
1863
+ # 4) Export registry-ready capability template package
1864
+ sce capability register --input .sce/reports/capability-iteration/scene.customer-order.template.json --json
1865
+ ```
1866
+
1867
+ Schema references:
1868
+ - UI contract: `docs/agent-runtime/capability-iteration-ui.schema.json`
1869
+ - Ontology mapping: `docs/ontology/capability-mapping.schema.json`
1870
+
1850
1871
  ### Scene Package Batch Publish
1851
1872
 
1852
1873
  ```bash
@@ -0,0 +1,154 @@
1
+ # Magicball 能力迭代 API 封装建议(基于 SCE CLI)
2
+
3
+ > 目标:把 SCE CLI 统一封装成 Magicball 内部 API,方便前端用标准 JSON 调用。
4
+
5
+ ---
6
+
7
+ ## 1. API 设计原则
8
+
9
+ - **一处封装**:统一执行 CLI,屏蔽差异
10
+ - **JSON 输出**:所有调用带 `--json`
11
+ - **可回放**:每一步的输出落盘并在 UI 可复用
12
+ - **错误可读**:将 stderr 错误结构化返回
13
+
14
+ ---
15
+
16
+ ## 2. 建议 API 列表
17
+
18
+ ### 2.1 Extract
19
+
20
+ ```
21
+ POST /api/capability/extract
22
+ ```
23
+
24
+ 请求:
25
+ ```json
26
+ {
27
+ "scene_id": "scene.customer-order",
28
+ "specs": ["01-00-order", "01-01-inventory"],
29
+ "sample_limit": 5
30
+ }
31
+ ```
32
+
33
+ 执行 CLI:
34
+ ```bash
35
+ sce capability extract --scene <scene_id> --specs <specs> --sample-limit <n> --json
36
+ ```
37
+
38
+ 响应:
39
+ - 返回 `capability-extract` payload
40
+
41
+ ---
42
+
43
+ ### 2.2 Score
44
+
45
+ ```
46
+ POST /api/capability/score
47
+ ```
48
+
49
+ 请求:
50
+ ```json
51
+ {
52
+ "candidate_file": ".sce/reports/capability-iteration/scene.customer-order.candidate.json"
53
+ }
54
+ ```
55
+
56
+ 执行 CLI:
57
+ ```bash
58
+ sce capability score --input <candidate_file> --json
59
+ ```
60
+
61
+ 响应:
62
+ - 返回 `capability-score` payload
63
+
64
+ ---
65
+
66
+ ### 2.3 Map
67
+
68
+ ```
69
+ POST /api/capability/map
70
+ ```
71
+
72
+ 请求:
73
+ ```json
74
+ {
75
+ "candidate_file": ".sce/reports/capability-iteration/scene.customer-order.candidate.json",
76
+ "ontology_file": ".sce/ontology/capability-mapping.json",
77
+ "template_id": "scene.customer-order",
78
+ "name": "Capability template: scene.customer-order",
79
+ "description": "Derived from scene.customer-order"
80
+ }
81
+ ```
82
+
83
+ 执行 CLI:
84
+ ```bash
85
+ sce capability map --input <candidate_file> --mapping <ontology_file> \
86
+ --template-id <template_id> --name "<name>" --description "<desc>" --json
87
+ ```
88
+
89
+ 响应:
90
+ - 返回 `capability-map` payload
91
+
92
+ ---
93
+
94
+ ### 2.4 Register
95
+
96
+ ```
97
+ POST /api/capability/register
98
+ ```
99
+
100
+ 请求:
101
+ ```json
102
+ {
103
+ "template_file": ".sce/reports/capability-iteration/scene.customer-order.template.json",
104
+ "risk_level": "medium",
105
+ "difficulty": "intermediate"
106
+ }
107
+ ```
108
+
109
+ 执行 CLI:
110
+ ```bash
111
+ sce capability register --input <template_file> --risk-level <level> --difficulty <level> --json
112
+ ```
113
+
114
+ 响应:
115
+ - 返回 `capability-register` payload
116
+
117
+ ---
118
+
119
+ ## 3. 通用返回结构(建议)
120
+
121
+ ```json
122
+ {
123
+ "success": true,
124
+ "data": { ...sce_payload },
125
+ "stderr": null
126
+ }
127
+ ```
128
+
129
+ 失败:
130
+ ```json
131
+ {
132
+ "success": false,
133
+ "data": null,
134
+ "stderr": "error message from sce"
135
+ }
136
+ ```
137
+
138
+ ---
139
+
140
+ ## 4. 前端调用建议
141
+
142
+ - 每一步 UI 都展示对应 `data.output_file`(如有)
143
+ - 支持“重新执行”按钮(调用同一 API)
144
+ - 失败时提示 `stderr`,并保留上一步数据可继续
145
+
146
+ ---
147
+
148
+ ## 5. 推荐顺序
149
+
150
+ 1. `/api/capability/extract`
151
+ 2. `/api/capability/score`
152
+ 3. `/api/capability/map`
153
+ 4. `/api/capability/register`
154
+
@@ -0,0 +1,172 @@
1
+ # Magicball 能力迭代管理(SCE 对接说明)
2
+
3
+ > 适用于:Magicball AI 助手页面新增顶部图标入口,内部用页签切换。
4
+ > 目标:从历史 `scene/spec/task` 中提炼可复用能力模板,并完成本体映射,形成可发布的能力资产。
5
+
6
+ ---
7
+
8
+ ## 1. 目标与范围
9
+
10
+ - 目标:从历史 `scene/spec/task` 中提炼能力模板,并完成 ontology 映射闭环。
11
+ - 范围:Magicball UI 新增「能力迭代」入口(顶部图标),内部用页签完成全流程。
12
+
13
+ ---
14
+
15
+ ## 2. UI 草图(文字版)
16
+
17
+ ### 2.1 能力迭代首页(Scene 盘点)
18
+ ```
19
+ ┌─────────────────────────────────────────────────────────────┐
20
+ │ 能力迭代 │
21
+ │ [筛选:时间 | 完成率 | 风险] [搜索:scene id] │
22
+ ├─────────────────────────────────────────────────────────────┤
23
+ │ Scene卡片:scene.customer-order │
24
+ │ - spec: 3 tasks: 42 completed: 36 pending: 6 │
25
+ │ - score: 未评估 │
26
+ │ [进入评估] │
27
+ ├─────────────────────────────────────────────────────────────┤
28
+ │ Scene卡片:scene.inventory-reconcile │
29
+ │ - spec: 2 tasks: 18 completed: 18 pending: 0 │
30
+ │ [进入评估] │
31
+ └─────────────────────────────────────────────────────────────┘
32
+ ```
33
+
34
+ ### 2.2 评估页(Spec/Task + 评分卡)
35
+ ```
36
+ ┌───────────────┬─────────────────────────────────────────────┐
37
+ │ Spec列表 │ 评分卡 │
38
+ │ - 01-00-demo │ value: 78 reuse: 66 stability: 85 risk: 20 │
39
+ │ - 01-01-check │ completion: 0.86 │
40
+ │ - 01-02-order │ │
41
+ ├───────────────┼─────────────────────────────────────────────┤
42
+ │ Task摘要 │ [生成模板候选] │
43
+ │ total: 42 │ │
44
+ │ completed:36 │ │
45
+ │ pending: 6 │ │
46
+ └───────────────┴─────────────────────────────────────────────┘
47
+ ```
48
+
49
+ ### 2.3 模板构建页(元信息 + 本体映射)
50
+ ```
51
+ ┌─────────────────────────────────────────────────────────────┐
52
+ │ 模板信息 │
53
+ │ name: [Capability template: scene.customer-order] │
54
+ │ desc: [模板描述 ...] │
55
+ │ tags: [order, customer, inventory] │
56
+ ├─────────────────────────────────────────────────────────────┤
57
+ │ 本体映射 │
58
+ │ domains: [commerce] │
59
+ │ entities: [Order, Customer] │
60
+ │ relations: [Order->Customer] │
61
+ │ business_rules: [OrderApproval] │
62
+ │ decisions: [RiskPolicy] │
63
+ ├─────────────────────────────────────────────────────────────┤
64
+ │ [保存映射] [进入发布] │
65
+ └─────────────────────────────────────────────────────────────┘
66
+ ```
67
+
68
+ ### 2.4 发布页(模板包生成)
69
+ ```
70
+ ┌─────────────────────────────────────────────────────────────┐
71
+ │ 发布结果 │
72
+ │ template_id: scene.customer-order │
73
+ │ output_dir: .sce/templates/exports/capability-scene_customer… │
74
+ │ files: │
75
+ │ - capability-template.json │
76
+ │ - template-registry.json │
77
+ │ [复制路径] [推送模板库] │
78
+ └─────────────────────────────────────────────────────────────┘
79
+ ```
80
+
81
+ ---
82
+
83
+ ## 3. 推荐 UI 架构(顶部图标 + 页签)
84
+
85
+ 顶部图标入口:`能力迭代`
86
+
87
+ 内部页签建议:
88
+ 1. `Scene 盘点`
89
+ 2. `评估`
90
+ 3. `模板构建`
91
+ 4. `发布`
92
+
93
+ 状态机建议:
94
+ `extract -> score -> map -> register`
95
+
96
+ ---
97
+
98
+ ## 4. SCE 接口参数(CLI 可封装)
99
+
100
+ ### 4.1 提取候选能力
101
+ ```bash
102
+ sce capability extract --scene <sceneId> --json
103
+ ```
104
+ 可选参数:
105
+ - `--specs <spec-id, spec-id>`
106
+ - `--out <path>`
107
+ - `--sample-limit <n>`
108
+
109
+ ### 4.2 评分
110
+ ```bash
111
+ sce capability score --input <candidate.json> --json
112
+ ```
113
+
114
+ ### 4.3 本体映射
115
+ ```bash
116
+ sce capability map --input <candidate.json> --mapping <ontology.json> --json
117
+ ```
118
+ 可选参数:
119
+ - `--template-id <id>`
120
+ - `--name <name>`
121
+ - `--description <desc>`
122
+ - `--category <category>`
123
+ - `--tags <tag1,tag2>`
124
+
125
+ ### 4.4 发布模板包
126
+ ```bash
127
+ sce capability register --input <template.json> --json
128
+ ```
129
+ 可选参数:
130
+ - `--out <dir>`
131
+ - `--risk-level <low|medium|high|critical>`
132
+ - `--difficulty <beginner|intermediate|advanced>`
133
+ - `--tags <tag1,tag2>`
134
+ - `--applicable-scenarios <scene1,scene2>`
135
+
136
+ ---
137
+
138
+ ## 5. 数据契约(前端对接)
139
+
140
+ - UI 契约:`docs/agent-runtime/capability-iteration-ui.schema.json`
141
+ - 本体映射 schema:`docs/ontology/capability-mapping.schema.json`
142
+
143
+ ---
144
+
145
+ ## 6. 前端适配建议
146
+
147
+ ### 6.1 状态管理
148
+ - 采用步骤式状态机:`extract -> score -> map -> register`
149
+ - 每一步输出的 JSON 都持久化,便于回放/复用
150
+
151
+ ### 6.2 体验优化
152
+ - Scene 首页显示完成率、待处理数、评分卡入口
153
+ - 评分卡统一可视化(value/reuse/stability/risk)
154
+ - 本体映射表单应支持快速导入与默认推荐值
155
+
156
+ ### 6.3 错误处理
157
+ - CLI 失败时直接展示错误原因
158
+ - `task_error` 存在时提示 spec 任务文件缺失或解析失败
159
+ - 保留上一步产物可继续重试
160
+
161
+ ### 6.4 权限与治理
162
+ - 若涉及写入本体或发布模板,建议通过 SCE auth lease 授权
163
+
164
+ ---
165
+
166
+ ## 7. 推荐路由
167
+
168
+ - `/capability` Scene 首页
169
+ - `/capability/scene/:sceneId` 评估页
170
+ - `/capability/scene/:sceneId/template` 模板构建页
171
+ - `/capability/scene/:sceneId/release` 发布页
172
+
@@ -0,0 +1,54 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://scene-capability-engine.dev/contracts/capability-mapping.schema.json",
4
+ "title": "Capability Ontology Mapping",
5
+ "type": "object",
6
+ "required": [
7
+ "ontology_scope"
8
+ ],
9
+ "properties": {
10
+ "ontology_scope": {
11
+ "type": "object",
12
+ "required": [
13
+ "domains",
14
+ "entities",
15
+ "relations",
16
+ "business_rules",
17
+ "decisions"
18
+ ],
19
+ "properties": {
20
+ "domains": {
21
+ "type": "array",
22
+ "items": { "type": "string" }
23
+ },
24
+ "entities": {
25
+ "type": "array",
26
+ "items": { "type": "string" }
27
+ },
28
+ "relations": {
29
+ "type": "array",
30
+ "items": { "type": "string" }
31
+ },
32
+ "business_rules": {
33
+ "type": "array",
34
+ "items": { "type": "string" }
35
+ },
36
+ "decisions": {
37
+ "type": "array",
38
+ "items": { "type": "string" }
39
+ }
40
+ },
41
+ "additionalProperties": false
42
+ },
43
+ "notes": {
44
+ "type": "string"
45
+ },
46
+ "source": {
47
+ "type": "string"
48
+ },
49
+ "version": {
50
+ "type": "string"
51
+ }
52
+ },
53
+ "additionalProperties": false
54
+ }
@@ -0,0 +1,634 @@
1
+ /**
2
+ * Capability Iteration Commands
3
+ *
4
+ * Extracts capability candidates from scene/spec/task history,
5
+ * scores candidates, maps them to ontology scope, and exports
6
+ * registry-ready capability template packages.
7
+ */
8
+
9
+ const fs = require('fs-extra');
10
+ const path = require('path');
11
+ const chalk = require('chalk');
12
+ const TaskClaimer = require('../task/task-claimer');
13
+ const { runStudioSpecGovernance } = require('../studio/spec-intake-governor');
14
+ const { SceStateStore } = require('../state/sce-state-store');
15
+ const packageJson = require('../../package.json');
16
+
17
+ const DEFAULT_ITERATION_DIR = '.sce/reports/capability-iteration';
18
+ const DEFAULT_EXPORT_ROOT = '.sce/templates/exports';
19
+
20
+ function normalizeText(value) {
21
+ if (typeof value !== 'string') {
22
+ return '';
23
+ }
24
+ return value.trim();
25
+ }
26
+
27
+ function normalizeStringArray(value) {
28
+ if (!Array.isArray(value)) {
29
+ return [];
30
+ }
31
+ return value.map((item) => normalizeText(item)).filter(Boolean);
32
+ }
33
+
34
+ function normalizeBoolean(value, fallback = false) {
35
+ if (typeof value === 'boolean') {
36
+ return value;
37
+ }
38
+ const normalized = normalizeText(`${value || ''}`).toLowerCase();
39
+ if (!normalized) {
40
+ return fallback;
41
+ }
42
+ if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) {
43
+ return true;
44
+ }
45
+ if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) {
46
+ return false;
47
+ }
48
+ return fallback;
49
+ }
50
+
51
+ function toPositiveInteger(value, fallback) {
52
+ const parsed = Number.parseInt(`${value}`, 10);
53
+ if (!Number.isFinite(parsed) || parsed <= 0) {
54
+ return fallback;
55
+ }
56
+ return parsed;
57
+ }
58
+
59
+ function buildDefaultCandidatePath(sceneId) {
60
+ const safeScene = normalizeText(sceneId).replace(/[^\w.-]+/g, '_') || 'scene';
61
+ return path.join(DEFAULT_ITERATION_DIR, `${safeScene}.candidate.json`);
62
+ }
63
+
64
+ function buildDefaultScorePath(sceneId) {
65
+ const safeScene = normalizeText(sceneId).replace(/[^\w.-]+/g, '_') || 'scene';
66
+ return path.join(DEFAULT_ITERATION_DIR, `${safeScene}.score.json`);
67
+ }
68
+
69
+ function buildDefaultTemplatePath(sceneId) {
70
+ const safeScene = normalizeText(sceneId).replace(/[^\w.-]+/g, '_') || 'scene';
71
+ return path.join(DEFAULT_ITERATION_DIR, `${safeScene}.template.json`);
72
+ }
73
+
74
+ function buildDefaultExportDir(templateId) {
75
+ const safeId = normalizeText(templateId).replace(/[^\w.-]+/g, '_') || 'capability';
76
+ return path.join(DEFAULT_EXPORT_ROOT, `capability-${safeId}`);
77
+ }
78
+
79
+ function buildSceneIdFromCandidate(candidate) {
80
+ return normalizeText(candidate && candidate.scene_id) || 'scene.unknown';
81
+ }
82
+
83
+ async function loadSceneIndexFromFile(projectPath, fileSystem) {
84
+ const indexPath = path.join(projectPath, '.sce', 'spec-governance', 'scene-index.json');
85
+ if (!await fileSystem.pathExists(indexPath)) {
86
+ return null;
87
+ }
88
+ try {
89
+ const data = await fileSystem.readJson(indexPath);
90
+ return {
91
+ source: indexPath,
92
+ data
93
+ };
94
+ } catch (_error) {
95
+ return null;
96
+ }
97
+ }
98
+
99
+ async function loadSceneIndexFromState(projectPath, fileSystem, env) {
100
+ try {
101
+ const stateStore = new SceStateStore(projectPath, {
102
+ fileSystem,
103
+ env
104
+ });
105
+ const records = await stateStore.listGovernanceSceneIndexRecords({ limit: 500 });
106
+ if (!Array.isArray(records)) {
107
+ return null;
108
+ }
109
+ const scenes = {};
110
+ for (const record of records) {
111
+ if (!record || !record.scene_id) {
112
+ continue;
113
+ }
114
+ scenes[record.scene_id] = {
115
+ total_specs: record.total_specs,
116
+ active_specs: record.active_specs,
117
+ completed_specs: record.completed_specs,
118
+ stale_specs: record.stale_specs,
119
+ spec_ids: Array.isArray(record.spec_ids) ? record.spec_ids : [],
120
+ active_spec_ids: Array.isArray(record.active_spec_ids) ? record.active_spec_ids : [],
121
+ stale_spec_ids: Array.isArray(record.stale_spec_ids) ? record.stale_spec_ids : []
122
+ };
123
+ }
124
+ return {
125
+ source: 'sqlite:governance_scene_index_registry',
126
+ data: {
127
+ schema_version: '1.0',
128
+ generated_at: new Date().toISOString(),
129
+ scene_filter: null,
130
+ scenes
131
+ }
132
+ };
133
+ } catch (_error) {
134
+ return null;
135
+ }
136
+ }
137
+
138
+ async function resolveSceneSpecs(sceneId, options, dependencies) {
139
+ const projectPath = dependencies.projectPath || process.cwd();
140
+ const fileSystem = dependencies.fileSystem || fs;
141
+ const env = dependencies.env || process.env;
142
+
143
+ const explicitSpecs = normalizeStringArray(options && options.specs);
144
+ if (explicitSpecs.length > 0) {
145
+ return {
146
+ scene_id: sceneId,
147
+ spec_ids: explicitSpecs,
148
+ source: 'options.specs'
149
+ };
150
+ }
151
+
152
+ const indexFile = await loadSceneIndexFromFile(projectPath, fileSystem);
153
+ if (indexFile && indexFile.data && indexFile.data.scenes && indexFile.data.scenes[sceneId]) {
154
+ const record = indexFile.data.scenes[sceneId];
155
+ return {
156
+ scene_id: sceneId,
157
+ spec_ids: Array.isArray(record.spec_ids) ? record.spec_ids : [],
158
+ source: indexFile.source
159
+ };
160
+ }
161
+
162
+ const indexState = await loadSceneIndexFromState(projectPath, fileSystem, env);
163
+ if (indexState && indexState.data && indexState.data.scenes && indexState.data.scenes[sceneId]) {
164
+ const record = indexState.data.scenes[sceneId];
165
+ return {
166
+ scene_id: sceneId,
167
+ spec_ids: Array.isArray(record.spec_ids) ? record.spec_ids : [],
168
+ source: indexState.source
169
+ };
170
+ }
171
+
172
+ const governanceReport = await runStudioSpecGovernance({
173
+ apply: false,
174
+ scene: sceneId
175
+ }, {
176
+ projectPath,
177
+ fileSystem
178
+ });
179
+ if (governanceReport && Array.isArray(governanceReport.scenes)) {
180
+ const target = governanceReport.scenes.find((scene) => normalizeText(scene.scene_id) === sceneId);
181
+ if (target) {
182
+ return {
183
+ scene_id: sceneId,
184
+ spec_ids: Array.isArray(target.specs)
185
+ ? target.specs.map((item) => normalizeText(item.spec_id)).filter(Boolean)
186
+ : [],
187
+ source: 'studio-spec-governance'
188
+ };
189
+ }
190
+ }
191
+
192
+ return {
193
+ scene_id: sceneId,
194
+ spec_ids: [],
195
+ source: 'unknown'
196
+ };
197
+ }
198
+
199
+ function summarizeTasks(tasks) {
200
+ const summary = {
201
+ total: 0,
202
+ completed: 0,
203
+ in_progress: 0,
204
+ queued: 0,
205
+ not_started: 0,
206
+ unknown: 0
207
+ };
208
+
209
+ if (!Array.isArray(tasks)) {
210
+ return summary;
211
+ }
212
+
213
+ summary.total = tasks.length;
214
+ tasks.forEach((task) => {
215
+ const status = normalizeText(task && task.status);
216
+ if (status === 'completed') {
217
+ summary.completed += 1;
218
+ } else if (status === 'in-progress') {
219
+ summary.in_progress += 1;
220
+ } else if (status === 'queued') {
221
+ summary.queued += 1;
222
+ } else if (status === 'not-started') {
223
+ summary.not_started += 1;
224
+ } else {
225
+ summary.unknown += 1;
226
+ }
227
+ });
228
+
229
+ return summary;
230
+ }
231
+
232
+ function buildCandidateSummary(specs) {
233
+ const summary = {
234
+ spec_count: specs.length,
235
+ task_total: 0,
236
+ task_completed: 0,
237
+ task_pending: 0
238
+ };
239
+ specs.forEach((spec) => {
240
+ const taskSummary = spec.task_summary || {};
241
+ summary.task_total += Number(taskSummary.total || 0);
242
+ summary.task_completed += Number(taskSummary.completed || 0);
243
+ });
244
+ summary.task_pending = Math.max(0, summary.task_total - summary.task_completed);
245
+ return summary;
246
+ }
247
+
248
+ function buildScoreFromCandidate(candidate) {
249
+ const summary = candidate && candidate.summary ? candidate.summary : {};
250
+ const taskTotal = Number(summary.task_total || 0);
251
+ const taskCompleted = Number(summary.task_completed || 0);
252
+ const specCount = Number(summary.spec_count || 0);
253
+ const completionRate = taskTotal > 0 ? taskCompleted / taskTotal : 0;
254
+ const reuseScore = Math.min(100, Math.round((specCount / 3) * 100));
255
+ const stabilityScore = Math.round(completionRate * 100);
256
+ const riskScore = Math.min(100, Math.round((1 - completionRate) * 100));
257
+ const valueScore = Math.round((stabilityScore * 0.5) + (reuseScore * 0.3) + ((100 - riskScore) * 0.2));
258
+
259
+ return {
260
+ completion_rate: Number(completionRate.toFixed(3)),
261
+ reuse_score: reuseScore,
262
+ stability_score: stabilityScore,
263
+ risk_score: riskScore,
264
+ value_score: valueScore
265
+ };
266
+ }
267
+
268
+ function buildTemplateCandidate(candidate, mapping, options) {
269
+ const sceneId = buildSceneIdFromCandidate(candidate);
270
+ const templateId = normalizeText(options && options.template_id)
271
+ || normalizeText(options && options.id)
272
+ || sceneId.replace(/[^\w.-]+/g, '_');
273
+ const name = normalizeText(options && options.name)
274
+ || `Capability template: ${sceneId}`;
275
+ const description = normalizeText(options && options.description)
276
+ || `Capability template derived from ${sceneId}`;
277
+ const category = normalizeText(options && options.category) || 'capability';
278
+ const tags = normalizeStringArray(options && options.tags);
279
+ const ontologyScope = (mapping && mapping.ontology_scope && typeof mapping.ontology_scope === 'object')
280
+ ? mapping.ontology_scope
281
+ : {
282
+ domains: [],
283
+ entities: [],
284
+ relations: [],
285
+ business_rules: [],
286
+ decisions: []
287
+ };
288
+
289
+ return {
290
+ mode: 'capability-template',
291
+ template_id: templateId,
292
+ name,
293
+ description,
294
+ category,
295
+ template_type: 'capability-template',
296
+ scene_id: sceneId,
297
+ source_candidate: candidate,
298
+ ontology_scope: ontologyScope,
299
+ tags,
300
+ created_at: new Date().toISOString()
301
+ };
302
+ }
303
+
304
+ function buildRegistryEntry(templateCandidate, options) {
305
+ const riskLevel = normalizeText(options && options.risk_level) || 'medium';
306
+ const difficulty = normalizeText(options && options.difficulty) || 'intermediate';
307
+ const applicable = normalizeStringArray(options && options.applicable_scenarios);
308
+ const tags = normalizeStringArray(options && options.tags);
309
+ const sceneId = buildSceneIdFromCandidate(templateCandidate);
310
+ const safeTags = tags.length > 0 ? tags : ['capability', sceneId];
311
+ const safeApplicable = applicable.length > 0 ? applicable : [sceneId];
312
+
313
+ return {
314
+ id: templateCandidate.template_id,
315
+ name: templateCandidate.name,
316
+ category: templateCandidate.category,
317
+ description: templateCandidate.description,
318
+ difficulty,
319
+ tags: safeTags,
320
+ applicable_scenarios: safeApplicable,
321
+ files: ['capability-template.json'],
322
+ template_type: 'capability-template',
323
+ min_sce_version: packageJson.version,
324
+ max_sce_version: null,
325
+ risk_level: riskLevel,
326
+ rollback_contract: {
327
+ supported: false,
328
+ strategy: 'n/a'
329
+ },
330
+ ontology_scope: templateCandidate.ontology_scope
331
+ };
332
+ }
333
+
334
+ async function runCapabilityExtractCommand(options = {}, dependencies = {}) {
335
+ const projectPath = dependencies.projectPath || process.cwd();
336
+ const fileSystem = dependencies.fileSystem || fs;
337
+ const env = dependencies.env || process.env;
338
+ const sceneId = normalizeText(options.scene || options.sceneId || options.scene_id);
339
+ const writeOutput = normalizeBoolean(options.write, true);
340
+
341
+ if (!sceneId) {
342
+ throw new Error('scene is required for capability extract');
343
+ }
344
+
345
+ const specResolution = await resolveSceneSpecs(sceneId, {
346
+ specs: options.specs
347
+ }, { projectPath, fileSystem, env });
348
+ const specIds = Array.isArray(specResolution.spec_ids) ? specResolution.spec_ids : [];
349
+
350
+ const taskClaimer = new TaskClaimer();
351
+ const specs = [];
352
+
353
+ for (const specId of specIds) {
354
+ const tasksPath = path.join(projectPath, '.sce', 'specs', specId, 'tasks.md');
355
+ let tasks = [];
356
+ let taskError = null;
357
+ if (await fileSystem.pathExists(tasksPath)) {
358
+ try {
359
+ tasks = await taskClaimer.parseTasks(tasksPath, { preferStatusMarkers: true });
360
+ } catch (error) {
361
+ taskError = error.message;
362
+ }
363
+ } else {
364
+ taskError = 'tasks.md missing';
365
+ }
366
+ const taskSummary = summarizeTasks(tasks);
367
+ specs.push({
368
+ spec_id: specId,
369
+ tasks_path: path.relative(projectPath, tasksPath),
370
+ task_summary: taskSummary,
371
+ task_sample: tasks.slice(0, toPositiveInteger(options.sample_limit, 5)).map((task) => ({
372
+ id: task.taskId,
373
+ title: task.title,
374
+ status: task.status
375
+ })),
376
+ task_error: taskError
377
+ });
378
+ }
379
+
380
+ const payload = {
381
+ mode: 'capability-extract',
382
+ scene_id: sceneId,
383
+ generated_at: new Date().toISOString(),
384
+ source: {
385
+ scene_index_source: specResolution.source || 'unknown',
386
+ spec_count: specIds.length
387
+ },
388
+ specs,
389
+ summary: buildCandidateSummary(specs)
390
+ };
391
+
392
+ const outputPath = normalizeText(options.out) || buildDefaultCandidatePath(sceneId);
393
+ if (writeOutput) {
394
+ await fileSystem.ensureDir(path.dirname(path.join(projectPath, outputPath)));
395
+ await fileSystem.writeJson(path.join(projectPath, outputPath), payload, { spaces: 2 });
396
+ payload.output_file = outputPath;
397
+ }
398
+
399
+ if (!normalizeBoolean(options.json, false)) {
400
+ console.log(chalk.green('✅ Capability candidate extracted'));
401
+ console.log(chalk.gray(` Scene: ${sceneId}`));
402
+ console.log(chalk.gray(` Specs: ${payload.summary.spec_count}`));
403
+ console.log(chalk.gray(` Tasks: ${payload.summary.task_total}`));
404
+ if (payload.output_file) {
405
+ console.log(chalk.gray(` Output: ${payload.output_file}`));
406
+ }
407
+ }
408
+
409
+ return payload;
410
+ }
411
+
412
+ async function runCapabilityScoreCommand(options = {}, dependencies = {}) {
413
+ const projectPath = dependencies.projectPath || process.cwd();
414
+ const fileSystem = dependencies.fileSystem || fs;
415
+ const inputPath = normalizeText(options.input || options.file);
416
+ if (!inputPath) {
417
+ throw new Error('input candidate file is required for capability score');
418
+ }
419
+
420
+ const candidate = await fileSystem.readJson(path.join(projectPath, inputPath));
421
+ const sceneId = buildSceneIdFromCandidate(candidate);
422
+ const scores = buildScoreFromCandidate(candidate);
423
+ const payload = {
424
+ mode: 'capability-score',
425
+ scene_id: sceneId,
426
+ generated_at: new Date().toISOString(),
427
+ input: inputPath,
428
+ scores,
429
+ summary: candidate && candidate.summary ? candidate.summary : null
430
+ };
431
+
432
+ const outputPath = normalizeText(options.out) || buildDefaultScorePath(sceneId);
433
+ if (normalizeBoolean(options.write, true)) {
434
+ await fileSystem.ensureDir(path.dirname(path.join(projectPath, outputPath)));
435
+ await fileSystem.writeJson(path.join(projectPath, outputPath), payload, { spaces: 2 });
436
+ payload.output_file = outputPath;
437
+ }
438
+
439
+ if (!normalizeBoolean(options.json, false)) {
440
+ console.log(chalk.green('✅ Capability score generated'));
441
+ console.log(chalk.gray(` Scene: ${sceneId}`));
442
+ console.log(chalk.gray(` Value score: ${scores.value_score}`));
443
+ if (payload.output_file) {
444
+ console.log(chalk.gray(` Output: ${payload.output_file}`));
445
+ }
446
+ }
447
+
448
+ return payload;
449
+ }
450
+
451
+ async function runCapabilityMapCommand(options = {}, dependencies = {}) {
452
+ const projectPath = dependencies.projectPath || process.cwd();
453
+ const fileSystem = dependencies.fileSystem || fs;
454
+ const inputPath = normalizeText(options.input || options.file);
455
+ if (!inputPath) {
456
+ throw new Error('input candidate file is required for capability map');
457
+ }
458
+
459
+ const mappingPath = normalizeText(options.mapping);
460
+ const candidate = await fileSystem.readJson(path.join(projectPath, inputPath));
461
+ const mapping = mappingPath
462
+ ? await fileSystem.readJson(path.join(projectPath, mappingPath))
463
+ : { ontology_scope: { domains: [], entities: [], relations: [], business_rules: [], decisions: [] } };
464
+
465
+ const templateCandidate = buildTemplateCandidate(candidate, mapping, options);
466
+ const sceneId = buildSceneIdFromCandidate(candidate);
467
+ const payload = {
468
+ mode: 'capability-map',
469
+ scene_id: sceneId,
470
+ generated_at: new Date().toISOString(),
471
+ input: inputPath,
472
+ mapping: mappingPath || null,
473
+ template: templateCandidate
474
+ };
475
+
476
+ const outputPath = normalizeText(options.out) || buildDefaultTemplatePath(sceneId);
477
+ if (normalizeBoolean(options.write, true)) {
478
+ await fileSystem.ensureDir(path.dirname(path.join(projectPath, outputPath)));
479
+ await fileSystem.writeJson(path.join(projectPath, outputPath), payload, { spaces: 2 });
480
+ payload.output_file = outputPath;
481
+ }
482
+
483
+ if (!normalizeBoolean(options.json, false)) {
484
+ console.log(chalk.green('✅ Capability ontology mapping prepared'));
485
+ console.log(chalk.gray(` Scene: ${sceneId}`));
486
+ if (payload.output_file) {
487
+ console.log(chalk.gray(` Output: ${payload.output_file}`));
488
+ }
489
+ }
490
+
491
+ return payload;
492
+ }
493
+
494
+ async function runCapabilityRegisterCommand(options = {}, dependencies = {}) {
495
+ const projectPath = dependencies.projectPath || process.cwd();
496
+ const fileSystem = dependencies.fileSystem || fs;
497
+ const inputPath = normalizeText(options.input || options.file);
498
+ if (!inputPath) {
499
+ throw new Error('input template file is required for capability register');
500
+ }
501
+
502
+ const payload = await fileSystem.readJson(path.join(projectPath, inputPath));
503
+ const templateCandidate = payload.template || payload;
504
+ if (!templateCandidate || !templateCandidate.template_id) {
505
+ throw new Error('template_id missing in capability template candidate');
506
+ }
507
+
508
+ const exportDir = normalizeText(options.out) || buildDefaultExportDir(templateCandidate.template_id);
509
+ const outputDirAbs = path.join(projectPath, exportDir);
510
+ await fileSystem.ensureDir(outputDirAbs);
511
+
512
+ const registryEntry = buildRegistryEntry(templateCandidate, options);
513
+ const registryPayload = {
514
+ version: '1.0',
515
+ templates: [registryEntry]
516
+ };
517
+
518
+ await fileSystem.writeJson(path.join(outputDirAbs, 'capability-template.json'), templateCandidate, { spaces: 2 });
519
+ await fileSystem.writeJson(path.join(outputDirAbs, 'template-registry.json'), registryPayload, { spaces: 2 });
520
+
521
+ const result = {
522
+ mode: 'capability-register',
523
+ template_id: templateCandidate.template_id,
524
+ output_dir: exportDir,
525
+ files: [
526
+ path.join(exportDir, 'capability-template.json'),
527
+ path.join(exportDir, 'template-registry.json')
528
+ ]
529
+ };
530
+
531
+ if (!normalizeBoolean(options.json, false)) {
532
+ console.log(chalk.green('✅ Capability template package exported'));
533
+ console.log(chalk.gray(` Template: ${templateCandidate.template_id}`));
534
+ console.log(chalk.gray(` Output: ${exportDir}`));
535
+ }
536
+
537
+ return result;
538
+ }
539
+
540
+ function registerCapabilityCommands(program) {
541
+ const capabilityCmd = program
542
+ .command('capability')
543
+ .description('Extract and manage capability templates from scene/spec/task history');
544
+
545
+ capabilityCmd
546
+ .command('extract')
547
+ .description('Extract capability candidate from a scene')
548
+ .requiredOption('--scene <scene-id>', 'Scene identifier')
549
+ .option('--specs <spec-ids>', 'Comma-separated spec identifiers')
550
+ .option('--out <path>', 'Output JSON path')
551
+ .option('--sample-limit <n>', 'Max tasks per spec in sample', '5')
552
+ .option('--no-write', 'Skip writing output file')
553
+ .option('--json', 'Output JSON to stdout')
554
+ .action(async (options) => {
555
+ const specs = normalizeText(options.specs)
556
+ ? normalizeText(options.specs).split(',').map((item) => normalizeText(item)).filter(Boolean)
557
+ : [];
558
+ await runCapabilityExtractCommand({
559
+ scene: options.scene,
560
+ specs,
561
+ out: options.out,
562
+ sample_limit: options.sampleLimit,
563
+ write: options.write,
564
+ json: options.json
565
+ });
566
+ });
567
+
568
+ capabilityCmd
569
+ .command('score')
570
+ .description('Score a capability candidate')
571
+ .requiredOption('--input <path>', 'Input candidate JSON')
572
+ .option('--out <path>', 'Output JSON path')
573
+ .option('--no-write', 'Skip writing output file')
574
+ .option('--json', 'Output JSON to stdout')
575
+ .action(async (options) => {
576
+ await runCapabilityScoreCommand(options);
577
+ });
578
+
579
+ capabilityCmd
580
+ .command('map')
581
+ .description('Attach ontology mapping to a capability candidate')
582
+ .requiredOption('--input <path>', 'Input candidate JSON')
583
+ .option('--mapping <path>', 'Ontology mapping JSON')
584
+ .option('--template-id <id>', 'Template identifier')
585
+ .option('--name <name>', 'Template name')
586
+ .option('--description <desc>', 'Template description')
587
+ .option('--category <category>', 'Template category')
588
+ .option('--tags <tags>', 'Comma-separated tags')
589
+ .option('--out <path>', 'Output JSON path')
590
+ .option('--no-write', 'Skip writing output file')
591
+ .option('--json', 'Output JSON to stdout')
592
+ .action(async (options) => {
593
+ const tags = normalizeText(options.tags)
594
+ ? normalizeText(options.tags).split(',').map((item) => normalizeText(item)).filter(Boolean)
595
+ : [];
596
+ await runCapabilityMapCommand({
597
+ ...options,
598
+ tags
599
+ });
600
+ });
601
+
602
+ capabilityCmd
603
+ .command('register')
604
+ .description('Export a registry-ready capability template package')
605
+ .requiredOption('--input <path>', 'Input template JSON (output of capability map)')
606
+ .option('--out <path>', 'Output directory')
607
+ .option('--difficulty <level>', 'Difficulty (beginner|intermediate|advanced)')
608
+ .option('--risk-level <level>', 'Risk level (low|medium|high|critical)')
609
+ .option('--tags <tags>', 'Comma-separated tags')
610
+ .option('--applicable-scenarios <scenes>', 'Comma-separated applicable scenarios')
611
+ .option('--json', 'Output JSON to stdout')
612
+ .action(async (options) => {
613
+ const tags = normalizeText(options.tags)
614
+ ? normalizeText(options.tags).split(',').map((item) => normalizeText(item)).filter(Boolean)
615
+ : [];
616
+ const applicable = normalizeText(options.applicableScenarios)
617
+ ? normalizeText(options.applicableScenarios).split(',').map((item) => normalizeText(item)).filter(Boolean)
618
+ : [];
619
+ await runCapabilityRegisterCommand({
620
+ ...options,
621
+ risk_level: options.riskLevel,
622
+ applicable_scenarios: applicable,
623
+ tags
624
+ });
625
+ });
626
+ }
627
+
628
+ module.exports = {
629
+ registerCapabilityCommands,
630
+ runCapabilityExtractCommand,
631
+ runCapabilityScoreCommand,
632
+ runCapabilityMapCommand,
633
+ runCapabilityRegisterCommand
634
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scene-capability-engine",
3
- "version": "3.6.9",
3
+ "version": "3.6.10",
4
4
  "description": "SCE (Scene Capability Engine) - A CLI tool and npm package for spec-driven development with AI coding assistants.",
5
5
  "main": "index.js",
6
6
  "bin": {