scene-capability-engine 3.6.15 → 3.6.16

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,10 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.6.16] - 2026-03-06
11
+
12
+ ### Added
13
+ - Capability registry validation now enforces ontology core triads for all capability templates.
14
+ - Capability catalog, match, and use payloads now expose `ontology_core` for direct UI rendering.
15
+ - Capability extract now reports triad readiness in candidate summaries.
16
+
10
17
  ## [3.6.15] - 2026-03-06
11
18
 
12
19
  ### Added
13
20
  - Capability plan apply result now includes preview lines + skipped titles for UI feedback.
21
+ - Capability iteration now enforces ontology core triads by default and exposes triad readiness in catalog/match/use payloads:
22
+ - entity + relation
23
+ - business rule
24
+ - decision strategy
14
25
 
15
26
  ## [3.6.14] - 2026-03-06
16
27
 
package/README.md CHANGED
@@ -218,5 +218,5 @@ MIT. See [LICENSE](LICENSE).
218
218
 
219
219
  ---
220
220
 
221
- **Version**: 3.6.15
221
+ **Version**: 3.6.16
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.15
221
+ **版本**:3.6.16
222
222
  **最后更新**:2026-03-05
@@ -1869,6 +1869,12 @@ sce scene template-render --package scene-erp --values '{"entity_name":"Order"}'
1869
1869
 
1870
1870
  ### Capability Iteration (scene -> template -> ontology)
1871
1871
 
1872
+ Capability candidates are now evaluated against the ontology core triad by default:
1873
+ - entity + relation
1874
+ - business rule
1875
+ - decision strategy
1876
+
1877
+
1872
1878
  ```bash
1873
1879
  # 1) Extract capability candidate from scene history
1874
1880
  sce capability extract --scene scene.customer-order --json
@@ -1890,6 +1896,8 @@ Schema references:
1890
1896
 
1891
1897
  ### Capability Library Reuse (query -> match -> use)
1892
1898
 
1899
+ `catalog/show/match/use` responses now include `ontology_core` so UI can render triad readiness directly.
1900
+
1893
1901
  ```bash
1894
1902
  # List capability templates
1895
1903
  sce capability catalog list --json
@@ -4,6 +4,12 @@
4
4
 
5
5
  ## 1. 能力库复用流程
6
6
 
7
+ 能力模板进入能力库前,默认要求补齐本体三项核心能力:
8
+ - 实体关系
9
+ - 业务规则
10
+ - 决策策略
11
+
12
+
7
13
  1. **查询**能力库(列表/搜索)
8
14
  2. **匹配**当前 spec 的问题域(基于 problem-domain-chain 里的 ontology)
9
15
  3. **使用**能力模板(生成可执行的 usage plan)
@@ -72,10 +78,10 @@ sce capability use --template <template-id> --spec <spec-id> --apply --json
72
78
 
73
79
  ### 4.2 能力库列表
74
80
  - 支持筛选:category / risk / source
75
- - 展示关键信息:`name` / `description` / `ontology_scope`
81
+ - 展示关键信息:`name` / `description` / `ontology_scope` / `ontology_core`
76
82
 
77
83
  ### 4.3 匹配结果
78
- - 展示 `score` + `score_components`
84
+ - 展示 `score` + `score_components` + `ontology_core.ready`
79
85
  - 支持一键生成 usage plan
80
86
 
81
87
  ### 4.4 使用计划
@@ -330,6 +330,83 @@ async function loadSpecDomainChain(projectPath, specId, fileSystem) {
330
330
  }
331
331
  }
332
332
 
333
+ function createEmptyOntologyScope() {
334
+ return {
335
+ domains: [],
336
+ entities: [],
337
+ relations: [],
338
+ business_rules: [],
339
+ decisions: []
340
+ };
341
+ }
342
+
343
+ function normalizeOntologyScope(scope) {
344
+ const candidate = scope && typeof scope === 'object' ? scope : {};
345
+ return {
346
+ domains: normalizeStringArray(candidate.domains),
347
+ entities: normalizeStringArray(candidate.entities),
348
+ relations: normalizeStringArray(candidate.relations),
349
+ business_rules: normalizeStringArray(candidate.business_rules),
350
+ decisions: normalizeStringArray(candidate.decisions)
351
+ };
352
+ }
353
+
354
+ function mergeOntologyScopes(scopes) {
355
+ const merged = createEmptyOntologyScope();
356
+ for (const scope of Array.isArray(scopes) ? scopes : []) {
357
+ const normalized = normalizeOntologyScope(scope);
358
+ for (const field of Object.keys(merged)) {
359
+ const combined = new Set([...(merged[field] || []), ...(normalized[field] || [])]);
360
+ merged[field] = Array.from(combined);
361
+ }
362
+ }
363
+ return merged;
364
+ }
365
+
366
+ function buildCoreOntologySummary(scope) {
367
+ const normalized = normalizeOntologyScope(scope);
368
+ const triads = {
369
+ entity_relation: {
370
+ required_fields: ['entities', 'relations'],
371
+ entity_count: normalized.entities.length,
372
+ relation_count: normalized.relations.length,
373
+ passed: normalized.entities.length > 0 && normalized.relations.length > 0
374
+ },
375
+ business_rules: {
376
+ required_fields: ['business_rules'],
377
+ count: normalized.business_rules.length,
378
+ passed: normalized.business_rules.length > 0
379
+ },
380
+ decision_strategy: {
381
+ required_fields: ['decisions'],
382
+ count: normalized.decisions.length,
383
+ passed: normalized.decisions.length > 0
384
+ }
385
+ };
386
+ const passedCount = Object.values(triads).filter((item) => item.passed).length;
387
+ return {
388
+ ontology_scope: normalized,
389
+ triads,
390
+ passed_count: passedCount,
391
+ total_count: 3,
392
+ coverage_ratio: Number((passedCount / 3).toFixed(3)),
393
+ ready: passedCount === 3,
394
+ missing: Object.entries(triads)
395
+ .filter(([, value]) => !value.passed)
396
+ .map(([key]) => key)
397
+ };
398
+ }
399
+
400
+ function assertCoreOntologySummary(summary, contextLabel = 'capability template') {
401
+ const details = summary || buildCoreOntologySummary(createEmptyOntologyScope());
402
+ if (details.ready) {
403
+ return details;
404
+ }
405
+ throw new Error(
406
+ `${contextLabel} missing required ontology triads: ${details.missing.join(', ')}`
407
+ );
408
+ }
409
+
333
410
  async function loadSceneIndexFromFile(projectPath, fileSystem) {
334
411
  const indexPath = path.join(projectPath, '.sce', 'spec-governance', 'scene-index.json');
335
412
  if (!await fileSystem.pathExists(indexPath)) {
@@ -504,13 +581,22 @@ function buildScoreFromCandidate(candidate) {
504
581
  const reuseScore = Math.min(100, Math.round((specCount / 3) * 100));
505
582
  const stabilityScore = Math.round(completionRate * 100);
506
583
  const riskScore = Math.min(100, Math.round((1 - completionRate) * 100));
507
- const valueScore = Math.round((stabilityScore * 0.5) + (reuseScore * 0.3) + ((100 - riskScore) * 0.2));
584
+ const ontologySummary = buildCoreOntologySummary(candidate && candidate.ontology_scope);
585
+ const ontologyCoreScore = Math.round(ontologySummary.coverage_ratio * 100);
586
+ const valueScore = Math.round(
587
+ (stabilityScore * 0.4) +
588
+ (reuseScore * 0.2) +
589
+ ((100 - riskScore) * 0.1) +
590
+ (ontologyCoreScore * 0.3)
591
+ );
508
592
 
509
593
  return {
510
594
  completion_rate: Number(completionRate.toFixed(3)),
511
595
  reuse_score: reuseScore,
512
596
  stability_score: stabilityScore,
513
597
  risk_score: riskScore,
598
+ ontology_core_score: ontologyCoreScore,
599
+ ontology_core: ontologySummary,
514
600
  value_score: valueScore
515
601
  };
516
602
  }
@@ -526,15 +612,12 @@ function buildTemplateCandidate(candidate, mapping, options) {
526
612
  || `Capability template derived from ${sceneId}`;
527
613
  const category = normalizeText(options && options.category) || 'capability';
528
614
  const tags = normalizeStringArray(options && options.tags);
529
- const ontologyScope = (mapping && mapping.ontology_scope && typeof mapping.ontology_scope === 'object')
530
- ? mapping.ontology_scope
531
- : {
532
- domains: [],
533
- entities: [],
534
- relations: [],
535
- business_rules: [],
536
- decisions: []
537
- };
615
+ const ontologyScope = normalizeOntologyScope(
616
+ (mapping && mapping.ontology_scope && typeof mapping.ontology_scope === 'object')
617
+ ? mapping.ontology_scope
618
+ : candidate && candidate.ontology_scope
619
+ );
620
+ const ontologyCore = buildCoreOntologySummary(ontologyScope);
538
621
 
539
622
  return {
540
623
  mode: 'capability-template',
@@ -546,6 +629,7 @@ function buildTemplateCandidate(candidate, mapping, options) {
546
629
  scene_id: sceneId,
547
630
  source_candidate: candidate,
548
631
  ontology_scope: ontologyScope,
632
+ ontology_core: ontologyCore,
549
633
  tags,
550
634
  created_at: new Date().toISOString()
551
635
  };
@@ -599,6 +683,8 @@ async function runCapabilityExtractCommand(options = {}, dependencies = {}) {
599
683
 
600
684
  const taskClaimer = new TaskClaimer();
601
685
  const specs = [];
686
+ const ontologyScopes = [];
687
+ const ontologyEvidence = [];
602
688
 
603
689
  for (const specId of specIds) {
604
690
  const tasksPath = path.join(projectPath, '.sce', 'specs', specId, 'tasks.md');
@@ -613,6 +699,16 @@ async function runCapabilityExtractCommand(options = {}, dependencies = {}) {
613
699
  } else {
614
700
  taskError = 'tasks.md missing';
615
701
  }
702
+ const domainChain = await loadSpecDomainChain(projectPath, specId, fileSystem);
703
+ const specOntologyScope = domainChain.payload ? buildOntologyScopeFromChain(domainChain.payload) : createEmptyOntologyScope();
704
+ if (domainChain.payload) {
705
+ ontologyScopes.push(specOntologyScope);
706
+ ontologyEvidence.push({
707
+ spec_id: specId,
708
+ source: path.relative(projectPath, domainChain.path),
709
+ triads: buildCoreOntologySummary(specOntologyScope)
710
+ });
711
+ }
616
712
  const taskSummary = summarizeTasks(tasks);
617
713
  specs.push({
618
714
  spec_id: specId,
@@ -623,10 +719,15 @@ async function runCapabilityExtractCommand(options = {}, dependencies = {}) {
623
719
  title: task.title,
624
720
  status: task.status
625
721
  })),
722
+ ontology_scope: specOntologyScope,
723
+ ontology_source: domainChain.exists ? path.relative(projectPath, domainChain.path) : null,
724
+ ontology_error: domainChain.error || null,
626
725
  task_error: taskError
627
726
  });
628
727
  }
629
728
 
729
+ const ontologyScope = mergeOntologyScopes(ontologyScopes);
730
+ const ontologyCore = buildCoreOntologySummary(ontologyScope);
630
731
  const payload = {
631
732
  mode: 'capability-extract',
632
733
  scene_id: sceneId,
@@ -636,7 +737,15 @@ async function runCapabilityExtractCommand(options = {}, dependencies = {}) {
636
737
  spec_count: specIds.length
637
738
  },
638
739
  specs,
639
- summary: buildCandidateSummary(specs)
740
+ ontology_scope: ontologyScope,
741
+ ontology_core: ontologyCore,
742
+ ontology_evidence: ontologyEvidence,
743
+ summary: {
744
+ ...buildCandidateSummary(specs),
745
+ ontology_triads_ready: ontologyCore.ready,
746
+ ontology_triads_coverage_ratio: ontologyCore.coverage_ratio,
747
+ ontology_missing_triads: ontologyCore.missing
748
+ }
640
749
  };
641
750
 
642
751
  const outputPath = normalizeText(options.out) || buildDefaultCandidatePath(sceneId);
@@ -754,6 +863,11 @@ async function runCapabilityRegisterCommand(options = {}, dependencies = {}) {
754
863
  if (!templateCandidate || !templateCandidate.template_id) {
755
864
  throw new Error('template_id missing in capability template candidate');
756
865
  }
866
+ const ontologySummary = assertCoreOntologySummary(
867
+ buildCoreOntologySummary(templateCandidate.ontology_scope),
868
+ 'capability template'
869
+ );
870
+ templateCandidate.ontology_scope = ontologySummary.ontology_scope;
757
871
 
758
872
  const exportDir = normalizeText(options.out) || buildDefaultExportDir(templateCandidate.template_id);
759
873
  const outputDirAbs = path.join(projectPath, exportDir);
@@ -772,6 +886,7 @@ async function runCapabilityRegisterCommand(options = {}, dependencies = {}) {
772
886
  mode: 'capability-register',
773
887
  template_id: templateCandidate.template_id,
774
888
  output_dir: exportDir,
889
+ ontology_core: ontologySummary,
775
890
  files: [
776
891
  path.join(exportDir, 'capability-template.json'),
777
892
  path.join(exportDir, 'template-registry.json')
@@ -922,6 +1037,7 @@ async function matchCapabilityTemplates(options = {}) {
922
1037
  description: template.description,
923
1038
  category: template.category,
924
1039
  risk_level: template.risk_level,
1040
+ ontology_core: template.ontology_core || buildCoreOntologySummary(template.ontology_scope || {}),
925
1041
  score: Math.round(totalScore * 100),
926
1042
  score_components: {
927
1043
  ontology: Number(overlap.score.toFixed(3)),
@@ -1006,7 +1122,8 @@ async function useCapabilityTemplate(options = {}) {
1006
1122
  name: template.name,
1007
1123
  source: template.source,
1008
1124
  description: template.description,
1009
- ontology_scope: template.ontology_scope || {}
1125
+ ontology_scope: template.ontology_scope || {},
1126
+ ontology_core: template.ontology_core || buildCoreOntologySummary(template.ontology_scope || {})
1010
1127
  },
1011
1128
  spec_id: specId,
1012
1129
  recommended_tasks: recommendedTasks
@@ -124,12 +124,16 @@ class RegistryParser {
124
124
  */
125
125
  normalizeTemplateEntry(template = {}) {
126
126
  const templateType = this._resolveTemplateType(template);
127
+ const ontologyScope = this._normalizeOntologyScope(template.ontology_scope);
127
128
  const normalized = {
128
129
  ...template,
129
130
  template_type: templateType,
130
131
  min_sce_version: template.min_sce_version ?? null,
131
132
  max_sce_version: template.max_sce_version ?? null,
132
- ontology_scope: this._normalizeOntologyScope(template.ontology_scope),
133
+ ontology_scope: ontologyScope,
134
+ ontology_core: templateType === 'capability-template'
135
+ ? this._buildCapabilityOntologyCore(ontologyScope)
136
+ : null,
133
137
  risk_level: template.risk_level ?? null,
134
138
  rollback_contract: this._normalizeRollbackContract(template.rollback_contract),
135
139
  applicable_scenarios: Array.isArray(template.applicable_scenarios) ? template.applicable_scenarios : [],
@@ -235,10 +239,10 @@ class RegistryParser {
235
239
  if (!this._isPlainObject(ontologyScope)) {
236
240
  errors.push(`${prefix}: capability-template requires "ontology_scope" object`);
237
241
  } else {
238
- const hasAnyScope = ['domains', 'entities', 'relations', 'business_rules', 'decisions']
239
- .some((fieldName) => Array.isArray(ontologyScope[fieldName]) && ontologyScope[fieldName].length > 0);
240
- if (!hasAnyScope) {
241
- errors.push(`${prefix}: capability-template ontology_scope must include at least one non-empty scope array`);
242
+ const normalizedOntologyScope = this._normalizeOntologyScope(ontologyScope);
243
+ const ontologyCore = this._buildCapabilityOntologyCore(normalizedOntologyScope);
244
+ if (!ontologyCore.ready) {
245
+ errors.push(`${prefix}: capability-template missing required ontology triads: ${ontologyCore.missing.join(', ')}`);
242
246
  }
243
247
  }
244
248
  }
@@ -655,6 +659,42 @@ class RegistryParser {
655
659
  return Object.keys(normalized).length > 0 ? normalized : null;
656
660
  }
657
661
 
662
+ _buildCapabilityOntologyCore(ontologyScope) {
663
+ const normalized = this._normalizeOntologyScope(ontologyScope) || {};
664
+ const entities = Array.isArray(normalized.entities) ? normalized.entities : [];
665
+ const relations = Array.isArray(normalized.relations) ? normalized.relations : [];
666
+ const businessRules = Array.isArray(normalized.business_rules) ? normalized.business_rules : [];
667
+ const decisions = Array.isArray(normalized.decisions) ? normalized.decisions : [];
668
+ const triads = {
669
+ entity_relation: {
670
+ required_fields: ['entities', 'relations'],
671
+ entity_count: entities.length,
672
+ relation_count: relations.length,
673
+ passed: entities.length > 0 && relations.length > 0
674
+ },
675
+ business_rules: {
676
+ required_fields: ['business_rules'],
677
+ count: businessRules.length,
678
+ passed: businessRules.length > 0
679
+ },
680
+ decision_strategy: {
681
+ required_fields: ['decisions'],
682
+ count: decisions.length,
683
+ passed: decisions.length > 0
684
+ }
685
+ };
686
+ const missing = Object.entries(triads)
687
+ .filter(([, value]) => !value.passed)
688
+ .map(([key]) => key);
689
+
690
+ return {
691
+ triads,
692
+ ready: missing.length === 0,
693
+ missing,
694
+ coverage_ratio: Number(((3 - missing.length) / 3).toFixed(3))
695
+ };
696
+ }
697
+
658
698
  /**
659
699
  * Normalizes rollback contract
660
700
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scene-capability-engine",
3
- "version": "3.6.15",
3
+ "version": "3.6.16",
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": {