scene-capability-engine 3.6.45 → 3.6.47
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 +22 -0
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/docs/agent-runtime/symbol-evidence.schema.json +1 -1
- package/docs/command-reference.md +8 -0
- package/docs/interactive-customization/dialogue-governance-policy-baseline.json +4 -1
- package/docs/interactive-customization/embedded-assistant-authorization-dialogue-rules.md +5 -0
- package/docs/releases/README.md +2 -0
- package/docs/releases/v3.6.46.md +23 -0
- package/docs/releases/v3.6.47.md +23 -0
- package/docs/sce-business-mode-map.md +2 -1
- package/docs/sce-capability-matrix-e2e-example.md +2 -1
- package/docs/security-governance-default-baseline.md +2 -0
- package/docs/starter-kit/README.md +3 -0
- package/docs/zh/releases/README.md +2 -0
- package/docs/zh/releases/v3.6.46.md +23 -0
- package/docs/zh/releases/v3.6.47.md +23 -0
- package/lib/workspace/takeover-baseline.js +293 -1
- package/package.json +6 -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/clarification-first-audit.js +322 -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 +873 -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 +370 -0
- package/template/.sce/README.md +1 -0
- package/template/.sce/steering/CORE_PRINCIPLES.md +25 -0
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const {
|
|
6
|
+
buildOntologyFromManifest,
|
|
7
|
+
validateOntology,
|
|
8
|
+
evaluateOntologySemanticQuality
|
|
9
|
+
} = require('../lib/scene-runtime/scene-ontology');
|
|
10
|
+
|
|
11
|
+
const DEFAULT_TEMPLATE_DIR = '.sce/templates/scene-packages';
|
|
12
|
+
const DEFAULT_OUT = '.sce/reports/moqui-template-baseline.json';
|
|
13
|
+
const DEFAULT_MARKDOWN_OUT = '.sce/reports/moqui-template-baseline.md';
|
|
14
|
+
const DEFAULT_MATCH = '(moqui|erp|suite|playbook|runbook|decision|action|governance)';
|
|
15
|
+
const DEFAULT_MIN_SCORE = 70;
|
|
16
|
+
const DEFAULT_MIN_VALID_RATE = 100;
|
|
17
|
+
|
|
18
|
+
function parseArgs(argv) {
|
|
19
|
+
const options = {
|
|
20
|
+
templateDir: DEFAULT_TEMPLATE_DIR,
|
|
21
|
+
out: DEFAULT_OUT,
|
|
22
|
+
markdownOut: DEFAULT_MARKDOWN_OUT,
|
|
23
|
+
match: DEFAULT_MATCH,
|
|
24
|
+
includeAll: false,
|
|
25
|
+
minScore: DEFAULT_MIN_SCORE,
|
|
26
|
+
minValidRate: DEFAULT_MIN_VALID_RATE,
|
|
27
|
+
compareWith: null,
|
|
28
|
+
failOnPortfolioFail: false,
|
|
29
|
+
json: false
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
33
|
+
const token = argv[i];
|
|
34
|
+
const next = argv[i + 1];
|
|
35
|
+
if (token === '--template-dir' && next) {
|
|
36
|
+
options.templateDir = next;
|
|
37
|
+
i += 1;
|
|
38
|
+
} else if (token === '--out' && next) {
|
|
39
|
+
options.out = next;
|
|
40
|
+
i += 1;
|
|
41
|
+
} else if (token === '--markdown-out' && next) {
|
|
42
|
+
options.markdownOut = next;
|
|
43
|
+
i += 1;
|
|
44
|
+
} else if (token === '--match' && next) {
|
|
45
|
+
options.match = next;
|
|
46
|
+
i += 1;
|
|
47
|
+
} else if (token === '--min-score' && next) {
|
|
48
|
+
options.minScore = Number(next);
|
|
49
|
+
i += 1;
|
|
50
|
+
} else if (token === '--min-valid-rate' && next) {
|
|
51
|
+
options.minValidRate = Number(next);
|
|
52
|
+
i += 1;
|
|
53
|
+
} else if (token === '--compare-with' && next) {
|
|
54
|
+
options.compareWith = next;
|
|
55
|
+
i += 1;
|
|
56
|
+
} else if (token === '--include-all') {
|
|
57
|
+
options.includeAll = true;
|
|
58
|
+
} else if (token === '--fail-on-portfolio-fail') {
|
|
59
|
+
options.failOnPortfolioFail = true;
|
|
60
|
+
} else if (token === '--json') {
|
|
61
|
+
options.json = true;
|
|
62
|
+
} else if (token === '--help' || token === '-h') {
|
|
63
|
+
printHelpAndExit(0);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!Number.isFinite(options.minScore) || options.minScore < 0 || options.minScore > 100) {
|
|
68
|
+
throw new Error('--min-score must be a number between 0 and 100');
|
|
69
|
+
}
|
|
70
|
+
if (!Number.isFinite(options.minValidRate) || options.minValidRate < 0 || options.minValidRate > 100) {
|
|
71
|
+
throw new Error('--min-valid-rate must be a number between 0 and 100');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return options;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function printHelpAndExit(code) {
|
|
78
|
+
const lines = [
|
|
79
|
+
'Usage: node scripts/moqui-template-baseline-report.js [options]',
|
|
80
|
+
'',
|
|
81
|
+
'Options:',
|
|
82
|
+
` --template-dir <path> Template root (default: ${DEFAULT_TEMPLATE_DIR})`,
|
|
83
|
+
` --out <path> JSON report path (default: ${DEFAULT_OUT})`,
|
|
84
|
+
` --markdown-out <path> Markdown report path (default: ${DEFAULT_MARKDOWN_OUT})`,
|
|
85
|
+
` --match <regex> Template selector regex (default: ${DEFAULT_MATCH})`,
|
|
86
|
+
' --include-all Disable selector filter and score all templates',
|
|
87
|
+
` --min-score <n> Baseline min semantic score (default: ${DEFAULT_MIN_SCORE})`,
|
|
88
|
+
` --min-valid-rate <n> Baseline min ontology valid-rate % (default: ${DEFAULT_MIN_VALID_RATE})`,
|
|
89
|
+
' --compare-with <path> Compare against a previous baseline JSON report',
|
|
90
|
+
' --fail-on-portfolio-fail Exit non-zero when portfolio baseline gate fails',
|
|
91
|
+
' --json Print JSON payload to stdout',
|
|
92
|
+
' -h, --help Show this help'
|
|
93
|
+
];
|
|
94
|
+
console.log(lines.join('\n'));
|
|
95
|
+
process.exit(code);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function toRate(numerator, denominator) {
|
|
99
|
+
if (!Number.isFinite(Number(denominator)) || Number(denominator) <= 0) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
return Number(((Number(numerator) / Number(denominator)) * 100).toFixed(2));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function computeTemplateFlags(ontologyValidation, quality, minScore) {
|
|
106
|
+
const metrics = quality && quality.metrics ? quality.metrics : {};
|
|
107
|
+
const entityCoverage = Number(metrics.entity_count) > 0;
|
|
108
|
+
const relationCoverage = Number(metrics.relation_count) > 0;
|
|
109
|
+
const businessRuleCoverage = Number(metrics.business_rule_total) > 0;
|
|
110
|
+
const decisionCoverage = Number(metrics.decision_total) > 0;
|
|
111
|
+
const businessRuleClosed = businessRuleCoverage && Number(metrics.business_rule_unmapped) === 0;
|
|
112
|
+
const decisionClosed = decisionCoverage && Number(metrics.decision_undecided) === 0;
|
|
113
|
+
const scorePassed = Number(quality && quality.score) >= Number(minScore);
|
|
114
|
+
const graphValid = Boolean(ontologyValidation && ontologyValidation.valid === true);
|
|
115
|
+
const baselinePassed = (
|
|
116
|
+
graphValid &&
|
|
117
|
+
scorePassed &&
|
|
118
|
+
entityCoverage &&
|
|
119
|
+
relationCoverage &&
|
|
120
|
+
businessRuleCoverage &&
|
|
121
|
+
businessRuleClosed &&
|
|
122
|
+
decisionCoverage &&
|
|
123
|
+
decisionClosed
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
graph_valid: graphValid,
|
|
128
|
+
score_passed: scorePassed,
|
|
129
|
+
entity_coverage: entityCoverage,
|
|
130
|
+
relation_coverage: relationCoverage,
|
|
131
|
+
business_rule_coverage: businessRuleCoverage,
|
|
132
|
+
business_rule_closed: businessRuleClosed,
|
|
133
|
+
decision_coverage: decisionCoverage,
|
|
134
|
+
decision_closed: decisionClosed,
|
|
135
|
+
baseline_passed: baselinePassed
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function collectGapReasons(flags) {
|
|
140
|
+
const gaps = [];
|
|
141
|
+
if (!flags.graph_valid) gaps.push('ontology graph invalid');
|
|
142
|
+
if (!flags.score_passed) gaps.push('semantic score below threshold');
|
|
143
|
+
if (!flags.entity_coverage) gaps.push('entity model missing');
|
|
144
|
+
if (!flags.relation_coverage) gaps.push('relation model missing');
|
|
145
|
+
if (!flags.business_rule_coverage) gaps.push('business rules missing');
|
|
146
|
+
if (flags.business_rule_coverage && !flags.business_rule_closed) gaps.push('unmapped business rules remain');
|
|
147
|
+
if (!flags.decision_coverage) gaps.push('decision logic missing');
|
|
148
|
+
if (flags.decision_coverage && !flags.decision_closed) gaps.push('undecided decisions remain');
|
|
149
|
+
return gaps;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function detectTemplateScope(templateId) {
|
|
153
|
+
const text = `${templateId || ''}`.toLowerCase();
|
|
154
|
+
if (/(moqui|erp)/.test(text)) {
|
|
155
|
+
return 'moqui_erp';
|
|
156
|
+
}
|
|
157
|
+
if (/(scene|suite|playbook|runbook|decision|action|governance)/.test(text)) {
|
|
158
|
+
return 'scene_orchestration';
|
|
159
|
+
}
|
|
160
|
+
return 'other';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function summarizeScopeBreakdown(templates) {
|
|
164
|
+
const breakdown = {
|
|
165
|
+
moqui_erp: 0,
|
|
166
|
+
scene_orchestration: 0,
|
|
167
|
+
other: 0
|
|
168
|
+
};
|
|
169
|
+
for (const item of templates) {
|
|
170
|
+
const scope = detectTemplateScope(item && item.template_id);
|
|
171
|
+
breakdown[scope] = Number(breakdown[scope] || 0) + 1;
|
|
172
|
+
}
|
|
173
|
+
return breakdown;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function countFlag(templates, flagName) {
|
|
177
|
+
return templates.filter(
|
|
178
|
+
(item) => item && item.baseline && item.baseline.flags && item.baseline.flags[flagName] === true
|
|
179
|
+
).length;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function buildCoverageMatrix(templates) {
|
|
183
|
+
const total = templates.length;
|
|
184
|
+
const entityCoverage = countFlag(templates, 'entity_coverage');
|
|
185
|
+
const relationCoverage = countFlag(templates, 'relation_coverage');
|
|
186
|
+
const businessRuleCoverage = countFlag(templates, 'business_rule_coverage');
|
|
187
|
+
const businessRuleClosed = countFlag(templates, 'business_rule_closed');
|
|
188
|
+
const decisionCoverage = countFlag(templates, 'decision_coverage');
|
|
189
|
+
const decisionClosed = countFlag(templates, 'decision_closed');
|
|
190
|
+
const graphValid = countFlag(templates, 'graph_valid');
|
|
191
|
+
const scorePassed = countFlag(templates, 'score_passed');
|
|
192
|
+
const baselinePassed = countFlag(templates, 'baseline_passed');
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
total_templates: total,
|
|
196
|
+
graph_valid: {
|
|
197
|
+
count: graphValid,
|
|
198
|
+
rate_percent: toRate(graphValid, total)
|
|
199
|
+
},
|
|
200
|
+
score_passed: {
|
|
201
|
+
count: scorePassed,
|
|
202
|
+
rate_percent: toRate(scorePassed, total)
|
|
203
|
+
},
|
|
204
|
+
entity_coverage: {
|
|
205
|
+
count: entityCoverage,
|
|
206
|
+
rate_percent: toRate(entityCoverage, total)
|
|
207
|
+
},
|
|
208
|
+
relation_coverage: {
|
|
209
|
+
count: relationCoverage,
|
|
210
|
+
rate_percent: toRate(relationCoverage, total)
|
|
211
|
+
},
|
|
212
|
+
business_rule_coverage: {
|
|
213
|
+
count: businessRuleCoverage,
|
|
214
|
+
rate_percent: toRate(businessRuleCoverage, total)
|
|
215
|
+
},
|
|
216
|
+
business_rule_closed: {
|
|
217
|
+
count: businessRuleClosed,
|
|
218
|
+
rate_percent: toRate(businessRuleClosed, total),
|
|
219
|
+
among_covered_rate_percent: toRate(businessRuleClosed, businessRuleCoverage)
|
|
220
|
+
},
|
|
221
|
+
decision_coverage: {
|
|
222
|
+
count: decisionCoverage,
|
|
223
|
+
rate_percent: toRate(decisionCoverage, total)
|
|
224
|
+
},
|
|
225
|
+
decision_closed: {
|
|
226
|
+
count: decisionClosed,
|
|
227
|
+
rate_percent: toRate(decisionClosed, total),
|
|
228
|
+
among_covered_rate_percent: toRate(decisionClosed, decisionCoverage)
|
|
229
|
+
},
|
|
230
|
+
baseline_passed: {
|
|
231
|
+
count: baselinePassed,
|
|
232
|
+
rate_percent: toRate(baselinePassed, total)
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function buildGapFrequency(templates) {
|
|
238
|
+
const counter = new Map();
|
|
239
|
+
for (const item of templates) {
|
|
240
|
+
const gaps = Array.isArray(item && item.baseline && item.baseline.gaps)
|
|
241
|
+
? item.baseline.gaps
|
|
242
|
+
: [];
|
|
243
|
+
for (const gap of gaps) {
|
|
244
|
+
const key = `${gap || ''}`.trim();
|
|
245
|
+
if (!key) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
counter.set(key, Number(counter.get(key) || 0) + 1);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return Array.from(counter.entries())
|
|
253
|
+
.map(([gap, count]) => ({ gap, count }))
|
|
254
|
+
.sort((a, b) => {
|
|
255
|
+
if (b.count !== a.count) {
|
|
256
|
+
return b.count - a.count;
|
|
257
|
+
}
|
|
258
|
+
return a.gap.localeCompare(b.gap);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function toDelta(currentValue, previousValue) {
|
|
263
|
+
if (!Number.isFinite(Number(currentValue)) || !Number.isFinite(Number(previousValue))) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
return Number((Number(currentValue) - Number(previousValue)).toFixed(2));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function formatDeltaPercent(metric = {}) {
|
|
270
|
+
const value = metric && Number.isFinite(Number(metric.rate_percent))
|
|
271
|
+
? Number(metric.rate_percent)
|
|
272
|
+
: null;
|
|
273
|
+
return value === null ? 'n/a' : `${value}%`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function metricLabel(metricName) {
|
|
277
|
+
const labels = {
|
|
278
|
+
graph_valid: 'graph-valid',
|
|
279
|
+
score_passed: 'score-passed',
|
|
280
|
+
entity_coverage: 'entity-coverage',
|
|
281
|
+
relation_coverage: 'relation-coverage',
|
|
282
|
+
business_rule_coverage: 'business-rule-coverage',
|
|
283
|
+
business_rule_closed: 'business-rule-closed',
|
|
284
|
+
decision_coverage: 'decision-coverage',
|
|
285
|
+
decision_closed: 'decision-closed',
|
|
286
|
+
baseline_passed: 'baseline-passed'
|
|
287
|
+
};
|
|
288
|
+
return labels[metricName] || metricName;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function readCoverageMetric(summary = {}, metricName = '') {
|
|
292
|
+
const matrix = summary && summary.coverage_matrix && typeof summary.coverage_matrix === 'object'
|
|
293
|
+
? summary.coverage_matrix
|
|
294
|
+
: {};
|
|
295
|
+
return matrix && matrix[metricName] && typeof matrix[metricName] === 'object'
|
|
296
|
+
? matrix[metricName]
|
|
297
|
+
: {};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function buildCoverageMatrixDeltas(currentSummary = {}, previousSummary = {}) {
|
|
301
|
+
const metricNames = [
|
|
302
|
+
'graph_valid',
|
|
303
|
+
'score_passed',
|
|
304
|
+
'entity_coverage',
|
|
305
|
+
'relation_coverage',
|
|
306
|
+
'business_rule_coverage',
|
|
307
|
+
'business_rule_closed',
|
|
308
|
+
'decision_coverage',
|
|
309
|
+
'decision_closed',
|
|
310
|
+
'baseline_passed'
|
|
311
|
+
];
|
|
312
|
+
const deltas = {};
|
|
313
|
+
|
|
314
|
+
for (const metricName of metricNames) {
|
|
315
|
+
const currentMetric = readCoverageMetric(currentSummary, metricName);
|
|
316
|
+
const previousMetric = readCoverageMetric(previousSummary, metricName);
|
|
317
|
+
const metricDelta = {
|
|
318
|
+
count: toDelta(currentMetric.count, previousMetric.count),
|
|
319
|
+
rate_percent: toDelta(currentMetric.rate_percent, previousMetric.rate_percent)
|
|
320
|
+
};
|
|
321
|
+
if (
|
|
322
|
+
Number.isFinite(Number(currentMetric.among_covered_rate_percent))
|
|
323
|
+
|| Number.isFinite(Number(previousMetric.among_covered_rate_percent))
|
|
324
|
+
) {
|
|
325
|
+
metricDelta.among_covered_rate_percent = toDelta(
|
|
326
|
+
currentMetric.among_covered_rate_percent,
|
|
327
|
+
previousMetric.among_covered_rate_percent
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
deltas[metricName] = metricDelta;
|
|
331
|
+
}
|
|
332
|
+
return deltas;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function buildCoverageMatrixRegressions(coverageMatrixDeltas = {}) {
|
|
336
|
+
const regressions = [];
|
|
337
|
+
const deltaMatrix = coverageMatrixDeltas && typeof coverageMatrixDeltas === 'object'
|
|
338
|
+
? coverageMatrixDeltas
|
|
339
|
+
: {};
|
|
340
|
+
for (const [metric, value] of Object.entries(deltaMatrix)) {
|
|
341
|
+
if (!value || typeof value !== 'object') {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const deltaRate = Number(value.rate_percent);
|
|
345
|
+
if (!Number.isFinite(deltaRate) || deltaRate >= 0) {
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
regressions.push({
|
|
349
|
+
metric,
|
|
350
|
+
label: metricLabel(metric),
|
|
351
|
+
delta_rate_percent: Number(deltaRate.toFixed(2))
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return regressions.sort((a, b) => {
|
|
356
|
+
if (a.delta_rate_percent !== b.delta_rate_percent) {
|
|
357
|
+
return a.delta_rate_percent - b.delta_rate_percent;
|
|
358
|
+
}
|
|
359
|
+
return a.metric.localeCompare(b.metric);
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function readFailedTemplates(report) {
|
|
364
|
+
const templates = Array.isArray(report && report.templates) ? report.templates : [];
|
|
365
|
+
return templates
|
|
366
|
+
.filter((item) => !(item && item.baseline && item.baseline.flags && item.baseline.flags.baseline_passed))
|
|
367
|
+
.map((item) => item && item.template_id)
|
|
368
|
+
.filter((id) => typeof id === 'string' && id.length > 0)
|
|
369
|
+
.sort((a, b) => a.localeCompare(b));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function buildComparison(report, previousReport) {
|
|
373
|
+
const currentSummary = report && report.summary ? report.summary : {};
|
|
374
|
+
const previousSummary = previousReport && previousReport.summary ? previousReport.summary : {};
|
|
375
|
+
const currentFailedTemplates = readFailedTemplates(report);
|
|
376
|
+
const previousFailedTemplates = readFailedTemplates(previousReport);
|
|
377
|
+
const previousFailedSet = new Set(previousFailedTemplates);
|
|
378
|
+
const currentFailedSet = new Set(currentFailedTemplates);
|
|
379
|
+
const newlyFailed = currentFailedTemplates
|
|
380
|
+
.filter((item) => !previousFailedSet.has(item))
|
|
381
|
+
.sort((a, b) => a.localeCompare(b));
|
|
382
|
+
const recovered = previousFailedTemplates
|
|
383
|
+
.filter((item) => !currentFailedSet.has(item))
|
|
384
|
+
.sort((a, b) => a.localeCompare(b));
|
|
385
|
+
const coverageMatrixDeltas = buildCoverageMatrixDeltas(currentSummary, previousSummary);
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
previous_generated_at: previousReport && previousReport.generated_at ? previousReport.generated_at : null,
|
|
389
|
+
previous_template_root: previousReport && previousReport.template_root ? previousReport.template_root : null,
|
|
390
|
+
deltas: {
|
|
391
|
+
scoped_templates: toDelta(currentSummary.scoped_templates, previousSummary.scoped_templates),
|
|
392
|
+
avg_score: toDelta(currentSummary.avg_score, previousSummary.avg_score),
|
|
393
|
+
valid_rate_percent: toDelta(currentSummary.valid_rate_percent, previousSummary.valid_rate_percent),
|
|
394
|
+
baseline_passed: toDelta(currentSummary.baseline_passed, previousSummary.baseline_passed),
|
|
395
|
+
baseline_failed: toDelta(currentSummary.baseline_failed, previousSummary.baseline_failed)
|
|
396
|
+
},
|
|
397
|
+
coverage_matrix_deltas: coverageMatrixDeltas,
|
|
398
|
+
coverage_matrix_regressions: buildCoverageMatrixRegressions(coverageMatrixDeltas),
|
|
399
|
+
portfolio: {
|
|
400
|
+
previous_passed: previousSummary.portfolio_passed === true,
|
|
401
|
+
current_passed: currentSummary.portfolio_passed === true,
|
|
402
|
+
changed: (previousSummary.portfolio_passed === true) !== (currentSummary.portfolio_passed === true)
|
|
403
|
+
},
|
|
404
|
+
failed_templates: {
|
|
405
|
+
previous: previousFailedTemplates,
|
|
406
|
+
current: currentFailedTemplates,
|
|
407
|
+
newly_failed: newlyFailed,
|
|
408
|
+
recovered
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function buildMarkdownReport(report) {
|
|
414
|
+
const lines = [];
|
|
415
|
+
lines.push('# Moqui Template Baseline Report');
|
|
416
|
+
lines.push('');
|
|
417
|
+
lines.push(`- Generated at: ${report.generated_at}`);
|
|
418
|
+
lines.push(`- Template root: ${report.template_root}`);
|
|
419
|
+
lines.push(`- Filter: ${report.filter.include_all ? 'all templates' : report.filter.match}`);
|
|
420
|
+
lines.push(`- Baseline thresholds: score>=${report.baseline.min_score}, valid-rate>=${report.baseline.min_valid_rate_percent}%`);
|
|
421
|
+
lines.push('');
|
|
422
|
+
lines.push('## Summary');
|
|
423
|
+
lines.push('');
|
|
424
|
+
lines.push(`- Total templates scanned: ${report.summary.total_templates}`);
|
|
425
|
+
lines.push(`- Templates in scope: ${report.summary.scoped_templates}`);
|
|
426
|
+
lines.push(`- Avg score: ${report.summary.avg_score === null ? 'n/a' : report.summary.avg_score}`);
|
|
427
|
+
lines.push(`- Ontology valid-rate: ${report.summary.valid_rate_percent === null ? 'n/a' : `${report.summary.valid_rate_percent}%`}`);
|
|
428
|
+
lines.push(`- Baseline passed: ${report.summary.baseline_passed}`);
|
|
429
|
+
lines.push(`- Baseline failed: ${report.summary.baseline_failed}`);
|
|
430
|
+
lines.push(`- Portfolio pass: ${report.summary.portfolio_passed ? 'yes' : 'no'}`);
|
|
431
|
+
if (report.summary.scope_breakdown) {
|
|
432
|
+
const scopes = report.summary.scope_breakdown;
|
|
433
|
+
lines.push(`- Scope mix: moqui/erp=${scopes.moqui_erp || 0}, scene-orchestration=${scopes.scene_orchestration || 0}, other=${scopes.other || 0}`);
|
|
434
|
+
}
|
|
435
|
+
lines.push('');
|
|
436
|
+
lines.push('## Capability Matrix');
|
|
437
|
+
lines.push('');
|
|
438
|
+
lines.push('| Dimension | Count | Rate |');
|
|
439
|
+
lines.push('| --- | ---: | ---: |');
|
|
440
|
+
const matrix = report.summary.coverage_matrix || {};
|
|
441
|
+
const matrixRows = [
|
|
442
|
+
['Graph valid', matrix.graph_valid],
|
|
443
|
+
['Score passed', matrix.score_passed],
|
|
444
|
+
['Entity coverage', matrix.entity_coverage],
|
|
445
|
+
['Relation coverage', matrix.relation_coverage],
|
|
446
|
+
['Business-rule coverage', matrix.business_rule_coverage],
|
|
447
|
+
['Business-rule closed', matrix.business_rule_closed],
|
|
448
|
+
['Decision coverage', matrix.decision_coverage],
|
|
449
|
+
['Decision closed', matrix.decision_closed],
|
|
450
|
+
['Baseline passed', matrix.baseline_passed]
|
|
451
|
+
];
|
|
452
|
+
for (const [name, item] of matrixRows) {
|
|
453
|
+
const count = item && Number.isFinite(Number(item.count)) ? Number(item.count) : 0;
|
|
454
|
+
const rate = item && Number.isFinite(Number(item.rate_percent)) ? `${item.rate_percent}%` : 'n/a';
|
|
455
|
+
lines.push(`| ${name} | ${count} | ${rate} |`);
|
|
456
|
+
}
|
|
457
|
+
lines.push('');
|
|
458
|
+
lines.push('## Templates');
|
|
459
|
+
lines.push('');
|
|
460
|
+
lines.push('| Template | Score | Graph | Baseline | Gaps |');
|
|
461
|
+
lines.push('| --- | ---: | --- | --- | --- |');
|
|
462
|
+
for (const item of report.templates) {
|
|
463
|
+
lines.push(`| ${item.template_id} | ${item.semantic.score} | ${item.ontology.valid ? 'valid' : 'invalid'} | ${item.baseline.flags.baseline_passed ? 'pass' : 'fail'} | ${item.baseline.gaps.join(', ') || 'none'} |`);
|
|
464
|
+
}
|
|
465
|
+
lines.push('');
|
|
466
|
+
lines.push('## Top Gaps');
|
|
467
|
+
lines.push('');
|
|
468
|
+
if (Array.isArray(report.summary.gap_frequency) && report.summary.gap_frequency.length > 0) {
|
|
469
|
+
for (const item of report.summary.gap_frequency.slice(0, 10)) {
|
|
470
|
+
lines.push(`- ${item.gap}: ${item.count}`);
|
|
471
|
+
}
|
|
472
|
+
} else {
|
|
473
|
+
lines.push('- none');
|
|
474
|
+
}
|
|
475
|
+
if (report.compare) {
|
|
476
|
+
const compare = report.compare;
|
|
477
|
+
const deltas = compare.deltas || {};
|
|
478
|
+
const matrixDeltas = compare.coverage_matrix_deltas || {};
|
|
479
|
+
const matrixRegressions = Array.isArray(compare.coverage_matrix_regressions)
|
|
480
|
+
? compare.coverage_matrix_regressions
|
|
481
|
+
: [];
|
|
482
|
+
const failedTemplates = compare.failed_templates || {};
|
|
483
|
+
lines.push('');
|
|
484
|
+
lines.push('## Trend vs Previous');
|
|
485
|
+
lines.push('');
|
|
486
|
+
lines.push(`- Previous generated at: ${compare.previous_generated_at || 'n/a'}`);
|
|
487
|
+
lines.push(`- Previous template root: ${compare.previous_template_root || 'n/a'}`);
|
|
488
|
+
lines.push(`- Delta scoped templates: ${deltas.scoped_templates === null ? 'n/a' : deltas.scoped_templates}`);
|
|
489
|
+
lines.push(`- Delta avg score: ${deltas.avg_score === null ? 'n/a' : deltas.avg_score}`);
|
|
490
|
+
lines.push(`- Delta valid-rate: ${deltas.valid_rate_percent === null ? 'n/a' : `${deltas.valid_rate_percent}%`}`);
|
|
491
|
+
lines.push(`- Delta baseline passed: ${deltas.baseline_passed === null ? 'n/a' : deltas.baseline_passed}`);
|
|
492
|
+
lines.push(`- Delta baseline failed: ${deltas.baseline_failed === null ? 'n/a' : deltas.baseline_failed}`);
|
|
493
|
+
lines.push(`- Delta entity coverage: ${formatDeltaPercent(matrixDeltas.entity_coverage)}`);
|
|
494
|
+
lines.push(`- Delta business-rule closed: ${formatDeltaPercent(matrixDeltas.business_rule_closed)}`);
|
|
495
|
+
lines.push(`- Delta decision closed: ${formatDeltaPercent(matrixDeltas.decision_closed)}`);
|
|
496
|
+
lines.push(
|
|
497
|
+
`- Matrix regressions: ${matrixRegressions.length > 0
|
|
498
|
+
? matrixRegressions.slice(0, 5).map(item => `${item.label}:${item.delta_rate_percent}%`).join(' | ')
|
|
499
|
+
: 'none'}`
|
|
500
|
+
);
|
|
501
|
+
lines.push(`- Portfolio transition: ${compare.portfolio.previous_passed ? 'pass' : 'fail'} -> ${compare.portfolio.current_passed ? 'pass' : 'fail'}`);
|
|
502
|
+
lines.push(`- Newly failed templates: ${failedTemplates.newly_failed && failedTemplates.newly_failed.length > 0 ? failedTemplates.newly_failed.join(', ') : 'none'}`);
|
|
503
|
+
lines.push(`- Recovered templates: ${failedTemplates.recovered && failedTemplates.recovered.length > 0 ? failedTemplates.recovered.join(', ') : 'none'}`);
|
|
504
|
+
}
|
|
505
|
+
return `${lines.join('\n')}\n`;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async function readTemplateContracts(templateRoot) {
|
|
509
|
+
const results = [];
|
|
510
|
+
if (!(await fs.pathExists(templateRoot))) {
|
|
511
|
+
return results;
|
|
512
|
+
}
|
|
513
|
+
const directories = await fs.readdir(templateRoot);
|
|
514
|
+
for (const name of directories) {
|
|
515
|
+
const dirPath = path.join(templateRoot, name);
|
|
516
|
+
const stat = await fs.stat(dirPath).catch(() => null);
|
|
517
|
+
if (!stat || !stat.isDirectory()) {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const contractPath = path.join(dirPath, 'scene-package.json');
|
|
521
|
+
if (!(await fs.pathExists(contractPath))) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
const contract = await fs.readJson(contractPath);
|
|
525
|
+
results.push({
|
|
526
|
+
templateId: name,
|
|
527
|
+
templatePath: dirPath,
|
|
528
|
+
contractPath,
|
|
529
|
+
contract
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
return results;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
async function main() {
|
|
536
|
+
const options = parseArgs(process.argv.slice(2));
|
|
537
|
+
const templateRoot = path.resolve(process.cwd(), options.templateDir);
|
|
538
|
+
const outPath = path.resolve(process.cwd(), options.out);
|
|
539
|
+
const markdownPath = path.resolve(process.cwd(), options.markdownOut);
|
|
540
|
+
const compareWithPath = options.compareWith
|
|
541
|
+
? path.resolve(process.cwd(), options.compareWith)
|
|
542
|
+
: null;
|
|
543
|
+
|
|
544
|
+
const selector = new RegExp(options.match, 'i');
|
|
545
|
+
const contracts = await readTemplateContracts(templateRoot);
|
|
546
|
+
const scoped = contracts.filter((item) => {
|
|
547
|
+
if (options.includeAll) {
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
const metadata = item.contract && item.contract.metadata ? item.contract.metadata : {};
|
|
551
|
+
const provides = item.contract && item.contract.capabilities && Array.isArray(item.contract.capabilities.provides)
|
|
552
|
+
? item.contract.capabilities.provides.join(' ')
|
|
553
|
+
: '';
|
|
554
|
+
const haystack = [item.templateId, metadata.name || '', metadata.group || '', provides].join(' ');
|
|
555
|
+
return selector.test(haystack);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
const templates = scoped.map((item) => {
|
|
559
|
+
const ontologyGraph = buildOntologyFromManifest(item.contract);
|
|
560
|
+
const ontologyValidation = validateOntology(ontologyGraph);
|
|
561
|
+
const semantic = evaluateOntologySemanticQuality(item.contract);
|
|
562
|
+
const flags = computeTemplateFlags(ontologyValidation, semantic, options.minScore);
|
|
563
|
+
const gaps = collectGapReasons(flags);
|
|
564
|
+
return {
|
|
565
|
+
template_id: item.templateId,
|
|
566
|
+
template_path: path.relative(process.cwd(), item.templatePath),
|
|
567
|
+
contract_path: path.relative(process.cwd(), item.contractPath),
|
|
568
|
+
capabilities_provides: item.contract
|
|
569
|
+
&& item.contract.capabilities
|
|
570
|
+
&& Array.isArray(item.contract.capabilities.provides)
|
|
571
|
+
? item.contract.capabilities.provides
|
|
572
|
+
: [],
|
|
573
|
+
ontology: {
|
|
574
|
+
valid: ontologyValidation.valid,
|
|
575
|
+
error_count: Array.isArray(ontologyValidation.errors) ? ontologyValidation.errors.length : 0
|
|
576
|
+
},
|
|
577
|
+
semantic: {
|
|
578
|
+
score: semantic.score,
|
|
579
|
+
level: semantic.level,
|
|
580
|
+
metrics: semantic.metrics
|
|
581
|
+
},
|
|
582
|
+
baseline: {
|
|
583
|
+
flags,
|
|
584
|
+
gaps
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
const validCount = templates.filter((item) => item.ontology.valid).length;
|
|
590
|
+
const scores = templates.map((item) => Number(item.semantic.score)).filter((value) => Number.isFinite(value));
|
|
591
|
+
const baselinePassedCount = templates.filter((item) => item.baseline.flags.baseline_passed).length;
|
|
592
|
+
const coverageMatrix = buildCoverageMatrix(templates);
|
|
593
|
+
const gapFrequency = buildGapFrequency(templates);
|
|
594
|
+
const summary = {
|
|
595
|
+
total_templates: contracts.length,
|
|
596
|
+
scoped_templates: templates.length,
|
|
597
|
+
avg_score: scores.length > 0
|
|
598
|
+
? Number((scores.reduce((acc, value) => acc + value, 0) / scores.length).toFixed(2))
|
|
599
|
+
: null,
|
|
600
|
+
valid_rate_percent: toRate(validCount, templates.length),
|
|
601
|
+
baseline_passed: baselinePassedCount,
|
|
602
|
+
baseline_failed: templates.length - baselinePassedCount,
|
|
603
|
+
scope_breakdown: summarizeScopeBreakdown(templates),
|
|
604
|
+
coverage_matrix: coverageMatrix,
|
|
605
|
+
gap_frequency: gapFrequency,
|
|
606
|
+
portfolio_passed: (
|
|
607
|
+
templates.length > 0 &&
|
|
608
|
+
Number(toRate(validCount, templates.length)) >= Number(options.minValidRate) &&
|
|
609
|
+
scores.length > 0 &&
|
|
610
|
+
Number((scores.reduce((acc, value) => acc + value, 0) / scores.length).toFixed(2)) >= Number(options.minScore)
|
|
611
|
+
)
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
const report = {
|
|
615
|
+
mode: 'moqui-template-baseline',
|
|
616
|
+
generated_at: new Date().toISOString(),
|
|
617
|
+
template_root: path.relative(process.cwd(), templateRoot) || '.',
|
|
618
|
+
filter: {
|
|
619
|
+
include_all: options.includeAll,
|
|
620
|
+
match: options.match
|
|
621
|
+
},
|
|
622
|
+
baseline: {
|
|
623
|
+
min_score: options.minScore,
|
|
624
|
+
min_valid_rate_percent: options.minValidRate
|
|
625
|
+
},
|
|
626
|
+
summary,
|
|
627
|
+
templates
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
if (compareWithPath) {
|
|
631
|
+
if (!(await fs.pathExists(compareWithPath))) {
|
|
632
|
+
throw new Error(`--compare-with file not found: ${path.relative(process.cwd(), compareWithPath)}`);
|
|
633
|
+
}
|
|
634
|
+
const previousReport = await fs.readJson(compareWithPath);
|
|
635
|
+
report.compare = buildComparison(report, previousReport);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
await fs.ensureDir(path.dirname(outPath));
|
|
639
|
+
await fs.writeJson(outPath, report, { spaces: 2 });
|
|
640
|
+
|
|
641
|
+
const markdown = buildMarkdownReport(report);
|
|
642
|
+
await fs.ensureDir(path.dirname(markdownPath));
|
|
643
|
+
await fs.writeFile(markdownPath, markdown, 'utf8');
|
|
644
|
+
|
|
645
|
+
if (options.json) {
|
|
646
|
+
console.log(JSON.stringify({
|
|
647
|
+
...report,
|
|
648
|
+
output: {
|
|
649
|
+
json: path.relative(process.cwd(), outPath),
|
|
650
|
+
markdown: path.relative(process.cwd(), markdownPath)
|
|
651
|
+
}
|
|
652
|
+
}, null, 2));
|
|
653
|
+
} else {
|
|
654
|
+
console.log('Moqui template baseline report generated.');
|
|
655
|
+
console.log(` JSON: ${path.relative(process.cwd(), outPath)}`);
|
|
656
|
+
console.log(` Markdown: ${path.relative(process.cwd(), markdownPath)}`);
|
|
657
|
+
console.log(` Scope: ${summary.scoped_templates}/${summary.total_templates}`);
|
|
658
|
+
console.log(` Avg score: ${summary.avg_score === null ? 'n/a' : summary.avg_score}`);
|
|
659
|
+
console.log(` Valid-rate: ${summary.valid_rate_percent === null ? 'n/a' : `${summary.valid_rate_percent}%`}`);
|
|
660
|
+
console.log(` Baseline passed: ${summary.baseline_passed}`);
|
|
661
|
+
if (report.compare) {
|
|
662
|
+
const deltas = report.compare.deltas || {};
|
|
663
|
+
console.log(` Delta avg score: ${deltas.avg_score === null ? 'n/a' : deltas.avg_score}`);
|
|
664
|
+
console.log(` Delta valid-rate: ${deltas.valid_rate_percent === null ? 'n/a' : `${deltas.valid_rate_percent}%`}`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (options.failOnPortfolioFail && !summary.portfolio_passed) {
|
|
669
|
+
const reason = (
|
|
670
|
+
`portfolio baseline gate failed (avg_score=${summary.avg_score === null ? 'n/a' : summary.avg_score}, `
|
|
671
|
+
+ `valid_rate=${summary.valid_rate_percent === null ? 'n/a' : `${summary.valid_rate_percent}%`}, `
|
|
672
|
+
+ `thresholds score>=${options.minScore}, valid-rate>=${options.minValidRate}%)`
|
|
673
|
+
);
|
|
674
|
+
console.error(`Moqui template baseline failed: ${reason}`);
|
|
675
|
+
process.exitCode = 2;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
main().catch((error) => {
|
|
680
|
+
console.error(`Failed to generate Moqui template baseline report: ${error.message}`);
|
|
681
|
+
process.exitCode = 1;
|
|
682
|
+
});
|