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.
- package/CHANGELOG.md +26 -0
- package/README.md +17 -6
- package/README.zh.md +18 -6
- package/bin/scene-capability-engine.js +4 -0
- package/docs/README.md +2 -2
- package/docs/command-reference.md +385 -8
- package/docs/document-governance.md +3 -2
- package/docs/integration-modes.md +62 -478
- package/docs/integration-philosophy.md +56 -263
- package/docs/magicball-cli-invocation-examples.md +1 -0
- package/docs/magicball-project-portfolio-contract.md +125 -4
- package/docs/project-management/README.md +14 -0
- package/docs/project-management/assurance/backup.md +3 -0
- package/docs/project-management/assurance/config.md +3 -0
- package/docs/project-management/assurance/evidence/README.md +3 -0
- package/docs/project-management/assurance/incidents/README.md +3 -0
- package/docs/project-management/assurance/logs.md +3 -0
- package/docs/project-management/assurance/overview.md +3 -0
- package/docs/project-management/assurance/recovery/README.md +3 -0
- package/docs/project-management/assurance/resource.md +3 -0
- package/docs/project-management/assurance/runbooks/README.md +3 -0
- package/docs/project-management/delivery/acceptance/README.md +3 -0
- package/docs/project-management/delivery/acceptance/evidence/README.md +3 -0
- package/docs/project-management/delivery/acceptance/exceptions/README.md +3 -0
- package/docs/project-management/delivery/acceptance/reports/README.md +3 -0
- package/docs/project-management/delivery/documents/changes.md +3 -0
- package/docs/project-management/delivery/documents/issues.md +3 -0
- package/docs/project-management/delivery/documents/overview.md +3 -0
- package/docs/project-management/delivery/documents/planning.md +3 -0
- package/docs/project-management/delivery/documents/requirements.md +3 -0
- package/docs/project-management/delivery/documents/tracking.md +3 -0
- package/docs/project-management/delivery/handoffs/README.md +3 -0
- package/docs/project-management/delivery/handoffs/evidence/README.md +3 -0
- package/docs/project-management/delivery/handoffs/records/README.md +3 -0
- package/docs/project-management/delivery/overview.md +10 -0
- package/docs/project-management/delivery/releases/README.md +3 -0
- package/docs/project-management/delivery/releases/baselines/README.md +3 -0
- package/docs/project-management/delivery/releases/evidence/README.md +3 -0
- package/docs/project-management/delivery/tables/changes.md +3 -0
- package/docs/project-management/delivery/tables/issues.md +3 -0
- package/docs/project-management/delivery/tables/planning.md +3 -0
- package/docs/project-management/delivery/tables/requirements.md +3 -0
- package/docs/project-management/delivery/tables/tracking.md +3 -0
- package/docs/project-management/environment/agent-discovery.md +3 -0
- package/docs/project-management/environment/development.md +3 -0
- package/docs/project-management/environment/overview.md +10 -0
- package/docs/project-management/environment/testing.md +3 -0
- package/docs/project-management/environment/version-alignment.md +3 -0
- package/docs/quick-start-with-ai-tools.md +68 -308
- package/docs/releases/README.md +3 -0
- package/docs/releases/v3.6.65.md +25 -0
- package/docs/releases/v3.6.66.md +23 -0
- package/docs/releases/v3.6.67.md +23 -0
- package/docs/steering-governance.md +64 -2
- package/docs/zh/README.md +2 -2
- package/docs/zh/releases/README.md +3 -0
- package/docs/zh/releases/v3.6.65.md +25 -0
- package/docs/zh/releases/v3.6.66.md +23 -0
- package/docs/zh/releases/v3.6.67.md +23 -0
- package/lib/commands/adopt.js +24 -0
- package/lib/commands/native.js +158 -0
- package/lib/commands/project.js +96 -0
- package/lib/commands/semantic.js +1459 -0
- package/lib/commands/session.js +74 -3
- package/lib/commands/spec-bootstrap.js +10 -1
- package/lib/commands/spec-gate.js +10 -1
- package/lib/commands/spec-pipeline.js +10 -1
- package/lib/commands/studio.js +405 -30
- package/lib/commands/task.js +141 -7
- package/lib/governance/supreme-principles.js +530 -0
- package/lib/problem/problem-evaluator.js +4 -0
- package/lib/project/candidate-inspection-service.js +24 -1
- package/lib/project/portfolio-projection-service.js +315 -5
- package/lib/project/project-channel-output.js +94 -0
- package/lib/project/project-channel-projection.js +181 -0
- package/lib/project/root-onboarding-service.js +107 -7
- package/lib/project/semantic-shared-source-projection.js +150 -0
- package/lib/project/supervision-action-model.js +277 -0
- package/lib/project/supervision-projection-service.js +305 -5
- package/lib/project/target-resolution-service.js +70 -5
- package/lib/project/visibility-policy.js +93 -0
- package/lib/runtime/multi-spec-scene-session.js +8 -1
- package/lib/runtime/project-channel-context-store.js +387 -0
- package/lib/runtime/project-channel-context.js +406 -0
- package/lib/runtime/scene-session-binding.js +46 -0
- package/lib/runtime/session-store.js +186 -0
- package/lib/runtime/steering-contract.js +7 -1
- package/lib/semantic/archive-report.js +283 -0
- package/lib/semantic/archive-routing.js +67 -0
- package/lib/semantic/backflow-report.js +245 -0
- package/lib/semantic/capability-contract.js +30 -0
- package/lib/semantic/delta-export.js +145 -0
- package/lib/semantic/interaction-observer.js +254 -0
- package/lib/semantic/kernel-loader.js +881 -0
- package/lib/semantic/native-runtime.js +359 -0
- package/lib/semantic/progress-ledger.js +433 -0
- package/lib/semantic/replay-evaluator.js +382 -0
- package/lib/semantic/shared-publication.js +592 -0
- package/lib/semantic/shared-source-config.js +183 -0
- package/lib/semantic/shared-source-connect.js +139 -0
- package/lib/semantic/shared-source-discovery.js +98 -0
- package/lib/semantic/shared-sync-export.js +413 -0
- package/lib/semantic/shared-sync-intake.js +592 -0
- package/lib/semantic/shared-sync-merge.js +547 -0
- package/lib/semantic/shared-sync-release.js +463 -0
- package/lib/semantic/supreme-intent-report.js +300 -0
- package/lib/state/sce-state-store.js +1360 -0
- package/lib/steering/context-sync-manager.js +276 -25
- package/lib/studio/spec-intake-governor.js +39 -3
- package/lib/studio/task-envelope.js +35 -2
- package/lib/workspace/takeover-baseline.js +342 -83
- package/package.json +7 -2
- package/scripts/agent-governance-baseline-audit.js +395 -0
- package/scripts/clarification-first-audit.js +9 -9
- package/scripts/deprecated-entry-audit.js +240 -0
- package/scripts/release-doc-version-audit.js +24 -0
- package/scripts/release-posture-report.js +262 -0
- package/template/.sce/README.md +62 -228
- package/template/.sce/config/semantic-shared-sources.json +5 -0
- package/template/.sce/config/supreme-principles-policy.json +105 -0
- package/template/.sce/config/takeover-baseline.json +7 -0
- package/template/.sce/steering/CORE_PRINCIPLES.md +23 -63
- package/template/.sce/steering/CURRENT_CONTEXT.md +4 -0
- package/template/.sce/steering/RULES_GUIDE.md +17 -9
- 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
|
-
|
|
164
|
+
blockedReasonCode
|
|
119
165
|
));
|
|
120
166
|
steps.push(buildStep(
|
|
121
167
|
'attach',
|
|
122
168
|
'failed',
|
|
123
|
-
|
|
124
|
-
PROJECT_CANDIDATE_REASON_CODES.
|
|
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
|
-
|
|
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
|
-
|
|
148
|
-
|
|
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:
|
|
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
|
+
};
|