scene-capability-engine 3.6.64 → 3.6.67

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 (125) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +17 -6
  3. package/README.zh.md +18 -6
  4. package/bin/scene-capability-engine.js +4 -0
  5. package/docs/README.md +2 -2
  6. package/docs/command-reference.md +385 -8
  7. package/docs/document-governance.md +3 -2
  8. package/docs/integration-modes.md +62 -478
  9. package/docs/integration-philosophy.md +56 -263
  10. package/docs/magicball-cli-invocation-examples.md +1 -0
  11. package/docs/magicball-project-portfolio-contract.md +125 -4
  12. package/docs/project-management/README.md +14 -0
  13. package/docs/project-management/assurance/backup.md +3 -0
  14. package/docs/project-management/assurance/config.md +3 -0
  15. package/docs/project-management/assurance/evidence/README.md +3 -0
  16. package/docs/project-management/assurance/incidents/README.md +3 -0
  17. package/docs/project-management/assurance/logs.md +3 -0
  18. package/docs/project-management/assurance/overview.md +3 -0
  19. package/docs/project-management/assurance/recovery/README.md +3 -0
  20. package/docs/project-management/assurance/resource.md +3 -0
  21. package/docs/project-management/assurance/runbooks/README.md +3 -0
  22. package/docs/project-management/delivery/acceptance/README.md +3 -0
  23. package/docs/project-management/delivery/acceptance/evidence/README.md +3 -0
  24. package/docs/project-management/delivery/acceptance/exceptions/README.md +3 -0
  25. package/docs/project-management/delivery/acceptance/reports/README.md +3 -0
  26. package/docs/project-management/delivery/documents/changes.md +3 -0
  27. package/docs/project-management/delivery/documents/issues.md +3 -0
  28. package/docs/project-management/delivery/documents/overview.md +3 -0
  29. package/docs/project-management/delivery/documents/planning.md +3 -0
  30. package/docs/project-management/delivery/documents/requirements.md +3 -0
  31. package/docs/project-management/delivery/documents/tracking.md +3 -0
  32. package/docs/project-management/delivery/handoffs/README.md +3 -0
  33. package/docs/project-management/delivery/handoffs/evidence/README.md +3 -0
  34. package/docs/project-management/delivery/handoffs/records/README.md +3 -0
  35. package/docs/project-management/delivery/overview.md +10 -0
  36. package/docs/project-management/delivery/releases/README.md +3 -0
  37. package/docs/project-management/delivery/releases/baselines/README.md +3 -0
  38. package/docs/project-management/delivery/releases/evidence/README.md +3 -0
  39. package/docs/project-management/delivery/tables/changes.md +3 -0
  40. package/docs/project-management/delivery/tables/issues.md +3 -0
  41. package/docs/project-management/delivery/tables/planning.md +3 -0
  42. package/docs/project-management/delivery/tables/requirements.md +3 -0
  43. package/docs/project-management/delivery/tables/tracking.md +3 -0
  44. package/docs/project-management/environment/agent-discovery.md +3 -0
  45. package/docs/project-management/environment/development.md +3 -0
  46. package/docs/project-management/environment/overview.md +10 -0
  47. package/docs/project-management/environment/testing.md +3 -0
  48. package/docs/project-management/environment/version-alignment.md +3 -0
  49. package/docs/quick-start-with-ai-tools.md +68 -308
  50. package/docs/releases/README.md +3 -0
  51. package/docs/releases/v3.6.65.md +25 -0
  52. package/docs/releases/v3.6.66.md +23 -0
  53. package/docs/releases/v3.6.67.md +23 -0
  54. package/docs/steering-governance.md +64 -2
  55. package/docs/zh/README.md +2 -2
  56. package/docs/zh/releases/README.md +3 -0
  57. package/docs/zh/releases/v3.6.65.md +25 -0
  58. package/docs/zh/releases/v3.6.66.md +23 -0
  59. package/docs/zh/releases/v3.6.67.md +23 -0
  60. package/lib/commands/adopt.js +24 -0
  61. package/lib/commands/native.js +158 -0
  62. package/lib/commands/project.js +96 -0
  63. package/lib/commands/semantic.js +1459 -0
  64. package/lib/commands/session.js +74 -3
  65. package/lib/commands/spec-bootstrap.js +10 -1
  66. package/lib/commands/spec-gate.js +10 -1
  67. package/lib/commands/spec-pipeline.js +10 -1
  68. package/lib/commands/studio.js +405 -30
  69. package/lib/commands/task.js +141 -7
  70. package/lib/governance/supreme-principles.js +530 -0
  71. package/lib/problem/problem-evaluator.js +4 -0
  72. package/lib/project/candidate-inspection-service.js +24 -1
  73. package/lib/project/portfolio-projection-service.js +315 -5
  74. package/lib/project/project-channel-output.js +94 -0
  75. package/lib/project/project-channel-projection.js +181 -0
  76. package/lib/project/root-onboarding-service.js +107 -7
  77. package/lib/project/semantic-shared-source-projection.js +150 -0
  78. package/lib/project/supervision-action-model.js +277 -0
  79. package/lib/project/supervision-projection-service.js +305 -5
  80. package/lib/project/target-resolution-service.js +70 -5
  81. package/lib/project/visibility-policy.js +93 -0
  82. package/lib/runtime/multi-spec-scene-session.js +8 -1
  83. package/lib/runtime/project-channel-context-store.js +387 -0
  84. package/lib/runtime/project-channel-context.js +406 -0
  85. package/lib/runtime/scene-session-binding.js +46 -0
  86. package/lib/runtime/session-store.js +186 -0
  87. package/lib/runtime/steering-contract.js +7 -1
  88. package/lib/semantic/archive-report.js +283 -0
  89. package/lib/semantic/archive-routing.js +67 -0
  90. package/lib/semantic/backflow-report.js +245 -0
  91. package/lib/semantic/capability-contract.js +30 -0
  92. package/lib/semantic/delta-export.js +145 -0
  93. package/lib/semantic/interaction-observer.js +254 -0
  94. package/lib/semantic/kernel-loader.js +881 -0
  95. package/lib/semantic/native-runtime.js +359 -0
  96. package/lib/semantic/progress-ledger.js +433 -0
  97. package/lib/semantic/replay-evaluator.js +382 -0
  98. package/lib/semantic/shared-publication.js +592 -0
  99. package/lib/semantic/shared-source-config.js +183 -0
  100. package/lib/semantic/shared-source-connect.js +139 -0
  101. package/lib/semantic/shared-source-discovery.js +98 -0
  102. package/lib/semantic/shared-sync-export.js +413 -0
  103. package/lib/semantic/shared-sync-intake.js +592 -0
  104. package/lib/semantic/shared-sync-merge.js +547 -0
  105. package/lib/semantic/shared-sync-release.js +463 -0
  106. package/lib/semantic/supreme-intent-report.js +300 -0
  107. package/lib/state/sce-state-store.js +1360 -0
  108. package/lib/steering/context-sync-manager.js +276 -25
  109. package/lib/studio/spec-intake-governor.js +39 -3
  110. package/lib/studio/task-envelope.js +35 -2
  111. package/lib/workspace/takeover-baseline.js +342 -83
  112. package/package.json +7 -2
  113. package/scripts/agent-governance-baseline-audit.js +395 -0
  114. package/scripts/clarification-first-audit.js +9 -9
  115. package/scripts/deprecated-entry-audit.js +240 -0
  116. package/scripts/release-doc-version-audit.js +24 -0
  117. package/scripts/release-posture-report.js +262 -0
  118. package/template/.sce/README.md +62 -228
  119. package/template/.sce/config/semantic-shared-sources.json +5 -0
  120. package/template/.sce/config/supreme-principles-policy.json +105 -0
  121. package/template/.sce/config/takeover-baseline.json +7 -0
  122. package/template/.sce/steering/CORE_PRINCIPLES.md +23 -63
  123. package/template/.sce/steering/CURRENT_CONTEXT.md +4 -0
  124. package/template/.sce/steering/RULES_GUIDE.md +17 -9
  125. package/template/README.md +32 -96
@@ -0,0 +1,181 @@
1
+ const fs = require('fs-extra');
2
+ const { ProjectChannelContextStore } = require('../runtime/project-channel-context-store');
3
+
4
+ function normalizeString(value) {
5
+ if (typeof value !== 'string') {
6
+ return '';
7
+ }
8
+ return value.trim();
9
+ }
10
+
11
+ function safeIsoAt(value) {
12
+ const normalized = normalizeString(value);
13
+ if (!normalized) {
14
+ return null;
15
+ }
16
+ const time = Date.parse(normalized);
17
+ return Number.isFinite(time) ? new Date(time).toISOString() : null;
18
+ }
19
+
20
+ function buildEmptyProjectChannelProjection() {
21
+ return {
22
+ summary: {
23
+ available: false,
24
+ contextProjectId: null,
25
+ canonicalProjectIdMatched: false,
26
+ focusedChannelId: null,
27
+ channelCount: 0,
28
+ storageMode: 'none'
29
+ },
30
+ resolvedChannel: null,
31
+ channels: [],
32
+ partial: false,
33
+ partialReasons: []
34
+ };
35
+ }
36
+
37
+ function sortChannels(channels = [], focusedChannelId = '') {
38
+ const normalizedFocusedChannelId = normalizeString(focusedChannelId);
39
+ return [...channels].sort((left, right) => {
40
+ const leftFocused = normalizeString(left && left.channelId) === normalizedFocusedChannelId ? 0 : 1;
41
+ const rightFocused = normalizeString(right && right.channelId) === normalizedFocusedChannelId ? 0 : 1;
42
+ if (leftFocused !== rightFocused) {
43
+ return leftFocused - rightFocused;
44
+ }
45
+ return `${left && left.channelId ? left.channelId : ''}`.localeCompare(`${right && right.channelId ? right.channelId : ''}`);
46
+ });
47
+ }
48
+
49
+ function buildChannelSummary(channel = {}, focusedChannelId = '') {
50
+ const channelId = normalizeString(channel.channelId);
51
+ return {
52
+ channelId,
53
+ focused: channelId === normalizeString(focusedChannelId),
54
+ activeScene: normalizeString(channel.activeScene) || null,
55
+ activeSpecId: normalizeString(channel.activeSpecId) || null,
56
+ activeDoc: normalizeString(channel.activeDoc) || null,
57
+ activeSessionPath: normalizeString(channel.activeSessionPath) || null,
58
+ runState: normalizeString(channel.runState) || null,
59
+ updatedAt: safeIsoAt(channel.updatedAt),
60
+ ...(channel.agentRuntime && typeof channel.agentRuntime === 'object'
61
+ ? {
62
+ agentRuntime: {
63
+ agentId: normalizeString(channel.agentRuntime.agentId) || null,
64
+ backendProfile: normalizeString(channel.agentRuntime.backendProfile) || null,
65
+ runtimeSessionId: normalizeString(channel.agentRuntime.runtimeSessionId) || null
66
+ }
67
+ }
68
+ : {})
69
+ };
70
+ }
71
+
72
+ async function listPersistedProjectChannelIds(projectRoot, fileSystem = fs) {
73
+ const store = new ProjectChannelContextStore(projectRoot, fileSystem);
74
+ const contexts = await store.listPersistedContexts();
75
+ return contexts.map((item) => item.projectId);
76
+ }
77
+
78
+ function chooseContextProjectId(projectIds = [], preferredProjectIds = []) {
79
+ const preferred = preferredProjectIds
80
+ .map((value) => normalizeString(value))
81
+ .filter(Boolean);
82
+ for (const preferredProjectId of preferred) {
83
+ if (projectIds.includes(preferredProjectId)) {
84
+ return {
85
+ projectId: preferredProjectId,
86
+ ambiguous: false
87
+ };
88
+ }
89
+ }
90
+ if (projectIds.length === 1) {
91
+ return {
92
+ projectId: projectIds[0],
93
+ ambiguous: false
94
+ };
95
+ }
96
+ return {
97
+ projectId: null,
98
+ ambiguous: projectIds.length > 1
99
+ };
100
+ }
101
+
102
+ async function buildProjectChannelProjection(projectRoot, options = {}, dependencies = {}) {
103
+ const fileSystem = dependencies.fileSystem || fs;
104
+ const store = dependencies.projectChannelContextStore || new ProjectChannelContextStore(projectRoot, fileSystem);
105
+ const preferredProjectIds = Array.isArray(options.preferredProjectIds)
106
+ ? options.preferredProjectIds
107
+ : [];
108
+ const requestedChannelId = normalizeString(options.channelId);
109
+ const partialReasons = [];
110
+
111
+ let projectIds = [];
112
+ let persistedContexts = [];
113
+ try {
114
+ persistedContexts = await store.listPersistedContexts();
115
+ projectIds = persistedContexts.map((item) => item.projectId);
116
+ } catch (_error) {
117
+ partialReasons.push('project_channel_context_unavailable');
118
+ }
119
+
120
+ const resolution = chooseContextProjectId(projectIds, preferredProjectIds);
121
+ if (resolution.ambiguous) {
122
+ partialReasons.push('project_channel_context_ambiguous');
123
+ }
124
+ if (!resolution.projectId) {
125
+ return {
126
+ ...buildEmptyProjectChannelProjection(),
127
+ partial: partialReasons.length > 0,
128
+ partialReasons
129
+ };
130
+ }
131
+
132
+ let context;
133
+ try {
134
+ context = await store.load(resolution.projectId, {
135
+ allowDefaultChannelSynthesis: false
136
+ });
137
+ } catch (_error) {
138
+ return {
139
+ ...buildEmptyProjectChannelProjection(),
140
+ partial: true,
141
+ partialReasons: [...partialReasons, 'project_channel_context_unavailable']
142
+ };
143
+ }
144
+
145
+ const focusedChannelId = normalizeString(context.focusedChannelId) || null;
146
+ const channels = sortChannels(
147
+ Object.values(context.channels || {}).map((channel) => buildChannelSummary(channel, focusedChannelId)),
148
+ focusedChannelId
149
+ );
150
+ if (channels.length === 0) {
151
+ partialReasons.push('project_channel_context_empty');
152
+ }
153
+ const resolvedChannel = channels.find((channel) => channel.channelId === requestedChannelId)
154
+ || channels.find((channel) => channel.focused)
155
+ || null;
156
+ const contextProjectId = normalizeString(context.projectId) || resolution.projectId;
157
+ const canonicalProjectIdMatched = preferredProjectIds.includes(contextProjectId);
158
+ const storageRecord = persistedContexts.find((item) => item.projectId === contextProjectId)
159
+ || persistedContexts.find((item) => item.projectId === resolution.projectId)
160
+ || null;
161
+
162
+ return {
163
+ summary: {
164
+ available: true,
165
+ contextProjectId,
166
+ canonicalProjectIdMatched,
167
+ focusedChannelId,
168
+ channelCount: channels.length,
169
+ storageMode: storageRecord ? storageRecord.storageMode : 'unknown'
170
+ },
171
+ resolvedChannel,
172
+ channels,
173
+ partial: partialReasons.length > 0,
174
+ partialReasons
175
+ };
176
+ }
177
+
178
+ module.exports = {
179
+ buildEmptyProjectChannelProjection,
180
+ buildProjectChannelProjection
181
+ };
@@ -3,6 +3,9 @@ const fs = require('fs-extra');
3
3
  const WorkspaceStateManager = require('../workspace/multi/workspace-state-manager');
4
4
  const SmartOrchestrator = require('../adoption/smart-orchestrator');
5
5
  const { applyTakeoverBaseline } = require('../workspace/takeover-baseline');
6
+ const { discoverSemanticSharedSourceDescriptors } = require('../semantic/shared-source-discovery');
7
+ const { buildProjectChannelProjection } = require('./project-channel-projection');
8
+ const { buildProjectChannelOutputFromProjection } = require('./project-channel-output');
6
9
  const {
7
10
  PROJECT_CANDIDATE_REASON_CODES,
8
11
  inspectProjectCandidate
@@ -17,6 +20,7 @@ const PROJECT_ONBOARDING_REASON_CODES = {
17
20
  ROOT_ACCEPTED: 'project.onboarding.root_accepted',
18
21
  IMPORT_NO_ACTIVATE: 'project.onboarding.import_no_activate',
19
22
  REGISTERED: 'project.onboarding.registered',
23
+ PUBLISHED: 'project.onboarding.published',
20
24
  ADOPTED: 'project.onboarding.adopted',
21
25
  SCAFFOLD_REUSED: 'project.onboarding.scaffold_reused',
22
26
  ADOPTION_FAILED: 'project.onboarding.adoption_failed'
@@ -38,6 +42,32 @@ function buildStep(key, status, detail, reasonCode) {
38
42
  };
39
43
  }
40
44
 
45
+ function buildPublication(preview = {}, options = {}) {
46
+ const status = normalizeString(options.status) || 'not_published';
47
+ const publishedAt = normalizeString(options.publishedAt);
48
+ return {
49
+ status,
50
+ visibleInPortfolio: options.visibleInPortfolio === true,
51
+ rootDir: preview.rootDir || null,
52
+ projectId: preview.projectId || null,
53
+ workspaceId: preview.workspaceId || null,
54
+ ...(publishedAt ? { publishedAt } : {})
55
+ };
56
+ }
57
+
58
+ function buildSemanticSharedSourceDiscoverySummary(discovery = {}) {
59
+ const summary = discovery && typeof discovery.summary === 'object'
60
+ ? discovery.summary
61
+ : {};
62
+ return {
63
+ totalDescriptors: Number.isFinite(summary.total) ? summary.total : 0,
64
+ approvedDescriptors: Number.isFinite(summary.approved) ? summary.approved : 0,
65
+ blockedDescriptors: Number.isFinite(summary.blocked) ? summary.blocked : 0,
66
+ items: Array.isArray(discovery.items) ? discovery.items : [],
67
+ blocked: Array.isArray(discovery.blocked) ? discovery.blocked : []
68
+ };
69
+ }
70
+
41
71
  function buildWorkspaceNameCandidate(rootDir) {
42
72
  const base = path.basename(rootDir).trim().toLowerCase();
43
73
  const normalized = base.replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
@@ -67,6 +97,10 @@ function buildFailureEnvelope(rootInspection, steps, detail, reasonCode) {
67
97
  success: false,
68
98
  preview: rootInspection,
69
99
  summary: rootInspection,
100
+ publication: buildPublication(rootInspection, {
101
+ status: 'not_published',
102
+ visibleInPortfolio: false
103
+ }),
70
104
  steps,
71
105
  error: {
72
106
  reasonCode,
@@ -111,23 +145,43 @@ async function runProjectRootOnboardingImport(options = {}, dependencies = {}) {
111
145
  const steps = [];
112
146
 
113
147
  if (rootInspection.kind === 'invalid') {
148
+ const blockedReasonCode = rootInspection.reasonCodes.includes(PROJECT_CANDIDATE_REASON_CODES.EPHEMERAL_ROOT)
149
+ ? PROJECT_CANDIDATE_REASON_CODES.EPHEMERAL_ROOT
150
+ : PROJECT_ONBOARDING_REASON_CODES.BLOCKED_BY_CANDIDATE;
151
+ const attachDetail = rootInspection.reasonCodes.includes(PROJECT_CANDIDATE_REASON_CODES.EPHEMERAL_ROOT)
152
+ ? 'Local root is reserved for ephemeral probe/import flow use and cannot become a managed project.'
153
+ : 'Local root is not accessible as a project directory.';
154
+ const publishDetail = rootInspection.reasonCodes.includes(PROJECT_CANDIDATE_REASON_CODES.EPHEMERAL_ROOT)
155
+ ? 'Canonical portfolio publication is blocked for ephemeral probe roots.'
156
+ : 'Canonical portfolio publication is blocked until the root is valid.';
157
+ const failureDetail = rootInspection.reasonCodes.includes(PROJECT_CANDIDATE_REASON_CODES.EPHEMERAL_ROOT)
158
+ ? 'Root inspection reported an ephemeral probe root that must not be imported.'
159
+ : 'Root inspection reported an invalid candidate state.';
114
160
  steps.push(buildStep(
115
161
  'register',
116
162
  'failed',
117
163
  'Root directory cannot be onboarded.',
118
- PROJECT_ONBOARDING_REASON_CODES.BLOCKED_BY_CANDIDATE
164
+ blockedReasonCode
119
165
  ));
120
166
  steps.push(buildStep(
121
167
  'attach',
122
168
  'failed',
123
- 'Local root is not accessible as a project directory.',
124
- PROJECT_CANDIDATE_REASON_CODES.ROOT_INACCESSIBLE
169
+ attachDetail,
170
+ rootInspection.reasonCodes.includes(PROJECT_CANDIDATE_REASON_CODES.EPHEMERAL_ROOT)
171
+ ? PROJECT_CANDIDATE_REASON_CODES.EPHEMERAL_ROOT
172
+ : PROJECT_CANDIDATE_REASON_CODES.ROOT_INACCESSIBLE
125
173
  ));
126
174
  steps.push(buildStep(
127
175
  'hydrate',
128
176
  'failed',
129
177
  'Onboarding cannot continue until the root is valid.',
130
- PROJECT_ONBOARDING_REASON_CODES.BLOCKED_BY_CANDIDATE
178
+ blockedReasonCode
179
+ ));
180
+ steps.push(buildStep(
181
+ 'publish',
182
+ 'skipped',
183
+ publishDetail,
184
+ blockedReasonCode
131
185
  ));
132
186
  steps.push(buildStep(
133
187
  'activate',
@@ -144,8 +198,8 @@ async function runProjectRootOnboardingImport(options = {}, dependencies = {}) {
144
198
  return buildFailureEnvelope(
145
199
  rootInspection,
146
200
  steps,
147
- 'Root inspection reported an invalid candidate state.',
148
- PROJECT_ONBOARDING_REASON_CODES.BLOCKED_BY_CANDIDATE
201
+ failureDetail,
202
+ blockedReasonCode
149
203
  );
150
204
  }
151
205
 
@@ -168,6 +222,12 @@ async function runProjectRootOnboardingImport(options = {}, dependencies = {}) {
168
222
  'Existing project metadata must be repaired before import.',
169
223
  PROJECT_CANDIDATE_REASON_CODES.INVALID_PROJECT_METADATA
170
224
  ));
225
+ steps.push(buildStep(
226
+ 'publish',
227
+ 'skipped',
228
+ 'Canonical portfolio publication is blocked by invalid project metadata.',
229
+ PROJECT_CANDIDATE_REASON_CODES.INVALID_PROJECT_METADATA
230
+ ));
171
231
  steps.push(buildStep(
172
232
  'activate',
173
233
  'skipped',
@@ -220,6 +280,12 @@ async function runProjectRootOnboardingImport(options = {}, dependencies = {}) {
220
280
  (importResult.errors || []).join('; ') || 'Adoption failed.',
221
281
  PROJECT_ONBOARDING_REASON_CODES.ADOPTION_FAILED
222
282
  ));
283
+ steps.push(buildStep(
284
+ 'publish',
285
+ 'skipped',
286
+ 'Canonical portfolio publication was not attempted because onboarding failed.',
287
+ PROJECT_ONBOARDING_REASON_CODES.ADOPTION_FAILED
288
+ ));
223
289
  steps.push(buildStep(
224
290
  'activate',
225
291
  'skipped',
@@ -298,6 +364,13 @@ async function runProjectRootOnboardingImport(options = {}, dependencies = {}) {
298
364
  ? PROJECT_ONBOARDING_REASON_CODES.ADOPTED
299
365
  : PROJECT_CANDIDATE_REASON_CODES.SCE_PRESENT
300
366
  ));
367
+ const publishedAt = new Date().toISOString();
368
+ steps.push(buildStep(
369
+ 'publish',
370
+ 'done',
371
+ `Project is visible in the canonical portfolio as ${onboardingPreview.projectId}.`,
372
+ PROJECT_ONBOARDING_REASON_CODES.PUBLISHED
373
+ ));
301
374
  steps.push(buildStep(
302
375
  'activate',
303
376
  'skipped',
@@ -315,12 +388,39 @@ async function runProjectRootOnboardingImport(options = {}, dependencies = {}) {
315
388
  : PROJECT_ONBOARDING_REASON_CODES.SCAFFOLD_REUSED
316
389
  ));
317
390
 
391
+ const semanticSharedSourceDiscovery = buildSemanticSharedSourceDiscoverySummary(
392
+ await discoverSemanticSharedSourceDescriptors(rootInspection.rootDir, {
393
+ fileSystem
394
+ })
395
+ );
396
+ const projectChannelProjection = await buildProjectChannelProjection(rootInspection.rootDir, {
397
+ preferredProjectIds: [onboardingPreview.projectId, onboardingPreview.workspaceId]
398
+ }, {
399
+ fileSystem
400
+ });
401
+
318
402
  return {
319
403
  mode: 'import',
320
- generated_at: new Date().toISOString(),
404
+ generated_at: publishedAt,
321
405
  success: true,
322
406
  preview: onboardingPreview,
323
407
  summary: onboardingPreview,
408
+ publication: buildPublication(onboardingPreview, {
409
+ status: 'published',
410
+ visibleInPortfolio: true,
411
+ publishedAt
412
+ }),
413
+ semanticSharedSourceDiscovery,
414
+ projectChannelContext: {
415
+ ...projectChannelProjection.summary
416
+ },
417
+ ...(projectChannelProjection.resolvedChannel
418
+ ? {
419
+ projectChannel: buildProjectChannelOutputFromProjection(projectChannelProjection, {
420
+ canonicalProjectId: onboardingPreview.projectId || null
421
+ })
422
+ }
423
+ : {}),
324
424
  steps,
325
425
  result: {
326
426
  rootDir: onboardingPreview.rootDir,
@@ -0,0 +1,150 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+ const { listSemanticSharedSources } = require('../semantic/shared-source-config');
4
+ const { discoverSemanticSharedSourceDescriptors } = require('../semantic/shared-source-discovery');
5
+
6
+ function normalizeString(value) {
7
+ if (typeof value !== 'string') {
8
+ return '';
9
+ }
10
+ return value.trim();
11
+ }
12
+
13
+ function buildSourceKey(name = '', bundle = '') {
14
+ const normalizedName = normalizeString(name) || 'semantic-shared';
15
+ const normalizedBundle = normalizeString(bundle);
16
+ return `${normalizedName}::${normalizedBundle}`;
17
+ }
18
+
19
+ function buildConfiguredSourceIndexes(items = []) {
20
+ const byKey = new Set();
21
+ const byName = new Set();
22
+ for (const item of items) {
23
+ const name = normalizeString(item && item.name) || 'semantic-shared';
24
+ const bundle = normalizeString(item && item.bundle);
25
+ byName.add(name);
26
+ if (bundle) {
27
+ byKey.add(buildSourceKey(name, bundle));
28
+ }
29
+ }
30
+ return {
31
+ byKey,
32
+ byName
33
+ };
34
+ }
35
+
36
+ function isDescriptorConnected(item = {}, configuredIndexes = {}) {
37
+ if (!item.approved) {
38
+ return false;
39
+ }
40
+ const name = normalizeString(item.source_name) || 'semantic-shared';
41
+ const bundle = normalizeString(item.bundle);
42
+ if (bundle && configuredIndexes.byKey instanceof Set && configuredIndexes.byKey.has(buildSourceKey(name, bundle))) {
43
+ return true;
44
+ }
45
+ return !bundle
46
+ && configuredIndexes.byName instanceof Set
47
+ && configuredIndexes.byName.has(name);
48
+ }
49
+
50
+ function buildEmptySummary() {
51
+ return {
52
+ totalDescriptors: 0,
53
+ approvedDescriptors: 0,
54
+ blockedDescriptors: 0,
55
+ configuredSources: 0,
56
+ connectedApprovedDescriptors: 0,
57
+ pendingApprovedDescriptors: 0,
58
+ hasApprovedDescriptors: false,
59
+ hasPendingApprovedDescriptors: false
60
+ };
61
+ }
62
+
63
+ async function readDescriptorUpdatedAt(projectRoot, descriptorFile, fileSystem = fs) {
64
+ const normalizedDescriptorFile = normalizeString(descriptorFile);
65
+ if (!normalizedDescriptorFile) {
66
+ return null;
67
+ }
68
+ const absolutePath = path.join(projectRoot, normalizedDescriptorFile);
69
+ try {
70
+ const stats = await fileSystem.stat(absolutePath);
71
+ if (stats && stats.mtime instanceof Date && Number.isFinite(stats.mtime.getTime())) {
72
+ return stats.mtime.toISOString();
73
+ }
74
+ } catch (_error) {
75
+ return null;
76
+ }
77
+ return null;
78
+ }
79
+
80
+ async function buildSemanticSharedSourceProjection(projectRoot, dependencies = {}) {
81
+ const fileSystem = dependencies.fileSystem || fs;
82
+ const partialReasons = [];
83
+ let discovery = {
84
+ summary: {
85
+ total: 0,
86
+ approved: 0,
87
+ blocked: 0
88
+ },
89
+ items: [],
90
+ blocked: []
91
+ };
92
+ let configuredSources = {
93
+ total: 0,
94
+ items: []
95
+ };
96
+
97
+ try {
98
+ discovery = await discoverSemanticSharedSourceDescriptors(projectRoot, {
99
+ fileSystem
100
+ });
101
+ } catch (_error) {
102
+ partialReasons.push('semantic_shared_source_discovery_unavailable');
103
+ }
104
+
105
+ try {
106
+ configuredSources = await listSemanticSharedSources({}, {
107
+ projectPath: projectRoot,
108
+ fileSystem
109
+ });
110
+ } catch (_error) {
111
+ partialReasons.push('semantic_shared_source_config_unavailable');
112
+ }
113
+
114
+ const configuredItems = Array.isArray(configuredSources.items) ? configuredSources.items : [];
115
+ const configuredIndexes = buildConfiguredSourceIndexes(configuredItems);
116
+ const sourceItems = Array.isArray(discovery.items) ? discovery.items : [];
117
+ const items = [];
118
+ for (const item of sourceItems) {
119
+ items.push({
120
+ ...item,
121
+ connected: isDescriptorConnected(item, configuredIndexes),
122
+ updated_at: await readDescriptorUpdatedAt(projectRoot, item.descriptor_file, fileSystem)
123
+ });
124
+ }
125
+ const approvedItems = items.filter((item) => item.approved);
126
+ const connectedApprovedItems = approvedItems.filter((item) => item.connected);
127
+ const pendingApprovedItems = approvedItems.filter((item) => !item.connected);
128
+
129
+ return {
130
+ summary: {
131
+ totalDescriptors: items.length,
132
+ approvedDescriptors: approvedItems.length,
133
+ blockedDescriptors: Array.isArray(discovery.blocked) ? discovery.blocked.length : 0,
134
+ configuredSources: configuredItems.length,
135
+ connectedApprovedDescriptors: connectedApprovedItems.length,
136
+ pendingApprovedDescriptors: pendingApprovedItems.length,
137
+ hasApprovedDescriptors: approvedItems.length > 0,
138
+ hasPendingApprovedDescriptors: pendingApprovedItems.length > 0
139
+ },
140
+ items,
141
+ blocked: Array.isArray(discovery.blocked) ? discovery.blocked : [],
142
+ partial: partialReasons.length > 0,
143
+ partialReasons
144
+ };
145
+ }
146
+
147
+ module.exports = {
148
+ buildEmptySummary,
149
+ buildSemanticSharedSourceProjection
150
+ };