scene-capability-engine 3.6.45 → 3.6.46
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/docs/releases/README.md +1 -0
- package/docs/releases/v3.6.46.md +23 -0
- package/docs/zh/releases/README.md +1 -0
- package/docs/zh/releases/v3.6.46.md +23 -0
- package/package.json +4 -2
- package/scripts/auto-strategy-router.js +231 -0
- package/scripts/capability-mapping-report.js +339 -0
- package/scripts/check-branding-consistency.js +140 -0
- package/scripts/check-sce-tracking.js +54 -0
- package/scripts/check-skip-allowlist.js +94 -0
- package/scripts/errorbook-registry-health-gate.js +172 -0
- package/scripts/errorbook-release-gate.js +132 -0
- package/scripts/failure-attribution-repair.js +317 -0
- package/scripts/git-managed-gate.js +464 -0
- package/scripts/interactive-approval-event-projection.js +400 -0
- package/scripts/interactive-approval-workflow.js +829 -0
- package/scripts/interactive-authorization-tier-evaluate.js +413 -0
- package/scripts/interactive-change-plan-gate.js +225 -0
- package/scripts/interactive-context-bridge.js +617 -0
- package/scripts/interactive-customization-loop.js +1690 -0
- package/scripts/interactive-dialogue-governance.js +842 -0
- package/scripts/interactive-feedback-log.js +253 -0
- package/scripts/interactive-flow-smoke.js +238 -0
- package/scripts/interactive-flow.js +1059 -0
- package/scripts/interactive-governance-report.js +1112 -0
- package/scripts/interactive-intent-build.js +707 -0
- package/scripts/interactive-loop-smoke.js +215 -0
- package/scripts/interactive-moqui-adapter.js +304 -0
- package/scripts/interactive-plan-build.js +426 -0
- package/scripts/interactive-runtime-policy-evaluate.js +495 -0
- package/scripts/interactive-work-order-build.js +552 -0
- package/scripts/matrix-regression-gate.js +167 -0
- package/scripts/moqui-core-regression-suite.js +397 -0
- package/scripts/moqui-lexicon-audit.js +651 -0
- package/scripts/moqui-matrix-remediation-phased-runner.js +865 -0
- package/scripts/moqui-matrix-remediation-queue.js +852 -0
- package/scripts/moqui-metadata-extract.js +1340 -0
- package/scripts/moqui-rebuild-gate.js +167 -0
- package/scripts/moqui-release-summary.js +729 -0
- package/scripts/moqui-standard-rebuild.js +1370 -0
- package/scripts/moqui-template-baseline-report.js +682 -0
- package/scripts/npm-package-runtime-asset-check.js +221 -0
- package/scripts/problem-closure-gate.js +441 -0
- package/scripts/release-asset-integrity-check.js +216 -0
- package/scripts/release-asset-nonempty-normalize.js +166 -0
- package/scripts/release-drift-evaluate.js +223 -0
- package/scripts/release-drift-signals.js +255 -0
- package/scripts/release-governance-snapshot-export.js +132 -0
- package/scripts/release-ops-weekly-summary.js +934 -0
- package/scripts/release-risk-remediation-bundle.js +315 -0
- package/scripts/release-weekly-ops-gate.js +423 -0
- package/scripts/state-migration-reconciliation-gate.js +110 -0
- package/scripts/state-storage-tiering-audit.js +337 -0
- package/scripts/steering-content-audit.js +393 -0
- package/scripts/symbol-evidence-locate.js +366 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.6.46] - 2026-03-13
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Added `scripts/npm-package-runtime-asset-check.js` and the `npm run gate:npm-runtime-assets` release gate to verify that every runtime `scripts/*.js` file is present in the npm pack dry-run payload before publish.
|
|
14
|
+
- Added Spec `123-00-npm-package-runtime-asset-integrity` with requirements, design, tasks, and deliverables to formalize the npm runtime asset integrity fix.
|
|
15
|
+
- Added unit coverage for runtime script discovery, pack payload parsing, missing-script detection, and pack execution failure reporting.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- Fixed npm package publish contents by including the root `scripts/` directory in the package `files` allowlist, so installed `sce` tarballs no longer crash on missing runtime assets such as `scripts/git-managed-gate.js`.
|
|
19
|
+
- Fixed the new runtime asset gate on Windows by executing `npm pack --json --dry-run` through a shell-compatible path and raising the output buffer ceiling for large package manifests.
|
|
20
|
+
|
|
10
21
|
## [3.6.45] - 2026-03-13
|
|
11
22
|
|
|
12
23
|
### Added
|
package/docs/releases/README.md
CHANGED
|
@@ -9,6 +9,7 @@ This directory stores release-facing documents:
|
|
|
9
9
|
## Archived Versions
|
|
10
10
|
|
|
11
11
|
- [Release checklist](../release-checklist.md)
|
|
12
|
+
- [v3.6.46 release notes](./v3.6.46.md)
|
|
12
13
|
- [v3.6.45 release notes](./v3.6.45.md)
|
|
13
14
|
- [v3.6.44 release notes](./v3.6.44.md)
|
|
14
15
|
- [v3.6.43 release notes](./v3.6.43.md)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# v3.6.46 Release Notes
|
|
2
|
+
|
|
3
|
+
Release date: 2026-03-13
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- Fixed the npm package publish surface so the root `scripts/` directory is included in the released tarball again.
|
|
8
|
+
- Added `npm run gate:npm-runtime-assets`, which inspects `npm pack --json --dry-run` output and blocks publish if any runtime `scripts/*.js` file would be missing from the package.
|
|
9
|
+
- Hardened the new gate for Windows by running `npm pack` through a shell-compatible path and increasing the command output buffer for the repository's large manifest.
|
|
10
|
+
|
|
11
|
+
## Verification
|
|
12
|
+
|
|
13
|
+
- `npx jest tests/unit/scripts/npm-package-runtime-asset-check.test.js --runInBand`
|
|
14
|
+
- `node scripts/npm-package-runtime-asset-check.js --json`
|
|
15
|
+
- `npm pack --json --dry-run`
|
|
16
|
+
- Installed the packed `scene-capability-engine-3.6.46.tgz` into a clean temp project and verified:
|
|
17
|
+
- `node node_modules/scene-capability-engine/bin/scene-capability-engine.js --version`
|
|
18
|
+
- `node node_modules/scene-capability-engine/bin/scene-capability-engine.js workspace delivery-audit --json`
|
|
19
|
+
|
|
20
|
+
## Release Notes
|
|
21
|
+
|
|
22
|
+
- This release fixes the broken runtime asset packaging observed in `3.6.44` and `3.6.45`. The failure mode was a missing `scripts/git-managed-gate.js`, but the underlying defect affected the full root `scripts/` runtime surface.
|
|
23
|
+
- For already-installed `3.6.44` or `3.6.45`, the smallest emergency patch is to restore `scripts/git-managed-gate.js` into the installed package. `3.6.46` is the proper fix and should replace those versions.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# v3.6.46 发布说明
|
|
2
|
+
|
|
3
|
+
发布日期:2026-03-13
|
|
4
|
+
|
|
5
|
+
## 重点变化
|
|
6
|
+
|
|
7
|
+
- 修复 npm 包发布内容,重新把根目录 `scripts/` 一并纳入 tarball,避免安装后的 `sce` 因缺少运行时脚本而崩溃。
|
|
8
|
+
- 新增 `npm run gate:npm-runtime-assets`,基于 `npm pack --json --dry-run` 检查所有运行时 `scripts/*.js` 是否都会进入发布包,缺失时直接阻断发布。
|
|
9
|
+
- 加固了该门禁在 Windows 下的执行路径,避免 `npm pack` 命令解析和大体积输出导致误判。
|
|
10
|
+
|
|
11
|
+
## 验证
|
|
12
|
+
|
|
13
|
+
- `npx jest tests/unit/scripts/npm-package-runtime-asset-check.test.js --runInBand`
|
|
14
|
+
- `node scripts/npm-package-runtime-asset-check.js --json`
|
|
15
|
+
- `npm pack --json --dry-run`
|
|
16
|
+
- 将打出的 `scene-capability-engine-3.6.46.tgz` 安装到全新临时工程后验证:
|
|
17
|
+
- `node node_modules/scene-capability-engine/bin/scene-capability-engine.js --version`
|
|
18
|
+
- `node node_modules/scene-capability-engine/bin/scene-capability-engine.js workspace delivery-audit --json`
|
|
19
|
+
|
|
20
|
+
## 发布说明
|
|
21
|
+
|
|
22
|
+
- 这次补丁修复的是 `3.6.44` 与 `3.6.45` 中已经暴露的 npm 运行时缺件问题。表面报错是 `scripts/git-managed-gate.js` 缺失,但根因是整个根级 `scripts/` 运行时面没有被发布进去。
|
|
23
|
+
- 对已经安装的 `3.6.44` 或 `3.6.45`,最小应急补丁是补回安装目录中的 `scripts/git-managed-gate.js`;`3.6.46` 才是完整的正式修复版本。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scene-capability-engine",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.46",
|
|
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": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"bin/",
|
|
12
12
|
"lib/",
|
|
13
|
+
"scripts/",
|
|
13
14
|
"template/",
|
|
14
15
|
"!template/.sce/tools/__pycache__/",
|
|
15
16
|
"!template/.sce/tools/**/*.pyc",
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
"test:interactive-flow-smoke": "node scripts/interactive-flow-smoke.js --json",
|
|
38
39
|
"test:skip-audit": "node scripts/check-skip-allowlist.js",
|
|
39
40
|
"test:sce-tracking": "node scripts/check-sce-tracking.js",
|
|
41
|
+
"gate:npm-runtime-assets": "node scripts/npm-package-runtime-asset-check.js --fail-on-violation",
|
|
40
42
|
"test:brand-consistency": "node scripts/check-branding-consistency.js",
|
|
41
43
|
"audit:steering": "node scripts/steering-content-audit.js --fail-on-error",
|
|
42
44
|
"audit:state-storage": "node scripts/state-storage-tiering-audit.js",
|
|
@@ -82,7 +84,7 @@
|
|
|
82
84
|
"gate:release-asset-integrity": "node scripts/release-asset-integrity-check.js",
|
|
83
85
|
"report:release-risk-remediation": "node scripts/release-risk-remediation-bundle.js --json",
|
|
84
86
|
"report:moqui-core-regression": "node scripts/moqui-core-regression-suite.js --json",
|
|
85
|
-
"prepublishOnly": "npm run test:release && npm run test:skip-audit && npm run test:sce-tracking && npm run test:brand-consistency && npm run audit:steering && npm run gate:git-managed && npm run gate:errorbook-registry-health && npm run gate:errorbook-release && npm run report:interactive-governance -- --fail-on-alert",
|
|
87
|
+
"prepublishOnly": "npm run test:release && npm run test:skip-audit && npm run test:sce-tracking && npm run gate:npm-runtime-assets && npm run test:brand-consistency && npm run audit:steering && npm run gate:git-managed && npm run gate:errorbook-registry-health && npm run gate:errorbook-release && npm run report:interactive-governance -- --fail-on-alert",
|
|
86
88
|
"publish:manual": "npm publish --access public",
|
|
87
89
|
"install-global": "npm install -g .",
|
|
88
90
|
"uninstall-global": "npm uninstall -g scene-capability-engine"
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
function readJsonFile(filePath) {
|
|
8
|
+
const resolved = path.resolve(filePath);
|
|
9
|
+
if (!fs.existsSync(resolved)) {
|
|
10
|
+
throw new Error(`file not found: ${filePath}`);
|
|
11
|
+
}
|
|
12
|
+
return JSON.parse(fs.readFileSync(resolved, 'utf8'));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function toBool(value, fallback = false) {
|
|
16
|
+
if (typeof value === 'boolean') {
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
if (typeof value === 'string') {
|
|
20
|
+
const lowered = value.trim().toLowerCase();
|
|
21
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(lowered)) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(lowered)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return fallback;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function toFiniteNumber(value, fallback = 0) {
|
|
32
|
+
const num = Number(value);
|
|
33
|
+
return Number.isFinite(num) ? num : fallback;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeInput(input = {}) {
|
|
37
|
+
const goalTypeRaw = typeof input.goal_type === 'string' ? input.goal_type.trim().toLowerCase() : '';
|
|
38
|
+
return {
|
|
39
|
+
goal_type: goalTypeRaw || 'unknown',
|
|
40
|
+
requires_write: toBool(input.requires_write, false),
|
|
41
|
+
high_risk: toBool(input.high_risk, false),
|
|
42
|
+
last_run_failed: toBool(input.last_run_failed, false),
|
|
43
|
+
has_rollback_checkpoint: toBool(input.has_rollback_checkpoint, false),
|
|
44
|
+
test_failures: toFiniteNumber(input.test_failures, 0),
|
|
45
|
+
changed_files: toFiniteNumber(input.changed_files, 0),
|
|
46
|
+
user_explicit_answer_only: toBool(input.user_explicit_answer_only, false),
|
|
47
|
+
user_explicit_rollback: toBool(input.user_explicit_rollback, false)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolvePolicy(policy = {}) {
|
|
52
|
+
const fallback = {
|
|
53
|
+
answer_only_goal_types: ['question', 'analysis', 'explain', 'summarize'],
|
|
54
|
+
rollback_when: {
|
|
55
|
+
require_checkpoint: true,
|
|
56
|
+
require_high_risk_or_explicit: true
|
|
57
|
+
},
|
|
58
|
+
code_fix_when_tests_fail: true
|
|
59
|
+
};
|
|
60
|
+
const answerTypes = Array.isArray(policy.answer_only_goal_types)
|
|
61
|
+
? policy.answer_only_goal_types
|
|
62
|
+
.map(item => `${item || ''}`.trim().toLowerCase())
|
|
63
|
+
.filter(Boolean)
|
|
64
|
+
: fallback.answer_only_goal_types;
|
|
65
|
+
return {
|
|
66
|
+
answer_only_goal_types: answerTypes.length > 0 ? answerTypes : fallback.answer_only_goal_types,
|
|
67
|
+
rollback_when: {
|
|
68
|
+
require_checkpoint: toBool(
|
|
69
|
+
policy.rollback_when && policy.rollback_when.require_checkpoint,
|
|
70
|
+
fallback.rollback_when.require_checkpoint
|
|
71
|
+
),
|
|
72
|
+
require_high_risk_or_explicit: toBool(
|
|
73
|
+
policy.rollback_when && policy.rollback_when.require_high_risk_or_explicit,
|
|
74
|
+
fallback.rollback_when.require_high_risk_or_explicit
|
|
75
|
+
)
|
|
76
|
+
},
|
|
77
|
+
code_fix_when_tests_fail: toBool(policy.code_fix_when_tests_fail, fallback.code_fix_when_tests_fail)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function evaluateStrategy(input = {}, policyInput = {}) {
|
|
82
|
+
const payload = normalizeInput(input);
|
|
83
|
+
const policy = resolvePolicy(policyInput);
|
|
84
|
+
const reasons = [];
|
|
85
|
+
let decision = 'answer_only';
|
|
86
|
+
let confidence = 'low';
|
|
87
|
+
|
|
88
|
+
if (payload.user_explicit_answer_only) {
|
|
89
|
+
decision = 'answer_only';
|
|
90
|
+
confidence = 'high';
|
|
91
|
+
reasons.push('explicit user instruction: answer only');
|
|
92
|
+
} else if (payload.user_explicit_rollback) {
|
|
93
|
+
const checkpointOk = !policy.rollback_when.require_checkpoint || payload.has_rollback_checkpoint;
|
|
94
|
+
const riskGateOk = !policy.rollback_when.require_high_risk_or_explicit || payload.high_risk || payload.user_explicit_rollback;
|
|
95
|
+
if (checkpointOk && riskGateOk) {
|
|
96
|
+
decision = 'rollback';
|
|
97
|
+
confidence = 'high';
|
|
98
|
+
reasons.push('explicit user instruction: rollback');
|
|
99
|
+
} else {
|
|
100
|
+
decision = 'answer_only';
|
|
101
|
+
confidence = 'medium';
|
|
102
|
+
reasons.push('rollback requested but preconditions are not met');
|
|
103
|
+
}
|
|
104
|
+
} else if (
|
|
105
|
+
payload.last_run_failed
|
|
106
|
+
&& payload.has_rollback_checkpoint
|
|
107
|
+
&& payload.high_risk
|
|
108
|
+
&& payload.changed_files > 0
|
|
109
|
+
) {
|
|
110
|
+
decision = 'rollback';
|
|
111
|
+
confidence = 'high';
|
|
112
|
+
reasons.push('last run failed with high risk and rollback checkpoint available');
|
|
113
|
+
} else if (
|
|
114
|
+
policy.answer_only_goal_types.includes(payload.goal_type)
|
|
115
|
+
&& !payload.requires_write
|
|
116
|
+
) {
|
|
117
|
+
decision = 'answer_only';
|
|
118
|
+
confidence = 'high';
|
|
119
|
+
reasons.push(`goal_type=${payload.goal_type} with no write requirement`);
|
|
120
|
+
} else if (
|
|
121
|
+
policy.code_fix_when_tests_fail
|
|
122
|
+
&& payload.test_failures > 0
|
|
123
|
+
&& payload.changed_files > 0
|
|
124
|
+
) {
|
|
125
|
+
decision = 'code_fix';
|
|
126
|
+
confidence = 'high';
|
|
127
|
+
reasons.push('tests failing after code changes, prioritize focused repair');
|
|
128
|
+
} else if (payload.requires_write || payload.goal_type === 'feature' || payload.goal_type === 'bugfix') {
|
|
129
|
+
decision = 'code_change';
|
|
130
|
+
confidence = 'medium';
|
|
131
|
+
reasons.push('write-required goal needs implementation changes');
|
|
132
|
+
} else {
|
|
133
|
+
decision = 'answer_only';
|
|
134
|
+
confidence = 'low';
|
|
135
|
+
reasons.push('insufficient signals for safe code changes');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const next_actions = [];
|
|
139
|
+
if (decision === 'rollback') {
|
|
140
|
+
next_actions.push('execute rollback to latest stable checkpoint');
|
|
141
|
+
next_actions.push('collect failure evidence before reattempt');
|
|
142
|
+
} else if (decision === 'code_fix') {
|
|
143
|
+
next_actions.push('locate failing symbols and failing tests first');
|
|
144
|
+
next_actions.push('apply minimal patch and re-run targeted tests');
|
|
145
|
+
} else if (decision === 'code_change') {
|
|
146
|
+
next_actions.push('confirm affected scope and symbol locations');
|
|
147
|
+
next_actions.push('implement change and run validation tests');
|
|
148
|
+
} else {
|
|
149
|
+
next_actions.push('answer request with evidence and constraints');
|
|
150
|
+
next_actions.push('avoid repository writes in current turn');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
mode: 'auto-strategy-router',
|
|
155
|
+
decision,
|
|
156
|
+
confidence,
|
|
157
|
+
reasons,
|
|
158
|
+
next_actions,
|
|
159
|
+
input: payload,
|
|
160
|
+
policy
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function parseArgs(argv = []) {
|
|
165
|
+
const options = {
|
|
166
|
+
input: null,
|
|
167
|
+
inputFile: '',
|
|
168
|
+
policyFile: '',
|
|
169
|
+
json: false
|
|
170
|
+
};
|
|
171
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
172
|
+
const token = argv[i];
|
|
173
|
+
if (token === '--input') {
|
|
174
|
+
options.input = argv[i + 1] || '';
|
|
175
|
+
i += 1;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (token === '--input-file') {
|
|
179
|
+
options.inputFile = argv[i + 1] || '';
|
|
180
|
+
i += 1;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (token === '--policy-file') {
|
|
184
|
+
options.policyFile = argv[i + 1] || '';
|
|
185
|
+
i += 1;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (token === '--json') {
|
|
189
|
+
options.json = true;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (token === '-h' || token === '--help') {
|
|
193
|
+
console.log([
|
|
194
|
+
'Usage:',
|
|
195
|
+
' node scripts/auto-strategy-router.js --input-file <path> [--policy-file <path>] --json',
|
|
196
|
+
' node scripts/auto-strategy-router.js --input \'{\"goal_type\":\"bugfix\",\"requires_write\":true}\' --json'
|
|
197
|
+
].join('\n'));
|
|
198
|
+
process.exit(0);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return options;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (require.main === module) {
|
|
205
|
+
const args = parseArgs(process.argv.slice(2));
|
|
206
|
+
let inputPayload = {};
|
|
207
|
+
let policyPayload = {};
|
|
208
|
+
if (args.inputFile) {
|
|
209
|
+
inputPayload = readJsonFile(args.inputFile);
|
|
210
|
+
} else if (args.input) {
|
|
211
|
+
inputPayload = JSON.parse(args.input);
|
|
212
|
+
}
|
|
213
|
+
if (args.policyFile) {
|
|
214
|
+
policyPayload = readJsonFile(args.policyFile);
|
|
215
|
+
}
|
|
216
|
+
const result = evaluateStrategy(inputPayload, policyPayload);
|
|
217
|
+
if (args.json) {
|
|
218
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
219
|
+
} else {
|
|
220
|
+
process.stdout.write(
|
|
221
|
+
`[auto-strategy-router] decision=${result.decision} confidence=${result.confidence} reasons=${result.reasons.join('; ')}\n`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
module.exports = {
|
|
227
|
+
evaluateStrategy,
|
|
228
|
+
normalizeInput,
|
|
229
|
+
parseArgs,
|
|
230
|
+
resolvePolicy
|
|
231
|
+
};
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
function parseArgs(argv = []) {
|
|
8
|
+
const options = {
|
|
9
|
+
input: '',
|
|
10
|
+
inputFile: '',
|
|
11
|
+
changesFile: '',
|
|
12
|
+
templatesFile: '',
|
|
13
|
+
ontologyFile: '',
|
|
14
|
+
out: '',
|
|
15
|
+
json: false
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
19
|
+
const token = argv[index];
|
|
20
|
+
const next = argv[index + 1];
|
|
21
|
+
if (token === '--input' && next) {
|
|
22
|
+
options.input = next;
|
|
23
|
+
index += 1;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (token === '--input-file' && next) {
|
|
27
|
+
options.inputFile = next;
|
|
28
|
+
index += 1;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (token === '--changes-file' && next) {
|
|
32
|
+
options.changesFile = next;
|
|
33
|
+
index += 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (token === '--templates-file' && next) {
|
|
37
|
+
options.templatesFile = next;
|
|
38
|
+
index += 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (token === '--ontology-file' && next) {
|
|
42
|
+
options.ontologyFile = next;
|
|
43
|
+
index += 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (token === '--out' && next) {
|
|
47
|
+
options.out = next;
|
|
48
|
+
index += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (token === '--json') {
|
|
52
|
+
options.json = true;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (token === '--help' || token === '-h') {
|
|
56
|
+
printHelpAndExit(0);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return options;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function printHelpAndExit(code) {
|
|
64
|
+
const lines = [
|
|
65
|
+
'Usage: node scripts/capability-mapping-report.js [options]',
|
|
66
|
+
'',
|
|
67
|
+
'Options:',
|
|
68
|
+
' --input <json> Inline input payload (changes/templates/ontology)',
|
|
69
|
+
' --input-file <path> Input JSON file (changes/templates/ontology)',
|
|
70
|
+
' --changes-file <path> Changes JSON file (array)',
|
|
71
|
+
' --templates-file <path> Template catalog JSON file (array)',
|
|
72
|
+
' --ontology-file <path> Ontology model JSON file',
|
|
73
|
+
' --out <path> Output report path',
|
|
74
|
+
' --json Print JSON payload',
|
|
75
|
+
' -h, --help Show this help'
|
|
76
|
+
];
|
|
77
|
+
console.log(lines.join('\n'));
|
|
78
|
+
process.exit(code);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function readJsonIfExists(filePath) {
|
|
82
|
+
if (!filePath) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const resolved = path.resolve(process.cwd(), filePath);
|
|
86
|
+
if (!(await fs.pathExists(resolved))) {
|
|
87
|
+
throw new Error(`file not found: ${filePath}`);
|
|
88
|
+
}
|
|
89
|
+
return fs.readJson(resolved);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function normalizeChanges(changes) {
|
|
93
|
+
const array = Array.isArray(changes) ? changes : [];
|
|
94
|
+
return array
|
|
95
|
+
.filter((item) => item && typeof item === 'object')
|
|
96
|
+
.map((item) => {
|
|
97
|
+
const type = `${item.type || item.node_type || ''}`.trim().toLowerCase();
|
|
98
|
+
const name = `${item.name || item.ref || item.id || ''}`.trim();
|
|
99
|
+
const changeId = `${item.change_id || item.id || (type && name ? `${type}:${name}` : name || type || 'unknown')}`;
|
|
100
|
+
return {
|
|
101
|
+
change_id: changeId,
|
|
102
|
+
type: type || 'unknown',
|
|
103
|
+
name: name || changeId,
|
|
104
|
+
operation: `${item.operation || item.action || 'update'}`.trim().toLowerCase()
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function normalizeTemplates(templates) {
|
|
110
|
+
const array = Array.isArray(templates) ? templates : [];
|
|
111
|
+
return array
|
|
112
|
+
.filter((item) => item && typeof item === 'object')
|
|
113
|
+
.map((item) => ({
|
|
114
|
+
id: `${item.id || item.template_id || item.name || 'unknown-template'}`.trim(),
|
|
115
|
+
capabilities: Array.isArray(item.capabilities)
|
|
116
|
+
? item.capabilities.map((capability) => `${capability}`.trim().toLowerCase()).filter(Boolean)
|
|
117
|
+
: []
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizeOntology(ontology = {}) {
|
|
122
|
+
const pickNames = (values) => {
|
|
123
|
+
if (!Array.isArray(values)) {
|
|
124
|
+
return new Set();
|
|
125
|
+
}
|
|
126
|
+
return new Set(
|
|
127
|
+
values
|
|
128
|
+
.map((value) => {
|
|
129
|
+
if (typeof value === 'string') {
|
|
130
|
+
return value.trim().toLowerCase();
|
|
131
|
+
}
|
|
132
|
+
if (value && typeof value === 'object') {
|
|
133
|
+
return `${value.name || value.ref || value.id || ''}`.trim().toLowerCase();
|
|
134
|
+
}
|
|
135
|
+
return '';
|
|
136
|
+
})
|
|
137
|
+
.filter(Boolean)
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
entities: pickNames(ontology.entities),
|
|
143
|
+
business_rules: pickNames(ontology.business_rules),
|
|
144
|
+
decision_strategies: pickNames(ontology.decision_strategies),
|
|
145
|
+
relations: Array.isArray(ontology.relations) ? ontology.relations : []
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function normalizeCapability(type, name) {
|
|
150
|
+
const loweredName = `${name || ''}`.trim().toLowerCase();
|
|
151
|
+
if (!loweredName) {
|
|
152
|
+
return 'unknown:unknown';
|
|
153
|
+
}
|
|
154
|
+
if (type === 'entity') {
|
|
155
|
+
return `entity:${loweredName}`;
|
|
156
|
+
}
|
|
157
|
+
if (type === 'business_rule' || type === 'rule') {
|
|
158
|
+
return `rule:${loweredName}`;
|
|
159
|
+
}
|
|
160
|
+
if (type === 'decision_strategy' || type === 'decision') {
|
|
161
|
+
return `decision:${loweredName}`;
|
|
162
|
+
}
|
|
163
|
+
if (type === 'service') {
|
|
164
|
+
return `service:${loweredName}`;
|
|
165
|
+
}
|
|
166
|
+
if (type === 'scene') {
|
|
167
|
+
return `scene:${loweredName}`;
|
|
168
|
+
}
|
|
169
|
+
return `${type || 'unknown'}:${loweredName}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function capabilityMatches(capability, templateCapability) {
|
|
173
|
+
if (!capability || !templateCapability) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
if (templateCapability === capability) {
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
if (templateCapability.endsWith('*')) {
|
|
180
|
+
const prefix = templateCapability.slice(0, -1);
|
|
181
|
+
return capability.startsWith(prefix);
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function findMappedTemplates(capability, templates) {
|
|
187
|
+
const mapped = [];
|
|
188
|
+
for (const template of templates) {
|
|
189
|
+
if (template.capabilities.some((item) => capabilityMatches(capability, item))) {
|
|
190
|
+
mapped.push(template.id);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return mapped;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function checkOntologyCoverage(type, name, ontology) {
|
|
197
|
+
const loweredName = `${name || ''}`.trim().toLowerCase();
|
|
198
|
+
if (!loweredName) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
if (type === 'entity') {
|
|
202
|
+
return ontology.entities.has(loweredName);
|
|
203
|
+
}
|
|
204
|
+
if (type === 'business_rule' || type === 'rule') {
|
|
205
|
+
return ontology.business_rules.has(loweredName);
|
|
206
|
+
}
|
|
207
|
+
if (type === 'decision_strategy' || type === 'decision') {
|
|
208
|
+
return ontology.decision_strategies.has(loweredName);
|
|
209
|
+
}
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function buildMappingReport({ changes, templates, ontology }) {
|
|
214
|
+
const mappingReport = [];
|
|
215
|
+
const missingCapabilities = [];
|
|
216
|
+
const recommendedTemplates = new Set();
|
|
217
|
+
const ontologyGaps = [];
|
|
218
|
+
|
|
219
|
+
for (const change of changes) {
|
|
220
|
+
const capability = normalizeCapability(change.type, change.name);
|
|
221
|
+
const mappedTemplates = findMappedTemplates(capability, templates);
|
|
222
|
+
const ontologyCovered = checkOntologyCoverage(change.type, change.name, ontology);
|
|
223
|
+
|
|
224
|
+
for (const templateId of mappedTemplates) {
|
|
225
|
+
recommendedTemplates.add(templateId);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (mappedTemplates.length === 0) {
|
|
229
|
+
missingCapabilities.push(capability);
|
|
230
|
+
}
|
|
231
|
+
if (!ontologyCovered) {
|
|
232
|
+
ontologyGaps.push({
|
|
233
|
+
change_id: change.change_id,
|
|
234
|
+
type: change.type,
|
|
235
|
+
name: change.name,
|
|
236
|
+
capability
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const status = mappedTemplates.length > 0
|
|
241
|
+
? 'mapped'
|
|
242
|
+
: (ontologyCovered ? 'missing_template' : 'missing_template_and_ontology_gap');
|
|
243
|
+
|
|
244
|
+
mappingReport.push({
|
|
245
|
+
change_id: change.change_id,
|
|
246
|
+
capability,
|
|
247
|
+
mapped_templates: mappedTemplates,
|
|
248
|
+
ontology_status: ontologyCovered ? 'covered' : 'gap',
|
|
249
|
+
status
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const totalChanges = mappingReport.length;
|
|
254
|
+
const mappedChanges = mappingReport.filter((item) => item.mapped_templates.length > 0).length;
|
|
255
|
+
const coveragePercent = totalChanges === 0
|
|
256
|
+
? 100
|
|
257
|
+
: Number(((mappedChanges / totalChanges) * 100).toFixed(2));
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
mode: 'capability-mapping-report',
|
|
261
|
+
generated_at: new Date().toISOString(),
|
|
262
|
+
mapping_report: mappingReport,
|
|
263
|
+
missing_capabilities: [...new Set(missingCapabilities)],
|
|
264
|
+
recommended_templates: [...recommendedTemplates],
|
|
265
|
+
ontology_gaps: ontologyGaps,
|
|
266
|
+
summary: {
|
|
267
|
+
total_changes: totalChanges,
|
|
268
|
+
mapped_changes: mappedChanges,
|
|
269
|
+
missing_capabilities: [...new Set(missingCapabilities)].length,
|
|
270
|
+
ontology_gaps: ontologyGaps.length,
|
|
271
|
+
coverage_percent: coveragePercent
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function loadInput(options) {
|
|
277
|
+
const inline = options.input ? JSON.parse(options.input) : null;
|
|
278
|
+
const inputFilePayload = options.inputFile ? await readJsonIfExists(options.inputFile) : null;
|
|
279
|
+
const changesPayload = options.changesFile ? await readJsonIfExists(options.changesFile) : null;
|
|
280
|
+
const templatesPayload = options.templatesFile ? await readJsonIfExists(options.templatesFile) : null;
|
|
281
|
+
const ontologyPayload = options.ontologyFile ? await readJsonIfExists(options.ontologyFile) : null;
|
|
282
|
+
|
|
283
|
+
const source = inline || inputFilePayload || {};
|
|
284
|
+
const changes = normalizeChanges(
|
|
285
|
+
changesPayload || source.changes || source.change_set || source.mapping_input
|
|
286
|
+
);
|
|
287
|
+
const templates = normalizeTemplates(
|
|
288
|
+
templatesPayload || source.templates || source.template_catalog || source.scene_templates
|
|
289
|
+
);
|
|
290
|
+
const ontology = normalizeOntology(
|
|
291
|
+
ontologyPayload || source.ontology || source.ontology_model || {}
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
changes,
|
|
296
|
+
templates,
|
|
297
|
+
ontology
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function main() {
|
|
302
|
+
const options = parseArgs(process.argv.slice(2));
|
|
303
|
+
const input = await loadInput(options);
|
|
304
|
+
const payload = buildMappingReport(input);
|
|
305
|
+
|
|
306
|
+
if (options.out) {
|
|
307
|
+
const outPath = path.resolve(process.cwd(), options.out);
|
|
308
|
+
await fs.ensureDir(path.dirname(outPath));
|
|
309
|
+
await fs.writeJson(outPath, payload, { spaces: 2 });
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (options.json) {
|
|
313
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
314
|
+
} else {
|
|
315
|
+
process.stdout.write(`capability mapping coverage=${payload.summary.coverage_percent}%\n`);
|
|
316
|
+
process.stdout.write(`missing_capabilities=${payload.summary.missing_capabilities}\n`);
|
|
317
|
+
process.stdout.write(`ontology_gaps=${payload.summary.ontology_gaps}\n`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (require.main === module) {
|
|
322
|
+
main().catch((error) => {
|
|
323
|
+
console.error(`capability-mapping-report failed: ${error.message}`);
|
|
324
|
+
process.exit(1);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
module.exports = {
|
|
329
|
+
parseArgs,
|
|
330
|
+
normalizeChanges,
|
|
331
|
+
normalizeTemplates,
|
|
332
|
+
normalizeOntology,
|
|
333
|
+
normalizeCapability,
|
|
334
|
+
capabilityMatches,
|
|
335
|
+
findMappedTemplates,
|
|
336
|
+
checkOntologyCoverage,
|
|
337
|
+
buildMappingReport,
|
|
338
|
+
loadInput
|
|
339
|
+
};
|