scene-capability-engine 3.6.32 → 3.6.36
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 +86 -1
- package/README.md +119 -122
- package/README.zh.md +123 -121
- package/bin/scene-capability-engine.js +11 -0
- package/docs/README.md +21 -32
- package/docs/auto-refactor-index.md +384 -0
- package/docs/command-reference.md +94 -2
- package/docs/magicball-adaptation-task-checklist-v1.md +385 -0
- package/docs/magicball-app-bundle-sqlite-and-command-draft.md +539 -0
- package/docs/magicball-capability-iteration-api.md +2 -0
- package/docs/magicball-capability-iteration-ui.md +2 -0
- package/docs/magicball-capability-library.md +2 -0
- package/docs/magicball-cli-invocation-examples.md +336 -0
- package/docs/magicball-frontend-state-and-command-mapping.md +244 -0
- package/docs/magicball-integration-doc-index.md +137 -0
- package/docs/magicball-integration-issue-tracker.md +218 -0
- package/docs/magicball-mode-home-and-ontology-empty-state-playbook.md +249 -0
- package/docs/magicball-sce-adaptation-guide.md +203 -0
- package/docs/magicball-three-mode-alignment-plan.md +551 -0
- package/docs/magicball-ui-surface-checklist.md +126 -0
- package/docs/magicball-write-auth-adaptation-guide.md +328 -0
- package/docs/refactor-completion-roadmap.md +116 -0
- package/docs/zh/README.md +27 -30
- package/docs/zh/refactor-completion-roadmap.md +116 -0
- package/lib/app/registry-config.js +73 -0
- package/lib/app/registry-sync-service.js +228 -0
- package/lib/auto/archive-schema-service.js +276 -0
- package/lib/auto/archive-summary.js +60 -0
- package/lib/auto/batch-goal-input-service.js +543 -0
- package/lib/auto/batch-output.js +201 -0
- package/lib/auto/batch-summary-storage-service.js +110 -0
- package/lib/auto/close-loop-batch-service.js +116 -0
- package/lib/auto/close-loop-controller-service.js +287 -0
- package/lib/auto/close-loop-program-service.js +283 -0
- package/lib/auto/close-loop-recovery-service.js +191 -0
- package/lib/auto/close-loop-session-storage-service.js +50 -0
- package/lib/auto/controller-lock-service.js +55 -0
- package/lib/auto/controller-output.js +32 -0
- package/lib/auto/controller-queue-service.js +127 -0
- package/lib/auto/controller-session-storage-service.js +105 -0
- package/lib/auto/governance-advisory-service.js +208 -0
- package/lib/auto/governance-close-loop-service.js +411 -0
- package/lib/auto/governance-maintenance-presenter.js +162 -0
- package/lib/auto/governance-maintenance-service.js +112 -0
- package/lib/auto/governance-session-presenter.js +70 -0
- package/lib/auto/governance-session-storage-service.js +198 -0
- package/lib/auto/governance-signals.js +139 -0
- package/lib/auto/governance-stats-presenter.js +337 -0
- package/lib/auto/governance-stats-service.js +115 -0
- package/lib/auto/governance-summary.js +703 -0
- package/lib/auto/handoff-capability-matrix-service.js +281 -0
- package/lib/auto/handoff-evidence-review-service.js +251 -0
- package/lib/auto/handoff-release-evidence-service.js +190 -0
- package/lib/auto/handoff-release-gate-history-loaders-service.js +502 -0
- package/lib/auto/handoff-release-gate-history-service.js +257 -0
- package/lib/auto/handoff-reporting-service.js +1407 -0
- package/lib/auto/handoff-run-service.js +486 -0
- package/lib/auto/handoff-snapshots-service.js +645 -0
- package/lib/auto/observability-service.js +132 -0
- package/lib/auto/output-writer.js +34 -0
- package/lib/auto/program-auto-remediation-service.js +130 -0
- package/lib/auto/program-diagnostics.js +138 -0
- package/lib/auto/program-governance-helpers.js +306 -0
- package/lib/auto/program-governance-loop-service.js +413 -0
- package/lib/auto/program-output.js +106 -0
- package/lib/auto/program-summary.js +183 -0
- package/lib/auto/recovery-memory-service.js +684 -0
- package/lib/auto/recovery-selection-service.js +52 -0
- package/lib/auto/retention-policy.js +98 -0
- package/lib/auto/session-persistence-service.js +106 -0
- package/lib/auto/session-presenter.js +105 -0
- package/lib/auto/session-prune-service.js +190 -0
- package/lib/auto/session-query-service.js +249 -0
- package/lib/auto/spec-protection.js +141 -0
- package/lib/commands/app.js +911 -0
- package/lib/commands/assurance.js +212 -0
- package/lib/commands/auto.js +1091 -11063
- package/lib/commands/mode.js +321 -0
- package/lib/commands/ontology.js +415 -0
- package/lib/commands/pm.js +422 -0
- package/lib/ontology/seed-profiles.js +160 -0
- package/lib/state/sce-state-store.js +3369 -1200
- package/package.json +1 -1
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
async function buildAutoHandoffMoquiBaselineSnapshot(projectPath, dependencies = {}) {
|
|
2
|
+
const { fs, path, spawnSync, toAutoHandoffCliPath, buildAutoHandoffMoquiCoverageRegressions, AUTO_HANDOFF_MOQUI_BASELINE_JSON_FILE, AUTO_HANDOFF_MOQUI_BASELINE_MARKDOWN_FILE } = dependencies;
|
|
3
|
+
const scriptPath = path.join(projectPath, 'scripts', 'moqui-template-baseline-report.js');
|
|
4
|
+
if (!(await fs.pathExists(scriptPath))) {
|
|
5
|
+
return {
|
|
6
|
+
status: 'skipped',
|
|
7
|
+
generated: false,
|
|
8
|
+
reason: `baseline script missing: ${toAutoHandoffCliPath(projectPath, scriptPath)}`
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const outputJsonPath = path.join(projectPath, AUTO_HANDOFF_MOQUI_BASELINE_JSON_FILE);
|
|
13
|
+
const outputMarkdownPath = path.join(projectPath, AUTO_HANDOFF_MOQUI_BASELINE_MARKDOWN_FILE);
|
|
14
|
+
await fs.ensureDir(path.dirname(outputJsonPath));
|
|
15
|
+
|
|
16
|
+
const scriptArgs = [
|
|
17
|
+
scriptPath,
|
|
18
|
+
'--out', outputJsonPath,
|
|
19
|
+
'--markdown-out', outputMarkdownPath,
|
|
20
|
+
'--json'
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
if (await fs.pathExists(outputJsonPath)) {
|
|
24
|
+
scriptArgs.push('--compare-with', outputJsonPath);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const execution = spawnSync(process.execPath, scriptArgs, {
|
|
28
|
+
cwd: projectPath,
|
|
29
|
+
encoding: 'utf8'
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const stdout = typeof execution.stdout === 'string' ? execution.stdout.trim() : '';
|
|
33
|
+
const stderr = typeof execution.stderr === 'string' ? execution.stderr.trim() : '';
|
|
34
|
+
|
|
35
|
+
if (execution.error) {
|
|
36
|
+
return {
|
|
37
|
+
status: 'error',
|
|
38
|
+
generated: false,
|
|
39
|
+
error: execution.error.message
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (execution.status !== 0) {
|
|
44
|
+
return {
|
|
45
|
+
status: 'error',
|
|
46
|
+
generated: false,
|
|
47
|
+
error: stderr || stdout || `baseline script exited with code ${execution.status}`
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let reportPayload = null;
|
|
52
|
+
try {
|
|
53
|
+
reportPayload = stdout ? JSON.parse(stdout) : await fs.readJson(outputJsonPath);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
status: 'error',
|
|
57
|
+
generated: false,
|
|
58
|
+
error: `failed to parse baseline payload: ${error.message}`
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const summary = reportPayload && reportPayload.summary && typeof reportPayload.summary === 'object'
|
|
63
|
+
? reportPayload.summary
|
|
64
|
+
: {};
|
|
65
|
+
const compare = reportPayload && reportPayload.compare && typeof reportPayload.compare === 'object'
|
|
66
|
+
? reportPayload.compare
|
|
67
|
+
: null;
|
|
68
|
+
const failedTemplates = compare && compare.failed_templates && typeof compare.failed_templates === 'object'
|
|
69
|
+
? compare.failed_templates
|
|
70
|
+
: {};
|
|
71
|
+
const scopeBreakdown = summary && summary.scope_breakdown && typeof summary.scope_breakdown === 'object'
|
|
72
|
+
? summary.scope_breakdown
|
|
73
|
+
: {};
|
|
74
|
+
const coverageMatrix = summary && summary.coverage_matrix && typeof summary.coverage_matrix === 'object'
|
|
75
|
+
? summary.coverage_matrix
|
|
76
|
+
: {};
|
|
77
|
+
const gapFrequency = summary && Array.isArray(summary.gap_frequency)
|
|
78
|
+
? summary.gap_frequency
|
|
79
|
+
: [];
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
status: summary.portfolio_passed === true ? 'passed' : 'failed',
|
|
83
|
+
generated: true,
|
|
84
|
+
summary: {
|
|
85
|
+
total_templates: Number(summary.total_templates) || 0,
|
|
86
|
+
scoped_templates: Number(summary.scoped_templates) || 0,
|
|
87
|
+
avg_score: Number.isFinite(Number(summary.avg_score)) ? Number(summary.avg_score) : null,
|
|
88
|
+
valid_rate_percent: Number.isFinite(Number(summary.valid_rate_percent)) ? Number(summary.valid_rate_percent) : null,
|
|
89
|
+
baseline_passed: Number(summary.baseline_passed) || 0,
|
|
90
|
+
baseline_failed: Number(summary.baseline_failed) || 0,
|
|
91
|
+
portfolio_passed: summary.portfolio_passed === true,
|
|
92
|
+
scope_breakdown: {
|
|
93
|
+
moqui_erp: Number(scopeBreakdown.moqui_erp) || 0,
|
|
94
|
+
scene_orchestration: Number(scopeBreakdown.scene_orchestration) || 0,
|
|
95
|
+
other: Number(scopeBreakdown.other) || 0
|
|
96
|
+
},
|
|
97
|
+
coverage_matrix: coverageMatrix,
|
|
98
|
+
gap_frequency: gapFrequency
|
|
99
|
+
},
|
|
100
|
+
compare: compare
|
|
101
|
+
? {
|
|
102
|
+
previous_generated_at: compare.previous_generated_at || null,
|
|
103
|
+
previous_template_root: compare.previous_template_root || null,
|
|
104
|
+
deltas: compare.deltas || null,
|
|
105
|
+
coverage_matrix_deltas: compare.coverage_matrix_deltas || null,
|
|
106
|
+
coverage_matrix_regressions: buildAutoHandoffMoquiCoverageRegressions(compare),
|
|
107
|
+
failed_templates: {
|
|
108
|
+
previous: Array.isArray(failedTemplates.previous) ? failedTemplates.previous : [],
|
|
109
|
+
current: Array.isArray(failedTemplates.current) ? failedTemplates.current : [],
|
|
110
|
+
newly_failed: Array.isArray(failedTemplates.newly_failed) ? failedTemplates.newly_failed : [],
|
|
111
|
+
recovered: Array.isArray(failedTemplates.recovered) ? failedTemplates.recovered : []
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
: null,
|
|
115
|
+
output: {
|
|
116
|
+
json: toAutoHandoffCliPath(projectPath, outputJsonPath),
|
|
117
|
+
markdown: toAutoHandoffCliPath(projectPath, outputMarkdownPath)
|
|
118
|
+
},
|
|
119
|
+
warnings: stderr ? [stderr] : []
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function buildAutoHandoffScenePackageBatchSnapshot(projectPath, manifestPath, dependencies = {}) {
|
|
124
|
+
const { fs, path, spawnSync, normalizeHandoffText, toAutoHandoffCliPath, parseAutoHandoffJsonFromCommandStdout, AUTO_HANDOFF_CLI_SCRIPT_FILE, AUTO_HANDOFF_SCENE_PACKAGE_BATCH_JSON_FILE, AUTO_HANDOFF_SCENE_PACKAGE_BATCH_TASK_QUEUE_FILE } = dependencies;
|
|
125
|
+
const manifestFile = normalizeHandoffText(manifestPath);
|
|
126
|
+
if (!manifestFile) {
|
|
127
|
+
return {
|
|
128
|
+
status: 'skipped',
|
|
129
|
+
generated: false,
|
|
130
|
+
reason: 'manifest path unavailable for scene package batch gate'
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (!(await fs.pathExists(AUTO_HANDOFF_CLI_SCRIPT_FILE))) {
|
|
134
|
+
return {
|
|
135
|
+
status: 'skipped',
|
|
136
|
+
generated: false,
|
|
137
|
+
reason: `sce cli script missing: ${toAutoHandoffCliPath(projectPath, AUTO_HANDOFF_CLI_SCRIPT_FILE)}`
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const outputJsonPath = path.join(projectPath, AUTO_HANDOFF_SCENE_PACKAGE_BATCH_JSON_FILE);
|
|
142
|
+
const taskQueuePath = path.join(projectPath, AUTO_HANDOFF_SCENE_PACKAGE_BATCH_TASK_QUEUE_FILE);
|
|
143
|
+
await fs.ensureDir(path.dirname(outputJsonPath));
|
|
144
|
+
|
|
145
|
+
const execution = spawnSync(
|
|
146
|
+
process.execPath,
|
|
147
|
+
[
|
|
148
|
+
AUTO_HANDOFF_CLI_SCRIPT_FILE,
|
|
149
|
+
'scene',
|
|
150
|
+
'package-publish-batch',
|
|
151
|
+
'--manifest', manifestFile,
|
|
152
|
+
'--dry-run',
|
|
153
|
+
'--ontology-report-out', outputJsonPath,
|
|
154
|
+
'--ontology-task-queue-out', taskQueuePath,
|
|
155
|
+
'--json'
|
|
156
|
+
],
|
|
157
|
+
{
|
|
158
|
+
cwd: projectPath,
|
|
159
|
+
encoding: 'utf8'
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const stdout = typeof execution.stdout === 'string' ? execution.stdout.trim() : '';
|
|
164
|
+
const stderr = typeof execution.stderr === 'string' ? execution.stderr.trim() : '';
|
|
165
|
+
|
|
166
|
+
if (execution.error) {
|
|
167
|
+
return {
|
|
168
|
+
status: 'error',
|
|
169
|
+
generated: false,
|
|
170
|
+
error: execution.error.message
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const payload = parseAutoHandoffJsonFromCommandStdout(stdout);
|
|
175
|
+
if (!payload || typeof payload !== 'object') {
|
|
176
|
+
const missingSpecArray = /manifest spec array (not found|is empty)/i.test(stderr);
|
|
177
|
+
if (missingSpecArray) {
|
|
178
|
+
return {
|
|
179
|
+
status: 'skipped',
|
|
180
|
+
generated: false,
|
|
181
|
+
reason: 'manifest specs are not scene package batch compatible',
|
|
182
|
+
warnings: stderr ? [stderr] : []
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
status: 'error',
|
|
187
|
+
generated: false,
|
|
188
|
+
error: stderr || stdout || `scene package publish-batch exited with code ${execution.status}`,
|
|
189
|
+
warnings: stderr ? [stderr] : []
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const summary = payload.summary && typeof payload.summary === 'object'
|
|
194
|
+
? payload.summary
|
|
195
|
+
: {};
|
|
196
|
+
const ontologySummary = payload.ontology_summary && typeof payload.ontology_summary === 'object'
|
|
197
|
+
? payload.ontology_summary
|
|
198
|
+
: {};
|
|
199
|
+
const batchGate = payload.batch_ontology_gate && typeof payload.batch_ontology_gate === 'object'
|
|
200
|
+
? payload.batch_ontology_gate
|
|
201
|
+
: {};
|
|
202
|
+
const batchGateFailures = Array.isArray(batchGate.failures) ? batchGate.failures : [];
|
|
203
|
+
const selected = Number(summary.selected) || 0;
|
|
204
|
+
const failed = Number(summary.failed) || 0;
|
|
205
|
+
|
|
206
|
+
if (selected <= 0 && failed <= 0) {
|
|
207
|
+
return {
|
|
208
|
+
status: 'skipped',
|
|
209
|
+
generated: false,
|
|
210
|
+
reason: 'no scene package publish candidates were selected from handoff manifest',
|
|
211
|
+
summary: {
|
|
212
|
+
selected,
|
|
213
|
+
published: Number(summary.published) || 0,
|
|
214
|
+
planned: Number(summary.planned) || 0,
|
|
215
|
+
failed,
|
|
216
|
+
skipped: Number(summary.skipped) || 0,
|
|
217
|
+
batch_gate_passed: batchGate.passed === true,
|
|
218
|
+
batch_gate_failure_count: batchGateFailures.length
|
|
219
|
+
},
|
|
220
|
+
output: {
|
|
221
|
+
json: toAutoHandoffCliPath(projectPath, outputJsonPath)
|
|
222
|
+
},
|
|
223
|
+
warnings: stderr ? [stderr] : []
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
status: payload.success === true ? 'passed' : 'failed',
|
|
229
|
+
generated: true,
|
|
230
|
+
mode: payload.mode || 'dry-run',
|
|
231
|
+
success: payload.success === true,
|
|
232
|
+
manifest: normalizeHandoffText(payload.manifest),
|
|
233
|
+
summary: {
|
|
234
|
+
selected,
|
|
235
|
+
published: Number(summary.published) || 0,
|
|
236
|
+
planned: Number(summary.planned) || 0,
|
|
237
|
+
failed,
|
|
238
|
+
skipped: Number(summary.skipped) || 0,
|
|
239
|
+
batch_gate_passed: batchGate.passed === true,
|
|
240
|
+
batch_gate_failure_count: batchGateFailures.length,
|
|
241
|
+
ontology_average_score: Number.isFinite(Number(ontologySummary.average_score))
|
|
242
|
+
? Number(ontologySummary.average_score)
|
|
243
|
+
: null,
|
|
244
|
+
ontology_valid_rate_percent: Number.isFinite(Number(ontologySummary.valid_rate_percent))
|
|
245
|
+
? Number(ontologySummary.valid_rate_percent)
|
|
246
|
+
: null
|
|
247
|
+
},
|
|
248
|
+
failures: Array.isArray(payload.failures)
|
|
249
|
+
? payload.failures.map(item => ({
|
|
250
|
+
spec: normalizeHandoffText(item && item.spec),
|
|
251
|
+
error: normalizeHandoffText(item && item.error)
|
|
252
|
+
}))
|
|
253
|
+
: [],
|
|
254
|
+
batch_ontology_gate: {
|
|
255
|
+
passed: batchGate.passed === true,
|
|
256
|
+
failures: batchGateFailures.map(item => ({
|
|
257
|
+
id: normalizeHandoffText(item && item.id),
|
|
258
|
+
message: normalizeHandoffText(item && item.message)
|
|
259
|
+
}))
|
|
260
|
+
},
|
|
261
|
+
task_queue: payload.ontology_task_queue && typeof payload.ontology_task_queue === 'object'
|
|
262
|
+
? {
|
|
263
|
+
output_path: normalizeHandoffText(payload.ontology_task_queue.output_path),
|
|
264
|
+
task_count: Number(payload.ontology_task_queue.task_count) || 0
|
|
265
|
+
}
|
|
266
|
+
: null,
|
|
267
|
+
output: {
|
|
268
|
+
json: toAutoHandoffCliPath(projectPath, outputJsonPath)
|
|
269
|
+
},
|
|
270
|
+
warnings: stderr ? [stderr] : []
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function buildAutoHandoffCapabilityCoverageSnapshot(projectPath, handoff = null, policy = {}, dependencies = {}) {
|
|
275
|
+
const { fs, path, normalizeHandoffText, toAutoHandoffCliPath, resolveMoquiCapabilityDescriptor, MOQUI_CAPABILITY_LEXICON_INDEX, moquiCapabilityMatch, AUTO_HANDOFF_MOQUI_CAPABILITY_COVERAGE_JSON_FILE, AUTO_HANDOFF_MOQUI_CAPABILITY_COVERAGE_MARKDOWN_FILE, renderMoquiCapabilityCoverageMarkdown } = dependencies;
|
|
276
|
+
const loadLatestMoquiCapabilityCoverageReport = dependencies.loadLatestMoquiCapabilityCoverageReport || (async () => null);
|
|
277
|
+
const buildCapabilityCoverageComparison = dependencies.buildCapabilityCoverageComparison || (() => null);
|
|
278
|
+
const expectedRaw = Array.isArray(handoff && handoff.capabilities)
|
|
279
|
+
? handoff.capabilities
|
|
280
|
+
: [];
|
|
281
|
+
const normalization = {
|
|
282
|
+
lexicon_version: MOQUI_CAPABILITY_LEXICON_INDEX && MOQUI_CAPABILITY_LEXICON_INDEX.version
|
|
283
|
+
? MOQUI_CAPABILITY_LEXICON_INDEX.version
|
|
284
|
+
: null,
|
|
285
|
+
expected_alias_mapped: [],
|
|
286
|
+
expected_deprecated_aliases: [],
|
|
287
|
+
expected_unknown: [],
|
|
288
|
+
provided_alias_mapped: [],
|
|
289
|
+
provided_deprecated_aliases: [],
|
|
290
|
+
provided_unknown: []
|
|
291
|
+
};
|
|
292
|
+
const warnings = [];
|
|
293
|
+
const minRequiredPercentPolicy = Number(policy.min_capability_coverage_percent);
|
|
294
|
+
const minRequiredPercentValue = Number.isFinite(minRequiredPercentPolicy)
|
|
295
|
+
? Number(minRequiredPercentPolicy.toFixed(2))
|
|
296
|
+
: 100;
|
|
297
|
+
const minSemanticRequiredPolicy = Number(policy.min_capability_semantic_percent);
|
|
298
|
+
const minSemanticRequiredValue = Number.isFinite(minSemanticRequiredPolicy)
|
|
299
|
+
? Number(minSemanticRequiredPolicy.toFixed(2))
|
|
300
|
+
: 100;
|
|
301
|
+
const addNormalizationRecord = (target, descriptor) => {
|
|
302
|
+
const list = Array.isArray(normalization[target]) ? normalization[target] : [];
|
|
303
|
+
const item = {
|
|
304
|
+
raw: descriptor.raw,
|
|
305
|
+
normalized: descriptor.normalized,
|
|
306
|
+
canonical: descriptor.canonical
|
|
307
|
+
};
|
|
308
|
+
const key = `${item.raw}|${item.normalized}|${item.canonical}`;
|
|
309
|
+
if (!list.some(existing => `${existing.raw}|${existing.normalized}|${existing.canonical}` === key)) {
|
|
310
|
+
list.push(item);
|
|
311
|
+
}
|
|
312
|
+
normalization[target] = list;
|
|
313
|
+
};
|
|
314
|
+
const expectedMap = new Map();
|
|
315
|
+
for (const rawCapability of expectedRaw) {
|
|
316
|
+
const descriptor = resolveMoquiCapabilityDescriptor(rawCapability, MOQUI_CAPABILITY_LEXICON_INDEX);
|
|
317
|
+
if (!descriptor) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (descriptor.is_alias) {
|
|
321
|
+
addNormalizationRecord('expected_alias_mapped', descriptor);
|
|
322
|
+
}
|
|
323
|
+
if (descriptor.is_deprecated_alias) {
|
|
324
|
+
addNormalizationRecord('expected_deprecated_aliases', descriptor);
|
|
325
|
+
warnings.push(
|
|
326
|
+
`manifest capability "${descriptor.raw}" is deprecated; use "${descriptor.deprecated_replacement || descriptor.canonical}" instead`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
if (!descriptor.is_known) {
|
|
330
|
+
addNormalizationRecord('expected_unknown', descriptor);
|
|
331
|
+
warnings.push(`manifest capability "${descriptor.raw}" is unknown to Moqui lexicon`);
|
|
332
|
+
}
|
|
333
|
+
if (!expectedMap.has(descriptor.canonical)) {
|
|
334
|
+
expectedMap.set(descriptor.canonical, {
|
|
335
|
+
capability: descriptor.canonical,
|
|
336
|
+
source_values: [descriptor.normalized]
|
|
337
|
+
});
|
|
338
|
+
} else {
|
|
339
|
+
const existing = expectedMap.get(descriptor.canonical);
|
|
340
|
+
if (!existing.source_values.includes(descriptor.normalized)) {
|
|
341
|
+
existing.source_values.push(descriptor.normalized);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const expected = Array.from(expectedMap.keys());
|
|
346
|
+
if (expected.length === 0) {
|
|
347
|
+
return {
|
|
348
|
+
status: 'skipped',
|
|
349
|
+
generated: false,
|
|
350
|
+
reason: 'manifest capabilities not declared',
|
|
351
|
+
summary: {
|
|
352
|
+
total_capabilities: 0,
|
|
353
|
+
covered_capabilities: 0,
|
|
354
|
+
uncovered_capabilities: 0,
|
|
355
|
+
coverage_percent: null,
|
|
356
|
+
min_required_percent: minRequiredPercentValue,
|
|
357
|
+
semantic_complete_capabilities: 0,
|
|
358
|
+
semantic_incomplete_capabilities: 0,
|
|
359
|
+
semantic_complete_percent: null,
|
|
360
|
+
min_semantic_required_percent: minSemanticRequiredValue,
|
|
361
|
+
semantic_passed: true,
|
|
362
|
+
passed: true
|
|
363
|
+
},
|
|
364
|
+
coverage: [],
|
|
365
|
+
gaps: [],
|
|
366
|
+
normalization,
|
|
367
|
+
warnings
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const templateRoot = path.join(projectPath, '.sce', 'templates', 'scene-packages');
|
|
372
|
+
if (!(await fs.pathExists(templateRoot))) {
|
|
373
|
+
return {
|
|
374
|
+
status: 'skipped',
|
|
375
|
+
generated: false,
|
|
376
|
+
reason: `template library not found: ${toAutoHandoffCliPath(projectPath, templateRoot)}`,
|
|
377
|
+
summary: {
|
|
378
|
+
total_capabilities: expected.length,
|
|
379
|
+
covered_capabilities: 0,
|
|
380
|
+
uncovered_capabilities: expected.length,
|
|
381
|
+
coverage_percent: 0,
|
|
382
|
+
min_required_percent: minRequiredPercentValue,
|
|
383
|
+
semantic_complete_capabilities: 0,
|
|
384
|
+
semantic_incomplete_capabilities: expected.length,
|
|
385
|
+
semantic_complete_percent: 0,
|
|
386
|
+
min_semantic_required_percent: minSemanticRequiredValue,
|
|
387
|
+
semantic_passed: false,
|
|
388
|
+
passed: false
|
|
389
|
+
},
|
|
390
|
+
coverage: expected.map(item => ({
|
|
391
|
+
capability: item,
|
|
392
|
+
covered: false,
|
|
393
|
+
matched_templates: [],
|
|
394
|
+
matched_provides: [],
|
|
395
|
+
matched_template_semantics: [],
|
|
396
|
+
semantic_complete: false,
|
|
397
|
+
semantic_missing_dimensions: [
|
|
398
|
+
'ontology.entities',
|
|
399
|
+
'ontology.relations',
|
|
400
|
+
'governance.business_rules',
|
|
401
|
+
'governance.decision_logic'
|
|
402
|
+
],
|
|
403
|
+
source_values: expectedMap.get(item).source_values
|
|
404
|
+
})),
|
|
405
|
+
gaps: expected,
|
|
406
|
+
normalization,
|
|
407
|
+
warnings
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const templateEntries = await fs.readdir(templateRoot);
|
|
412
|
+
const templates = [];
|
|
413
|
+
for (const entry of templateEntries) {
|
|
414
|
+
const templateDir = path.join(templateRoot, entry);
|
|
415
|
+
let stat = null;
|
|
416
|
+
try {
|
|
417
|
+
stat = await fs.stat(templateDir);
|
|
418
|
+
} catch (_error) {
|
|
419
|
+
stat = null;
|
|
420
|
+
}
|
|
421
|
+
if (!stat || !stat.isDirectory()) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const contractFile = path.join(templateDir, 'scene-package.json');
|
|
425
|
+
if (!(await fs.pathExists(contractFile))) {
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
const payload = await fs.readJson(contractFile);
|
|
430
|
+
const providesRaw = [];
|
|
431
|
+
const contractProvides = payload && payload.contract && payload.contract.capabilities && payload.contract.capabilities.provides;
|
|
432
|
+
const rootProvides = payload && payload.capabilities && payload.capabilities.provides;
|
|
433
|
+
if (Array.isArray(contractProvides)) {
|
|
434
|
+
providesRaw.push(...contractProvides);
|
|
435
|
+
}
|
|
436
|
+
if (Array.isArray(rootProvides)) {
|
|
437
|
+
providesRaw.push(...rootProvides);
|
|
438
|
+
}
|
|
439
|
+
const provides = [];
|
|
440
|
+
for (const providedCapability of providesRaw) {
|
|
441
|
+
const descriptor = resolveMoquiCapabilityDescriptor(providedCapability, MOQUI_CAPABILITY_LEXICON_INDEX);
|
|
442
|
+
if (!descriptor) {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (descriptor.is_alias) {
|
|
446
|
+
addNormalizationRecord('provided_alias_mapped', descriptor);
|
|
447
|
+
}
|
|
448
|
+
if (descriptor.is_deprecated_alias) {
|
|
449
|
+
addNormalizationRecord('provided_deprecated_aliases', descriptor);
|
|
450
|
+
warnings.push(
|
|
451
|
+
`template "${entry}" uses deprecated capability "${descriptor.raw}" (canonical "${descriptor.deprecated_replacement || descriptor.canonical}")`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
if (!descriptor.is_known) {
|
|
455
|
+
addNormalizationRecord('provided_unknown', descriptor);
|
|
456
|
+
}
|
|
457
|
+
provides.push(descriptor.canonical);
|
|
458
|
+
}
|
|
459
|
+
const governanceContract = payload && payload.governance_contract && typeof payload.governance_contract === 'object'
|
|
460
|
+
? payload.governance_contract
|
|
461
|
+
: {};
|
|
462
|
+
const ontologyModel = payload && payload.ontology_model && typeof payload.ontology_model === 'object'
|
|
463
|
+
? payload.ontology_model
|
|
464
|
+
: {};
|
|
465
|
+
const businessRules = Array.isArray(governanceContract.business_rules)
|
|
466
|
+
? governanceContract.business_rules
|
|
467
|
+
: [];
|
|
468
|
+
const decisionLogic = Array.isArray(governanceContract.decision_logic)
|
|
469
|
+
? governanceContract.decision_logic
|
|
470
|
+
: [];
|
|
471
|
+
const ontologyEntities = Array.isArray(ontologyModel.entities)
|
|
472
|
+
? ontologyModel.entities
|
|
473
|
+
: [];
|
|
474
|
+
const ontologyRelations = Array.isArray(ontologyModel.relations)
|
|
475
|
+
? ontologyModel.relations
|
|
476
|
+
: [];
|
|
477
|
+
const semanticMissingDimensions = [];
|
|
478
|
+
if (ontologyEntities.length <= 0) {
|
|
479
|
+
semanticMissingDimensions.push('ontology.entities');
|
|
480
|
+
}
|
|
481
|
+
if (ontologyRelations.length <= 0) {
|
|
482
|
+
semanticMissingDimensions.push('ontology.relations');
|
|
483
|
+
}
|
|
484
|
+
if (businessRules.length <= 0) {
|
|
485
|
+
semanticMissingDimensions.push('governance.business_rules');
|
|
486
|
+
}
|
|
487
|
+
if (decisionLogic.length <= 0) {
|
|
488
|
+
semanticMissingDimensions.push('governance.decision_logic');
|
|
489
|
+
}
|
|
490
|
+
const uniqueProvides = Array.from(new Set(provides));
|
|
491
|
+
if (uniqueProvides.length > 0 && semanticMissingDimensions.length > 0) {
|
|
492
|
+
warnings.push(
|
|
493
|
+
`template "${entry}" semantic coverage missing: ${semanticMissingDimensions.join(', ')}`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
templates.push({
|
|
497
|
+
template_id: entry,
|
|
498
|
+
provides: uniqueProvides,
|
|
499
|
+
semantic: {
|
|
500
|
+
ontology_entities_count: ontologyEntities.length,
|
|
501
|
+
ontology_relations_count: ontologyRelations.length,
|
|
502
|
+
business_rules_count: businessRules.length,
|
|
503
|
+
decision_logic_count: decisionLogic.length,
|
|
504
|
+
missing_dimensions: semanticMissingDimensions,
|
|
505
|
+
complete: semanticMissingDimensions.length === 0
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
} catch (_error) {
|
|
509
|
+
// Ignore malformed template package entries.
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const coverage = expected.map(capability => {
|
|
514
|
+
const matchedTemplates = [];
|
|
515
|
+
const matchedProvides = [];
|
|
516
|
+
const matchedTemplateSemantics = [];
|
|
517
|
+
let hasOntologyEntities = false;
|
|
518
|
+
let hasOntologyRelations = false;
|
|
519
|
+
let hasBusinessRules = false;
|
|
520
|
+
let hasDecisionLogic = false;
|
|
521
|
+
for (const template of templates) {
|
|
522
|
+
const providedMatched = template.provides.filter(item => moquiCapabilityMatch(capability, item));
|
|
523
|
+
if (providedMatched.length > 0) {
|
|
524
|
+
matchedTemplates.push(template.template_id);
|
|
525
|
+
matchedProvides.push(...providedMatched);
|
|
526
|
+
const semantic = template.semantic && typeof template.semantic === 'object'
|
|
527
|
+
? template.semantic
|
|
528
|
+
: {};
|
|
529
|
+
const templateSemantic = {
|
|
530
|
+
template_id: template.template_id,
|
|
531
|
+
ontology_entities_count: Number(semantic.ontology_entities_count) || 0,
|
|
532
|
+
ontology_relations_count: Number(semantic.ontology_relations_count) || 0,
|
|
533
|
+
business_rules_count: Number(semantic.business_rules_count) || 0,
|
|
534
|
+
decision_logic_count: Number(semantic.decision_logic_count) || 0,
|
|
535
|
+
missing_dimensions: Array.isArray(semantic.missing_dimensions) ? semantic.missing_dimensions : [],
|
|
536
|
+
complete: semantic.complete === true
|
|
537
|
+
};
|
|
538
|
+
matchedTemplateSemantics.push(templateSemantic);
|
|
539
|
+
hasOntologyEntities = hasOntologyEntities || templateSemantic.ontology_entities_count > 0;
|
|
540
|
+
hasOntologyRelations = hasOntologyRelations || templateSemantic.ontology_relations_count > 0;
|
|
541
|
+
hasBusinessRules = hasBusinessRules || templateSemantic.business_rules_count > 0;
|
|
542
|
+
hasDecisionLogic = hasDecisionLogic || templateSemantic.decision_logic_count > 0;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
const semanticMissingDimensions = [];
|
|
546
|
+
if (!hasOntologyEntities) {
|
|
547
|
+
semanticMissingDimensions.push('ontology.entities');
|
|
548
|
+
}
|
|
549
|
+
if (!hasOntologyRelations) {
|
|
550
|
+
semanticMissingDimensions.push('ontology.relations');
|
|
551
|
+
}
|
|
552
|
+
if (!hasBusinessRules) {
|
|
553
|
+
semanticMissingDimensions.push('governance.business_rules');
|
|
554
|
+
}
|
|
555
|
+
if (!hasDecisionLogic) {
|
|
556
|
+
semanticMissingDimensions.push('governance.decision_logic');
|
|
557
|
+
}
|
|
558
|
+
const uniqueProvides = Array.from(new Set(matchedProvides)).sort();
|
|
559
|
+
return {
|
|
560
|
+
capability,
|
|
561
|
+
covered: matchedTemplates.length > 0,
|
|
562
|
+
source_values: expectedMap.has(capability) ? expectedMap.get(capability).source_values : [],
|
|
563
|
+
matched_templates: Array.from(new Set(matchedTemplates)).sort(),
|
|
564
|
+
matched_provides: uniqueProvides,
|
|
565
|
+
matched_template_semantics: matchedTemplateSemantics,
|
|
566
|
+
semantic_complete: semanticMissingDimensions.length === 0,
|
|
567
|
+
semantic_missing_dimensions: semanticMissingDimensions
|
|
568
|
+
};
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
const coveredCount = coverage.filter(item => item.covered).length;
|
|
572
|
+
const semanticCompleteCount = coverage.filter(item => item.semantic_complete).length;
|
|
573
|
+
const uncovered = coverage.filter(item => !item.covered).map(item => item.capability);
|
|
574
|
+
const coveragePercent = expected.length > 0
|
|
575
|
+
? Number(((coveredCount / expected.length) * 100).toFixed(2))
|
|
576
|
+
: null;
|
|
577
|
+
const semanticCompletePercent = expected.length > 0
|
|
578
|
+
? Number(((semanticCompleteCount / expected.length) * 100).toFixed(2))
|
|
579
|
+
: null;
|
|
580
|
+
const minRequiredPercent = minRequiredPercentValue;
|
|
581
|
+
const minSemanticRequiredPercent = minSemanticRequiredValue;
|
|
582
|
+
const passed = Number.isFinite(coveragePercent) && Number.isFinite(minRequiredPercent)
|
|
583
|
+
? coveragePercent >= minRequiredPercent
|
|
584
|
+
: false;
|
|
585
|
+
const semanticPassed = Number.isFinite(semanticCompletePercent) && Number.isFinite(minSemanticRequiredPercent)
|
|
586
|
+
? semanticCompletePercent >= minSemanticRequiredPercent
|
|
587
|
+
: false;
|
|
588
|
+
|
|
589
|
+
const payload = {
|
|
590
|
+
mode: 'moqui-capability-coverage',
|
|
591
|
+
generated_at: new Date().toISOString(),
|
|
592
|
+
expected_capabilities: expected,
|
|
593
|
+
summary: {
|
|
594
|
+
total_capabilities: expected.length,
|
|
595
|
+
covered_capabilities: coveredCount,
|
|
596
|
+
uncovered_capabilities: expected.length - coveredCount,
|
|
597
|
+
coverage_percent: coveragePercent,
|
|
598
|
+
min_required_percent: minRequiredPercent,
|
|
599
|
+
semantic_complete_capabilities: semanticCompleteCount,
|
|
600
|
+
semantic_incomplete_capabilities: expected.length - semanticCompleteCount,
|
|
601
|
+
semantic_complete_percent: semanticCompletePercent,
|
|
602
|
+
min_semantic_required_percent: minSemanticRequiredPercent,
|
|
603
|
+
semantic_passed: semanticPassed,
|
|
604
|
+
passed
|
|
605
|
+
},
|
|
606
|
+
coverage,
|
|
607
|
+
gaps: uncovered,
|
|
608
|
+
normalization,
|
|
609
|
+
warnings: Array.from(new Set(warnings))
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
const previousPayload = await loadLatestMoquiCapabilityCoverageReport(projectPath);
|
|
613
|
+
if (previousPayload) {
|
|
614
|
+
payload.compare = buildCapabilityCoverageComparison(payload, previousPayload);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const outputJsonPath = path.join(projectPath, AUTO_HANDOFF_MOQUI_CAPABILITY_COVERAGE_JSON_FILE);
|
|
618
|
+
const outputMarkdownPath = path.join(projectPath, AUTO_HANDOFF_MOQUI_CAPABILITY_COVERAGE_MARKDOWN_FILE);
|
|
619
|
+
await fs.ensureDir(path.dirname(outputJsonPath));
|
|
620
|
+
await fs.writeJson(outputJsonPath, payload, { spaces: 2 });
|
|
621
|
+
await fs.writeFile(outputMarkdownPath, renderMoquiCapabilityCoverageMarkdown(payload), 'utf8');
|
|
622
|
+
|
|
623
|
+
return {
|
|
624
|
+
status: 'evaluated',
|
|
625
|
+
generated: true,
|
|
626
|
+
summary: payload.summary,
|
|
627
|
+
coverage: payload.coverage,
|
|
628
|
+
gaps: payload.gaps,
|
|
629
|
+
normalization: payload.normalization,
|
|
630
|
+
warnings: payload.warnings,
|
|
631
|
+
compare: payload.compare || null,
|
|
632
|
+
output: {
|
|
633
|
+
json: toAutoHandoffCliPath(projectPath, outputJsonPath),
|
|
634
|
+
markdown: toAutoHandoffCliPath(projectPath, outputMarkdownPath)
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
module.exports = {
|
|
640
|
+
buildAutoHandoffMoquiBaselineSnapshot,
|
|
641
|
+
buildAutoHandoffScenePackageBatchSnapshot,
|
|
642
|
+
buildAutoHandoffCapabilityCoverageSnapshot
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
|