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 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
@@ -218,5 +218,5 @@ MIT. See [LICENSE](LICENSE).
218
218
 
219
219
  ---
220
220
 
221
- **Version**: 3.6.13
221
+ **Version**: 3.6.15
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.13
221
+ **版本**:3.6.15
222
222
  **最后更新**:2026-03-05
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scene-capability-engine",
3
- "version": "3.6.13",
3
+ "version": "3.6.15",
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": {