scene-capability-engine 3.6.13 → 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 CHANGED
@@ -7,6 +7,11 @@ 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
+
10
15
  ## [3.6.13] - 2026-03-06
11
16
 
12
17
  ### 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.14
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.14
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,136 @@ 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
+ 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
+
181
311
  async function loadSpecDomainChain(projectPath, specId, fileSystem) {
182
312
  const specPath = path.join(projectPath, '.sce', 'specs', specId);
183
313
  const domainChainPath = path.join(specPath, DOMAIN_CHAIN_RELATIVE_PATH);
@@ -821,6 +951,9 @@ async function useCapabilityTemplate(options = {}) {
821
951
  if (!templateId) {
822
952
  throw new Error('template is required for capability use');
823
953
  }
954
+ if (normalizeBoolean(options.apply, false) && normalizeBoolean(options.write, true) === false) {
955
+ throw new Error('cannot use --apply with --no-write');
956
+ }
824
957
  const specId = normalizeText(options.spec || options.specId) || null;
825
958
  const manager = new TemplateManager();
826
959
  const template = await manager.showTemplate(templateId);
@@ -878,6 +1011,17 @@ async function useCapabilityTemplate(options = {}) {
878
1011
  plan.output_file = outputPath;
879
1012
  }
880
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
+
881
1025
  if (!normalizeBoolean(options.json, false)) {
882
1026
  console.log(chalk.green('✅ Capability use plan generated'));
883
1027
  console.log(chalk.gray(` Template: ${template.id}`));
@@ -1099,6 +1243,8 @@ function registerCapabilityCommands(program) {
1099
1243
  .requiredOption('--template <template-id>', 'Capability template identifier')
1100
1244
  .option('--spec <spec-id>', 'Spec identifier')
1101
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')
1102
1248
  .option('--no-write', 'Skip writing output file')
1103
1249
  .option('--json', 'Output JSON to stdout')
1104
1250
  .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.14",
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": {