scene-capability-engine 3.6.13 → 3.6.15
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 +10 -0
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/docs/command-reference.md +3 -0
- package/docs/magicball-capability-library.md +6 -0
- package/lib/commands/capability.js +154 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.6.15] - 2026-03-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Capability plan apply result now includes preview lines + skipped titles for UI feedback.
|
|
14
|
+
|
|
15
|
+
## [3.6.14] - 2026-03-06
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- `sce capability use --apply` to append recommended tasks to spec tasks.md.
|
|
19
|
+
|
|
10
20
|
## [3.6.13] - 2026-03-06
|
|
11
21
|
|
|
12
22
|
### Added
|
package/README.md
CHANGED
package/README.zh.md
CHANGED
|
@@ -1906,6 +1906,9 @@ sce capability match --spec 01-02-customer-order --query "订单 库存" --limit
|
|
|
1906
1906
|
|
|
1907
1907
|
# Generate a usage plan for a spec
|
|
1908
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
|
|
1909
1912
|
```
|
|
1910
1913
|
|
|
1911
1914
|
### Scene Package Batch Publish
|
|
@@ -38,6 +38,12 @@ sce capability use --template <template-id> --spec <spec-id> --json
|
|
|
38
38
|
|
|
39
39
|
输出:`capability-use-plan`(用于 UI 展示和后续手工应用)。
|
|
40
40
|
|
|
41
|
+
如需直接写入 spec 任务:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
sce capability use --template <template-id> --spec <spec-id> --apply --json
|
|
45
|
+
```
|
|
46
|
+
|
|
41
47
|
## 3. API 封装建议(CLI -> HTTP)
|
|
42
48
|
|
|
43
49
|
建议 Magicball 后端封装为:
|
|
@@ -178,6 +178,144 @@ function buildKeywordScore(template, queryTokens) {
|
|
|
178
178
|
return hits / queryTokens.length;
|
|
179
179
|
}
|
|
180
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
|
+
const skippedTitles = [];
|
|
249
|
+
let nextTaskId = registry.maxTaskId + 1;
|
|
250
|
+
let duplicateCount = 0;
|
|
251
|
+
|
|
252
|
+
for (const entry of recommended) {
|
|
253
|
+
const title = normalizeText(entry && entry.title);
|
|
254
|
+
if (!title) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const titleKey = title.toLowerCase();
|
|
258
|
+
if (registry.existingTitles.has(titleKey)) {
|
|
259
|
+
duplicateCount += 1;
|
|
260
|
+
skippedTitles.push(title);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
registry.existingTitles.add(titleKey);
|
|
264
|
+
|
|
265
|
+
lines.push(createCapabilityTaskLine(nextTaskId, title, {
|
|
266
|
+
templateId: plan.template.id,
|
|
267
|
+
templateSource: plan.template.source
|
|
268
|
+
}));
|
|
269
|
+
|
|
270
|
+
addedTasks.push({
|
|
271
|
+
task_id: nextTaskId,
|
|
272
|
+
title
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
nextTaskId += 1;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (addedTasks.length === 0) {
|
|
279
|
+
return {
|
|
280
|
+
tasks_path: tasksPath,
|
|
281
|
+
section_title: sectionTitle,
|
|
282
|
+
added_count: 0,
|
|
283
|
+
skipped_duplicates: duplicateCount,
|
|
284
|
+
skipped_titles: skippedTitles,
|
|
285
|
+
preview_lines: [],
|
|
286
|
+
skipped_reason: recommended.length === 0
|
|
287
|
+
? 'no recommended tasks'
|
|
288
|
+
: 'all recommended tasks already exist in tasks.md',
|
|
289
|
+
added_tasks: []
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const prefix = currentContent.trimEnd();
|
|
294
|
+
const chunks = [
|
|
295
|
+
prefix,
|
|
296
|
+
'',
|
|
297
|
+
sectionTitle,
|
|
298
|
+
'',
|
|
299
|
+
...lines,
|
|
300
|
+
''
|
|
301
|
+
];
|
|
302
|
+
|
|
303
|
+
const nextContent = chunks.join('\n');
|
|
304
|
+
await fileSystem.writeFile(tasksPath, nextContent, 'utf8');
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
tasks_path: tasksPath,
|
|
308
|
+
section_title: sectionTitle,
|
|
309
|
+
added_count: addedTasks.length,
|
|
310
|
+
skipped_duplicates: duplicateCount,
|
|
311
|
+
skipped_titles: skippedTitles,
|
|
312
|
+
preview_lines: lines,
|
|
313
|
+
first_task_id: addedTasks[0].task_id,
|
|
314
|
+
last_task_id: addedTasks[addedTasks.length - 1].task_id,
|
|
315
|
+
added_tasks: addedTasks
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
181
319
|
async function loadSpecDomainChain(projectPath, specId, fileSystem) {
|
|
182
320
|
const specPath = path.join(projectPath, '.sce', 'specs', specId);
|
|
183
321
|
const domainChainPath = path.join(specPath, DOMAIN_CHAIN_RELATIVE_PATH);
|
|
@@ -821,6 +959,9 @@ async function useCapabilityTemplate(options = {}) {
|
|
|
821
959
|
if (!templateId) {
|
|
822
960
|
throw new Error('template is required for capability use');
|
|
823
961
|
}
|
|
962
|
+
if (normalizeBoolean(options.apply, false) && normalizeBoolean(options.write, true) === false) {
|
|
963
|
+
throw new Error('cannot use --apply with --no-write');
|
|
964
|
+
}
|
|
824
965
|
const specId = normalizeText(options.spec || options.specId) || null;
|
|
825
966
|
const manager = new TemplateManager();
|
|
826
967
|
const template = await manager.showTemplate(templateId);
|
|
@@ -878,6 +1019,17 @@ async function useCapabilityTemplate(options = {}) {
|
|
|
878
1019
|
plan.output_file = outputPath;
|
|
879
1020
|
}
|
|
880
1021
|
|
|
1022
|
+
if (normalizeBoolean(options.apply, false)) {
|
|
1023
|
+
if (!specId) {
|
|
1024
|
+
throw new Error('spec is required for --apply');
|
|
1025
|
+
}
|
|
1026
|
+
plan.apply = await appendCapabilityPlanToSpecTasks({
|
|
1027
|
+
projectPath,
|
|
1028
|
+
spec: specId,
|
|
1029
|
+
sectionTitle: options.sectionTitle
|
|
1030
|
+
}, plan, fileSystem);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
881
1033
|
if (!normalizeBoolean(options.json, false)) {
|
|
882
1034
|
console.log(chalk.green('✅ Capability use plan generated'));
|
|
883
1035
|
console.log(chalk.gray(` Template: ${template.id}`));
|
|
@@ -1099,6 +1251,8 @@ function registerCapabilityCommands(program) {
|
|
|
1099
1251
|
.requiredOption('--template <template-id>', 'Capability template identifier')
|
|
1100
1252
|
.option('--spec <spec-id>', 'Spec identifier')
|
|
1101
1253
|
.option('--out <path>', 'Output JSON path')
|
|
1254
|
+
.option('--apply', 'Append recommended tasks to spec tasks.md')
|
|
1255
|
+
.option('--section-title <title>', 'Custom section title for tasks.md')
|
|
1102
1256
|
.option('--no-write', 'Skip writing output file')
|
|
1103
1257
|
.option('--json', 'Output JSON to stdout')
|
|
1104
1258
|
.action(async (options) => {
|
package/package.json
CHANGED