scene-capability-engine 3.6.57 → 3.6.59

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 (36) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +5 -3
  3. package/README.zh.md +5 -3
  4. package/bin/scene-capability-engine.js +2 -0
  5. package/docs/command-reference.md +72 -0
  6. package/docs/magicball-adaptation-task-checklist-v1.md +65 -10
  7. package/docs/magicball-cli-invocation-examples.md +53 -8
  8. package/docs/magicball-engineering-projection-contract.md +175 -0
  9. package/docs/magicball-frontend-state-and-command-mapping.md +42 -5
  10. package/docs/magicball-integration-doc-index.md +19 -5
  11. package/docs/magicball-integration-issue-tracker.md +15 -5
  12. package/docs/magicball-mode-home-and-ontology-empty-state-playbook.md +13 -5
  13. package/docs/magicball-project-portfolio-contract.md +216 -0
  14. package/docs/magicball-sce-adaptation-guide.md +18 -4
  15. package/docs/magicball-ui-surface-checklist.md +25 -0
  16. package/docs/magicball-write-auth-adaptation-guide.md +3 -1
  17. package/docs/release-checklist.md +8 -0
  18. package/docs/releases/README.md +2 -0
  19. package/docs/releases/v3.6.58.md +27 -0
  20. package/docs/releases/v3.6.59.md +18 -0
  21. package/docs/zh/release-checklist.md +8 -0
  22. package/docs/zh/releases/README.md +2 -0
  23. package/docs/zh/releases/v3.6.58.md +27 -0
  24. package/docs/zh/releases/v3.6.59.md +18 -0
  25. package/lib/app/engineering-scaffold-service.js +154 -0
  26. package/lib/commands/app.js +442 -13
  27. package/lib/commands/project.js +105 -0
  28. package/lib/commands/scene.js +16 -0
  29. package/lib/project/portfolio-projection-service.js +389 -0
  30. package/lib/project/supervision-projection-service.js +329 -0
  31. package/lib/project/target-resolution-service.js +180 -0
  32. package/lib/scene/delivery-projection-service.js +650 -0
  33. package/package.json +6 -2
  34. package/scripts/magicball-engineering-contract-audit.js +347 -0
  35. package/scripts/magicball-project-contract-audit.js +254 -0
  36. package/template/.sce/README.md +2 -2
@@ -10,6 +10,7 @@ const { getCurrentDeviceProfile } = require('../device/current-device');
10
10
  const { loadDeviceOverride } = require('../device/device-override-store');
11
11
  const { loadAppRegistryConfig, saveAppRegistryConfig } = require('../app/registry-config');
12
12
  const { syncBundleRegistry, syncServiceCatalog } = require('../app/registry-sync-service');
13
+ const { scaffoldEngineeringWorkspace } = require('../app/engineering-scaffold-service');
13
14
 
14
15
  function normalizeString(value) {
15
16
  if (typeof value !== 'string') {
@@ -213,27 +214,311 @@ function buildAppInstallStateItem(bundle = {}, currentDevice = null) {
213
214
  };
214
215
  }
215
216
 
216
- function buildEngineeringSummary(graph = {}) {
217
+ const ENGINEERING_PREVIEW_REASON_CODES = Object.freeze({
218
+ SOURCE_MISSING: 'engineering.source_missing',
219
+ PROJECTION_MISSING: 'engineering.projection_missing',
220
+ WORKSPACE_UNAVAILABLE: 'engineering.workspace_unavailable',
221
+ HYDRATE_REQUIRED: 'engineering.hydrate_required',
222
+ ACTIVATE_REQUIRED: 'engineering.activate_required',
223
+ UPSTREAM_CONTRACT_MISSING: 'engineering.upstream_contract_missing'
224
+ });
225
+
226
+ function buildEngineeringReadinessReasonCodes(graph = {}) {
217
227
  const bundle = graph.bundle || {};
218
228
  const engineeringProject = graph.engineering_project || {};
219
229
  const metadata = engineeringProject.metadata && typeof engineeringProject.metadata === 'object'
220
230
  ? engineeringProject.metadata
221
231
  : {};
232
+ const attached = Boolean(
233
+ engineeringProject.engineering_project_id
234
+ || engineeringProject.repo_url
235
+ || engineeringProject.project_key
236
+ || engineeringProject.project_name
237
+ );
238
+ const sourceKnown = Boolean(normalizeString(engineeringProject.repo_url));
239
+ const workspacePath = normalizeString(engineeringProject.workspace_path) || null;
240
+ const hydrated = Boolean(metadata.hydration && metadata.hydration.status === 'ready');
241
+ const active = Boolean(metadata.activation && metadata.activation.active === true);
242
+ const reasonCodes = [];
243
+
244
+ if (!attached || !sourceKnown) {
245
+ reasonCodes.push(ENGINEERING_PREVIEW_REASON_CODES.SOURCE_MISSING);
246
+ }
247
+ if (!workspacePath) {
248
+ reasonCodes.push(ENGINEERING_PREVIEW_REASON_CODES.PROJECTION_MISSING);
249
+ reasonCodes.push(ENGINEERING_PREVIEW_REASON_CODES.WORKSPACE_UNAVAILABLE);
250
+ } else if (!hydrated) {
251
+ reasonCodes.push(ENGINEERING_PREVIEW_REASON_CODES.HYDRATE_REQUIRED);
252
+ } else if (!active) {
253
+ reasonCodes.push(ENGINEERING_PREVIEW_REASON_CODES.ACTIVATE_REQUIRED);
254
+ }
255
+
256
+ return Array.from(new Set(reasonCodes));
257
+ }
258
+
259
+ function buildEngineeringPreviewNextActions(graph = {}) {
260
+ const engineeringProject = graph.engineering_project || {};
261
+ const metadata = engineeringProject.metadata && typeof engineeringProject.metadata === 'object'
262
+ ? engineeringProject.metadata
263
+ : {};
264
+ const attached = Boolean(
265
+ engineeringProject.engineering_project_id
266
+ || engineeringProject.repo_url
267
+ || engineeringProject.project_key
268
+ || engineeringProject.project_name
269
+ );
270
+ const sourceKnown = Boolean(normalizeString(engineeringProject.repo_url));
271
+ const workspacePath = normalizeString(engineeringProject.workspace_path) || null;
272
+ const hydrated = Boolean(metadata.hydration && metadata.hydration.status === 'ready');
273
+ const active = Boolean(metadata.activation && metadata.activation.active === true);
274
+ const nextActions = [];
275
+
276
+ if (!attached || !sourceKnown) {
277
+ nextActions.push('attach');
278
+ }
279
+ if (!workspacePath || !hydrated) {
280
+ nextActions.push('hydrate');
281
+ }
282
+ if (workspacePath && hydrated && !active) {
283
+ nextActions.push('activate');
284
+ }
285
+ if (engineeringProject.dirty_state === true) {
286
+ nextActions.push('review_local_changes');
287
+ }
288
+
289
+ return Array.from(new Set(nextActions));
290
+ }
291
+
292
+ function buildEngineeringPreviewSummary(graph = {}) {
293
+ const bundle = graph.bundle || {};
294
+ const engineeringProject = graph.engineering_project || {};
295
+ const metadata = engineeringProject.metadata && typeof engineeringProject.metadata === 'object'
296
+ ? engineeringProject.metadata
297
+ : {};
298
+ const attached = Boolean(
299
+ engineeringProject.engineering_project_id
300
+ || engineeringProject.repo_url
301
+ || engineeringProject.project_key
302
+ || engineeringProject.project_name
303
+ );
304
+ const sourceKnown = Boolean(normalizeString(engineeringProject.repo_url));
305
+ const workspacePath = normalizeString(engineeringProject.workspace_path) || null;
306
+ const hydrated = Boolean(metadata.hydration && metadata.hydration.status === 'ready');
307
+ const active = Boolean(metadata.activation && metadata.activation.active === true);
308
+ const readinessReasonCodes = buildEngineeringReadinessReasonCodes(graph);
309
+
222
310
  return {
223
311
  app_id: bundle.app_id || null,
224
- app_name: bundle.app_name || null,
225
- engineering_project_id: engineeringProject.engineering_project_id || null,
226
- project_name: engineeringProject.project_name || null,
227
- repo_url: engineeringProject.repo_url || null,
228
- current_branch: engineeringProject.current_branch || null,
229
- workspace_path: engineeringProject.workspace_path || null,
230
- code_version: engineeringProject.code_version || null,
231
- dirty_state: engineeringProject.dirty_state === true,
232
- hydrated: Boolean(metadata.hydration && metadata.hydration.status === 'ready'),
233
- active: Boolean(metadata.activation && metadata.activation.active === true)
312
+ appKey: bundle.app_key || null,
313
+ appName: bundle.app_name || null,
314
+ engineeringProjectId: engineeringProject.engineering_project_id || null,
315
+ projectName: engineeringProject.project_name || null,
316
+ projectKey: engineeringProject.project_key || null,
317
+ repoUrl: engineeringProject.repo_url || null,
318
+ provider: engineeringProject.repo_provider || null,
319
+ branch: engineeringProject.current_branch || engineeringProject.default_branch || null,
320
+ codeVersion: engineeringProject.code_version || null,
321
+ workspacePath,
322
+ dirtyState: engineeringProject.dirty_state === true,
323
+ attached,
324
+ hydrated,
325
+ active,
326
+ sourceKnown,
327
+ projectionReady: Boolean(workspacePath),
328
+ readinessReasonCodes,
329
+ nextActions: buildEngineeringPreviewNextActions(graph)
330
+ };
331
+ }
332
+
333
+ function buildEngineeringSummary(graph = {}) {
334
+ const preview = buildEngineeringPreviewSummary(graph);
335
+ return {
336
+ app_id: preview.app_id,
337
+ app_name: preview.appName,
338
+ engineering_project_id: preview.engineeringProjectId,
339
+ project_name: preview.projectName,
340
+ repo_url: preview.repoUrl,
341
+ current_branch: preview.branch,
342
+ workspace_path: preview.workspacePath,
343
+ code_version: preview.codeVersion,
344
+ dirty_state: preview.dirtyState,
345
+ hydrated: preview.hydrated,
346
+ active: preview.active,
347
+ sourceKnown: preview.sourceKnown,
348
+ projectionReady: preview.projectionReady,
349
+ readinessReasonCodes: preview.readinessReasonCodes,
350
+ nextActions: preview.nextActions
351
+ };
352
+ }
353
+
354
+ function buildEngineeringOwnershipRelation(graph = {}, currentDevice = null) {
355
+ const bundle = graph.bundle || {};
356
+ const engineeringProject = graph.engineering_project || {};
357
+ const metadata = engineeringProject.metadata && typeof engineeringProject.metadata === 'object'
358
+ ? engineeringProject.metadata
359
+ : {};
360
+ const ownership = metadata.ownership && typeof metadata.ownership === 'object'
361
+ ? metadata.ownership
362
+ : {};
363
+ const sharedPolicy = normalizeString(ownership.shared_policy || ownership.sharedPolicy) || null;
364
+ const explicitOwnershipType = normalizeString(ownership.ownership_type || ownership.ownershipType).toLowerCase();
365
+ const workspacePath = normalizeString(engineeringProject.workspace_path) || null;
366
+ let ownershipType = 'unresolved';
367
+
368
+ if (['local', 'shared', 'unresolved'].includes(explicitOwnershipType)) {
369
+ ownershipType = explicitOwnershipType;
370
+ } else if (sharedPolicy) {
371
+ ownershipType = 'shared';
372
+ } else if (workspacePath) {
373
+ ownershipType = 'local';
374
+ }
375
+
376
+ return {
377
+ appKey: bundle.app_key || null,
378
+ workspaceId: normalizeString(bundle.workspace_id || ownership.workspace_id || ownership.workspaceId) || null,
379
+ userId: normalizeString(ownership.user_id || ownership.userId) || null,
380
+ deviceId: normalizeString(ownership.device_id || ownership.deviceId) || (ownershipType === 'local' && currentDevice ? currentDevice.device_id : null),
381
+ ownershipType,
382
+ sharedPolicy
234
383
  };
235
384
  }
236
385
 
386
+ function buildEngineeringActionSteps(actionMode, graph = {}) {
387
+ const preview = buildEngineeringPreviewSummary(graph);
388
+ const attachReady = preview.attached && preview.sourceKnown;
389
+ const hydrateReady = preview.projectionReady && preview.hydrated;
390
+ const activateReady = preview.active;
391
+ const steps = [{
392
+ key: 'register',
393
+ status: 'done',
394
+ detail: 'App bundle is registered in SCE.'
395
+ }];
396
+
397
+ if (attachReady) {
398
+ steps.push({
399
+ key: 'attach',
400
+ status: 'done',
401
+ detail: 'Engineering source metadata is attached.'
402
+ });
403
+ } else {
404
+ steps.push({
405
+ key: 'attach',
406
+ status: 'pending',
407
+ reasonCode: ENGINEERING_PREVIEW_REASON_CODES.SOURCE_MISSING,
408
+ detail: 'Attach engineering source metadata before continuing.'
409
+ });
410
+ }
411
+
412
+ if (!attachReady) {
413
+ steps.push({
414
+ key: 'hydrate',
415
+ status: 'skipped',
416
+ reasonCode: ENGINEERING_PREVIEW_REASON_CODES.SOURCE_MISSING,
417
+ detail: 'Hydration is blocked until engineering source metadata is attached.'
418
+ });
419
+ } else if (!preview.projectionReady) {
420
+ steps.push({
421
+ key: 'hydrate',
422
+ status: 'pending',
423
+ reasonCode: ENGINEERING_PREVIEW_REASON_CODES.PROJECTION_MISSING,
424
+ detail: 'Create or bind a local engineering workspace before hydration.'
425
+ });
426
+ } else if (!preview.hydrated) {
427
+ steps.push({
428
+ key: 'hydrate',
429
+ status: 'pending',
430
+ reasonCode: ENGINEERING_PREVIEW_REASON_CODES.HYDRATE_REQUIRED,
431
+ detail: 'Engineering workspace exists but is not hydrated yet.'
432
+ });
433
+ } else {
434
+ steps.push({
435
+ key: 'hydrate',
436
+ status: 'done',
437
+ detail: 'Engineering workspace is hydrated.'
438
+ });
439
+ }
440
+
441
+ if (actionMode === 'import' && hydrateReady && !activateReady) {
442
+ steps.push({
443
+ key: 'activate',
444
+ status: 'skipped',
445
+ detail: 'Import completes without activation in phase-1.'
446
+ });
447
+ return steps;
448
+ }
449
+
450
+ if (!attachReady) {
451
+ steps.push({
452
+ key: 'activate',
453
+ status: 'skipped',
454
+ reasonCode: ENGINEERING_PREVIEW_REASON_CODES.SOURCE_MISSING,
455
+ detail: 'Activation is blocked until engineering source metadata is attached.'
456
+ });
457
+ } else if (!preview.projectionReady) {
458
+ steps.push({
459
+ key: 'activate',
460
+ status: 'skipped',
461
+ reasonCode: ENGINEERING_PREVIEW_REASON_CODES.PROJECTION_MISSING,
462
+ detail: 'Activation is blocked until a local engineering workspace is available.'
463
+ });
464
+ } else if (!preview.hydrated) {
465
+ steps.push({
466
+ key: 'activate',
467
+ status: 'skipped',
468
+ reasonCode: ENGINEERING_PREVIEW_REASON_CODES.HYDRATE_REQUIRED,
469
+ detail: 'Activation is blocked until hydration completes.'
470
+ });
471
+ } else if (!preview.active) {
472
+ steps.push({
473
+ key: 'activate',
474
+ status: 'pending',
475
+ reasonCode: ENGINEERING_PREVIEW_REASON_CODES.ACTIVATE_REQUIRED,
476
+ detail: 'Engineering workspace is ready but not active.'
477
+ });
478
+ } else {
479
+ steps.push({
480
+ key: 'activate',
481
+ status: 'done',
482
+ detail: 'Engineering workspace is active.'
483
+ });
484
+ }
485
+
486
+ return steps;
487
+ }
488
+
489
+ function buildEngineeringActionSuccess(actionMode, steps = []) {
490
+ if (actionMode === 'import') {
491
+ return ['register', 'attach', 'hydrate'].every((key) => steps.some((step) => step.key === key && step.status === 'done'));
492
+ }
493
+ return ['register', 'attach', 'hydrate', 'activate'].every((key) => steps.some((step) => step.key === key && step.status === 'done'));
494
+ }
495
+
496
+ async function runAppEngineeringActionEnvelopeCommand(actionMode, options = {}, dependencies = {}) {
497
+ const appRef = normalizeString(options.app);
498
+ if (!appRef) {
499
+ throw new Error('--app is required');
500
+ }
501
+ const { graph } = await requireAppGraph(appRef, dependencies);
502
+ const preview = buildEngineeringPreviewSummary(graph);
503
+ const steps = buildEngineeringActionSteps(actionMode, graph);
504
+ const payload = {
505
+ mode: actionMode,
506
+ generated_at: new Date().toISOString(),
507
+ query: {
508
+ app: appRef
509
+ },
510
+ success: buildEngineeringActionSuccess(actionMode, steps),
511
+ summary: preview,
512
+ preview,
513
+ steps,
514
+ bundle: graph.bundle,
515
+ engineering_project: graph.engineering_project,
516
+ scene_bindings: graph.scene_bindings || []
517
+ };
518
+ printPayload(payload, options, `App Engineering ${actionMode === 'open' ? 'Open' : 'Import'}`);
519
+ return payload;
520
+ }
521
+
237
522
  function deriveEngineeringProjectId(bundle = {}) {
238
523
  const appKey = normalizeString(bundle.app_key);
239
524
  if (appKey) {
@@ -560,13 +845,15 @@ async function runAppEngineeringShowCommand(options = {}, dependencies = {}) {
560
845
  throw new Error('--app is required');
561
846
  }
562
847
  const { graph } = await requireAppGraph(appRef, dependencies);
848
+ const preview = buildEngineeringPreviewSummary(graph);
563
849
  const payload = {
564
850
  mode: 'app-engineering-show',
565
851
  generated_at: new Date().toISOString(),
566
852
  query: {
567
853
  app: appRef
568
854
  },
569
- summary: buildEngineeringSummary(graph),
855
+ summary: preview,
856
+ preview,
570
857
  bundle: graph.bundle,
571
858
  engineering_project: graph.engineering_project,
572
859
  scene_bindings: graph.scene_bindings || []
@@ -575,6 +862,67 @@ async function runAppEngineeringShowCommand(options = {}, dependencies = {}) {
575
862
  return payload;
576
863
  }
577
864
 
865
+ async function runAppEngineeringPreviewCommand(options = {}, dependencies = {}) {
866
+ const appRef = normalizeString(options.app);
867
+ if (!appRef) {
868
+ throw new Error('--app is required');
869
+ }
870
+ const { graph } = await requireAppGraph(appRef, dependencies);
871
+ const preview = buildEngineeringPreviewSummary(graph);
872
+ const payload = {
873
+ mode: 'app-engineering-preview',
874
+ generated_at: new Date().toISOString(),
875
+ query: {
876
+ app: appRef
877
+ },
878
+ summary: preview,
879
+ preview,
880
+ bundle: graph.bundle,
881
+ engineering_project: graph.engineering_project,
882
+ scene_bindings: graph.scene_bindings || []
883
+ };
884
+ printPayload(payload, options, 'App Engineering Preview');
885
+ return payload;
886
+ }
887
+
888
+ async function runAppEngineeringOwnershipCommand(options = {}, dependencies = {}) {
889
+ const appRef = normalizeString(options.app);
890
+ if (!appRef) {
891
+ throw new Error('--app is required');
892
+ }
893
+ const projectPath = dependencies.projectPath || process.cwd();
894
+ const fileSystem = dependencies.fileSystem || fs;
895
+ const { graph } = await requireAppGraph(appRef, dependencies);
896
+ const currentDevice = await getCurrentDeviceProfile(projectPath, {
897
+ fileSystem,
898
+ persistIfMissing: false
899
+ });
900
+ const ownership = buildEngineeringOwnershipRelation(graph, currentDevice);
901
+ const payload = {
902
+ mode: 'app-engineering-ownership',
903
+ generated_at: new Date().toISOString(),
904
+ query: {
905
+ app: appRef
906
+ },
907
+ summary: ownership,
908
+ ownership,
909
+ current_device: currentDevice,
910
+ bundle: graph.bundle,
911
+ engineering_project: graph.engineering_project,
912
+ scene_bindings: graph.scene_bindings || []
913
+ };
914
+ printPayload(payload, options, 'App Engineering Ownership');
915
+ return payload;
916
+ }
917
+
918
+ async function runAppEngineeringOpenCommand(options = {}, dependencies = {}) {
919
+ return runAppEngineeringActionEnvelopeCommand('open', options, dependencies);
920
+ }
921
+
922
+ async function runAppEngineeringImportCommand(options = {}, dependencies = {}) {
923
+ return runAppEngineeringActionEnvelopeCommand('import', options, dependencies);
924
+ }
925
+
578
926
  async function runAppEngineeringAttachCommand(options = {}, dependencies = {}) {
579
927
  const appRef = normalizeString(options.app);
580
928
  if (!appRef) {
@@ -675,6 +1023,42 @@ async function runAppEngineeringHydrateCommand(options = {}, dependencies = {})
675
1023
  return payload;
676
1024
  }
677
1025
 
1026
+ async function runAppEngineeringScaffoldCommand(options = {}, dependencies = {}) {
1027
+ const appRef = normalizeString(options.app);
1028
+ if (!appRef) {
1029
+ throw new Error('--app is required');
1030
+ }
1031
+ await ensureAuthorized('app:engineering:scaffold', options, dependencies);
1032
+ const { graph } = await requireAppGraph(appRef, dependencies);
1033
+ const engineeringProject = graph.engineering_project || {};
1034
+ const workspacePath = normalizeString(options.workspacePath) || normalizeString(engineeringProject.workspace_path);
1035
+ if (!workspacePath) {
1036
+ throw new Error('engineering workspace_path is not set; run app engineering hydrate first or pass --workspace-path');
1037
+ }
1038
+
1039
+ const result = await scaffoldEngineeringWorkspace({
1040
+ workspacePath,
1041
+ overwritePolicy: options.overwritePolicy,
1042
+ fileSystem: dependencies.fileSystem || fs,
1043
+ templateRoot: dependencies.templateRoot
1044
+ });
1045
+ const payload = {
1046
+ mode: 'app-engineering-scaffold',
1047
+ generated_at: new Date().toISOString(),
1048
+ query: {
1049
+ app: appRef
1050
+ },
1051
+ success: result.failedDirectoryCount === 0 && result.failedFileCount === 0,
1052
+ summary: result,
1053
+ result,
1054
+ bundle: graph.bundle,
1055
+ engineering_project: graph.engineering_project,
1056
+ scene_bindings: graph.scene_bindings || []
1057
+ };
1058
+ printPayload(payload, options, 'App Engineering Scaffold');
1059
+ return payload;
1060
+ }
1061
+
678
1062
  async function runAppEngineeringActivateCommand(options = {}, dependencies = {}) {
679
1063
  const appRef = normalizeString(options.app);
680
1064
  if (!appRef) {
@@ -1246,9 +1630,37 @@ function registerAppCommands(program) {
1246
1630
  .command('engineering')
1247
1631
  .description('Manage engineering project projection for one app bundle');
1248
1632
 
1633
+ engineering
1634
+ .command('preview')
1635
+ .description('Preview engineering readiness for one app bundle')
1636
+ .requiredOption('--app <app-id-or-key>', 'App id or app key')
1637
+ .option('--json', 'Print machine-readable JSON output')
1638
+ .action((options) => safeRun(runAppEngineeringPreviewCommand, options, 'app engineering preview'));
1639
+
1640
+ engineering
1641
+ .command('ownership')
1642
+ .description('Show the read-only engineering ownership relation for one app bundle')
1643
+ .requiredOption('--app <app-id-or-key>', 'App id or app key')
1644
+ .option('--json', 'Print machine-readable JSON output')
1645
+ .action((options) => safeRun(runAppEngineeringOwnershipCommand, options, 'app engineering ownership'));
1646
+
1647
+ engineering
1648
+ .command('open')
1649
+ .description('Return the canonical engineering open result envelope')
1650
+ .requiredOption('--app <app-id-or-key>', 'App id or app key')
1651
+ .option('--json', 'Print machine-readable JSON output')
1652
+ .action((options) => safeRun(runAppEngineeringOpenCommand, options, 'app engineering open'));
1653
+
1654
+ engineering
1655
+ .command('import')
1656
+ .description('Return the canonical engineering import result envelope')
1657
+ .requiredOption('--app <app-id-or-key>', 'App id or app key')
1658
+ .option('--json', 'Print machine-readable JSON output')
1659
+ .action((options) => safeRun(runAppEngineeringImportCommand, options, 'app engineering import'));
1660
+
1249
1661
  engineering
1250
1662
  .command('show')
1251
- .description('Show engineering projection for one app bundle')
1663
+ .description('Show engineering projection for one app bundle (compatibility alias of preview)')
1252
1664
  .requiredOption('--app <app-id-or-key>', 'App id or app key')
1253
1665
  .option('--json', 'Print machine-readable JSON output')
1254
1666
  .action((options) => safeRun(runAppEngineeringShowCommand, options, 'app engineering show'));
@@ -1281,6 +1693,18 @@ function registerAppCommands(program) {
1281
1693
  .option('--json', 'Print machine-readable JSON output')
1282
1694
  .action((options) => safeRun(runAppEngineeringHydrateCommand, options, 'app engineering hydrate'));
1283
1695
 
1696
+ engineering
1697
+ .command('scaffold')
1698
+ .description('Scaffold SCE baseline files into the engineering workspace')
1699
+ .requiredOption('--app <app-id-or-key>', 'App id or app key')
1700
+ .option('--workspace-path <path>', 'Override workspace path for scaffold')
1701
+ .option('--overwrite-policy <policy>', 'Overwrite policy: never, missing-only, explicit', 'missing-only')
1702
+ .option('--auth-lease <lease-id>', 'Write authorization lease id')
1703
+ .option('--auth-password <password>', 'Inline auth password if policy allows')
1704
+ .option('--actor <actor>', 'Audit actor override')
1705
+ .option('--json', 'Print machine-readable JSON output')
1706
+ .action((options) => safeRun(runAppEngineeringScaffoldCommand, options, 'app engineering scaffold'));
1707
+
1284
1708
  engineering
1285
1709
  .command('activate')
1286
1710
  .description('Activate engineering workspace for one app bundle')
@@ -1311,9 +1735,14 @@ module.exports = {
1311
1735
  runAppRuntimeActivateCommand,
1312
1736
  runAppRuntimeUninstallCommand,
1313
1737
  runAppInstallStateListCommand,
1738
+ runAppEngineeringPreviewCommand,
1739
+ runAppEngineeringOwnershipCommand,
1740
+ runAppEngineeringOpenCommand,
1741
+ runAppEngineeringImportCommand,
1314
1742
  runAppEngineeringShowCommand,
1315
1743
  runAppEngineeringAttachCommand,
1316
1744
  runAppEngineeringHydrateCommand,
1745
+ runAppEngineeringScaffoldCommand,
1317
1746
  runAppEngineeringActivateCommand,
1318
1747
  registerAppCommands
1319
1748
  };
@@ -0,0 +1,105 @@
1
+ const chalk = require('chalk');
2
+ const { buildProjectPortfolioProjection } = require('../project/portfolio-projection-service');
3
+ const { buildProjectSupervisionProjection } = require('../project/supervision-projection-service');
4
+ const { resolveProjectTarget } = require('../project/target-resolution-service');
5
+
6
+ async function runProjectPortfolioShowCommand(options = {}, dependencies = {}) {
7
+ const payload = await buildProjectPortfolioProjection(options, dependencies);
8
+ if (options.json) {
9
+ console.log(JSON.stringify(payload, null, 2));
10
+ } else {
11
+ console.log(chalk.blue('Project Portfolio'));
12
+ console.log(` Active Project: ${payload.activeProjectId || 'none'}`);
13
+ console.log(` Visible Projects: ${payload.projects.length}`);
14
+ }
15
+ return payload;
16
+ }
17
+
18
+ async function runProjectTargetResolveCommand(options = {}, dependencies = {}) {
19
+ const payload = await resolveProjectTarget(options, dependencies);
20
+ if (options.json) {
21
+ console.log(JSON.stringify(payload, null, 2));
22
+ } else {
23
+ console.log(chalk.blue('Project Target Resolve'));
24
+ console.log(` Status: ${payload.status}`);
25
+ console.log(` Resolved Project: ${payload.resolvedProjectId || 'none'}`);
26
+ }
27
+ return payload;
28
+ }
29
+
30
+ async function runProjectSupervisionShowCommand(options = {}, dependencies = {}) {
31
+ const payload = await buildProjectSupervisionProjection(options, dependencies);
32
+ if (options.json) {
33
+ console.log(JSON.stringify(payload, null, 2));
34
+ } else {
35
+ console.log(chalk.blue('Project Supervision'));
36
+ console.log(` Project: ${payload.projectId}`);
37
+ console.log(` Blocked: ${payload.summary.blockedCount}`);
38
+ console.log(` Handoff: ${payload.summary.handoffCount}`);
39
+ console.log(` Risk: ${payload.summary.riskCount}`);
40
+ }
41
+ return payload;
42
+ }
43
+
44
+ function safeRun(handler, options = {}, context = 'project command') {
45
+ Promise.resolve(handler(options))
46
+ .catch((error) => {
47
+ if (options.json) {
48
+ console.log(JSON.stringify({ success: false, error: error.message }, null, 2));
49
+ } else {
50
+ console.error(chalk.red(`${context} failed:`), error.message);
51
+ }
52
+ process.exitCode = 1;
53
+ });
54
+ }
55
+
56
+ function registerProjectCommands(program) {
57
+ const project = program
58
+ .command('project')
59
+ .description('Inspect multi-project portfolio and routing projections');
60
+
61
+ const portfolio = project
62
+ .command('portfolio')
63
+ .description('Inspect the caller-visible project portfolio');
64
+
65
+ portfolio
66
+ .command('show')
67
+ .description('Show the canonical project portfolio projection')
68
+ .option('--workspace <name>', 'Resolve caller context against one registered workspace')
69
+ .option('--json', 'Print machine-readable JSON output')
70
+ .action((options) => safeRun(runProjectPortfolioShowCommand, options, 'project portfolio show'));
71
+
72
+ const target = project
73
+ .command('target')
74
+ .description('Resolve target project against the caller-visible portfolio');
75
+
76
+ target
77
+ .command('resolve')
78
+ .description('Resolve one project target without mutating active workspace selection')
79
+ .option('--request <text>', 'Routing request text')
80
+ .option('--current-project <id>', 'Caller asserted current project id')
81
+ .option('--workspace <name>', 'Resolve caller context against one registered workspace')
82
+ .option('--device <id>', 'Opaque caller device id')
83
+ .option('--tool-instance-id <id>', 'Opaque caller tool instance id')
84
+ .option('--json', 'Print machine-readable JSON output')
85
+ .action((options) => safeRun(runProjectTargetResolveCommand, options, 'project target resolve'));
86
+
87
+ const supervision = project
88
+ .command('supervision')
89
+ .description('Inspect project-scoped supervision projection');
90
+
91
+ supervision
92
+ .command('show')
93
+ .description('Show project-scoped supervision summary and drillback items')
94
+ .requiredOption('--project <id>', 'Visible project id')
95
+ .option('--cursor <cursor>', 'Best-effort incremental checkpoint; full snapshot remains supported')
96
+ .option('--json', 'Print machine-readable JSON output')
97
+ .action((options) => safeRun(runProjectSupervisionShowCommand, options, 'project supervision show'));
98
+ }
99
+
100
+ module.exports = {
101
+ runProjectPortfolioShowCommand,
102
+ runProjectTargetResolveCommand,
103
+ runProjectSupervisionShowCommand,
104
+ registerProjectCommands
105
+ };
@@ -11,6 +11,7 @@ const { executeInstallPlan } = require('../app/install-apply-runner');
11
11
  const { runAppRuntimeInstallCommand, runAppRuntimeActivateCommand, runAppRuntimeUninstallCommand } = require('./app');
12
12
  const { loadDeviceOverride } = require('../device/device-override-store');
13
13
  const { getCurrentDeviceProfile } = require('../device/current-device');
14
+ const { runSceneDeliveryShowCommand } = require('../scene/delivery-projection-service');
14
15
 
15
16
  const semver = require('semver');
16
17
  const SceneLoader = require('../scene-runtime/scene-loader');
@@ -255,6 +256,20 @@ function registerSceneCommands(program) {
255
256
  await runSceneWorkspaceCommand(runSceneWorkspaceApplyCommand, options, 'scene workspace apply');
256
257
  });
257
258
 
259
+ const deliveryCmd = sceneCmd
260
+ .command('delivery')
261
+ .description('Inspect scene delivery projection envelopes');
262
+
263
+ deliveryCmd
264
+ .command('show')
265
+ .description('Show delivery projection for one scene and optional spec')
266
+ .requiredOption('--scene <id>', 'Scene id')
267
+ .option('--spec <id>', 'Optional spec id filter')
268
+ .option('--json', 'Print machine-readable JSON output')
269
+ .action(async (options) => {
270
+ await runSceneWorkspaceCommand(runSceneDeliveryShowCommand, options, 'scene delivery show');
271
+ });
272
+
258
273
  sceneCmd
259
274
  .command('validate')
260
275
  .description('Validate scene manifest from spec or file path')
@@ -16777,6 +16792,7 @@ module.exports = {
16777
16792
  validateSceneContributeOptions,
16778
16793
  runSceneContributeCommand,
16779
16794
  printSceneContributeSummary,
16795
+ runSceneDeliveryShowCommand,
16780
16796
  runSceneWorkspaceListCommand,
16781
16797
  runSceneWorkspaceShowCommand,
16782
16798
  runSceneWorkspaceApplyCommand,