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 +11 -0
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/docs/command-reference.md +8 -0
- package/docs/magicball-capability-library.md +8 -2
- package/lib/commands/capability.js +129 -12
- package/lib/templates/registry-parser.js +45 -5
- package/package.json +1 -1
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
package/README.zh.md
CHANGED
|
@@ -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
|
|
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 = (
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
239
|
-
|
|
240
|
-
if (!
|
|
241
|
-
errors.push(`${prefix}: capability-template
|
|
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