scene-capability-engine 3.6.15 → 3.6.17
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 +17 -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 +26 -2
- package/lib/commands/capability.js +170 -18
- package/lib/templates/registry-parser.js +45 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,10 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.6.17] - 2026-03-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Capability catalog/list/search/show/match/use payloads now expose `ontology_core_ui` for direct UI badge rendering.
|
|
14
|
+
- Magicball capability library guide now documents direct triad badge fields for list and match views.
|
|
15
|
+
|
|
16
|
+
## [3.6.16] - 2026-03-06
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- Capability registry validation now enforces ontology core triads for all capability templates.
|
|
20
|
+
- Capability catalog, match, and use payloads now expose `ontology_core` for direct UI rendering.
|
|
21
|
+
- Capability extract now reports triad readiness in candidate summaries.
|
|
22
|
+
|
|
10
23
|
## [3.6.15] - 2026-03-06
|
|
11
24
|
|
|
12
25
|
### Added
|
|
13
26
|
- Capability plan apply result now includes preview lines + skipped titles for UI feedback.
|
|
27
|
+
- Capability iteration now enforces ontology core triads by default and exposes triad readiness in catalog/match/use payloads:
|
|
28
|
+
- entity + relation
|
|
29
|
+
- business rule
|
|
30
|
+
- decision strategy
|
|
14
31
|
|
|
15
32
|
## [3.6.14] - 2026-03-06
|
|
16
33
|
|
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/list/search/show/match/use` responses now include `ontology_core` and `ontology_core_ui` 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 使用计划
|
|
@@ -84,6 +90,14 @@ sce capability use --template <template-id> --spec <spec-id> --apply --json
|
|
|
84
90
|
|
|
85
91
|
## 5. 输出结构(关键字段)
|
|
86
92
|
|
|
93
|
+
列表、详情、匹配、使用计划里的模板对象都应直接消费:
|
|
94
|
+
- `ontology_core`
|
|
95
|
+
- `ontology_core_ui.ready`
|
|
96
|
+
- `ontology_core_ui.coverage_percent`
|
|
97
|
+
- `ontology_core_ui.missing`
|
|
98
|
+
- `ontology_core_ui.triads.entity_relation|business_rules|decision_strategy`
|
|
99
|
+
|
|
100
|
+
|
|
87
101
|
### capability-match
|
|
88
102
|
```json
|
|
89
103
|
{
|
|
@@ -94,6 +108,16 @@ sce capability use --template <template-id> --spec <spec-id> --apply --json
|
|
|
94
108
|
"matches": [
|
|
95
109
|
{
|
|
96
110
|
"template_id": "customer-order-core",
|
|
111
|
+
"ontology_core_ui": {
|
|
112
|
+
"ready": true,
|
|
113
|
+
"coverage_percent": 100,
|
|
114
|
+
"missing": [],
|
|
115
|
+
"triads": {
|
|
116
|
+
"entity_relation": true,
|
|
117
|
+
"business_rules": true,
|
|
118
|
+
"decision_strategy": true
|
|
119
|
+
}
|
|
120
|
+
},
|
|
97
121
|
"score": 82,
|
|
98
122
|
"score_components": {
|
|
99
123
|
"ontology": 0.72,
|
|
@@ -330,6 +330,115 @@ 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
|
+
|
|
410
|
+
function buildOntologyCoreUiState(summary) {
|
|
411
|
+
const details = summary || buildCoreOntologySummary(createEmptyOntologyScope());
|
|
412
|
+
const labels = {
|
|
413
|
+
entity_relation: 'entity_relation',
|
|
414
|
+
business_rules: 'business_rules',
|
|
415
|
+
decision_strategy: 'decision_strategy'
|
|
416
|
+
};
|
|
417
|
+
return {
|
|
418
|
+
ready: details.ready === true,
|
|
419
|
+
coverage_ratio: Number(details.coverage_ratio || 0),
|
|
420
|
+
coverage_percent: Math.round(Number(details.coverage_ratio || 0) * 100),
|
|
421
|
+
missing: Array.isArray(details.missing) ? details.missing : [],
|
|
422
|
+
missing_labels: (Array.isArray(details.missing) ? details.missing : []).map((key) => labels[key] || key),
|
|
423
|
+
triads: {
|
|
424
|
+
entity_relation: Boolean(details.triads && details.triads.entity_relation && details.triads.entity_relation.passed),
|
|
425
|
+
business_rules: Boolean(details.triads && details.triads.business_rules && details.triads.business_rules.passed),
|
|
426
|
+
decision_strategy: Boolean(details.triads && details.triads.decision_strategy && details.triads.decision_strategy.passed)
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function enrichCapabilityTemplateForUi(template) {
|
|
432
|
+
const ontologyCore = template && template.ontology_core
|
|
433
|
+
? template.ontology_core
|
|
434
|
+
: buildCoreOntologySummary(template && template.ontology_scope ? template.ontology_scope : createEmptyOntologyScope());
|
|
435
|
+
return {
|
|
436
|
+
...template,
|
|
437
|
+
ontology_core: ontologyCore,
|
|
438
|
+
ontology_core_ui: buildOntologyCoreUiState(ontologyCore)
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
333
442
|
async function loadSceneIndexFromFile(projectPath, fileSystem) {
|
|
334
443
|
const indexPath = path.join(projectPath, '.sce', 'spec-governance', 'scene-index.json');
|
|
335
444
|
if (!await fileSystem.pathExists(indexPath)) {
|
|
@@ -504,13 +613,22 @@ function buildScoreFromCandidate(candidate) {
|
|
|
504
613
|
const reuseScore = Math.min(100, Math.round((specCount / 3) * 100));
|
|
505
614
|
const stabilityScore = Math.round(completionRate * 100);
|
|
506
615
|
const riskScore = Math.min(100, Math.round((1 - completionRate) * 100));
|
|
507
|
-
const
|
|
616
|
+
const ontologySummary = buildCoreOntologySummary(candidate && candidate.ontology_scope);
|
|
617
|
+
const ontologyCoreScore = Math.round(ontologySummary.coverage_ratio * 100);
|
|
618
|
+
const valueScore = Math.round(
|
|
619
|
+
(stabilityScore * 0.4) +
|
|
620
|
+
(reuseScore * 0.2) +
|
|
621
|
+
((100 - riskScore) * 0.1) +
|
|
622
|
+
(ontologyCoreScore * 0.3)
|
|
623
|
+
);
|
|
508
624
|
|
|
509
625
|
return {
|
|
510
626
|
completion_rate: Number(completionRate.toFixed(3)),
|
|
511
627
|
reuse_score: reuseScore,
|
|
512
628
|
stability_score: stabilityScore,
|
|
513
629
|
risk_score: riskScore,
|
|
630
|
+
ontology_core_score: ontologyCoreScore,
|
|
631
|
+
ontology_core: ontologySummary,
|
|
514
632
|
value_score: valueScore
|
|
515
633
|
};
|
|
516
634
|
}
|
|
@@ -526,15 +644,12 @@ function buildTemplateCandidate(candidate, mapping, options) {
|
|
|
526
644
|
|| `Capability template derived from ${sceneId}`;
|
|
527
645
|
const category = normalizeText(options && options.category) || 'capability';
|
|
528
646
|
const tags = normalizeStringArray(options && options.tags);
|
|
529
|
-
const ontologyScope = (
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
business_rules: [],
|
|
536
|
-
decisions: []
|
|
537
|
-
};
|
|
647
|
+
const ontologyScope = normalizeOntologyScope(
|
|
648
|
+
(mapping && mapping.ontology_scope && typeof mapping.ontology_scope === 'object')
|
|
649
|
+
? mapping.ontology_scope
|
|
650
|
+
: candidate && candidate.ontology_scope
|
|
651
|
+
);
|
|
652
|
+
const ontologyCore = buildCoreOntologySummary(ontologyScope);
|
|
538
653
|
|
|
539
654
|
return {
|
|
540
655
|
mode: 'capability-template',
|
|
@@ -546,6 +661,7 @@ function buildTemplateCandidate(candidate, mapping, options) {
|
|
|
546
661
|
scene_id: sceneId,
|
|
547
662
|
source_candidate: candidate,
|
|
548
663
|
ontology_scope: ontologyScope,
|
|
664
|
+
ontology_core: ontologyCore,
|
|
549
665
|
tags,
|
|
550
666
|
created_at: new Date().toISOString()
|
|
551
667
|
};
|
|
@@ -599,6 +715,8 @@ async function runCapabilityExtractCommand(options = {}, dependencies = {}) {
|
|
|
599
715
|
|
|
600
716
|
const taskClaimer = new TaskClaimer();
|
|
601
717
|
const specs = [];
|
|
718
|
+
const ontologyScopes = [];
|
|
719
|
+
const ontologyEvidence = [];
|
|
602
720
|
|
|
603
721
|
for (const specId of specIds) {
|
|
604
722
|
const tasksPath = path.join(projectPath, '.sce', 'specs', specId, 'tasks.md');
|
|
@@ -613,6 +731,16 @@ async function runCapabilityExtractCommand(options = {}, dependencies = {}) {
|
|
|
613
731
|
} else {
|
|
614
732
|
taskError = 'tasks.md missing';
|
|
615
733
|
}
|
|
734
|
+
const domainChain = await loadSpecDomainChain(projectPath, specId, fileSystem);
|
|
735
|
+
const specOntologyScope = domainChain.payload ? buildOntologyScopeFromChain(domainChain.payload) : createEmptyOntologyScope();
|
|
736
|
+
if (domainChain.payload) {
|
|
737
|
+
ontologyScopes.push(specOntologyScope);
|
|
738
|
+
ontologyEvidence.push({
|
|
739
|
+
spec_id: specId,
|
|
740
|
+
source: path.relative(projectPath, domainChain.path),
|
|
741
|
+
triads: buildCoreOntologySummary(specOntologyScope)
|
|
742
|
+
});
|
|
743
|
+
}
|
|
616
744
|
const taskSummary = summarizeTasks(tasks);
|
|
617
745
|
specs.push({
|
|
618
746
|
spec_id: specId,
|
|
@@ -623,10 +751,15 @@ async function runCapabilityExtractCommand(options = {}, dependencies = {}) {
|
|
|
623
751
|
title: task.title,
|
|
624
752
|
status: task.status
|
|
625
753
|
})),
|
|
754
|
+
ontology_scope: specOntologyScope,
|
|
755
|
+
ontology_source: domainChain.exists ? path.relative(projectPath, domainChain.path) : null,
|
|
756
|
+
ontology_error: domainChain.error || null,
|
|
626
757
|
task_error: taskError
|
|
627
758
|
});
|
|
628
759
|
}
|
|
629
760
|
|
|
761
|
+
const ontologyScope = mergeOntologyScopes(ontologyScopes);
|
|
762
|
+
const ontologyCore = buildCoreOntologySummary(ontologyScope);
|
|
630
763
|
const payload = {
|
|
631
764
|
mode: 'capability-extract',
|
|
632
765
|
scene_id: sceneId,
|
|
@@ -636,7 +769,15 @@ async function runCapabilityExtractCommand(options = {}, dependencies = {}) {
|
|
|
636
769
|
spec_count: specIds.length
|
|
637
770
|
},
|
|
638
771
|
specs,
|
|
639
|
-
|
|
772
|
+
ontology_scope: ontologyScope,
|
|
773
|
+
ontology_core: ontologyCore,
|
|
774
|
+
ontology_evidence: ontologyEvidence,
|
|
775
|
+
summary: {
|
|
776
|
+
...buildCandidateSummary(specs),
|
|
777
|
+
ontology_triads_ready: ontologyCore.ready,
|
|
778
|
+
ontology_triads_coverage_ratio: ontologyCore.coverage_ratio,
|
|
779
|
+
ontology_missing_triads: ontologyCore.missing
|
|
780
|
+
}
|
|
640
781
|
};
|
|
641
782
|
|
|
642
783
|
const outputPath = normalizeText(options.out) || buildDefaultCandidatePath(sceneId);
|
|
@@ -754,6 +895,11 @@ async function runCapabilityRegisterCommand(options = {}, dependencies = {}) {
|
|
|
754
895
|
if (!templateCandidate || !templateCandidate.template_id) {
|
|
755
896
|
throw new Error('template_id missing in capability template candidate');
|
|
756
897
|
}
|
|
898
|
+
const ontologySummary = assertCoreOntologySummary(
|
|
899
|
+
buildCoreOntologySummary(templateCandidate.ontology_scope),
|
|
900
|
+
'capability template'
|
|
901
|
+
);
|
|
902
|
+
templateCandidate.ontology_scope = ontologySummary.ontology_scope;
|
|
757
903
|
|
|
758
904
|
const exportDir = normalizeText(options.out) || buildDefaultExportDir(templateCandidate.template_id);
|
|
759
905
|
const outputDirAbs = path.join(projectPath, exportDir);
|
|
@@ -772,6 +918,7 @@ async function runCapabilityRegisterCommand(options = {}, dependencies = {}) {
|
|
|
772
918
|
mode: 'capability-register',
|
|
773
919
|
template_id: templateCandidate.template_id,
|
|
774
920
|
output_dir: exportDir,
|
|
921
|
+
ontology_core: ontologySummary,
|
|
775
922
|
files: [
|
|
776
923
|
path.join(exportDir, 'capability-template.json'),
|
|
777
924
|
path.join(exportDir, 'template-registry.json')
|
|
@@ -797,7 +944,8 @@ function displayCapabilityCatalog(templates, options = {}) {
|
|
|
797
944
|
}
|
|
798
945
|
return;
|
|
799
946
|
}
|
|
800
|
-
templates.forEach((
|
|
947
|
+
templates.forEach((rawTemplate) => {
|
|
948
|
+
const template = enrichCapabilityTemplateForUi(rawTemplate);
|
|
801
949
|
const sourcePrefix = template.source && template.source !== 'official'
|
|
802
950
|
? chalk.gray(`[${template.source}] `)
|
|
803
951
|
: '';
|
|
@@ -811,13 +959,13 @@ function displayCapabilityCatalog(templates, options = {}) {
|
|
|
811
959
|
|
|
812
960
|
async function listCapabilityCatalog(options = {}) {
|
|
813
961
|
const manager = new TemplateManager();
|
|
814
|
-
const templates = await manager.listTemplates({
|
|
962
|
+
const templates = (await manager.listTemplates({
|
|
815
963
|
category: options.category,
|
|
816
964
|
source: options.source,
|
|
817
965
|
templateType: 'capability-template',
|
|
818
966
|
compatibleWith: options.compatibleWith,
|
|
819
967
|
riskLevel: options.risk
|
|
820
|
-
});
|
|
968
|
+
})).map((template) => enrichCapabilityTemplateForUi(template));
|
|
821
969
|
if (normalizeBoolean(options.json, false)) {
|
|
822
970
|
return {
|
|
823
971
|
mode: 'capability-catalog-list',
|
|
@@ -830,13 +978,13 @@ async function listCapabilityCatalog(options = {}) {
|
|
|
830
978
|
|
|
831
979
|
async function searchCapabilityCatalog(keyword, options = {}) {
|
|
832
980
|
const manager = new TemplateManager();
|
|
833
|
-
const templates = await manager.searchTemplates(keyword, {
|
|
981
|
+
const templates = (await manager.searchTemplates(keyword, {
|
|
834
982
|
category: options.category,
|
|
835
983
|
source: options.source,
|
|
836
984
|
templateType: 'capability-template',
|
|
837
985
|
compatibleWith: options.compatibleWith,
|
|
838
986
|
riskLevel: options.risk
|
|
839
|
-
});
|
|
987
|
+
})).map((template) => enrichCapabilityTemplateForUi(template));
|
|
840
988
|
if (normalizeBoolean(options.json, false)) {
|
|
841
989
|
return {
|
|
842
990
|
mode: 'capability-catalog-search',
|
|
@@ -850,7 +998,7 @@ async function searchCapabilityCatalog(keyword, options = {}) {
|
|
|
850
998
|
|
|
851
999
|
async function showCapabilityTemplate(templatePath, options = {}) {
|
|
852
1000
|
const manager = new TemplateManager();
|
|
853
|
-
const template = await manager.showTemplate(templatePath);
|
|
1001
|
+
const template = enrichCapabilityTemplateForUi(await manager.showTemplate(templatePath));
|
|
854
1002
|
const { sourceName, templateId } = parseTemplatePath(templatePath);
|
|
855
1003
|
await manager.ensureCached(sourceName);
|
|
856
1004
|
const sourcePath = manager.cacheManager.getSourceCachePath(sourceName);
|
|
@@ -922,6 +1070,8 @@ async function matchCapabilityTemplates(options = {}) {
|
|
|
922
1070
|
description: template.description,
|
|
923
1071
|
category: template.category,
|
|
924
1072
|
risk_level: template.risk_level,
|
|
1073
|
+
ontology_core: template.ontology_core || buildCoreOntologySummary(template.ontology_scope || {}),
|
|
1074
|
+
ontology_core_ui: buildOntologyCoreUiState(template.ontology_core || buildCoreOntologySummary(template.ontology_scope || {})),
|
|
925
1075
|
score: Math.round(totalScore * 100),
|
|
926
1076
|
score_components: {
|
|
927
1077
|
ontology: Number(overlap.score.toFixed(3)),
|
|
@@ -1006,7 +1156,9 @@ async function useCapabilityTemplate(options = {}) {
|
|
|
1006
1156
|
name: template.name,
|
|
1007
1157
|
source: template.source,
|
|
1008
1158
|
description: template.description,
|
|
1009
|
-
ontology_scope: template.ontology_scope || {}
|
|
1159
|
+
ontology_scope: template.ontology_scope || {},
|
|
1160
|
+
ontology_core: template.ontology_core || buildCoreOntologySummary(template.ontology_scope || {}),
|
|
1161
|
+
ontology_core_ui: buildOntologyCoreUiState(template.ontology_core || buildCoreOntologySummary(template.ontology_scope || {}))
|
|
1010
1162
|
},
|
|
1011
1163
|
spec_id: specId,
|
|
1012
1164
|
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