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
|
@@ -0,0 +1,1340 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs-extra');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_PROJECT_DIR = '.';
|
|
8
|
+
const DEFAULT_OUT = 'docs/moqui/metadata-catalog.json';
|
|
9
|
+
const DEFAULT_MARKDOWN_OUT = 'docs/moqui/metadata-catalog.md';
|
|
10
|
+
const DEFAULT_HANDOFF_MANIFEST = 'docs/handoffs/handoff-manifest.json';
|
|
11
|
+
const DEFAULT_CAPABILITY_MATRIX = 'docs/handoffs/capability-matrix.md';
|
|
12
|
+
const DEFAULT_EVIDENCE_DIR = 'docs/handoffs/evidence';
|
|
13
|
+
const DEFAULT_SALVAGE_DIR = '.sce/recovery/salvage';
|
|
14
|
+
const MAX_HINT_ITEMS = 64;
|
|
15
|
+
|
|
16
|
+
function parseArgs(argv) {
|
|
17
|
+
const options = {
|
|
18
|
+
projectDir: DEFAULT_PROJECT_DIR,
|
|
19
|
+
out: DEFAULT_OUT,
|
|
20
|
+
markdownOut: DEFAULT_MARKDOWN_OUT,
|
|
21
|
+
json: false
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
25
|
+
const token = argv[i];
|
|
26
|
+
const next = argv[i + 1];
|
|
27
|
+
if (token === '--project-dir' && next) {
|
|
28
|
+
options.projectDir = next;
|
|
29
|
+
i += 1;
|
|
30
|
+
} else if (token === '--out' && next) {
|
|
31
|
+
options.out = next;
|
|
32
|
+
i += 1;
|
|
33
|
+
} else if (token === '--markdown-out' && next) {
|
|
34
|
+
options.markdownOut = next;
|
|
35
|
+
i += 1;
|
|
36
|
+
} else if (token === '--json') {
|
|
37
|
+
options.json = true;
|
|
38
|
+
} else if (token === '--help' || token === '-h') {
|
|
39
|
+
printHelpAndExit(0);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return options;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function printHelpAndExit(code) {
|
|
47
|
+
const lines = [
|
|
48
|
+
'Usage: node scripts/moqui-metadata-extract.js [options]',
|
|
49
|
+
'',
|
|
50
|
+
'Options:',
|
|
51
|
+
` --project-dir <path> Moqui project root to scan (default: ${DEFAULT_PROJECT_DIR})`,
|
|
52
|
+
` --out <path> Metadata JSON output path (default: ${DEFAULT_OUT})`,
|
|
53
|
+
` --markdown-out <path> Metadata markdown summary path (default: ${DEFAULT_MARKDOWN_OUT})`,
|
|
54
|
+
' --json Print JSON payload to stdout',
|
|
55
|
+
' -h, --help Show this help'
|
|
56
|
+
];
|
|
57
|
+
console.log(lines.join('\n'));
|
|
58
|
+
process.exit(code);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeText(value) {
|
|
62
|
+
if (value === undefined || value === null) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const text = `${value}`.trim();
|
|
66
|
+
return text.length > 0 ? text : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeIdentifier(value) {
|
|
70
|
+
const text = normalizeText(value);
|
|
71
|
+
if (!text) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
return text
|
|
75
|
+
.toLowerCase()
|
|
76
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
77
|
+
.replace(/^-+|-+$/g, '');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function normalizeEntityName(entityName, packageName) {
|
|
81
|
+
const entity = normalizeText(entityName);
|
|
82
|
+
if (!entity) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
if (entity.includes('.')) {
|
|
86
|
+
return entity;
|
|
87
|
+
}
|
|
88
|
+
const pkg = normalizeText(packageName);
|
|
89
|
+
return pkg ? `${pkg}.${entity}` : entity;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseAttributes(tagText) {
|
|
93
|
+
const attrs = {};
|
|
94
|
+
if (!tagText) {
|
|
95
|
+
return attrs;
|
|
96
|
+
}
|
|
97
|
+
const pattern = /([a-zA-Z0-9:_-]+)\s*=\s*("([^"]*)"|'([^']*)')/g;
|
|
98
|
+
let match = pattern.exec(tagText);
|
|
99
|
+
while (match) {
|
|
100
|
+
const key = `${match[1]}`.trim();
|
|
101
|
+
const value = normalizeText(match[3] !== undefined ? match[3] : match[4]);
|
|
102
|
+
if (key && value !== null) {
|
|
103
|
+
attrs[key] = value;
|
|
104
|
+
}
|
|
105
|
+
match = pattern.exec(tagText);
|
|
106
|
+
}
|
|
107
|
+
return attrs;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function firstDefined(source, keys) {
|
|
111
|
+
for (const key of keys) {
|
|
112
|
+
const value = source && Object.prototype.hasOwnProperty.call(source, key)
|
|
113
|
+
? source[key]
|
|
114
|
+
: undefined;
|
|
115
|
+
const normalized = normalizeText(value);
|
|
116
|
+
if (normalized) {
|
|
117
|
+
return normalized;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function toArray(value) {
|
|
124
|
+
if (Array.isArray(value)) {
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
if (value === undefined || value === null) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
return [value];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function readPathValue(payload, pathText) {
|
|
134
|
+
if (!payload || typeof payload !== 'object') {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
const segments = `${pathText || ''}`
|
|
138
|
+
.split('.')
|
|
139
|
+
.map(token => token.trim())
|
|
140
|
+
.filter(Boolean);
|
|
141
|
+
let cursor = payload;
|
|
142
|
+
for (const segment of segments) {
|
|
143
|
+
if (!cursor || typeof cursor !== 'object') {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
cursor = cursor[segment];
|
|
147
|
+
}
|
|
148
|
+
return cursor;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function collectArrayFromPaths(payload, paths) {
|
|
152
|
+
for (const pathText of paths) {
|
|
153
|
+
const raw = readPathValue(payload, pathText);
|
|
154
|
+
if (Array.isArray(raw)) {
|
|
155
|
+
return raw;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function pickField(source, candidates) {
|
|
162
|
+
for (const candidate of candidates) {
|
|
163
|
+
const value = readPathValue(source, candidate);
|
|
164
|
+
const text = normalizeText(value);
|
|
165
|
+
if (text) {
|
|
166
|
+
return text;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function readNumberFirst(payload, paths) {
|
|
173
|
+
for (const pathText of paths) {
|
|
174
|
+
const value = readPathValue(payload, pathText);
|
|
175
|
+
const number = Number(value);
|
|
176
|
+
if (Number.isFinite(number) && number > 0) {
|
|
177
|
+
return number;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function collectEntityModels(content, sourceFile) {
|
|
184
|
+
const models = [];
|
|
185
|
+
const blocks = content.match(/<entity\b[\s\S]*?<\/entity>/gi) || [];
|
|
186
|
+
for (const block of blocks) {
|
|
187
|
+
const openTagMatch = block.match(/<entity\b[^>]*>/i);
|
|
188
|
+
if (!openTagMatch) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const attrs = parseAttributes(openTagMatch[0]);
|
|
192
|
+
const name = firstDefined(attrs, ['entity-name', 'name']);
|
|
193
|
+
const packageName = firstDefined(attrs, ['package-name', 'package']);
|
|
194
|
+
const fullName = normalizeEntityName(name, packageName);
|
|
195
|
+
if (!fullName) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const relations = [];
|
|
200
|
+
const relationPattern = /<relationship\b[^>]*>/gi;
|
|
201
|
+
let relationMatch = relationPattern.exec(block);
|
|
202
|
+
while (relationMatch) {
|
|
203
|
+
const relationAttrs = parseAttributes(relationMatch[0]);
|
|
204
|
+
const related = firstDefined(relationAttrs, [
|
|
205
|
+
'related-entity-name',
|
|
206
|
+
'related-entity',
|
|
207
|
+
'related',
|
|
208
|
+
'entity-name'
|
|
209
|
+
]);
|
|
210
|
+
const normalizedRelated = normalizeText(related);
|
|
211
|
+
if (normalizedRelated) {
|
|
212
|
+
relations.push(normalizedRelated);
|
|
213
|
+
}
|
|
214
|
+
relationMatch = relationPattern.exec(block);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
models.push({
|
|
218
|
+
name: fullName,
|
|
219
|
+
package: packageName,
|
|
220
|
+
relations,
|
|
221
|
+
source_file: sourceFile
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return models;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function collectServiceModels(content, sourceFile) {
|
|
228
|
+
const models = [];
|
|
229
|
+
const blocks = content.match(/<service\b[\s\S]*?<\/service>/gi) || [];
|
|
230
|
+
for (const block of blocks) {
|
|
231
|
+
const openTagMatch = block.match(/<service\b[^>]*>/i);
|
|
232
|
+
if (!openTagMatch) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const attrs = parseAttributes(openTagMatch[0]);
|
|
236
|
+
const verb = firstDefined(attrs, ['verb']);
|
|
237
|
+
const noun = firstDefined(attrs, ['noun']);
|
|
238
|
+
const explicitName = firstDefined(attrs, ['service-name', 'name']);
|
|
239
|
+
let name = explicitName;
|
|
240
|
+
if (!name && (verb || noun)) {
|
|
241
|
+
name = `${verb || 'service'}#${noun || 'operation'}`;
|
|
242
|
+
}
|
|
243
|
+
if (!name) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const entities = [];
|
|
248
|
+
const entityRefPattern = /entity-name\s*=\s*("([^"]*)"|'([^']*)')/gi;
|
|
249
|
+
let entityMatch = entityRefPattern.exec(block);
|
|
250
|
+
while (entityMatch) {
|
|
251
|
+
const value = normalizeText(entityMatch[2] !== undefined ? entityMatch[2] : entityMatch[3]);
|
|
252
|
+
if (value) {
|
|
253
|
+
entities.push(value);
|
|
254
|
+
}
|
|
255
|
+
entityMatch = entityRefPattern.exec(block);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
models.push({
|
|
259
|
+
name,
|
|
260
|
+
verb,
|
|
261
|
+
noun,
|
|
262
|
+
entities,
|
|
263
|
+
source_file: sourceFile
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return models;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function collectScreenModels(content, sourceFile) {
|
|
270
|
+
const hasScreen = /<screen\b/i.test(content);
|
|
271
|
+
if (!hasScreen) {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const screenTagMatch = content.match(/<screen\b[^>]*>/i);
|
|
276
|
+
const screenAttrs = parseAttributes(screenTagMatch ? screenTagMatch[0] : '');
|
|
277
|
+
const screenPath = firstDefined(screenAttrs, ['name', 'location']) || sourceFile;
|
|
278
|
+
|
|
279
|
+
const services = [];
|
|
280
|
+
const servicePattern = /service-name\s*=\s*("([^"]*)"|'([^']*)')/gi;
|
|
281
|
+
let serviceMatch = servicePattern.exec(content);
|
|
282
|
+
while (serviceMatch) {
|
|
283
|
+
const value = normalizeText(serviceMatch[2] !== undefined ? serviceMatch[2] : serviceMatch[3]);
|
|
284
|
+
if (value) {
|
|
285
|
+
services.push(value);
|
|
286
|
+
}
|
|
287
|
+
serviceMatch = servicePattern.exec(content);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const entities = [];
|
|
291
|
+
const entityPattern = /entity-name\s*=\s*("([^"]*)"|'([^']*)')/gi;
|
|
292
|
+
let entityMatch = entityPattern.exec(content);
|
|
293
|
+
while (entityMatch) {
|
|
294
|
+
const value = normalizeText(entityMatch[2] !== undefined ? entityMatch[2] : entityMatch[3]);
|
|
295
|
+
if (value) {
|
|
296
|
+
entities.push(value);
|
|
297
|
+
}
|
|
298
|
+
entityMatch = entityPattern.exec(content);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return [{
|
|
302
|
+
path: screenPath,
|
|
303
|
+
services,
|
|
304
|
+
entities,
|
|
305
|
+
source_file: sourceFile
|
|
306
|
+
}];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function collectFormModels(content, sourceFile) {
|
|
310
|
+
const models = [];
|
|
311
|
+
const blocks = content.match(/<(form-single|form-list|form)\b[\s\S]*?<\/\1>/gi) || [];
|
|
312
|
+
for (const block of blocks) {
|
|
313
|
+
const openTagMatch = block.match(/<(form-single|form-list|form)\b[^>]*>/i);
|
|
314
|
+
if (!openTagMatch) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const attrs = parseAttributes(openTagMatch[0]);
|
|
318
|
+
const name = firstDefined(attrs, ['name']) || `${sourceFile}#form`;
|
|
319
|
+
const fieldCount = (block.match(/<field\b/gi) || []).length;
|
|
320
|
+
models.push({
|
|
321
|
+
name,
|
|
322
|
+
screen: sourceFile,
|
|
323
|
+
field_count: fieldCount,
|
|
324
|
+
source_file: sourceFile
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const selfClosing = content.match(/<(form-single|form-list|form)\b[^>]*\/>/gi) || [];
|
|
329
|
+
for (const tag of selfClosing) {
|
|
330
|
+
const attrs = parseAttributes(tag);
|
|
331
|
+
const name = firstDefined(attrs, ['name']);
|
|
332
|
+
if (!name) {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
models.push({
|
|
336
|
+
name,
|
|
337
|
+
screen: sourceFile,
|
|
338
|
+
field_count: 0,
|
|
339
|
+
source_file: sourceFile
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return models;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function collectNamedTags(content, tagName, fieldLabel, sourceFile) {
|
|
347
|
+
const items = [];
|
|
348
|
+
const pattern = new RegExp(`<${tagName}\\b[^>]*>`, 'gi');
|
|
349
|
+
let match = pattern.exec(content);
|
|
350
|
+
while (match) {
|
|
351
|
+
const attrs = parseAttributes(match[0]);
|
|
352
|
+
const value = firstDefined(attrs, ['name', 'id', fieldLabel]);
|
|
353
|
+
if (value) {
|
|
354
|
+
items.push({
|
|
355
|
+
name: value,
|
|
356
|
+
source_file: sourceFile
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
match = pattern.exec(content);
|
|
360
|
+
}
|
|
361
|
+
return items;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function parseNamedArray(entries, fieldCandidates, sourceFile) {
|
|
365
|
+
const result = [];
|
|
366
|
+
for (const entry of toArray(entries)) {
|
|
367
|
+
const value = typeof entry === 'string'
|
|
368
|
+
? normalizeText(entry)
|
|
369
|
+
: pickField(entry, fieldCandidates);
|
|
370
|
+
if (!value) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
result.push({
|
|
374
|
+
name: value,
|
|
375
|
+
source_file: sourceFile
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function collectModelsFromScenePackage(payload, sourceFile) {
|
|
382
|
+
if (!payload || typeof payload !== 'object') {
|
|
383
|
+
return {
|
|
384
|
+
entities: [],
|
|
385
|
+
services: [],
|
|
386
|
+
screens: [],
|
|
387
|
+
forms: [],
|
|
388
|
+
businessRules: [],
|
|
389
|
+
decisions: [],
|
|
390
|
+
templateRefs: [],
|
|
391
|
+
capabilityRefs: []
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const ontologyModel = payload.ontology_model && typeof payload.ontology_model === 'object'
|
|
396
|
+
? payload.ontology_model
|
|
397
|
+
: {};
|
|
398
|
+
const capabilityContract = payload.capability_contract && typeof payload.capability_contract === 'object'
|
|
399
|
+
? payload.capability_contract
|
|
400
|
+
: {};
|
|
401
|
+
const governanceContract = payload.governance_contract && typeof payload.governance_contract === 'object'
|
|
402
|
+
? payload.governance_contract
|
|
403
|
+
: {};
|
|
404
|
+
const capabilities = payload.capabilities && typeof payload.capabilities === 'object'
|
|
405
|
+
? payload.capabilities
|
|
406
|
+
: {};
|
|
407
|
+
const artifacts = payload.artifacts && typeof payload.artifacts === 'object'
|
|
408
|
+
? payload.artifacts
|
|
409
|
+
: {};
|
|
410
|
+
const metadata = payload.metadata && typeof payload.metadata === 'object'
|
|
411
|
+
? payload.metadata
|
|
412
|
+
: {};
|
|
413
|
+
const agentHints = payload.agent_hints && typeof payload.agent_hints === 'object'
|
|
414
|
+
? payload.agent_hints
|
|
415
|
+
: {};
|
|
416
|
+
|
|
417
|
+
const entities = [];
|
|
418
|
+
const services = [];
|
|
419
|
+
const screens = [];
|
|
420
|
+
const forms = [];
|
|
421
|
+
const businessRules = [];
|
|
422
|
+
const decisions = [];
|
|
423
|
+
const bindingRefs = [];
|
|
424
|
+
const ontologyEntityRefs = [];
|
|
425
|
+
|
|
426
|
+
const ontologyEntities = toArray(ontologyModel.entities);
|
|
427
|
+
for (const item of ontologyEntities) {
|
|
428
|
+
if (!item || typeof item !== 'object') {
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
const id = pickField(item, ['id', 'name', 'ref', 'entity']);
|
|
432
|
+
if (!id) {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const type = normalizeIdentifier(pickField(item, ['type'])) || '';
|
|
436
|
+
if (type === 'entity' || /^entity:/i.test(id)) {
|
|
437
|
+
const entityRef = id.replace(/^entity:/i, '');
|
|
438
|
+
ontologyEntityRefs.push(entityRef);
|
|
439
|
+
entities.push({
|
|
440
|
+
name: entityRef,
|
|
441
|
+
package: null,
|
|
442
|
+
relations: [],
|
|
443
|
+
source_file: sourceFile
|
|
444
|
+
});
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (['query', 'invoke', 'service', 'action', 'command', 'operation'].includes(type)) {
|
|
448
|
+
services.push({
|
|
449
|
+
name: id,
|
|
450
|
+
verb: null,
|
|
451
|
+
noun: null,
|
|
452
|
+
entities: [],
|
|
453
|
+
source_file: sourceFile
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
for (const binding of toArray(capabilityContract.bindings)) {
|
|
459
|
+
if (!binding || typeof binding !== 'object') {
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
const ref = pickField(binding, ['ref', 'service', 'service_name', 'name', 'id']);
|
|
463
|
+
if (!ref) {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
services.push({
|
|
467
|
+
name: ref,
|
|
468
|
+
verb: pickField(binding, ['verb']),
|
|
469
|
+
noun: pickField(binding, ['noun']),
|
|
470
|
+
entities: toArray(binding.entities || binding.entity_refs)
|
|
471
|
+
.map(item => (typeof item === 'string' ? normalizeText(item) : pickField(item, ['name', 'id', 'entity'])))
|
|
472
|
+
.filter(Boolean),
|
|
473
|
+
source_file: sourceFile
|
|
474
|
+
});
|
|
475
|
+
bindingRefs.push(ref);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const entryScene = pickField(artifacts, ['entry_scene', 'entryScene', 'scene']);
|
|
479
|
+
const resolvedScenePath = entryScene
|
|
480
|
+
? `${sourceFile}#${entryScene}`
|
|
481
|
+
: null;
|
|
482
|
+
if (entryScene) {
|
|
483
|
+
screens.push({
|
|
484
|
+
path: resolvedScenePath,
|
|
485
|
+
services: bindingRefs.slice(0, MAX_HINT_ITEMS),
|
|
486
|
+
entities: deduplicateBy(ontologyEntityRefs.map(name => ({ name })), item => item.name).map(item => item.name),
|
|
487
|
+
source_file: sourceFile
|
|
488
|
+
});
|
|
489
|
+
} else {
|
|
490
|
+
const sceneName = pickField(metadata, ['name', 'summary']);
|
|
491
|
+
if (sceneName) {
|
|
492
|
+
screens.push({
|
|
493
|
+
path: `scene/${normalizeIdentifier(sceneName)}`,
|
|
494
|
+
services: bindingRefs.slice(0, MAX_HINT_ITEMS),
|
|
495
|
+
entities: deduplicateBy(ontologyEntityRefs.map(name => ({ name })), item => item.name).map(item => item.name),
|
|
496
|
+
source_file: sourceFile
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const parameterCount = Math.max(
|
|
502
|
+
toArray(payload.parameters).length,
|
|
503
|
+
Math.min(toArray(capabilityContract.bindings).length, 6)
|
|
504
|
+
);
|
|
505
|
+
if (parameterCount > 0) {
|
|
506
|
+
const formName = pickField(metadata, ['name']) || normalizeIdentifier(sourceFile) || 'scene-form';
|
|
507
|
+
forms.push({
|
|
508
|
+
name: `${formName}-input-form`,
|
|
509
|
+
screen: resolvedScenePath || sourceFile,
|
|
510
|
+
field_count: parameterCount,
|
|
511
|
+
source_file: sourceFile
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
businessRules.push(...parseNamedArray(governanceContract.business_rules, ['name', 'id', 'rule', 'description'], sourceFile));
|
|
516
|
+
businessRules.push(...parseNamedArray(ontologyModel.business_rules, ['name', 'id', 'rule', 'description'], sourceFile));
|
|
517
|
+
decisions.push(...parseNamedArray(governanceContract.decision_logic, ['name', 'id', 'decision', 'description'], sourceFile));
|
|
518
|
+
decisions.push(...parseNamedArray(ontologyModel.decision_logic, ['name', 'id', 'decision', 'description'], sourceFile));
|
|
519
|
+
decisions.push(...parseNamedArray(agentHints.decision_logic, ['name', 'id', 'decision'], sourceFile));
|
|
520
|
+
|
|
521
|
+
const templateRefs = [];
|
|
522
|
+
const templateName = pickField(metadata, ['name']);
|
|
523
|
+
if (templateName) {
|
|
524
|
+
templateRefs.push(templateName);
|
|
525
|
+
}
|
|
526
|
+
const capabilityRefs = toArray(capabilities.provides)
|
|
527
|
+
.concat(toArray(capabilities.requires))
|
|
528
|
+
.map(item => (typeof item === 'string' ? normalizeText(item) : pickField(item, ['name', 'id'])))
|
|
529
|
+
.filter(Boolean);
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
entities,
|
|
533
|
+
services,
|
|
534
|
+
screens,
|
|
535
|
+
forms,
|
|
536
|
+
businessRules,
|
|
537
|
+
decisions,
|
|
538
|
+
templateRefs,
|
|
539
|
+
capabilityRefs
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function collectModelsFromGenericJson(payload, sourceFile) {
|
|
544
|
+
if (!payload || typeof payload !== 'object') {
|
|
545
|
+
return {
|
|
546
|
+
entities: [],
|
|
547
|
+
services: [],
|
|
548
|
+
screens: [],
|
|
549
|
+
forms: [],
|
|
550
|
+
businessRules: [],
|
|
551
|
+
decisions: [],
|
|
552
|
+
businessRuleTotalHint: 0,
|
|
553
|
+
decisionTotalHint: 0
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const entities = collectArrayFromPaths(payload, [
|
|
558
|
+
'entities',
|
|
559
|
+
'entity_catalog',
|
|
560
|
+
'entity_catalog.entities',
|
|
561
|
+
'catalog.entities'
|
|
562
|
+
]).map((entry) => {
|
|
563
|
+
const name = typeof entry === 'string'
|
|
564
|
+
? normalizeText(entry)
|
|
565
|
+
: pickField(entry, ['name', 'id', 'entity', 'entity_name', 'ref']);
|
|
566
|
+
if (!name) {
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
name,
|
|
571
|
+
package: typeof entry === 'object' ? pickField(entry, ['package', 'package_name']) : null,
|
|
572
|
+
relations: [],
|
|
573
|
+
source_file: sourceFile
|
|
574
|
+
};
|
|
575
|
+
}).filter(Boolean);
|
|
576
|
+
|
|
577
|
+
const services = collectArrayFromPaths(payload, [
|
|
578
|
+
'services',
|
|
579
|
+
'service_catalog',
|
|
580
|
+
'service_catalog.services',
|
|
581
|
+
'catalog.services',
|
|
582
|
+
'capability_contract.bindings'
|
|
583
|
+
]).map((entry) => {
|
|
584
|
+
const name = typeof entry === 'string'
|
|
585
|
+
? normalizeText(entry)
|
|
586
|
+
: pickField(entry, ['name', 'id', 'service', 'service_name', 'ref']);
|
|
587
|
+
if (!name) {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
return {
|
|
591
|
+
name,
|
|
592
|
+
verb: typeof entry === 'object' ? pickField(entry, ['verb']) : null,
|
|
593
|
+
noun: typeof entry === 'object' ? pickField(entry, ['noun']) : null,
|
|
594
|
+
entities: [],
|
|
595
|
+
source_file: sourceFile
|
|
596
|
+
};
|
|
597
|
+
}).filter(Boolean);
|
|
598
|
+
|
|
599
|
+
const screens = collectArrayFromPaths(payload, [
|
|
600
|
+
'screens',
|
|
601
|
+
'screen_catalog',
|
|
602
|
+
'screen_catalog.screens',
|
|
603
|
+
'catalog.screens',
|
|
604
|
+
'scenes',
|
|
605
|
+
'pages'
|
|
606
|
+
]).map((entry) => {
|
|
607
|
+
const screenPath = typeof entry === 'string'
|
|
608
|
+
? normalizeText(entry)
|
|
609
|
+
: pickField(entry, ['path', 'screen_path', 'name', 'id', 'ref']);
|
|
610
|
+
if (!screenPath) {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
path: screenPath,
|
|
615
|
+
services: [],
|
|
616
|
+
entities: [],
|
|
617
|
+
source_file: sourceFile
|
|
618
|
+
};
|
|
619
|
+
}).filter(Boolean);
|
|
620
|
+
|
|
621
|
+
const forms = collectArrayFromPaths(payload, [
|
|
622
|
+
'forms',
|
|
623
|
+
'form_catalog',
|
|
624
|
+
'form_catalog.forms',
|
|
625
|
+
'catalog.forms'
|
|
626
|
+
]).map((entry) => {
|
|
627
|
+
const name = typeof entry === 'string'
|
|
628
|
+
? normalizeText(entry)
|
|
629
|
+
: pickField(entry, ['name', 'id', 'form', 'form_name', 'ref']);
|
|
630
|
+
if (!name) {
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
const fieldCount = typeof entry === 'object'
|
|
634
|
+
? toArray(entry.fields || entry.field_defs || entry.columns).length
|
|
635
|
+
: 0;
|
|
636
|
+
return {
|
|
637
|
+
name,
|
|
638
|
+
screen: typeof entry === 'object' ? pickField(entry, ['screen', 'screen_path', 'screen_ref']) : null,
|
|
639
|
+
field_count: fieldCount,
|
|
640
|
+
source_file: sourceFile
|
|
641
|
+
};
|
|
642
|
+
}).filter(Boolean);
|
|
643
|
+
|
|
644
|
+
const businessRules = []
|
|
645
|
+
.concat(parseNamedArray(collectArrayFromPaths(payload, ['business_rules', 'rules', 'rule_catalog']), ['name', 'id', 'rule'], sourceFile))
|
|
646
|
+
.concat(parseNamedArray(readPathValue(payload, 'governance_contract.business_rules'), ['name', 'id', 'rule', 'description'], sourceFile))
|
|
647
|
+
.concat(parseNamedArray(readPathValue(payload, 'ontology_model.business_rules'), ['name', 'id', 'rule', 'description'], sourceFile));
|
|
648
|
+
const decisions = []
|
|
649
|
+
.concat(parseNamedArray(collectArrayFromPaths(payload, ['decisions', 'decision_logic', 'decision_catalog']), ['name', 'id', 'decision'], sourceFile))
|
|
650
|
+
.concat(parseNamedArray(readPathValue(payload, 'governance_contract.decision_logic'), ['name', 'id', 'decision', 'description'], sourceFile))
|
|
651
|
+
.concat(parseNamedArray(readPathValue(payload, 'ontology_model.decision_logic'), ['name', 'id', 'decision', 'description'], sourceFile));
|
|
652
|
+
|
|
653
|
+
const businessRuleTotalHint = readNumberFirst(payload, [
|
|
654
|
+
'business_rules.total',
|
|
655
|
+
'coverage.business_rules.total',
|
|
656
|
+
'metrics.business_rules.total',
|
|
657
|
+
'ontology_validation.business_rules.total'
|
|
658
|
+
]);
|
|
659
|
+
const decisionTotalHint = readNumberFirst(payload, [
|
|
660
|
+
'decision_logic.total',
|
|
661
|
+
'coverage.decision_logic.total',
|
|
662
|
+
'metrics.decision_logic.total',
|
|
663
|
+
'ontology_validation.decision_logic.total'
|
|
664
|
+
]);
|
|
665
|
+
|
|
666
|
+
return {
|
|
667
|
+
entities,
|
|
668
|
+
services,
|
|
669
|
+
screens,
|
|
670
|
+
forms,
|
|
671
|
+
businessRules,
|
|
672
|
+
decisions,
|
|
673
|
+
businessRuleTotalHint,
|
|
674
|
+
decisionTotalHint
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function collectHandoffHints(payload, sourceFile) {
|
|
679
|
+
if (!payload || typeof payload !== 'object') {
|
|
680
|
+
return {
|
|
681
|
+
templates: [],
|
|
682
|
+
capabilities: [],
|
|
683
|
+
specScenePackagePaths: [],
|
|
684
|
+
businessRuleTotalHint: 0,
|
|
685
|
+
decisionTotalHint: 0
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const templates = toArray(payload.templates)
|
|
690
|
+
.map(entry => (typeof entry === 'string'
|
|
691
|
+
? normalizeText(entry)
|
|
692
|
+
: pickField(entry, ['id', 'name', 'template', 'template_id', 'path'])))
|
|
693
|
+
.filter(Boolean);
|
|
694
|
+
const capabilities = toArray(payload.capabilities)
|
|
695
|
+
.map(entry => (typeof entry === 'string'
|
|
696
|
+
? normalizeText(entry)
|
|
697
|
+
: pickField(entry, ['id', 'name', 'capability', 'ref'])))
|
|
698
|
+
.filter(Boolean);
|
|
699
|
+
|
|
700
|
+
const specScenePackagePaths = toArray(payload.specs)
|
|
701
|
+
.map((entry) => {
|
|
702
|
+
if (!entry || typeof entry !== 'object') {
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
return pickField(entry, ['scene_package', 'spec_package', 'package', 'specPackage']);
|
|
706
|
+
})
|
|
707
|
+
.filter(Boolean);
|
|
708
|
+
|
|
709
|
+
const businessRuleTotalHint = readNumberFirst(payload, [
|
|
710
|
+
'ontology_validation.business_rules.total',
|
|
711
|
+
'business_rules.total',
|
|
712
|
+
'coverage.business_rules.total',
|
|
713
|
+
'metrics.business_rules.total'
|
|
714
|
+
]);
|
|
715
|
+
const decisionTotalHint = readNumberFirst(payload, [
|
|
716
|
+
'ontology_validation.decision_logic.total',
|
|
717
|
+
'decision_logic.total',
|
|
718
|
+
'coverage.decision_logic.total',
|
|
719
|
+
'metrics.decision_logic.total'
|
|
720
|
+
]);
|
|
721
|
+
|
|
722
|
+
const knownGaps = parseNamedArray(payload.known_gaps, ['name', 'id', 'gap'], sourceFile)
|
|
723
|
+
.map(item => item.name)
|
|
724
|
+
.filter(Boolean);
|
|
725
|
+
|
|
726
|
+
return {
|
|
727
|
+
templates,
|
|
728
|
+
capabilities,
|
|
729
|
+
specScenePackagePaths,
|
|
730
|
+
businessRuleTotalHint,
|
|
731
|
+
decisionTotalHint,
|
|
732
|
+
knownGaps
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function classifyMatrixToken(token) {
|
|
737
|
+
const text = normalizeText(token);
|
|
738
|
+
if (!text) {
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
const lower = text.toLowerCase();
|
|
742
|
+
if (/(scene--|template)/.test(lower)) return 'template';
|
|
743
|
+
if (/(decision|routing|strategy)/.test(lower)) return 'decision';
|
|
744
|
+
if (/(rule|policy|governance|compliance)/.test(lower)) return 'business_rule';
|
|
745
|
+
if (/(form|entry)/.test(lower)) return 'form';
|
|
746
|
+
if (/(screen|scene|ui|page|dashboard|panel|dialog)/.test(lower)) return 'screen';
|
|
747
|
+
if (/(service|workflow|approval|invoke|query|report|ops)/.test(lower)) return 'service';
|
|
748
|
+
return 'entity';
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function collectMatrixHints(content, sourceFile) {
|
|
752
|
+
const entities = [];
|
|
753
|
+
const services = [];
|
|
754
|
+
const screens = [];
|
|
755
|
+
const forms = [];
|
|
756
|
+
const businessRules = [];
|
|
757
|
+
const decisions = [];
|
|
758
|
+
const templates = [];
|
|
759
|
+
const capabilities = [];
|
|
760
|
+
|
|
761
|
+
const lines = `${content || ''}`.split(/\r?\n/);
|
|
762
|
+
for (const line of lines) {
|
|
763
|
+
const text = normalizeText(line);
|
|
764
|
+
if (!text || !text.startsWith('|')) {
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
if (/^\|\s*-+\s*\|/.test(text)) {
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
const columns = text
|
|
771
|
+
.split('|')
|
|
772
|
+
.map(item => item.trim())
|
|
773
|
+
.filter(Boolean);
|
|
774
|
+
if (columns.length < 3) {
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
const normalizedColumns = columns.map(item => (item || '').toLowerCase());
|
|
778
|
+
const headerLabels = new Set([
|
|
779
|
+
'priority',
|
|
780
|
+
'track',
|
|
781
|
+
'item',
|
|
782
|
+
'spec',
|
|
783
|
+
'moqui capability',
|
|
784
|
+
'capability focus',
|
|
785
|
+
'sce scene pattern',
|
|
786
|
+
'template id',
|
|
787
|
+
'ontology anchors',
|
|
788
|
+
'governance/gate focus',
|
|
789
|
+
'status',
|
|
790
|
+
'result'
|
|
791
|
+
]);
|
|
792
|
+
const headerMatches = normalizedColumns.filter(item => headerLabels.has(item)).length;
|
|
793
|
+
if (headerMatches >= 2) {
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
const lowerLine = text.toLowerCase();
|
|
797
|
+
if (/priority|capability|template id|status/.test(lowerLine) && /---/.test(lowerLine)) {
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const inlineTokens = [];
|
|
802
|
+
const tokenPattern = /`([^`]+)`/g;
|
|
803
|
+
let match = tokenPattern.exec(text);
|
|
804
|
+
while (match) {
|
|
805
|
+
const token = normalizeText(match[1]);
|
|
806
|
+
if (token) {
|
|
807
|
+
inlineTokens.push(token);
|
|
808
|
+
}
|
|
809
|
+
match = tokenPattern.exec(text);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
for (const token of inlineTokens) {
|
|
813
|
+
const kind = classifyMatrixToken(token);
|
|
814
|
+
if (kind === 'template') {
|
|
815
|
+
templates.push(token);
|
|
816
|
+
} else if (kind === 'service') {
|
|
817
|
+
services.push({
|
|
818
|
+
name: token,
|
|
819
|
+
verb: null,
|
|
820
|
+
noun: null,
|
|
821
|
+
entities: [],
|
|
822
|
+
source_file: sourceFile
|
|
823
|
+
});
|
|
824
|
+
} else if (kind === 'screen') {
|
|
825
|
+
screens.push({
|
|
826
|
+
path: token,
|
|
827
|
+
services: [],
|
|
828
|
+
entities: [],
|
|
829
|
+
source_file: sourceFile
|
|
830
|
+
});
|
|
831
|
+
} else if (kind === 'form') {
|
|
832
|
+
forms.push({
|
|
833
|
+
name: token,
|
|
834
|
+
screen: sourceFile,
|
|
835
|
+
field_count: 0,
|
|
836
|
+
source_file: sourceFile
|
|
837
|
+
});
|
|
838
|
+
} else if (kind === 'business_rule') {
|
|
839
|
+
businessRules.push({
|
|
840
|
+
name: token,
|
|
841
|
+
source_file: sourceFile
|
|
842
|
+
});
|
|
843
|
+
} else if (kind === 'decision') {
|
|
844
|
+
decisions.push({
|
|
845
|
+
name: token,
|
|
846
|
+
source_file: sourceFile
|
|
847
|
+
});
|
|
848
|
+
} else if (kind === 'entity') {
|
|
849
|
+
entities.push({
|
|
850
|
+
name: token,
|
|
851
|
+
package: null,
|
|
852
|
+
relations: [],
|
|
853
|
+
source_file: sourceFile
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (columns.length >= 3) {
|
|
859
|
+
const capabilityText = normalizeText(columns[2]);
|
|
860
|
+
if (capabilityText && !/^(full|pass|in progress|template-ready|matrix-intake-ready)$/i.test(capabilityText)) {
|
|
861
|
+
capabilities.push(capabilityText);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
return {
|
|
867
|
+
entities,
|
|
868
|
+
services,
|
|
869
|
+
screens,
|
|
870
|
+
forms,
|
|
871
|
+
businessRules,
|
|
872
|
+
decisions,
|
|
873
|
+
templates,
|
|
874
|
+
capabilities
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function inferModelsFromHints(hints, sourceFile) {
|
|
879
|
+
const entities = [];
|
|
880
|
+
const services = [];
|
|
881
|
+
const screens = [];
|
|
882
|
+
const forms = [];
|
|
883
|
+
const businessRules = [];
|
|
884
|
+
const decisions = [];
|
|
885
|
+
|
|
886
|
+
const hintRefs = deduplicateBy(
|
|
887
|
+
toArray(hints.templates).concat(toArray(hints.capabilities)).map(item => ({ name: item })),
|
|
888
|
+
item => item && item.name
|
|
889
|
+
)
|
|
890
|
+
.map(item => item.name)
|
|
891
|
+
.slice(0, MAX_HINT_ITEMS);
|
|
892
|
+
|
|
893
|
+
const addRule = (name) => {
|
|
894
|
+
const text = normalizeText(name);
|
|
895
|
+
if (!text) {
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
businessRules.push({ name: text, source_file: sourceFile });
|
|
899
|
+
};
|
|
900
|
+
const addDecision = (name) => {
|
|
901
|
+
const text = normalizeText(name);
|
|
902
|
+
if (!text) {
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
decisions.push({ name: text, source_file: sourceFile });
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
for (const rawRef of hintRefs) {
|
|
909
|
+
const ref = normalizeText(rawRef);
|
|
910
|
+
if (!ref) {
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
const slug = normalizeIdentifier(ref);
|
|
914
|
+
if (!slug) {
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
const lower = ref.toLowerCase();
|
|
918
|
+
|
|
919
|
+
if (/(entity|model|master|party|product|order|inventory|procurement|shipment|return|rma|quality|bom|routing|equipment|cost|invoice|employee|calendar|project|wbs|engineering)/.test(lower)) {
|
|
920
|
+
entities.push({
|
|
921
|
+
name: `hint.${slug}`,
|
|
922
|
+
package: 'hint',
|
|
923
|
+
relations: [],
|
|
924
|
+
source_file: sourceFile
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
if (/(service|workflow|approval|invoke|action|orchestration|query|report|ops|governance|runtime)/.test(lower)) {
|
|
928
|
+
services.push({
|
|
929
|
+
name: `hint.service.${slug}`,
|
|
930
|
+
verb: null,
|
|
931
|
+
noun: null,
|
|
932
|
+
entities: [],
|
|
933
|
+
source_file: sourceFile
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
if (/(screen|scene|ui|page|panel|dialog|dashboard|hub)/.test(lower)) {
|
|
937
|
+
screens.push({
|
|
938
|
+
path: `hint/${slug}`,
|
|
939
|
+
services: [],
|
|
940
|
+
entities: [],
|
|
941
|
+
source_file: sourceFile
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
if (/(form|entry)/.test(lower)) {
|
|
945
|
+
forms.push({
|
|
946
|
+
name: `hint-${slug}-form`,
|
|
947
|
+
screen: `hint/${slug}`,
|
|
948
|
+
field_count: 0,
|
|
949
|
+
source_file: sourceFile
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
if (/(rule|governance|policy|compliance|audit)/.test(lower)) {
|
|
953
|
+
addRule(`hint.rule.${slug}`);
|
|
954
|
+
}
|
|
955
|
+
if (/(decision|routing|strategy|approval)/.test(lower)) {
|
|
956
|
+
addDecision(`hint.decision.${slug}`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
const ruleTotalHint = Number(hints.businessRuleTotalHint) || 0;
|
|
961
|
+
for (let i = businessRules.length; i < ruleTotalHint; i += 1) {
|
|
962
|
+
addRule(`hint.rule.inferred-${i + 1}`);
|
|
963
|
+
}
|
|
964
|
+
const decisionTotalHint = Number(hints.decisionTotalHint) || 0;
|
|
965
|
+
for (let i = decisions.length; i < decisionTotalHint; i += 1) {
|
|
966
|
+
addDecision(`hint.decision.inferred-${i + 1}`);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
return {
|
|
970
|
+
entities,
|
|
971
|
+
services,
|
|
972
|
+
screens,
|
|
973
|
+
forms,
|
|
974
|
+
businessRules,
|
|
975
|
+
decisions
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
async function listProjectFiles(projectDir) {
|
|
980
|
+
const xmlFiles = [];
|
|
981
|
+
const scenePackageFiles = [];
|
|
982
|
+
const evidenceJsonFiles = [];
|
|
983
|
+
const salvageJsonFiles = [];
|
|
984
|
+
const stack = [projectDir];
|
|
985
|
+
|
|
986
|
+
while (stack.length > 0) {
|
|
987
|
+
const current = stack.pop();
|
|
988
|
+
const entries = await fs.readdir(current).catch(() => []);
|
|
989
|
+
for (const name of entries) {
|
|
990
|
+
const fullPath = path.join(current, name);
|
|
991
|
+
const stat = await fs.stat(fullPath).catch(() => null);
|
|
992
|
+
if (!stat) {
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
if (stat.isDirectory()) {
|
|
996
|
+
stack.push(fullPath);
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
if (!stat.isFile()) {
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
const relativePath = path.relative(projectDir, fullPath).replace(/\\/g, '/');
|
|
1003
|
+
const normalizedRelative = relativePath.toLowerCase();
|
|
1004
|
+
if (/\.xml$/i.test(name)) {
|
|
1005
|
+
xmlFiles.push(fullPath);
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
if (/^scene-package\.json$/i.test(name)) {
|
|
1009
|
+
scenePackageFiles.push(fullPath);
|
|
1010
|
+
}
|
|
1011
|
+
if (/\.json$/i.test(name) && normalizedRelative.startsWith(`${DEFAULT_EVIDENCE_DIR.toLowerCase()}/`)) {
|
|
1012
|
+
evidenceJsonFiles.push(fullPath);
|
|
1013
|
+
}
|
|
1014
|
+
if (/\.json$/i.test(name) && normalizedRelative.startsWith(`${DEFAULT_SALVAGE_DIR.toLowerCase()}/`)) {
|
|
1015
|
+
salvageJsonFiles.push(fullPath);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
xmlFiles.sort();
|
|
1021
|
+
scenePackageFiles.sort();
|
|
1022
|
+
evidenceJsonFiles.sort();
|
|
1023
|
+
salvageJsonFiles.sort();
|
|
1024
|
+
return {
|
|
1025
|
+
xmlFiles,
|
|
1026
|
+
scenePackageFiles,
|
|
1027
|
+
evidenceJsonFiles,
|
|
1028
|
+
salvageJsonFiles
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function deduplicateBy(items, keySelector) {
|
|
1033
|
+
const result = [];
|
|
1034
|
+
const seen = new Set();
|
|
1035
|
+
for (const item of items) {
|
|
1036
|
+
const key = normalizeIdentifier(keySelector(item));
|
|
1037
|
+
if (!key || seen.has(key)) {
|
|
1038
|
+
continue;
|
|
1039
|
+
}
|
|
1040
|
+
seen.add(key);
|
|
1041
|
+
result.push(item);
|
|
1042
|
+
}
|
|
1043
|
+
return result;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function buildMarkdownReport(report) {
|
|
1047
|
+
const lines = [];
|
|
1048
|
+
lines.push('# Moqui Metadata Catalog');
|
|
1049
|
+
lines.push('');
|
|
1050
|
+
lines.push(`- Generated at: ${report.generated_at}`);
|
|
1051
|
+
lines.push(`- Source project: ${report.source_project}`);
|
|
1052
|
+
lines.push(`- XML files scanned: ${report.scan.xml_file_count}`);
|
|
1053
|
+
lines.push(`- Scene packages scanned: ${report.scan.scene_package_file_count}`);
|
|
1054
|
+
lines.push(`- Handoff manifest: ${report.scan.handoff_manifest_found ? 'found' : 'not found'}`);
|
|
1055
|
+
lines.push(`- Capability matrix: ${report.scan.capability_matrix_found ? 'found' : 'not found'}`);
|
|
1056
|
+
lines.push(`- Evidence JSON scanned: ${report.scan.evidence_json_file_count}`);
|
|
1057
|
+
lines.push(`- Salvage JSON scanned: ${report.scan.salvage_json_file_count}`);
|
|
1058
|
+
lines.push('');
|
|
1059
|
+
lines.push('## Summary');
|
|
1060
|
+
lines.push('');
|
|
1061
|
+
lines.push(`- Entities: ${report.summary.entities}`);
|
|
1062
|
+
lines.push(`- Services: ${report.summary.services}`);
|
|
1063
|
+
lines.push(`- Screens: ${report.summary.screens}`);
|
|
1064
|
+
lines.push(`- Forms: ${report.summary.forms}`);
|
|
1065
|
+
lines.push(`- Business rules: ${report.summary.business_rules}`);
|
|
1066
|
+
lines.push(`- Decisions: ${report.summary.decisions}`);
|
|
1067
|
+
lines.push('');
|
|
1068
|
+
|
|
1069
|
+
const samples = [
|
|
1070
|
+
['Entities', report.entities.map(item => item.name)],
|
|
1071
|
+
['Services', report.services.map(item => item.name)],
|
|
1072
|
+
['Screens', report.screens.map(item => item.path)],
|
|
1073
|
+
['Forms', report.forms.map(item => item.name)],
|
|
1074
|
+
['Business Rules', report.business_rules.map(item => item.name)],
|
|
1075
|
+
['Decisions', report.decisions.map(item => item.name)]
|
|
1076
|
+
];
|
|
1077
|
+
|
|
1078
|
+
for (const [title, values] of samples) {
|
|
1079
|
+
lines.push(`## ${title}`);
|
|
1080
|
+
lines.push('');
|
|
1081
|
+
if (!values || values.length === 0) {
|
|
1082
|
+
lines.push('- none');
|
|
1083
|
+
lines.push('');
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
for (const value of values.slice(0, 10)) {
|
|
1087
|
+
lines.push(`- ${value}`);
|
|
1088
|
+
}
|
|
1089
|
+
if (values.length > 10) {
|
|
1090
|
+
lines.push(`- ... (+${values.length - 10} more)`);
|
|
1091
|
+
}
|
|
1092
|
+
lines.push('');
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
return `${lines.join('\n')}\n`;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
async function main() {
|
|
1099
|
+
const options = parseArgs(process.argv.slice(2));
|
|
1100
|
+
const projectDir = path.resolve(process.cwd(), options.projectDir);
|
|
1101
|
+
const outPath = path.resolve(process.cwd(), options.out);
|
|
1102
|
+
const markdownPath = path.resolve(process.cwd(), options.markdownOut);
|
|
1103
|
+
const handoffManifestPath = path.resolve(projectDir, DEFAULT_HANDOFF_MANIFEST);
|
|
1104
|
+
const capabilityMatrixPath = path.resolve(projectDir, DEFAULT_CAPABILITY_MATRIX);
|
|
1105
|
+
|
|
1106
|
+
if (!(await fs.pathExists(projectDir))) {
|
|
1107
|
+
throw new Error(`project directory not found: ${path.relative(process.cwd(), projectDir)}`);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
const scannedFiles = await listProjectFiles(projectDir);
|
|
1111
|
+
const xmlFiles = scannedFiles.xmlFiles;
|
|
1112
|
+
const entitiesRaw = [];
|
|
1113
|
+
const servicesRaw = [];
|
|
1114
|
+
const screensRaw = [];
|
|
1115
|
+
const formsRaw = [];
|
|
1116
|
+
const businessRulesRaw = [];
|
|
1117
|
+
const decisionsRaw = [];
|
|
1118
|
+
const hintTemplates = [];
|
|
1119
|
+
const hintCapabilities = [];
|
|
1120
|
+
const hintKnownGaps = [];
|
|
1121
|
+
let businessRuleTotalHint = 0;
|
|
1122
|
+
let decisionTotalHint = 0;
|
|
1123
|
+
const parsedScenePackageFiles = new Set();
|
|
1124
|
+
|
|
1125
|
+
for (const filePath of xmlFiles) {
|
|
1126
|
+
const sourceFile = path.relative(projectDir, filePath).replace(/\\/g, '/');
|
|
1127
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
1128
|
+
entitiesRaw.push(...collectEntityModels(content, sourceFile));
|
|
1129
|
+
servicesRaw.push(...collectServiceModels(content, sourceFile));
|
|
1130
|
+
screensRaw.push(...collectScreenModels(content, sourceFile));
|
|
1131
|
+
formsRaw.push(...collectFormModels(content, sourceFile));
|
|
1132
|
+
businessRulesRaw.push(...collectNamedTags(content, 'rule', 'rule', sourceFile));
|
|
1133
|
+
decisionsRaw.push(...collectNamedTags(content, 'decision', 'decision', sourceFile));
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
for (const filePath of scannedFiles.scenePackageFiles) {
|
|
1137
|
+
const sourceFile = path.relative(projectDir, filePath).replace(/\\/g, '/');
|
|
1138
|
+
const payload = await fs.readJson(filePath).catch(() => null);
|
|
1139
|
+
if (!payload) {
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
const models = collectModelsFromScenePackage(payload, sourceFile);
|
|
1143
|
+
entitiesRaw.push(...models.entities);
|
|
1144
|
+
servicesRaw.push(...models.services);
|
|
1145
|
+
screensRaw.push(...models.screens);
|
|
1146
|
+
formsRaw.push(...models.forms);
|
|
1147
|
+
businessRulesRaw.push(...models.businessRules);
|
|
1148
|
+
decisionsRaw.push(...models.decisions);
|
|
1149
|
+
hintTemplates.push(...models.templateRefs);
|
|
1150
|
+
hintCapabilities.push(...models.capabilityRefs);
|
|
1151
|
+
parsedScenePackageFiles.add(path.resolve(filePath).toLowerCase());
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
const handoffManifestFound = await fs.pathExists(handoffManifestPath);
|
|
1155
|
+
if (handoffManifestFound) {
|
|
1156
|
+
const payload = await fs.readJson(handoffManifestPath).catch(() => null);
|
|
1157
|
+
if (payload) {
|
|
1158
|
+
const sourceFile = path.relative(projectDir, handoffManifestPath).replace(/\\/g, '/');
|
|
1159
|
+
const hints = collectHandoffHints(payload, sourceFile);
|
|
1160
|
+
hintTemplates.push(...hints.templates);
|
|
1161
|
+
hintCapabilities.push(...hints.capabilities);
|
|
1162
|
+
hintKnownGaps.push(...toArray(hints.knownGaps));
|
|
1163
|
+
businessRuleTotalHint = Math.max(businessRuleTotalHint, Number(hints.businessRuleTotalHint) || 0);
|
|
1164
|
+
decisionTotalHint = Math.max(decisionTotalHint, Number(hints.decisionTotalHint) || 0);
|
|
1165
|
+
|
|
1166
|
+
for (const scenePackageRelative of hints.specScenePackagePaths) {
|
|
1167
|
+
const resolved = path.resolve(projectDir, scenePackageRelative);
|
|
1168
|
+
const resolvedKey = resolved.toLowerCase();
|
|
1169
|
+
if (parsedScenePackageFiles.has(resolvedKey)) {
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
if (!(await fs.pathExists(resolved))) {
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
const packagePayload = await fs.readJson(resolved).catch(() => null);
|
|
1176
|
+
if (!packagePayload) {
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
const sceneSourceFile = path.relative(projectDir, resolved).replace(/\\/g, '/');
|
|
1180
|
+
const models = collectModelsFromScenePackage(packagePayload, sceneSourceFile);
|
|
1181
|
+
entitiesRaw.push(...models.entities);
|
|
1182
|
+
servicesRaw.push(...models.services);
|
|
1183
|
+
screensRaw.push(...models.screens);
|
|
1184
|
+
formsRaw.push(...models.forms);
|
|
1185
|
+
businessRulesRaw.push(...models.businessRules);
|
|
1186
|
+
decisionsRaw.push(...models.decisions);
|
|
1187
|
+
hintTemplates.push(...models.templateRefs);
|
|
1188
|
+
hintCapabilities.push(...models.capabilityRefs);
|
|
1189
|
+
parsedScenePackageFiles.add(resolvedKey);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
const capabilityMatrixFound = await fs.pathExists(capabilityMatrixPath);
|
|
1195
|
+
if (capabilityMatrixFound) {
|
|
1196
|
+
const content = await fs.readFile(capabilityMatrixPath, 'utf8').catch(() => null);
|
|
1197
|
+
if (content) {
|
|
1198
|
+
const sourceFile = path.relative(projectDir, capabilityMatrixPath).replace(/\\/g, '/');
|
|
1199
|
+
const matrixHints = collectMatrixHints(content, sourceFile);
|
|
1200
|
+
entitiesRaw.push(...matrixHints.entities);
|
|
1201
|
+
servicesRaw.push(...matrixHints.services);
|
|
1202
|
+
screensRaw.push(...matrixHints.screens);
|
|
1203
|
+
formsRaw.push(...matrixHints.forms);
|
|
1204
|
+
businessRulesRaw.push(...matrixHints.businessRules);
|
|
1205
|
+
decisionsRaw.push(...matrixHints.decisions);
|
|
1206
|
+
hintTemplates.push(...matrixHints.templates);
|
|
1207
|
+
hintCapabilities.push(...matrixHints.capabilities);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
for (const filePath of scannedFiles.evidenceJsonFiles.concat(scannedFiles.salvageJsonFiles)) {
|
|
1212
|
+
const sourceFile = path.relative(projectDir, filePath).replace(/\\/g, '/');
|
|
1213
|
+
const payload = await fs.readJson(filePath).catch(() => null);
|
|
1214
|
+
if (!payload) {
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
const models = collectModelsFromGenericJson(payload, sourceFile);
|
|
1218
|
+
entitiesRaw.push(...models.entities);
|
|
1219
|
+
servicesRaw.push(...models.services);
|
|
1220
|
+
screensRaw.push(...models.screens);
|
|
1221
|
+
formsRaw.push(...models.forms);
|
|
1222
|
+
businessRulesRaw.push(...models.businessRules);
|
|
1223
|
+
decisionsRaw.push(...models.decisions);
|
|
1224
|
+
businessRuleTotalHint = Math.max(businessRuleTotalHint, Number(models.businessRuleTotalHint) || 0);
|
|
1225
|
+
decisionTotalHint = Math.max(decisionTotalHint, Number(models.decisionTotalHint) || 0);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
const inferred = inferModelsFromHints({
|
|
1229
|
+
templates: hintTemplates,
|
|
1230
|
+
capabilities: hintCapabilities,
|
|
1231
|
+
businessRuleTotalHint,
|
|
1232
|
+
decisionTotalHint
|
|
1233
|
+
}, `${DEFAULT_HANDOFF_MANIFEST}#inferred`);
|
|
1234
|
+
entitiesRaw.push(...inferred.entities);
|
|
1235
|
+
servicesRaw.push(...inferred.services);
|
|
1236
|
+
screensRaw.push(...inferred.screens);
|
|
1237
|
+
formsRaw.push(...inferred.forms);
|
|
1238
|
+
businessRulesRaw.push(...inferred.businessRules);
|
|
1239
|
+
decisionsRaw.push(...inferred.decisions);
|
|
1240
|
+
|
|
1241
|
+
const entities = deduplicateBy(entitiesRaw, item => item && item.name).map(item => ({
|
|
1242
|
+
name: item.name,
|
|
1243
|
+
package: item.package || null,
|
|
1244
|
+
relations: deduplicateBy((item.relations || []).map(name => ({ name })), relation => relation.name).map(
|
|
1245
|
+
relation => relation.name
|
|
1246
|
+
),
|
|
1247
|
+
source_file: item.source_file
|
|
1248
|
+
}));
|
|
1249
|
+
const services = deduplicateBy(servicesRaw, item => item && item.name).map(item => ({
|
|
1250
|
+
name: item.name,
|
|
1251
|
+
verb: item.verb || null,
|
|
1252
|
+
noun: item.noun || null,
|
|
1253
|
+
entities: deduplicateBy((item.entities || []).map(name => ({ name })), ref => ref.name).map(ref => ref.name),
|
|
1254
|
+
source_file: item.source_file
|
|
1255
|
+
}));
|
|
1256
|
+
const screens = deduplicateBy(screensRaw, item => item && item.path).map(item => ({
|
|
1257
|
+
path: item.path,
|
|
1258
|
+
services: deduplicateBy((item.services || []).map(name => ({ name })), ref => ref.name).map(ref => ref.name),
|
|
1259
|
+
entities: deduplicateBy((item.entities || []).map(name => ({ name })), ref => ref.name).map(ref => ref.name),
|
|
1260
|
+
source_file: item.source_file
|
|
1261
|
+
}));
|
|
1262
|
+
const forms = deduplicateBy(formsRaw, item => item && item.name).map(item => ({
|
|
1263
|
+
name: item.name,
|
|
1264
|
+
screen: item.screen || null,
|
|
1265
|
+
field_count: Number(item.field_count) || 0,
|
|
1266
|
+
source_file: item.source_file
|
|
1267
|
+
}));
|
|
1268
|
+
const businessRules = deduplicateBy(businessRulesRaw, item => item && item.name).slice(0, MAX_HINT_ITEMS * 4);
|
|
1269
|
+
const decisions = deduplicateBy(decisionsRaw, item => item && item.name).slice(0, MAX_HINT_ITEMS * 4);
|
|
1270
|
+
|
|
1271
|
+
const report = {
|
|
1272
|
+
mode: 'moqui-metadata-extract',
|
|
1273
|
+
generated_at: new Date().toISOString(),
|
|
1274
|
+
source_project: projectDir.replace(/\\/g, '/'),
|
|
1275
|
+
scan: {
|
|
1276
|
+
xml_file_count: xmlFiles.length,
|
|
1277
|
+
scene_package_file_count: scannedFiles.scenePackageFiles.length,
|
|
1278
|
+
handoff_manifest_found: handoffManifestFound,
|
|
1279
|
+
capability_matrix_found: capabilityMatrixFound,
|
|
1280
|
+
evidence_json_file_count: scannedFiles.evidenceJsonFiles.length,
|
|
1281
|
+
salvage_json_file_count: scannedFiles.salvageJsonFiles.length,
|
|
1282
|
+
xml_files: xmlFiles.map(file => path.relative(projectDir, file).replace(/\\/g, '/')),
|
|
1283
|
+
scene_package_files: scannedFiles.scenePackageFiles.map(file => path.relative(projectDir, file).replace(/\\/g, '/'))
|
|
1284
|
+
},
|
|
1285
|
+
hints: {
|
|
1286
|
+
templates: deduplicateBy(hintTemplates.map(name => ({ name })), item => item.name)
|
|
1287
|
+
.map(item => item.name)
|
|
1288
|
+
.slice(0, MAX_HINT_ITEMS),
|
|
1289
|
+
capabilities: deduplicateBy(hintCapabilities.map(name => ({ name })), item => item.name)
|
|
1290
|
+
.map(item => item.name)
|
|
1291
|
+
.slice(0, MAX_HINT_ITEMS),
|
|
1292
|
+
known_gaps: deduplicateBy(hintKnownGaps.map(name => ({ name })), item => item.name)
|
|
1293
|
+
.map(item => item.name)
|
|
1294
|
+
.slice(0, Math.floor(MAX_HINT_ITEMS / 2)),
|
|
1295
|
+
business_rule_total_hint: businessRuleTotalHint,
|
|
1296
|
+
decision_total_hint: decisionTotalHint
|
|
1297
|
+
},
|
|
1298
|
+
summary: {
|
|
1299
|
+
entities: entities.length,
|
|
1300
|
+
services: services.length,
|
|
1301
|
+
screens: screens.length,
|
|
1302
|
+
forms: forms.length,
|
|
1303
|
+
business_rules: businessRules.length,
|
|
1304
|
+
decisions: decisions.length
|
|
1305
|
+
},
|
|
1306
|
+
entities,
|
|
1307
|
+
services,
|
|
1308
|
+
screens,
|
|
1309
|
+
forms,
|
|
1310
|
+
business_rules: businessRules,
|
|
1311
|
+
decisions
|
|
1312
|
+
};
|
|
1313
|
+
|
|
1314
|
+
await fs.ensureDir(path.dirname(outPath));
|
|
1315
|
+
await fs.writeJson(outPath, report, { spaces: 2 });
|
|
1316
|
+
await fs.ensureDir(path.dirname(markdownPath));
|
|
1317
|
+
await fs.writeFile(markdownPath, buildMarkdownReport(report), 'utf8');
|
|
1318
|
+
|
|
1319
|
+
const stdoutPayload = {
|
|
1320
|
+
...report,
|
|
1321
|
+
output: {
|
|
1322
|
+
json: path.relative(process.cwd(), outPath),
|
|
1323
|
+
markdown: path.relative(process.cwd(), markdownPath)
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
if (options.json) {
|
|
1328
|
+
console.log(JSON.stringify(stdoutPayload, null, 2));
|
|
1329
|
+
} else {
|
|
1330
|
+
console.log('Moqui metadata catalog extracted.');
|
|
1331
|
+
console.log(` JSON: ${path.relative(process.cwd(), outPath)}`);
|
|
1332
|
+
console.log(` Markdown: ${path.relative(process.cwd(), markdownPath)}`);
|
|
1333
|
+
console.log(` XML scanned: ${report.scan.xml_file_count}`);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
main().catch((error) => {
|
|
1338
|
+
console.error(`Failed to extract Moqui metadata catalog: ${error.message}`);
|
|
1339
|
+
process.exitCode = 1;
|
|
1340
|
+
});
|