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.
Files changed (83) hide show
  1. package/CHANGELOG.md +86 -1
  2. package/README.md +119 -122
  3. package/README.zh.md +123 -121
  4. package/bin/scene-capability-engine.js +11 -0
  5. package/docs/README.md +21 -32
  6. package/docs/auto-refactor-index.md +384 -0
  7. package/docs/command-reference.md +94 -2
  8. package/docs/magicball-adaptation-task-checklist-v1.md +385 -0
  9. package/docs/magicball-app-bundle-sqlite-and-command-draft.md +539 -0
  10. package/docs/magicball-capability-iteration-api.md +2 -0
  11. package/docs/magicball-capability-iteration-ui.md +2 -0
  12. package/docs/magicball-capability-library.md +2 -0
  13. package/docs/magicball-cli-invocation-examples.md +336 -0
  14. package/docs/magicball-frontend-state-and-command-mapping.md +244 -0
  15. package/docs/magicball-integration-doc-index.md +137 -0
  16. package/docs/magicball-integration-issue-tracker.md +218 -0
  17. package/docs/magicball-mode-home-and-ontology-empty-state-playbook.md +249 -0
  18. package/docs/magicball-sce-adaptation-guide.md +203 -0
  19. package/docs/magicball-three-mode-alignment-plan.md +551 -0
  20. package/docs/magicball-ui-surface-checklist.md +126 -0
  21. package/docs/magicball-write-auth-adaptation-guide.md +328 -0
  22. package/docs/refactor-completion-roadmap.md +116 -0
  23. package/docs/zh/README.md +27 -30
  24. package/docs/zh/refactor-completion-roadmap.md +116 -0
  25. package/lib/app/registry-config.js +73 -0
  26. package/lib/app/registry-sync-service.js +228 -0
  27. package/lib/auto/archive-schema-service.js +276 -0
  28. package/lib/auto/archive-summary.js +60 -0
  29. package/lib/auto/batch-goal-input-service.js +543 -0
  30. package/lib/auto/batch-output.js +201 -0
  31. package/lib/auto/batch-summary-storage-service.js +110 -0
  32. package/lib/auto/close-loop-batch-service.js +116 -0
  33. package/lib/auto/close-loop-controller-service.js +287 -0
  34. package/lib/auto/close-loop-program-service.js +283 -0
  35. package/lib/auto/close-loop-recovery-service.js +191 -0
  36. package/lib/auto/close-loop-session-storage-service.js +50 -0
  37. package/lib/auto/controller-lock-service.js +55 -0
  38. package/lib/auto/controller-output.js +32 -0
  39. package/lib/auto/controller-queue-service.js +127 -0
  40. package/lib/auto/controller-session-storage-service.js +105 -0
  41. package/lib/auto/governance-advisory-service.js +208 -0
  42. package/lib/auto/governance-close-loop-service.js +411 -0
  43. package/lib/auto/governance-maintenance-presenter.js +162 -0
  44. package/lib/auto/governance-maintenance-service.js +112 -0
  45. package/lib/auto/governance-session-presenter.js +70 -0
  46. package/lib/auto/governance-session-storage-service.js +198 -0
  47. package/lib/auto/governance-signals.js +139 -0
  48. package/lib/auto/governance-stats-presenter.js +337 -0
  49. package/lib/auto/governance-stats-service.js +115 -0
  50. package/lib/auto/governance-summary.js +703 -0
  51. package/lib/auto/handoff-capability-matrix-service.js +281 -0
  52. package/lib/auto/handoff-evidence-review-service.js +251 -0
  53. package/lib/auto/handoff-release-evidence-service.js +190 -0
  54. package/lib/auto/handoff-release-gate-history-loaders-service.js +502 -0
  55. package/lib/auto/handoff-release-gate-history-service.js +257 -0
  56. package/lib/auto/handoff-reporting-service.js +1407 -0
  57. package/lib/auto/handoff-run-service.js +486 -0
  58. package/lib/auto/handoff-snapshots-service.js +645 -0
  59. package/lib/auto/observability-service.js +132 -0
  60. package/lib/auto/output-writer.js +34 -0
  61. package/lib/auto/program-auto-remediation-service.js +130 -0
  62. package/lib/auto/program-diagnostics.js +138 -0
  63. package/lib/auto/program-governance-helpers.js +306 -0
  64. package/lib/auto/program-governance-loop-service.js +413 -0
  65. package/lib/auto/program-output.js +106 -0
  66. package/lib/auto/program-summary.js +183 -0
  67. package/lib/auto/recovery-memory-service.js +684 -0
  68. package/lib/auto/recovery-selection-service.js +52 -0
  69. package/lib/auto/retention-policy.js +98 -0
  70. package/lib/auto/session-persistence-service.js +106 -0
  71. package/lib/auto/session-presenter.js +105 -0
  72. package/lib/auto/session-prune-service.js +190 -0
  73. package/lib/auto/session-query-service.js +249 -0
  74. package/lib/auto/spec-protection.js +141 -0
  75. package/lib/commands/app.js +911 -0
  76. package/lib/commands/assurance.js +212 -0
  77. package/lib/commands/auto.js +1091 -11063
  78. package/lib/commands/mode.js +321 -0
  79. package/lib/commands/ontology.js +415 -0
  80. package/lib/commands/pm.js +422 -0
  81. package/lib/ontology/seed-profiles.js +160 -0
  82. package/lib/state/sce-state-store.js +3369 -1200
  83. package/package.json +1 -1
@@ -0,0 +1,911 @@
1
+ const path = require('path');
2
+ const chalk = require('chalk');
3
+ const fs = require('fs-extra');
4
+ const { ensureWriteAuthorization } = require('../security/write-authorization');
5
+ const { getSceStateStore } = require('../state/sce-state-store');
6
+ const { loadAppRegistryConfig, saveAppRegistryConfig } = require('../app/registry-config');
7
+ const { syncBundleRegistry, syncServiceCatalog } = require('../app/registry-sync-service');
8
+
9
+ function normalizeString(value) {
10
+ if (typeof value !== 'string') {
11
+ return '';
12
+ }
13
+ return value.trim();
14
+ }
15
+
16
+ function normalizePositiveInteger(value, fallback = 50, max = 1000) {
17
+ const parsed = Number.parseInt(`${value}`, 10);
18
+ if (!Number.isFinite(parsed) || parsed <= 0) {
19
+ return fallback;
20
+ }
21
+ return Math.min(parsed, max);
22
+ }
23
+
24
+ function normalizeBoolean(value, fallback = false) {
25
+ if (typeof value === 'boolean') {
26
+ return value;
27
+ }
28
+ const normalized = normalizeString(`${value || ''}`).toLowerCase();
29
+ if (!normalized) {
30
+ return fallback;
31
+ }
32
+ if (['1', 'true', 'yes', 'on'].includes(normalized)) {
33
+ return true;
34
+ }
35
+ if (['0', 'false', 'no', 'off'].includes(normalized)) {
36
+ return false;
37
+ }
38
+ return fallback;
39
+ }
40
+
41
+ function createStore(dependencies = {}) {
42
+ const projectPath = dependencies.projectPath || process.cwd();
43
+ const fileSystem = dependencies.fileSystem || fs;
44
+ const env = dependencies.env || process.env;
45
+ return dependencies.stateStore || getSceStateStore(projectPath, {
46
+ fileSystem,
47
+ env
48
+ });
49
+ }
50
+
51
+ function printPayload(payload, options = {}, title = 'App Bundle') {
52
+ if (options.json) {
53
+ console.log(JSON.stringify(payload, null, 2));
54
+ return;
55
+ }
56
+
57
+ console.log(chalk.blue(title));
58
+ if (payload.mode) {
59
+ console.log(` Mode: ${payload.mode}`);
60
+ }
61
+ if (payload.summary && typeof payload.summary === 'object') {
62
+ for (const [key, value] of Object.entries(payload.summary)) {
63
+ console.log(` ${key}: ${value}`);
64
+ }
65
+ }
66
+ if (Array.isArray(payload.items)) {
67
+ payload.items.forEach((item) => {
68
+ console.log(` - ${item.app_id} | ${item.app_key} | ${item.app_name} | ${item.status}`);
69
+ });
70
+ }
71
+ }
72
+
73
+ function buildBundleSummary(graph = {}) {
74
+ const bundle = graph.bundle || {};
75
+ const runtimeRelease = graph.runtime_release || {};
76
+ const ontologyBundle = graph.ontology_bundle || {};
77
+ const engineeringProject = graph.engineering_project || {};
78
+ const sceneBindings = Array.isArray(graph.scene_bindings) ? graph.scene_bindings : [];
79
+ return {
80
+ app_id: bundle.app_id || null,
81
+ app_key: bundle.app_key || null,
82
+ app_name: bundle.app_name || null,
83
+ status: bundle.status || null,
84
+ environment: bundle.environment || null,
85
+ runtime_version: runtimeRelease.runtime_version || null,
86
+ ontology_version: ontologyBundle.ontology_version || null,
87
+ code_version: engineeringProject.code_version || null,
88
+ scene_binding_count: sceneBindings.length
89
+ };
90
+ }
91
+
92
+ function buildRuntimeSummary(graph = {}) {
93
+ const bundle = graph.bundle || {};
94
+ const runtimeRelease = graph.runtime_release || {};
95
+ const metadata = bundle.metadata && typeof bundle.metadata === 'object' ? bundle.metadata : {};
96
+ const installation = metadata.runtime_installation && typeof metadata.runtime_installation === 'object'
97
+ ? metadata.runtime_installation
98
+ : {};
99
+ const serviceCatalog = metadata.service_catalog && typeof metadata.service_catalog === 'object'
100
+ ? metadata.service_catalog
101
+ : {};
102
+ const releases = Array.isArray(serviceCatalog.releases) ? serviceCatalog.releases : [];
103
+ return {
104
+ app_id: bundle.app_id || null,
105
+ app_name: bundle.app_name || null,
106
+ runtime_release_id: bundle.runtime_release_id || runtimeRelease.release_id || null,
107
+ runtime_version: runtimeRelease.runtime_version || null,
108
+ release_status: runtimeRelease.release_status || null,
109
+ runtime_status: runtimeRelease.runtime_status || null,
110
+ install_status: installation.status || 'not-installed',
111
+ install_root: installation.install_root || null,
112
+ release_count: releases.length,
113
+ active_release_id: installation.release_id || bundle.runtime_release_id || runtimeRelease.release_id || null
114
+ };
115
+ }
116
+
117
+ function buildEngineeringSummary(graph = {}) {
118
+ const bundle = graph.bundle || {};
119
+ const engineeringProject = graph.engineering_project || {};
120
+ const metadata = engineeringProject.metadata && typeof engineeringProject.metadata === 'object'
121
+ ? engineeringProject.metadata
122
+ : {};
123
+ return {
124
+ app_id: bundle.app_id || null,
125
+ app_name: bundle.app_name || null,
126
+ engineering_project_id: engineeringProject.engineering_project_id || null,
127
+ project_name: engineeringProject.project_name || null,
128
+ repo_url: engineeringProject.repo_url || null,
129
+ current_branch: engineeringProject.current_branch || null,
130
+ workspace_path: engineeringProject.workspace_path || null,
131
+ code_version: engineeringProject.code_version || null,
132
+ dirty_state: engineeringProject.dirty_state === true,
133
+ hydrated: Boolean(metadata.hydration && metadata.hydration.status === 'ready'),
134
+ active: Boolean(metadata.activation && metadata.activation.active === true)
135
+ };
136
+ }
137
+
138
+ function deriveEngineeringProjectId(bundle = {}) {
139
+ const appKey = normalizeString(bundle.app_key);
140
+ if (appKey) {
141
+ return `eng.${appKey.replace(/[^a-zA-Z0-9._-]+/g, '-')}`;
142
+ }
143
+ const appId = normalizeString(bundle.app_id).replace(/^app\./, '');
144
+ return appId ? `eng.${appId.replace(/[^a-zA-Z0-9._-]+/g, '-')}` : null;
145
+ }
146
+
147
+ function graphToRegisterPayload(graph = {}) {
148
+ const bundle = graph.bundle || {};
149
+ const runtimeRelease = graph.runtime_release || {};
150
+ const ontologyBundle = graph.ontology_bundle || {};
151
+ const engineeringProject = graph.engineering_project || {};
152
+ return {
153
+ app_id: bundle.app_id,
154
+ app_key: bundle.app_key,
155
+ app_name: bundle.app_name,
156
+ app_slug: bundle.app_slug || undefined,
157
+ workspace_id: bundle.workspace_id || undefined,
158
+ runtime_release_id: bundle.runtime_release_id || undefined,
159
+ ontology_bundle_id: bundle.ontology_bundle_id || undefined,
160
+ engineering_project_id: bundle.engineering_project_id || undefined,
161
+ default_scene_id: bundle.default_scene_id || undefined,
162
+ environment: bundle.environment || undefined,
163
+ status: bundle.status || undefined,
164
+ source_origin: bundle.source_origin || undefined,
165
+ tags: Array.isArray(bundle.tags) ? bundle.tags : [],
166
+ metadata: bundle.metadata && typeof bundle.metadata === 'object' ? bundle.metadata : {},
167
+ runtime: runtimeRelease && runtimeRelease.release_id ? {
168
+ release_id: runtimeRelease.release_id,
169
+ runtime_version: runtimeRelease.runtime_version,
170
+ release_channel: runtimeRelease.release_channel,
171
+ release_status: runtimeRelease.release_status,
172
+ entrypoint: runtimeRelease.entrypoint,
173
+ runtime_status: runtimeRelease.runtime_status,
174
+ release_notes_file: runtimeRelease.release_notes_file,
175
+ release_evidence_file: runtimeRelease.release_evidence_file,
176
+ published_at: runtimeRelease.published_at,
177
+ source_updated_at: runtimeRelease.source_updated_at,
178
+ metadata: runtimeRelease.metadata || {}
179
+ } : undefined,
180
+ ontology: ontologyBundle && ontologyBundle.ontology_bundle_id ? {
181
+ ontology_bundle_id: ontologyBundle.ontology_bundle_id,
182
+ ontology_version: ontologyBundle.ontology_version,
183
+ template_version: ontologyBundle.template_version,
184
+ capability_catalog_version: ontologyBundle.capability_catalog_version,
185
+ triad_revision: ontologyBundle.triad_revision,
186
+ triad_status: ontologyBundle.triad_status,
187
+ publish_readiness: ontologyBundle.publish_readiness,
188
+ template_source: ontologyBundle.template_source,
189
+ capability_set: Array.isArray(ontologyBundle.capability_set) ? ontologyBundle.capability_set : [],
190
+ summary: ontologyBundle.summary || {},
191
+ metadata: ontologyBundle.metadata || {}
192
+ } : undefined,
193
+ engineering: engineeringProject && engineeringProject.engineering_project_id ? {
194
+ engineering_project_id: engineeringProject.engineering_project_id,
195
+ project_key: engineeringProject.project_key,
196
+ project_name: engineeringProject.project_name,
197
+ repo_url: engineeringProject.repo_url,
198
+ repo_provider: engineeringProject.repo_provider,
199
+ default_branch: engineeringProject.default_branch,
200
+ current_branch: engineeringProject.current_branch,
201
+ commit_sha: engineeringProject.commit_sha,
202
+ workspace_path: engineeringProject.workspace_path,
203
+ code_version: engineeringProject.code_version,
204
+ synced_runtime_release_id: engineeringProject.synced_runtime_release_id,
205
+ dirty_state: engineeringProject.dirty_state === true,
206
+ auth_policy: engineeringProject.auth_policy || {},
207
+ metadata: engineeringProject.metadata || {}
208
+ } : undefined,
209
+ scene_bindings: Array.isArray(graph.scene_bindings) ? graph.scene_bindings.map((item) => ({
210
+ scene_id: item.scene_id,
211
+ binding_role: item.binding_role,
212
+ source: item.source,
213
+ metadata: item.metadata || {}
214
+ })) : []
215
+ };
216
+ }
217
+
218
+ async function requireAppGraph(appRef, dependencies = {}) {
219
+ const store = createStore(dependencies);
220
+ const graph = await store.getAppBundleGraph(appRef);
221
+ if (!graph) {
222
+ throw new Error(`app bundle not found: ${appRef}`);
223
+ }
224
+ return { store, graph };
225
+ }
226
+
227
+ async function ensureAuthorized(action, options = {}, dependencies = {}) {
228
+ const projectPath = dependencies.projectPath || process.cwd();
229
+ const fileSystem = dependencies.fileSystem || fs;
230
+ const env = dependencies.env || process.env;
231
+ await ensureWriteAuthorization(action, {
232
+ authLease: options.authLease,
233
+ authPassword: options.authPassword,
234
+ actor: options.actor
235
+ }, {
236
+ projectPath,
237
+ fileSystem,
238
+ env
239
+ });
240
+ }
241
+
242
+ async function runAppBundleListCommand(options = {}, dependencies = {}) {
243
+ const store = createStore(dependencies);
244
+ const items = await store.listAppBundles({
245
+ limit: normalizePositiveInteger(options.limit, 50, 1000),
246
+ status: options.status,
247
+ environment: options.environment,
248
+ workspaceId: options.workspaceId,
249
+ query: options.query
250
+ });
251
+ const payload = {
252
+ mode: 'app-bundle-list',
253
+ generated_at: new Date().toISOString(),
254
+ query: {
255
+ limit: normalizePositiveInteger(options.limit, 50, 1000),
256
+ status: normalizeString(options.status) || null,
257
+ environment: normalizeString(options.environment) || null,
258
+ workspace_id: normalizeString(options.workspaceId) || null,
259
+ query: normalizeString(options.query) || null
260
+ },
261
+ summary: {
262
+ total: Array.isArray(items) ? items.length : 0
263
+ },
264
+ items: Array.isArray(items) ? items : []
265
+ };
266
+ printPayload(payload, options, 'App Bundle List');
267
+ return payload;
268
+ }
269
+
270
+ async function runAppBundleShowCommand(options = {}, dependencies = {}) {
271
+ const appRef = normalizeString(options.app || options.appRef);
272
+ if (!appRef) {
273
+ throw new Error('--app is required');
274
+ }
275
+ const { graph } = await requireAppGraph(appRef, dependencies);
276
+ const payload = {
277
+ mode: 'app-bundle-show',
278
+ generated_at: new Date().toISOString(),
279
+ query: {
280
+ app: appRef
281
+ },
282
+ summary: buildBundleSummary(graph),
283
+ bundle: graph.bundle,
284
+ runtime_release: graph.runtime_release,
285
+ ontology_bundle: graph.ontology_bundle,
286
+ engineering_project: graph.engineering_project,
287
+ scene_bindings: graph.scene_bindings || []
288
+ };
289
+ printPayload(payload, options, 'App Bundle Show');
290
+ return payload;
291
+ }
292
+
293
+ async function runAppBundleRegisterCommand(options = {}, dependencies = {}) {
294
+ const inputFile = normalizeString(options.input);
295
+ if (!inputFile) {
296
+ throw new Error('--input is required');
297
+ }
298
+ await ensureAuthorized('app:bundle:register', options, dependencies);
299
+ const projectPath = dependencies.projectPath || process.cwd();
300
+ const fileSystem = dependencies.fileSystem || fs;
301
+ const env = dependencies.env || process.env;
302
+ const resolvedInput = path.isAbsolute(inputFile)
303
+ ? inputFile
304
+ : path.join(projectPath, inputFile);
305
+ const payloadJson = await fileSystem.readJson(resolvedInput);
306
+ const store = createStore({ ...dependencies, projectPath, fileSystem, env });
307
+ const graph = await store.registerAppBundle(payloadJson);
308
+ const payload = {
309
+ mode: 'app-bundle-register',
310
+ success: true,
311
+ input_file: resolvedInput,
312
+ summary: buildBundleSummary(graph),
313
+ bundle: graph.bundle,
314
+ runtime_release: graph.runtime_release,
315
+ ontology_bundle: graph.ontology_bundle,
316
+ engineering_project: graph.engineering_project,
317
+ scene_bindings: graph.scene_bindings || []
318
+ };
319
+ printPayload(payload, options, 'App Bundle Register');
320
+ return payload;
321
+ }
322
+
323
+ async function runAppEngineeringShowCommand(options = {}, dependencies = {}) {
324
+ const appRef = normalizeString(options.app);
325
+ if (!appRef) {
326
+ throw new Error('--app is required');
327
+ }
328
+ const { graph } = await requireAppGraph(appRef, dependencies);
329
+ const payload = {
330
+ mode: 'app-engineering-show',
331
+ generated_at: new Date().toISOString(),
332
+ query: {
333
+ app: appRef
334
+ },
335
+ summary: buildEngineeringSummary(graph),
336
+ bundle: graph.bundle,
337
+ engineering_project: graph.engineering_project,
338
+ scene_bindings: graph.scene_bindings || []
339
+ };
340
+ printPayload(payload, options, 'App Engineering Show');
341
+ return payload;
342
+ }
343
+
344
+ async function runAppEngineeringAttachCommand(options = {}, dependencies = {}) {
345
+ const appRef = normalizeString(options.app);
346
+ if (!appRef) {
347
+ throw new Error('--app is required');
348
+ }
349
+ const repoUrl = normalizeString(options.repo);
350
+ if (!repoUrl) {
351
+ throw new Error('--repo is required');
352
+ }
353
+ await ensureAuthorized('app:engineering:attach', options, dependencies);
354
+ const { store, graph } = await requireAppGraph(appRef, dependencies);
355
+ const nextPayload = graphToRegisterPayload(graph);
356
+ const bundle = graph.bundle || {};
357
+ const currentEngineering = nextPayload.engineering && typeof nextPayload.engineering === 'object'
358
+ ? nextPayload.engineering
359
+ : {};
360
+ const engineeringProjectId = currentEngineering.engineering_project_id || deriveEngineeringProjectId(bundle);
361
+ const nowIso = new Date().toISOString();
362
+ nextPayload.engineering_project_id = engineeringProjectId;
363
+ nextPayload.engineering = {
364
+ ...currentEngineering,
365
+ engineering_project_id: engineeringProjectId,
366
+ project_name: normalizeString(options.projectName) || currentEngineering.project_name || bundle.app_name || null,
367
+ project_key: normalizeString(options.projectKey) || currentEngineering.project_key || bundle.app_key || null,
368
+ repo_url: repoUrl,
369
+ repo_provider: normalizeString(options.provider) || currentEngineering.repo_provider || null,
370
+ default_branch: normalizeString(options.branch) || currentEngineering.default_branch || null,
371
+ current_branch: normalizeString(options.branch) || currentEngineering.current_branch || null,
372
+ workspace_path: normalizeString(options.workspacePath) || currentEngineering.workspace_path || null,
373
+ code_version: normalizeString(options.codeVersion) || currentEngineering.code_version || null,
374
+ dirty_state: currentEngineering.dirty_state === true,
375
+ metadata: {
376
+ ...(currentEngineering.metadata || {}),
377
+ attachment: {
378
+ attached_at: nowIso,
379
+ source: 'sce app engineering attach',
380
+ repo_url: repoUrl
381
+ }
382
+ }
383
+ };
384
+ const updated = await store.registerAppBundle(nextPayload);
385
+ const payload = {
386
+ mode: 'app-engineering-attach',
387
+ success: true,
388
+ summary: buildEngineeringSummary(updated),
389
+ bundle: updated.bundle,
390
+ engineering_project: updated.engineering_project,
391
+ scene_bindings: updated.scene_bindings || []
392
+ };
393
+ printPayload(payload, options, 'App Engineering Attach');
394
+ return payload;
395
+ }
396
+
397
+ async function runAppEngineeringHydrateCommand(options = {}, dependencies = {}) {
398
+ const appRef = normalizeString(options.app);
399
+ if (!appRef) {
400
+ throw new Error('--app is required');
401
+ }
402
+ await ensureAuthorized('app:engineering:hydrate', options, dependencies);
403
+ const projectPath = dependencies.projectPath || process.cwd();
404
+ const fileSystem = dependencies.fileSystem || fs;
405
+ const { store, graph } = await requireAppGraph(appRef, { ...dependencies, projectPath, fileSystem });
406
+ const bundle = graph.bundle || {};
407
+ const nextPayload = graphToRegisterPayload(graph);
408
+ const currentEngineering = nextPayload.engineering && typeof nextPayload.engineering === 'object'
409
+ ? nextPayload.engineering
410
+ : {};
411
+ const engineeringProjectId = currentEngineering.engineering_project_id || deriveEngineeringProjectId(bundle);
412
+ const workspacePath = normalizeString(options.workspacePath)
413
+ || normalizeString(currentEngineering.workspace_path)
414
+ || path.join(projectPath, '.sce', 'apps', bundle.app_key || bundle.app_id || engineeringProjectId, 'engineering');
415
+ await fileSystem.ensureDir(workspacePath);
416
+ const nowIso = new Date().toISOString();
417
+ nextPayload.engineering_project_id = engineeringProjectId;
418
+ nextPayload.engineering = {
419
+ ...currentEngineering,
420
+ engineering_project_id: engineeringProjectId,
421
+ workspace_path: workspacePath,
422
+ metadata: {
423
+ ...(currentEngineering.metadata || {}),
424
+ hydration: {
425
+ status: 'ready',
426
+ hydrated_at: nowIso,
427
+ source: 'sce app engineering hydrate'
428
+ }
429
+ }
430
+ };
431
+ const updated = await store.registerAppBundle(nextPayload);
432
+ const payload = {
433
+ mode: 'app-engineering-hydrate',
434
+ success: true,
435
+ summary: buildEngineeringSummary(updated),
436
+ bundle: updated.bundle,
437
+ engineering_project: updated.engineering_project,
438
+ hydrated_workspace_path: workspacePath
439
+ };
440
+ printPayload(payload, options, 'App Engineering Hydrate');
441
+ return payload;
442
+ }
443
+
444
+ async function runAppEngineeringActivateCommand(options = {}, dependencies = {}) {
445
+ const appRef = normalizeString(options.app);
446
+ if (!appRef) {
447
+ throw new Error('--app is required');
448
+ }
449
+ await ensureAuthorized('app:engineering:activate', options, dependencies);
450
+ const { store, graph } = await requireAppGraph(appRef, dependencies);
451
+ const nextPayload = graphToRegisterPayload(graph);
452
+ const bundle = graph.bundle || {};
453
+ const currentEngineering = nextPayload.engineering && typeof nextPayload.engineering === 'object'
454
+ ? nextPayload.engineering
455
+ : {};
456
+ const engineeringProjectId = currentEngineering.engineering_project_id || deriveEngineeringProjectId(bundle);
457
+ const workspacePath = normalizeString(options.workspacePath) || normalizeString(currentEngineering.workspace_path);
458
+ if (!workspacePath) {
459
+ throw new Error('engineering workspace_path is not set; run app engineering attach/hydrate first or pass --workspace-path');
460
+ }
461
+ const nowIso = new Date().toISOString();
462
+ nextPayload.engineering_project_id = engineeringProjectId;
463
+ nextPayload.engineering = {
464
+ ...currentEngineering,
465
+ engineering_project_id: engineeringProjectId,
466
+ workspace_path: workspacePath,
467
+ metadata: {
468
+ ...(currentEngineering.metadata || {}),
469
+ activation: {
470
+ active: true,
471
+ activated_at: nowIso,
472
+ source: 'sce app engineering activate'
473
+ }
474
+ }
475
+ };
476
+ const updated = await store.registerAppBundle(nextPayload);
477
+ const payload = {
478
+ mode: 'app-engineering-activate',
479
+ success: true,
480
+ summary: buildEngineeringSummary(updated),
481
+ bundle: updated.bundle,
482
+ engineering_project: updated.engineering_project,
483
+ activated_workspace_path: workspacePath
484
+ };
485
+ printPayload(payload, options, 'App Engineering Activate');
486
+ return payload;
487
+ }
488
+
489
+ async function runAppRegistryStatusCommand(options = {}, dependencies = {}) {
490
+ const projectPath = dependencies.projectPath || process.cwd();
491
+ const fileSystem = dependencies.fileSystem || fs;
492
+ const loaded = await loadAppRegistryConfig(projectPath, fileSystem);
493
+ const payload = {
494
+ mode: 'app-registry-status',
495
+ generated_at: new Date().toISOString(),
496
+ config_path: loaded.config_path,
497
+ config: loaded.config
498
+ };
499
+ printPayload(payload, options, 'App Registry Status');
500
+ return payload;
501
+ }
502
+
503
+ async function runAppRegistryConfigureCommand(options = {}, dependencies = {}) {
504
+ await ensureAuthorized('app:registry:configure', options, dependencies);
505
+ const projectPath = dependencies.projectPath || process.cwd();
506
+ const fileSystem = dependencies.fileSystem || fs;
507
+ const saved = await saveAppRegistryConfig({
508
+ bundle_registry: {
509
+ repo_url: normalizeString(options.bundleRepoUrl) || undefined,
510
+ branch: normalizeString(options.bundleBranch) || undefined,
511
+ index_url: normalizeString(options.bundleIndexUrl) || undefined
512
+ },
513
+ service_catalog: {
514
+ repo_url: normalizeString(options.serviceRepoUrl) || undefined,
515
+ branch: normalizeString(options.serviceBranch) || undefined,
516
+ index_url: normalizeString(options.serviceIndexUrl) || undefined
517
+ }
518
+ }, projectPath, fileSystem);
519
+ const payload = {
520
+ mode: 'app-registry-configure',
521
+ success: true,
522
+ config_path: saved.config_path,
523
+ config: saved.config
524
+ };
525
+ printPayload(payload, options, 'App Registry Configure');
526
+ return payload;
527
+ }
528
+
529
+ async function runAppRegistrySyncBundlesCommand(options = {}, dependencies = {}) {
530
+ const store = createStore(dependencies);
531
+ const payload = await syncBundleRegistry(options, { ...dependencies, stateStore: store });
532
+ printPayload(payload, options, 'App Registry Sync Bundles');
533
+ return payload;
534
+ }
535
+
536
+ async function runAppRegistrySyncCatalogCommand(options = {}, dependencies = {}) {
537
+ const store = createStore(dependencies);
538
+ const payload = await syncServiceCatalog(options, { ...dependencies, stateStore: store });
539
+ printPayload(payload, options, 'App Registry Sync Catalog');
540
+ return payload;
541
+ }
542
+
543
+ async function runAppRegistrySyncAllCommand(options = {}, dependencies = {}) {
544
+ const store = createStore(dependencies);
545
+ const bundle = await syncBundleRegistry(options, { ...dependencies, stateStore: store });
546
+ const catalog = await syncServiceCatalog(options, { ...dependencies, stateStore: store });
547
+ const payload = {
548
+ mode: 'app-registry-sync',
549
+ generated_at: new Date().toISOString(),
550
+ bundle_registry: bundle,
551
+ service_catalog: catalog,
552
+ summary: {
553
+ bundle_synced_count: bundle.synced_count,
554
+ catalog_synced_count: catalog.synced_count
555
+ }
556
+ };
557
+ printPayload(payload, options, 'App Registry Sync');
558
+ return payload;
559
+ }
560
+
561
+ async function runAppRuntimeShowCommand(options = {}, dependencies = {}) {
562
+ const appRef = normalizeString(options.app);
563
+ if (!appRef) {
564
+ throw new Error('--app is required');
565
+ }
566
+ const { graph } = await requireAppGraph(appRef, dependencies);
567
+ const metadata = graph.bundle && graph.bundle.metadata && typeof graph.bundle.metadata === 'object' ? graph.bundle.metadata : {};
568
+ const serviceCatalog = metadata.service_catalog && typeof metadata.service_catalog === 'object' ? metadata.service_catalog : {};
569
+ const payload = {
570
+ mode: 'app-runtime-show',
571
+ generated_at: new Date().toISOString(),
572
+ query: {
573
+ app: appRef
574
+ },
575
+ summary: buildRuntimeSummary(graph),
576
+ bundle: graph.bundle,
577
+ runtime_release: graph.runtime_release,
578
+ runtime_installation: metadata.runtime_installation || null,
579
+ service_catalog: {
580
+ default_release_id: serviceCatalog.default_release_id || null,
581
+ release_count: Array.isArray(serviceCatalog.releases) ? serviceCatalog.releases.length : 0,
582
+ source: serviceCatalog.source || null
583
+ }
584
+ };
585
+ printPayload(payload, options, 'App Runtime Show');
586
+ return payload;
587
+ }
588
+
589
+ async function runAppRuntimeReleasesCommand(options = {}, dependencies = {}) {
590
+ const appRef = normalizeString(options.app);
591
+ if (!appRef) {
592
+ throw new Error('--app is required');
593
+ }
594
+ const { graph } = await requireAppGraph(appRef, dependencies);
595
+ const metadata = graph.bundle && graph.bundle.metadata && typeof graph.bundle.metadata === 'object' ? graph.bundle.metadata : {};
596
+ const serviceCatalog = metadata.service_catalog && typeof metadata.service_catalog === 'object' ? metadata.service_catalog : {};
597
+ const releases = Array.isArray(serviceCatalog.releases) ? serviceCatalog.releases : [];
598
+ const payload = {
599
+ mode: 'app-runtime-releases',
600
+ generated_at: new Date().toISOString(),
601
+ query: {
602
+ app: appRef
603
+ },
604
+ summary: {
605
+ total: releases.length,
606
+ default_release_id: serviceCatalog.default_release_id || null
607
+ },
608
+ items: releases,
609
+ view_model: {
610
+ type: 'table',
611
+ columns: ['release_id', 'runtime_version', 'release_channel', 'release_status', 'runtime_status', 'published_at']
612
+ },
613
+ mb_status: graph.runtime_release && graph.runtime_release.runtime_status ? graph.runtime_release.runtime_status : (graph.bundle && graph.bundle.status ? graph.bundle.status : 'unknown')
614
+ };
615
+ printPayload(payload, options, 'App Runtime Releases');
616
+ return payload;
617
+ }
618
+
619
+ async function runAppRuntimeInstallCommand(options = {}, dependencies = {}) {
620
+ const appRef = normalizeString(options.app);
621
+ if (!appRef) {
622
+ throw new Error('--app is required');
623
+ }
624
+ await ensureAuthorized('app:runtime:install', options, dependencies);
625
+ const projectPath = dependencies.projectPath || process.cwd();
626
+ const fileSystem = dependencies.fileSystem || fs;
627
+ const { store, graph } = await requireAppGraph(appRef, dependencies);
628
+ const nextPayload = graphToRegisterPayload(graph);
629
+ const serviceCatalog = nextPayload.metadata && nextPayload.metadata.service_catalog && typeof nextPayload.metadata.service_catalog === 'object'
630
+ ? nextPayload.metadata.service_catalog
631
+ : {};
632
+ const releases = Array.isArray(serviceCatalog.releases) ? serviceCatalog.releases : [];
633
+ const releaseId = normalizeString(options.release) || normalizeString(serviceCatalog.default_release_id) || normalizeString(releases[0] && releases[0].release_id);
634
+ const selectedRelease = releases.find((item) => normalizeString(item && item.release_id) == releaseId) || null;
635
+ if (!selectedRelease) {
636
+ throw new Error('runtime release not found; sync service catalog first or pass --release with a valid release id');
637
+ }
638
+ const installRoot = normalizeString(options.installRoot) || path.join(projectPath, '.sce', 'apps', nextPayload.app_key || nextPayload.app_id, 'runtime', releaseId);
639
+ await fileSystem.ensureDir(installRoot);
640
+ nextPayload.metadata = nextPayload.metadata || {};
641
+ nextPayload.metadata.runtime_installation = {
642
+ status: 'installed',
643
+ install_root: installRoot,
644
+ release_id: releaseId,
645
+ installed_at: new Date().toISOString(),
646
+ source: 'sce app runtime install'
647
+ };
648
+ const updated = await store.registerAppBundle(nextPayload);
649
+ const payload = {
650
+ mode: 'app-runtime-install',
651
+ success: true,
652
+ summary: buildRuntimeSummary(updated),
653
+ runtime_installation: updated.bundle && updated.bundle.metadata ? updated.bundle.metadata.runtime_installation : null
654
+ };
655
+ printPayload(payload, options, 'App Runtime Install');
656
+ return payload;
657
+ }
658
+
659
+ async function runAppRuntimeActivateCommand(options = {}, dependencies = {}) {
660
+ const appRef = normalizeString(options.app);
661
+ if (!appRef) {
662
+ throw new Error('--app is required');
663
+ }
664
+ await ensureAuthorized('app:runtime:activate', options, dependencies);
665
+ const { store, graph } = await requireAppGraph(appRef, dependencies);
666
+ const nextPayload = graphToRegisterPayload(graph);
667
+ const serviceCatalog = nextPayload.metadata && nextPayload.metadata.service_catalog && typeof nextPayload.metadata.service_catalog === 'object'
668
+ ? nextPayload.metadata.service_catalog
669
+ : {};
670
+ const releases = Array.isArray(serviceCatalog.releases) ? serviceCatalog.releases : [];
671
+ const releaseId = normalizeString(options.release) || normalizeString(serviceCatalog.default_release_id) || normalizeString(releases[0] && releases[0].release_id);
672
+ const selectedRelease = releases.find((item) => normalizeString(item && item.release_id) == releaseId) || null;
673
+ if (!selectedRelease) {
674
+ throw new Error('runtime release not found; sync service catalog first or pass --release with a valid release id');
675
+ }
676
+ nextPayload.runtime_release_id = releaseId;
677
+ nextPayload.runtime = {
678
+ release_id: releaseId,
679
+ runtime_version: normalizeString(selectedRelease.runtime_version),
680
+ release_channel: normalizeString(selectedRelease.release_channel) || null,
681
+ release_status: normalizeString(selectedRelease.release_status) || 'published',
682
+ entrypoint: normalizeString(selectedRelease.entrypoint) || null,
683
+ runtime_status: normalizeString(selectedRelease.runtime_status) || 'ready',
684
+ release_notes_file: normalizeString(selectedRelease.release_notes_file) || null,
685
+ release_evidence_file: normalizeString(selectedRelease.release_evidence_file) || null,
686
+ published_at: normalizeString(selectedRelease.published_at) || null,
687
+ metadata: selectedRelease.metadata && typeof selectedRelease.metadata === 'object' ? selectedRelease.metadata : {}
688
+ };
689
+ nextPayload.metadata = nextPayload.metadata || {};
690
+ nextPayload.metadata.runtime_activation = {
691
+ active_release_id: releaseId,
692
+ activated_at: new Date().toISOString(),
693
+ source: 'sce app runtime activate'
694
+ };
695
+ const updated = await store.registerAppBundle(nextPayload);
696
+ const payload = {
697
+ mode: 'app-runtime-activate',
698
+ success: true,
699
+ summary: buildRuntimeSummary(updated),
700
+ runtime_release: updated.runtime_release,
701
+ runtime_activation: updated.bundle && updated.bundle.metadata ? updated.bundle.metadata.runtime_activation : null
702
+ };
703
+ printPayload(payload, options, 'App Runtime Activate');
704
+ return payload;
705
+ }
706
+
707
+ function safeRun(handler, options = {}, context = 'app command') {
708
+ Promise.resolve(handler(options))
709
+ .catch((error) => {
710
+ if (options.json) {
711
+ console.log(JSON.stringify({ success: false, error: error.message }, null, 2));
712
+ } else {
713
+ console.error(chalk.red(`${context} failed:`), error.message);
714
+ }
715
+ process.exitCode = 1;
716
+ });
717
+ }
718
+
719
+ function registerAppCommands(program) {
720
+ const app = program
721
+ .command('app')
722
+ .description('Manage app bundles that bind application/ontology/engineering projections');
723
+
724
+ const bundle = app
725
+ .command('bundle')
726
+ .description('Manage app bundle registry');
727
+
728
+ bundle
729
+ .command('list')
730
+ .description('List app bundles')
731
+ .option('--limit <n>', 'Maximum rows', '50')
732
+ .option('--status <status>', 'Filter by status')
733
+ .option('--environment <env>', 'Filter by environment')
734
+ .option('--workspace-id <id>', 'Filter by workspace id')
735
+ .option('--query <text>', 'Free-text query against id/key/name')
736
+ .option('--json', 'Print machine-readable JSON output')
737
+ .action((options) => safeRun(runAppBundleListCommand, options, 'app bundle list'));
738
+
739
+ bundle
740
+ .command('show')
741
+ .description('Show one app bundle with linked runtime/ontology/engineering records')
742
+ .requiredOption('--app <app-id-or-key>', 'App id or app key')
743
+ .option('--json', 'Print machine-readable JSON output')
744
+ .action((options) => safeRun(runAppBundleShowCommand, options, 'app bundle show'));
745
+
746
+ bundle
747
+ .command('register')
748
+ .description('Register or update an app bundle from JSON input')
749
+ .requiredOption('--input <path>', 'Bundle JSON input file')
750
+ .option('--auth-lease <lease-id>', 'Write authorization lease id')
751
+ .option('--auth-password <password>', 'Inline auth password if policy allows')
752
+ .option('--actor <actor>', 'Audit actor override')
753
+ .option('--json', 'Print machine-readable JSON output')
754
+ .action((options) => safeRun(runAppBundleRegisterCommand, options, 'app bundle register'));
755
+
756
+ const registry = app
757
+ .command('registry')
758
+ .description('Manage remote app bundle and service catalog registry configuration');
759
+
760
+ registry
761
+ .command('status')
762
+ .description('Show current registry configuration')
763
+ .option('--json', 'Print machine-readable JSON output')
764
+ .action((options) => safeRun(runAppRegistryStatusCommand, options, 'app registry status'));
765
+
766
+ registry
767
+ .command('configure')
768
+ .description('Configure bundle registry and service catalog sources')
769
+ .option('--bundle-repo-url <url>', 'Bundle registry repository URL')
770
+ .option('--bundle-branch <branch>', 'Bundle registry branch')
771
+ .option('--bundle-index-url <url>', 'Bundle registry index URL')
772
+ .option('--service-repo-url <url>', 'Service catalog repository URL')
773
+ .option('--service-branch <branch>', 'Service catalog branch')
774
+ .option('--service-index-url <url>', 'Service catalog index URL')
775
+ .option('--auth-lease <lease-id>', 'Write authorization lease id')
776
+ .option('--auth-password <password>', 'Inline auth password if policy allows')
777
+ .option('--actor <actor>', 'Audit actor override')
778
+ .option('--json', 'Print machine-readable JSON output')
779
+ .action((options) => safeRun(runAppRegistryConfigureCommand, options, 'app registry configure'));
780
+
781
+ registry
782
+ .command('sync-bundles')
783
+ .description('Sync remote app bundle registry into local SCE app bundle state')
784
+ .option('--index-url <url-or-path>', 'Override bundle index URL/path')
785
+ .option('--json', 'Print machine-readable JSON output')
786
+ .action((options) => safeRun(runAppRegistrySyncBundlesCommand, options, 'app registry sync bundles'));
787
+
788
+ registry
789
+ .command('sync-catalog')
790
+ .description('Sync remote app service catalog into local app bundle runtime metadata')
791
+ .option('--index-url <url-or-path>', 'Override service catalog index URL/path')
792
+ .option('--json', 'Print machine-readable JSON output')
793
+ .action((options) => safeRun(runAppRegistrySyncCatalogCommand, options, 'app registry sync catalog'));
794
+
795
+ registry
796
+ .command('sync')
797
+ .description('Sync both app bundle registry and app service catalog')
798
+ .option('--json', 'Print machine-readable JSON output')
799
+ .action((options) => safeRun(runAppRegistrySyncAllCommand, options, 'app registry sync'));
800
+
801
+ const runtime = app
802
+ .command('runtime')
803
+ .description('Manage runtime projection for one app bundle');
804
+
805
+ runtime
806
+ .command('show')
807
+ .description('Show runtime projection for one app bundle')
808
+ .requiredOption('--app <app-id-or-key>', 'App id or app key')
809
+ .option('--json', 'Print machine-readable JSON output')
810
+ .action((options) => safeRun(runAppRuntimeShowCommand, options, 'app runtime show'));
811
+
812
+ runtime
813
+ .command('releases')
814
+ .description('List runtime releases known for one app bundle')
815
+ .requiredOption('--app <app-id-or-key>', 'App id or app key')
816
+ .option('--json', 'Print machine-readable JSON output')
817
+ .action((options) => safeRun(runAppRuntimeReleasesCommand, options, 'app runtime releases'));
818
+
819
+ runtime
820
+ .command('install')
821
+ .description('Install runtime release for one app bundle')
822
+ .requiredOption('--app <app-id-or-key>', 'App id or app key')
823
+ .option('--release <release-id>', 'Runtime release id')
824
+ .option('--install-root <path>', 'Install root path')
825
+ .option('--auth-lease <lease-id>', 'Write authorization lease id')
826
+ .option('--auth-password <password>', 'Inline auth password if policy allows')
827
+ .option('--actor <actor>', 'Audit actor override')
828
+ .option('--json', 'Print machine-readable JSON output')
829
+ .action((options) => safeRun(runAppRuntimeInstallCommand, options, 'app runtime install'));
830
+
831
+ runtime
832
+ .command('activate')
833
+ .description('Activate runtime release for one app bundle')
834
+ .requiredOption('--app <app-id-or-key>', 'App id or app key')
835
+ .option('--release <release-id>', 'Runtime release id')
836
+ .option('--auth-lease <lease-id>', 'Write authorization lease id')
837
+ .option('--auth-password <password>', 'Inline auth password if policy allows')
838
+ .option('--actor <actor>', 'Audit actor override')
839
+ .option('--json', 'Print machine-readable JSON output')
840
+ .action((options) => safeRun(runAppRuntimeActivateCommand, options, 'app runtime activate'));
841
+
842
+ const engineering = app
843
+ .command('engineering')
844
+ .description('Manage engineering project projection for one app bundle');
845
+
846
+ engineering
847
+ .command('show')
848
+ .description('Show engineering projection for one app bundle')
849
+ .requiredOption('--app <app-id-or-key>', 'App id or app key')
850
+ .option('--json', 'Print machine-readable JSON output')
851
+ .action((options) => safeRun(runAppEngineeringShowCommand, options, 'app engineering show'));
852
+
853
+ engineering
854
+ .command('attach')
855
+ .description('Attach engineering project metadata to one app bundle')
856
+ .requiredOption('--app <app-id-or-key>', 'App id or app key')
857
+ .requiredOption('--repo <repo-url>', 'Repository url')
858
+ .option('--provider <provider>', 'Repository provider')
859
+ .option('--branch <branch>', 'Default/current branch')
860
+ .option('--workspace-path <path>', 'Workspace path to bind')
861
+ .option('--project-name <name>', 'Project display name override')
862
+ .option('--project-key <key>', 'Project key override')
863
+ .option('--code-version <version>', 'Code version summary')
864
+ .option('--auth-lease <lease-id>', 'Write authorization lease id')
865
+ .option('--auth-password <password>', 'Inline auth password if policy allows')
866
+ .option('--actor <actor>', 'Audit actor override')
867
+ .option('--json', 'Print machine-readable JSON output')
868
+ .action((options) => safeRun(runAppEngineeringAttachCommand, options, 'app engineering attach'));
869
+
870
+ engineering
871
+ .command('hydrate')
872
+ .description('Prepare local engineering workspace for one app bundle')
873
+ .requiredOption('--app <app-id-or-key>', 'App id or app key')
874
+ .option('--workspace-path <path>', 'Explicit workspace path')
875
+ .option('--auth-lease <lease-id>', 'Write authorization lease id')
876
+ .option('--auth-password <password>', 'Inline auth password if policy allows')
877
+ .option('--actor <actor>', 'Audit actor override')
878
+ .option('--json', 'Print machine-readable JSON output')
879
+ .action((options) => safeRun(runAppEngineeringHydrateCommand, options, 'app engineering hydrate'));
880
+
881
+ engineering
882
+ .command('activate')
883
+ .description('Activate engineering workspace for one app bundle')
884
+ .requiredOption('--app <app-id-or-key>', 'App id or app key')
885
+ .option('--workspace-path <path>', 'Override workspace path for activation')
886
+ .option('--auth-lease <lease-id>', 'Write authorization lease id')
887
+ .option('--auth-password <password>', 'Inline auth password if policy allows')
888
+ .option('--actor <actor>', 'Audit actor override')
889
+ .option('--json', 'Print machine-readable JSON output')
890
+ .action((options) => safeRun(runAppEngineeringActivateCommand, options, 'app engineering activate'));
891
+ }
892
+
893
+ module.exports = {
894
+ runAppBundleListCommand,
895
+ runAppBundleShowCommand,
896
+ runAppBundleRegisterCommand,
897
+ runAppRegistryStatusCommand,
898
+ runAppRegistryConfigureCommand,
899
+ runAppRegistrySyncBundlesCommand,
900
+ runAppRegistrySyncCatalogCommand,
901
+ runAppRegistrySyncAllCommand,
902
+ runAppRuntimeShowCommand,
903
+ runAppRuntimeReleasesCommand,
904
+ runAppRuntimeInstallCommand,
905
+ runAppRuntimeActivateCommand,
906
+ runAppEngineeringShowCommand,
907
+ runAppEngineeringAttachCommand,
908
+ runAppEngineeringHydrateCommand,
909
+ runAppEngineeringActivateCommand,
910
+ registerAppCommands
911
+ };