scene-capability-engine 3.6.28 → 3.6.32

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,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.6.32] - 2026-03-07
11
+
12
+ ### Fixed
13
+ - Restored auto session/governance retention and stats CLI paths after refactor, including full integration coverage for version/legacy/takeover/auto flows.
14
+
15
+ ## [3.6.31] - 2026-03-07
16
+
17
+ ### Added
18
+ - Continued refactor pass: extracted command-adjacent Magicball, capability, studio, scene, and auto helper/service modules to reduce top-heavy command files.
19
+ - Added Magicball contract index to make schema discovery and frontend adaptation deterministic.
20
+
21
+ ## [3.6.30] - 2026-03-07
22
+
23
+ ### Added
24
+ - Continued internal refactor: extracted shared Magicball modules, capability services, studio task envelope/intents, scene doctor helpers, and auto session metrics helpers.
25
+ - Added Magicball contract index for schema/discovery entrypoint.
26
+
27
+ ## [3.6.29] - 2026-03-06
28
+
29
+ ### Added
30
+ - Added shared Magicball schemas for status, task feedback, and timeline view contracts.
31
+ - Command reference and Magicball integration docs now reference the shared schemas explicitly.
32
+
10
33
  ## [3.6.28] - 2026-03-06
11
34
 
12
35
  ### Added
package/README.md CHANGED
@@ -218,5 +218,5 @@ MIT. See [LICENSE](LICENSE).
218
218
 
219
219
  ---
220
220
 
221
- **Version**: 3.6.28
221
+ **Version**: 3.6.32
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.28
221
+ **版本**:3.6.32
222
222
  **最后更新**:2026-03-05
@@ -0,0 +1,25 @@
1
+ # Magicball Contract Index
2
+
3
+ Schema references:
4
+ - `docs/agent-runtime/magicball-status.schema.json`
5
+ - `docs/agent-runtime/magicball-task-feedback.schema.json`
6
+ - `docs/agent-runtime/magicball-timeline-view.schema.json`
7
+ - `docs/agent-runtime/capability-iteration-ui.schema.json`
8
+
9
+ Recommended consumption order:
10
+ 1. `magicball-status.schema.json`
11
+ 2. `magicball-task-feedback.schema.json`
12
+ 3. `magicball-timeline-view.schema.json`
13
+ 4. `capability-iteration-ui.schema.json`
14
+
15
+ Usage mapping:
16
+ - Task cards: `task.feedback_model`
17
+ - Timeline panels: `timeline list/show -> view_model`
18
+ - Capability inventory homepage: `capability inventory`
19
+
20
+ Implementation modules:
21
+ - `lib/magicball/status-language.js`
22
+ - `lib/magicball/task-feedback-model.js`
23
+ - `lib/magicball/capability-inventory-view-model.js`
24
+ - `lib/magicball/timeline-view-model.js`
25
+ - `lib/capability/inventory-service.js`
@@ -0,0 +1,33 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://scene-capability-engine.dev/contracts/magicball-status.schema.json",
4
+ "title": "Magicball Shared Status Contract",
5
+ "type": "object",
6
+ "required": [
7
+ "attention_level",
8
+ "status_tone",
9
+ "status_label",
10
+ "blocking_summary",
11
+ "recommended_action"
12
+ ],
13
+ "properties": {
14
+ "attention_level": {
15
+ "type": "string",
16
+ "enum": ["critical", "high", "medium", "low"]
17
+ },
18
+ "status_tone": {
19
+ "type": "string",
20
+ "enum": ["danger", "warning", "info", "success"]
21
+ },
22
+ "status_label": {
23
+ "type": ["string", "null"]
24
+ },
25
+ "blocking_summary": {
26
+ "type": ["string", "null"]
27
+ },
28
+ "recommended_action": {
29
+ "type": ["string", "null"]
30
+ }
31
+ },
32
+ "additionalProperties": false
33
+ }
@@ -0,0 +1,79 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://scene-capability-engine.dev/contracts/magicball-task-feedback.schema.json",
4
+ "title": "Magicball Task Feedback View Contract",
5
+ "type": "object",
6
+ "required": [
7
+ "version",
8
+ "problem",
9
+ "execution",
10
+ "diagnosis",
11
+ "evidence",
12
+ "next_step",
13
+ "mb_status"
14
+ ],
15
+ "properties": {
16
+ "version": {
17
+ "type": "string"
18
+ },
19
+ "problem": {
20
+ "type": "object",
21
+ "required": ["component", "action", "expected", "actual"],
22
+ "properties": {
23
+ "component": { "type": ["string", "null"] },
24
+ "action": { "type": ["string", "null"] },
25
+ "expected": { "type": ["string", "null"] },
26
+ "actual": { "type": ["string", "null"] }
27
+ },
28
+ "additionalProperties": false
29
+ },
30
+ "execution": {
31
+ "type": "object",
32
+ "required": ["stage", "status", "summary", "blocking_summary"],
33
+ "properties": {
34
+ "stage": { "type": "string" },
35
+ "status": { "type": "string" },
36
+ "summary": { "type": "array", "items": { "type": "string" } },
37
+ "blocking_summary": { "type": ["string", "null"] }
38
+ },
39
+ "additionalProperties": false
40
+ },
41
+ "diagnosis": {
42
+ "type": "object",
43
+ "required": ["hypothesis", "chain_checkpoint", "root_cause_confidence"],
44
+ "properties": {
45
+ "hypothesis": { "type": ["string", "null"] },
46
+ "chain_checkpoint": { "type": "string" },
47
+ "root_cause_confidence": { "type": "string", "enum": ["low", "medium", "high"] }
48
+ },
49
+ "additionalProperties": false
50
+ },
51
+ "evidence": {
52
+ "type": "object",
53
+ "required": ["file_count", "file_paths", "command_count", "error_count", "verification_result", "regression_scope"],
54
+ "properties": {
55
+ "file_count": { "type": "integer", "minimum": 0 },
56
+ "file_paths": { "type": "array", "items": { "type": "string" } },
57
+ "command_count": { "type": "integer", "minimum": 0 },
58
+ "error_count": { "type": "integer", "minimum": 0 },
59
+ "verification_result": { "type": "string" },
60
+ "regression_scope": { "type": "array", "items": { "type": "string" } }
61
+ },
62
+ "additionalProperties": false
63
+ },
64
+ "next_step": {
65
+ "type": "object",
66
+ "required": ["recommended_action", "next_action", "next_command"],
67
+ "properties": {
68
+ "recommended_action": { "type": ["string", "null"] },
69
+ "next_action": { "type": ["string", "null"] },
70
+ "next_command": { "type": ["string", "null"] }
71
+ },
72
+ "additionalProperties": false
73
+ },
74
+ "mb_status": {
75
+ "$ref": "./magicball-status.schema.json"
76
+ }
77
+ },
78
+ "additionalProperties": false
79
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://scene-capability-engine.dev/contracts/magicball-timeline-view.schema.json",
4
+ "title": "Magicball Timeline View Contract",
5
+ "type": "object",
6
+ "required": ["summary", "entries"],
7
+ "properties": {
8
+ "summary": {
9
+ "type": "object",
10
+ "required": ["total", "latest_snapshot_id", "latest_created_at", "dirty_snapshot_count", "scene_count", "trigger_counts"],
11
+ "properties": {
12
+ "total": { "type": "integer", "minimum": 0 },
13
+ "latest_snapshot_id": { "type": ["string", "null"] },
14
+ "latest_created_at": { "type": ["string", "null"] },
15
+ "dirty_snapshot_count": { "type": "integer", "minimum": 0 },
16
+ "scene_count": { "type": "integer", "minimum": 0 },
17
+ "trigger_counts": {
18
+ "type": "object",
19
+ "additionalProperties": { "type": "integer", "minimum": 0 }
20
+ }
21
+ },
22
+ "additionalProperties": false
23
+ },
24
+ "entries": {
25
+ "type": "array",
26
+ "items": {
27
+ "type": "object",
28
+ "required": ["snapshot_id", "title", "subtitle", "trigger", "event", "created_at", "scene_id", "session_id", "file_count", "branch", "head", "dirty_count", "attention_level", "show_command", "restore_command"],
29
+ "properties": {
30
+ "snapshot_id": { "type": ["string", "null"] },
31
+ "title": { "type": ["string", "null"] },
32
+ "subtitle": { "type": ["string", "null"] },
33
+ "trigger": { "type": ["string", "null"] },
34
+ "event": { "type": ["string", "null"] },
35
+ "created_at": { "type": ["string", "null"] },
36
+ "scene_id": { "type": ["string", "null"] },
37
+ "session_id": { "type": ["string", "null"] },
38
+ "file_count": { "type": "integer", "minimum": 0 },
39
+ "branch": { "type": ["string", "null"] },
40
+ "head": { "type": ["string", "null"] },
41
+ "dirty_count": { "type": "integer", "minimum": 0 },
42
+ "attention_level": { "type": "string", "enum": ["high", "medium", "low"] },
43
+ "show_command": { "type": ["string", "null"] },
44
+ "restore_command": { "type": ["string", "null"] }
45
+ },
46
+ "additionalProperties": false
47
+ }
48
+ }
49
+ },
50
+ "additionalProperties": false
51
+ }
@@ -1902,6 +1902,11 @@ sce capability register --input .sce/reports/capability-iteration/scene.customer
1902
1902
 
1903
1903
  Schema references:
1904
1904
  - UI contract: `docs/agent-runtime/capability-iteration-ui.schema.json`
1905
+ - Magicball shared status: `docs/agent-runtime/magicball-status.schema.json`
1906
+ - Magicball task feedback: `docs/agent-runtime/magicball-task-feedback.schema.json`
1907
+ - Magicball timeline view: `docs/agent-runtime/magicball-timeline-view.schema.json`
1908
+ - Magicball contract index: `docs/agent-runtime/magicball-contract-index.md`
1909
+ Implementation modules are listed in the contract index for internal maintainers.
1905
1910
  - Ontology mapping: `docs/ontology/capability-mapping.schema.json`
1906
1911
 
1907
1912
  ### Capability Library Reuse (query -> match -> use)
@@ -145,6 +145,8 @@ sce capability register --input <template.json> --json
145
145
  ## 5. 数据契约(前端对接)
146
146
 
147
147
  - UI 契约:`docs/agent-runtime/capability-iteration-ui.schema.json`
148
+ - 统一状态语言:`docs/agent-runtime/magicball-status.schema.json`
149
+ - 契约索引:`docs/agent-runtime/magicball-contract-index.md`
148
150
  - 本体映射 schema:`docs/ontology/capability-mapping.schema.json`
149
151
 
150
152
  ---
@@ -2,6 +2,8 @@
2
2
 
3
3
  > 目标:在 Magicball UI 中提供“能力库检索/匹配/使用”闭环,加速场景能力落地。
4
4
 
5
+ Schema reference: `docs/agent-runtime/magicball-status.schema.json`
6
+
5
7
  ## 1. 能力库复用流程
6
8
 
7
9
  能力模板进入能力库前,默认要求补齐本体三项核心能力:
@@ -4,6 +4,13 @@
4
4
 
5
5
  ## 1. 任务反馈模型(来源:`sce studio events --json`)
6
6
 
7
+ Schema references:
8
+ - `docs/agent-runtime/magicball-status.schema.json`
9
+ - `docs/agent-runtime/magicball-task-feedback.schema.json`
10
+ - `docs/agent-runtime/magicball-timeline-view.schema.json`
11
+ - `docs/agent-runtime/magicball-contract-index.md`
12
+
13
+
7
14
  SCE 现在在 `task` 下增加:
8
15
  - `feedback_model.version`
9
16
  - `feedback_model.problem`
@@ -132,3 +139,10 @@ SCE 现在会在任务反馈模型中提供 `mb_status`:
132
139
  - `recommended_action`
133
140
 
134
141
  Magicball 可直接用这组字段控制颜色、图标、提示文案。
142
+
143
+ ## 7. 维护说明
144
+
145
+ - 共享实现位于 `lib/magicball/*`
146
+ - `studio` 命令只负责任务流编排与事件产出
147
+ - `timeline` 命令只负责快照读写与 view model 挂载
148
+ - 共享契约入口:`docs/agent-runtime/magicball-contract-index.md`
@@ -0,0 +1,53 @@
1
+ function buildStatusCounts(entries = [], normalizeStatusToken = (value) => value) {
2
+ const counts = {};
3
+ const safeEntries = Array.isArray(entries) ? entries : [];
4
+ for (const entry of safeEntries) {
5
+ const status = normalizeStatusToken(entry && entry.status) || 'unknown';
6
+ counts[status] = (counts[status] || 0) + 1;
7
+ }
8
+ return counts;
9
+ }
10
+
11
+ function buildQueueFormatCounts(entries = []) {
12
+ const counts = {};
13
+ const safeEntries = Array.isArray(entries) ? entries : [];
14
+ for (const entry of safeEntries) {
15
+ const format = String(entry && entry.queue_format ? entry.queue_format : '').trim().toLowerCase() || 'unknown';
16
+ counts[format] = (counts[format] || 0) + 1;
17
+ }
18
+ return counts;
19
+ }
20
+
21
+ function buildMasterSpecCounts(entries = []) {
22
+ const counts = {};
23
+ const safeEntries = Array.isArray(entries) ? entries : [];
24
+ for (const entry of safeEntries) {
25
+ const masterSpec = String(entry && entry.master_spec ? entry.master_spec : '').trim();
26
+ if (!masterSpec) {
27
+ continue;
28
+ }
29
+ counts[masterSpec] = (counts[masterSpec] || 0) + 1;
30
+ }
31
+ return counts;
32
+ }
33
+
34
+ function buildTopCountEntries(counterMap, limit = 10) {
35
+ const source = counterMap && typeof counterMap === 'object' ? counterMap : {};
36
+ const maxItems = Number.isInteger(limit) && limit > 0 ? limit : 10;
37
+ return Object.entries(source)
38
+ .map(([key, count]) => ({ key, count: Number(count) || 0 }))
39
+ .sort((left, right) => {
40
+ if (right.count !== left.count) {
41
+ return right.count - left.count;
42
+ }
43
+ return left.key.localeCompare(right.key);
44
+ })
45
+ .slice(0, maxItems);
46
+ }
47
+
48
+ module.exports = {
49
+ buildStatusCounts,
50
+ buildQueueFormatCounts,
51
+ buildMasterSpecCounts,
52
+ buildTopCountEntries
53
+ };
@@ -0,0 +1,248 @@
1
+ const path = require('path');
2
+ const fsExtra = require('fs-extra');
3
+ const TemplateManager = require('../templates/template-manager');
4
+
5
+ function normalizeText(value) {
6
+ if (typeof value !== 'string') {
7
+ return '';
8
+ }
9
+ return value.trim();
10
+ }
11
+
12
+ function normalizeBoolean(value, fallback = false) {
13
+ if (typeof value === 'boolean') {
14
+ return value;
15
+ }
16
+ const normalized = normalizeText(String(value || '')).toLowerCase();
17
+ if (!normalized) {
18
+ return fallback;
19
+ }
20
+ if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) {
21
+ return true;
22
+ }
23
+ if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) {
24
+ return false;
25
+ }
26
+ return fallback;
27
+ }
28
+
29
+ function toPositiveInteger(value, fallback) {
30
+ const parsed = Number.parseInt(String(value), 10);
31
+ if (!Number.isFinite(parsed) || parsed <= 0) {
32
+ return fallback;
33
+ }
34
+ return parsed;
35
+ }
36
+
37
+ async function listCapabilityCatalogService(options = {}, dependencies = {}) {
38
+ const manager = dependencies.manager || new TemplateManager();
39
+ const templates = dependencies.filterCapabilityCatalogEntries((await manager.listTemplates({
40
+ category: options.category,
41
+ source: options.source,
42
+ templateType: 'capability-template',
43
+ compatibleWith: options.compatibleWith,
44
+ riskLevel: options.risk
45
+ })).map((template) => dependencies.enrichCapabilityTemplateForUi(template)), options);
46
+ return {
47
+ mode: 'capability-catalog-list',
48
+ templates
49
+ };
50
+ }
51
+
52
+ async function searchCapabilityCatalogService(keyword, options = {}, dependencies = {}) {
53
+ const manager = dependencies.manager || new TemplateManager();
54
+ const templates = dependencies.filterCapabilityCatalogEntries((await manager.searchTemplates(keyword, {
55
+ category: options.category,
56
+ source: options.source,
57
+ templateType: 'capability-template',
58
+ compatibleWith: options.compatibleWith,
59
+ riskLevel: options.risk
60
+ })).map((template) => dependencies.enrichCapabilityTemplateForUi(template)), options);
61
+ return {
62
+ mode: 'capability-catalog-search',
63
+ keyword,
64
+ templates
65
+ };
66
+ }
67
+
68
+ async function showCapabilityTemplateService(templatePath, options = {}, dependencies = {}) {
69
+ const manager = dependencies.manager || new TemplateManager();
70
+ const template = dependencies.enrichCapabilityTemplateForUi(await manager.showTemplate(templatePath));
71
+ const parsed = dependencies.parseTemplatePath(templatePath);
72
+ await manager.ensureCached(parsed.sourceName);
73
+ const sourcePath = manager.cacheManager.getSourceCachePath(parsed.sourceName);
74
+ const templateDir = path.join(sourcePath, parsed.templateId);
75
+ const capabilityFile = path.join(templateDir, 'capability-template.json');
76
+ let templatePayload = null;
77
+ if (await fsExtra.pathExists(capabilityFile)) {
78
+ try {
79
+ templatePayload = await fsExtra.readJson(capabilityFile);
80
+ } catch (_error) {
81
+ templatePayload = null;
82
+ }
83
+ }
84
+ return {
85
+ mode: 'capability-catalog-show',
86
+ template,
87
+ template_file: await fsExtra.pathExists(capabilityFile) ? capabilityFile : null,
88
+ payload: templatePayload
89
+ };
90
+ }
91
+
92
+ async function matchCapabilityTemplatesService(options = {}, dependencies = {}) {
93
+ const projectPath = options.projectPath || process.cwd();
94
+ const fileSystem = options.fileSystem || fsExtra;
95
+ const specId = normalizeText(options.spec || options.specId);
96
+ if (!specId) {
97
+ throw new Error('spec is required for capability match');
98
+ }
99
+ const chain = await dependencies.loadSpecDomainChain(projectPath, specId, fileSystem);
100
+ if (!chain.exists && normalizeBoolean(options.strict, false)) {
101
+ throw new Error('problem-domain-chain missing for spec ' + specId);
102
+ }
103
+ if (chain.error && normalizeBoolean(options.strict, false)) {
104
+ throw new Error('problem-domain-chain invalid: ' + chain.error);
105
+ }
106
+ const domainChain = chain.payload || {};
107
+ const specScope = dependencies.buildOntologyScopeFromChain(domainChain);
108
+ const queryTokens = dependencies.normalizeTokenList(options.query)
109
+ .concat(dependencies.normalizeTokenList(domainChain.problem && domainChain.problem.statement))
110
+ .concat(dependencies.normalizeTokenList(domainChain.scene_id));
111
+ const manager = dependencies.manager || new TemplateManager();
112
+ const templates = await manager.listTemplates({
113
+ source: options.source,
114
+ templateType: 'capability-template',
115
+ compatibleWith: options.compatibleWith,
116
+ riskLevel: options.risk
117
+ });
118
+ const matches = templates.map((template) => {
119
+ const overlap = dependencies.buildOntologyOverlap(specScope, template.ontology_scope || {});
120
+ const scenarioScore = template.applicable_scenarios && domainChain.scene_id
121
+ ? (template.applicable_scenarios.includes(domainChain.scene_id) ? 1 : 0)
122
+ : 0;
123
+ const keywordScore = dependencies.buildKeywordScore(template, queryTokens);
124
+ const totalScore = (overlap.score * 0.6) + (scenarioScore * 0.2) + (keywordScore * 0.2);
125
+ const ontologyCore = template.ontology_core || dependencies.buildCoreOntologySummary(template.ontology_scope || {});
126
+ return {
127
+ template_id: template.id,
128
+ source: template.source,
129
+ name: template.name,
130
+ description: template.description,
131
+ category: template.category,
132
+ risk_level: template.risk_level,
133
+ ontology_core: ontologyCore,
134
+ ontology_core_ui: dependencies.buildOntologyCoreUiState(ontologyCore),
135
+ score: Math.round(totalScore * 100),
136
+ score_components: {
137
+ ontology: Number(overlap.score.toFixed(3)),
138
+ scenario: scenarioScore,
139
+ keyword: Number(keywordScore.toFixed(3))
140
+ },
141
+ overlap
142
+ };
143
+ }).sort((a, b) => b.score - a.score);
144
+
145
+ const limit = toPositiveInteger(options.limit, 10);
146
+ return {
147
+ mode: 'capability-match',
148
+ spec_id: specId,
149
+ scene_id: domainChain.scene_id || null,
150
+ query: normalizeText(options.query) || null,
151
+ ontology_source: chain.exists ? chain.path : null,
152
+ match_count: matches.length,
153
+ matches: matches.slice(0, limit),
154
+ warnings: chain.exists ? [] : ['problem-domain-chain missing; ontology-based match unavailable']
155
+ };
156
+ }
157
+
158
+ async function useCapabilityTemplateService(options = {}, dependencies = {}) {
159
+ const projectPath = options.projectPath || process.cwd();
160
+ const fileSystem = options.fileSystem || fsExtra;
161
+ const templateId = normalizeText(options.template || options.id);
162
+ if (!templateId) {
163
+ throw new Error('template is required for capability use');
164
+ }
165
+ if (normalizeBoolean(options.apply, false) && normalizeBoolean(options.write, true) === false) {
166
+ throw new Error('cannot use --apply with --no-write');
167
+ }
168
+ const specId = normalizeText(options.spec || options.specId) || null;
169
+ const manager = dependencies.manager || new TemplateManager();
170
+ const template = await manager.showTemplate(templateId);
171
+ const parsed = dependencies.parseTemplatePath(templateId);
172
+ await manager.ensureCached(parsed.sourceName);
173
+ const sourcePath = manager.cacheManager.getSourceCachePath(parsed.sourceName);
174
+ const templateDir = path.join(sourcePath, parsed.templateId);
175
+ const capabilityFile = path.join(templateDir, 'capability-template.json');
176
+ let templatePayload = null;
177
+ if (await fileSystem.pathExists(capabilityFile)) {
178
+ try {
179
+ templatePayload = await fileSystem.readJson(capabilityFile);
180
+ } catch (_error) {
181
+ templatePayload = null;
182
+ }
183
+ }
184
+
185
+ const recommendedTasks = [];
186
+ if (templatePayload && templatePayload.source_candidate && Array.isArray(templatePayload.source_candidate.specs)) {
187
+ templatePayload.source_candidate.specs.forEach((spec) => {
188
+ const sample = Array.isArray(spec.task_sample) ? spec.task_sample : [];
189
+ sample.forEach((task) => {
190
+ if (task && task.title) {
191
+ recommendedTasks.push({
192
+ title: task.title,
193
+ source_spec_id: spec.spec_id || null,
194
+ source_task_id: task.id || null
195
+ });
196
+ }
197
+ });
198
+ });
199
+ }
200
+ if (recommendedTasks.length === 0) {
201
+ recommendedTasks.push({ title: 'Implement capability scope: ' + (template.name || parsed.templateId) });
202
+ }
203
+
204
+ const ontologyCore = template.ontology_core || dependencies.buildCoreOntologySummary(template.ontology_scope || {});
205
+ const plan = {
206
+ mode: 'capability-use-plan',
207
+ generated_at: new Date().toISOString(),
208
+ template: {
209
+ id: template.id,
210
+ name: template.name,
211
+ source: template.source,
212
+ description: template.description,
213
+ ontology_scope: template.ontology_scope || {},
214
+ ontology_core: ontologyCore,
215
+ ontology_core_ui: dependencies.buildOntologyCoreUiState(ontologyCore)
216
+ },
217
+ spec_id: specId,
218
+ recommended_tasks: recommendedTasks
219
+ };
220
+
221
+ const outputPath = normalizeText(options.out) || dependencies.buildDefaultUsePlanPath(specId || 'spec', template.id);
222
+ if (normalizeBoolean(options.write, true)) {
223
+ await fileSystem.ensureDir(path.dirname(path.join(projectPath, outputPath)));
224
+ await fileSystem.writeJson(path.join(projectPath, outputPath), plan, { spaces: 2 });
225
+ plan.output_file = outputPath;
226
+ }
227
+
228
+ if (normalizeBoolean(options.apply, false)) {
229
+ if (!specId) {
230
+ throw new Error('spec is required for --apply');
231
+ }
232
+ plan.apply = await dependencies.appendCapabilityPlanToSpecTasks({
233
+ projectPath,
234
+ spec: specId,
235
+ sectionTitle: options.sectionTitle
236
+ }, plan, fileSystem);
237
+ }
238
+
239
+ return plan;
240
+ }
241
+
242
+ module.exports = {
243
+ listCapabilityCatalogService,
244
+ searchCapabilityCatalogService,
245
+ showCapabilityTemplateService,
246
+ matchCapabilityTemplatesService,
247
+ useCapabilityTemplateService
248
+ };