scene-capability-engine 3.6.44 → 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 +25 -0
- package/bin/scene-capability-engine.js +36 -2
- package/docs/command-reference.md +5 -0
- package/docs/releases/README.md +2 -0
- package/docs/releases/v3.6.45.md +18 -0
- package/docs/releases/v3.6.46.md +23 -0
- package/docs/zh/releases/README.md +2 -0
- package/docs/zh/releases/v3.6.45.md +18 -0
- package/docs/zh/releases/v3.6.46.md +23 -0
- package/lib/workspace/collab-governance-audit.js +575 -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
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const LEGACY_PATTERNS = [
|
|
8
|
+
{
|
|
9
|
+
label: 'legacy CLI alias',
|
|
10
|
+
regex: /\bkse\b/gi
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
label: 'legacy social handle',
|
|
14
|
+
regex: /@kse_dev/gi
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
label: 'legacy GitHub repository URL',
|
|
18
|
+
regex: /https:\/\/github\.com\/heguangyong\/kiro-spec-engine/gi
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
label: 'legacy template repository name',
|
|
22
|
+
regex: /\bkse-spec-templates\b/gi
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
label: 'legacy product naming',
|
|
26
|
+
regex: /\bKiro Spec Engine\b|\bScene Capability Orchestrator\b|场景能力编排引擎/g
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
label: 'legacy Kiro IDE wording',
|
|
30
|
+
regex: /\bKiro IDE\b/gi
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
label: 'legacy npm package name',
|
|
34
|
+
regex: /\bsco-engine\b/gi
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const EXCLUDE_PREFIXES = [
|
|
39
|
+
'.sce/',
|
|
40
|
+
'tests/fixtures/',
|
|
41
|
+
'node_modules/'
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const EXCLUDE_FILES = new Set([
|
|
45
|
+
'scripts/check-branding-consistency.js'
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
const HISTORY_ALLOW_PREFIXES = [
|
|
49
|
+
'docs/releases/',
|
|
50
|
+
'docs/zh/releases/'
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const HISTORY_ALLOW_FILES = new Set([
|
|
54
|
+
'CHANGELOG.md'
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const TEXT_EXTENSIONS = new Set([
|
|
58
|
+
'.js', '.json', '.md', '.txt', '.yml', '.yaml'
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
function getTrackedFiles() {
|
|
62
|
+
const output = execSync('git ls-files', { encoding: 'utf8' });
|
|
63
|
+
return output
|
|
64
|
+
.split(/\r?\n/)
|
|
65
|
+
.map((line) => line.trim())
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function shouldScan(filePath) {
|
|
70
|
+
if (EXCLUDE_FILES.has(filePath)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (EXCLUDE_PREFIXES.some((prefix) => filePath.startsWith(prefix))) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
79
|
+
return TEXT_EXTENSIONS.has(ext);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isHistoryDocument(filePath) {
|
|
83
|
+
return HISTORY_ALLOW_FILES.has(filePath)
|
|
84
|
+
|| HISTORY_ALLOW_PREFIXES.some((prefix) => filePath.startsWith(prefix));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function scanFile(filePath) {
|
|
88
|
+
let content = '';
|
|
89
|
+
try {
|
|
90
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
91
|
+
} catch (_error) {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const hits = [];
|
|
96
|
+
|
|
97
|
+
for (const pattern of LEGACY_PATTERNS) {
|
|
98
|
+
const matches = content.match(pattern.regex);
|
|
99
|
+
if (matches && matches.length > 0) {
|
|
100
|
+
if (isHistoryDocument(filePath)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
hits.push({
|
|
104
|
+
label: pattern.label,
|
|
105
|
+
count: matches.length
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return hits;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function main() {
|
|
114
|
+
const files = getTrackedFiles().filter(shouldScan);
|
|
115
|
+
const violations = [];
|
|
116
|
+
|
|
117
|
+
for (const filePath of files) {
|
|
118
|
+
const hits = scanFile(filePath);
|
|
119
|
+
if (hits.length > 0) {
|
|
120
|
+
violations.push({ filePath, hits });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (violations.length === 0) {
|
|
125
|
+
console.log('Branding consistency check passed: no legacy naming references found.');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.error('Branding consistency check failed. Found legacy naming references:');
|
|
130
|
+
for (const violation of violations) {
|
|
131
|
+
const summary = violation.hits
|
|
132
|
+
.map((hit) => `${hit.label} x${hit.count}`)
|
|
133
|
+
.join(', ');
|
|
134
|
+
console.error(`- ${violation.filePath}: ${summary}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
main();
|