scene-capability-engine 3.6.60 → 3.6.62

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.
@@ -0,0 +1,350 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+ const WorkspaceStateManager = require('../workspace/multi/workspace-state-manager');
4
+ const SmartOrchestrator = require('../adoption/smart-orchestrator');
5
+ const { applyTakeoverBaseline } = require('../workspace/takeover-baseline');
6
+ const {
7
+ PROJECT_CANDIDATE_REASON_CODES,
8
+ inspectProjectCandidate
9
+ } = require('./candidate-inspection-service');
10
+ const {
11
+ buildWorkspaceProjectId
12
+ } = require('./portfolio-projection-service');
13
+
14
+ const PROJECT_ONBOARDING_REASON_CODES = {
15
+ MISSING_ROOT: 'project.onboarding.blocked.missing_root',
16
+ BLOCKED_BY_CANDIDATE: 'project.onboarding.blocked.candidate_state',
17
+ ROOT_ACCEPTED: 'project.onboarding.root_accepted',
18
+ IMPORT_NO_ACTIVATE: 'project.onboarding.import_no_activate',
19
+ REGISTERED: 'project.onboarding.registered',
20
+ ADOPTED: 'project.onboarding.adopted',
21
+ SCAFFOLD_REUSED: 'project.onboarding.scaffold_reused',
22
+ ADOPTION_FAILED: 'project.onboarding.adoption_failed'
23
+ };
24
+
25
+ function normalizeString(value) {
26
+ if (typeof value !== 'string') {
27
+ return '';
28
+ }
29
+ return value.trim();
30
+ }
31
+
32
+ function buildStep(key, status, detail, reasonCode) {
33
+ return {
34
+ key,
35
+ status,
36
+ ...(reasonCode ? { reasonCode } : {}),
37
+ ...(detail ? { detail } : {})
38
+ };
39
+ }
40
+
41
+ function buildWorkspaceNameCandidate(rootDir) {
42
+ const base = path.basename(rootDir).trim().toLowerCase();
43
+ const normalized = base.replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
44
+ return normalized || 'project';
45
+ }
46
+
47
+ async function allocateWorkspaceId(rootDir, stateManager) {
48
+ const base = buildWorkspaceNameCandidate(rootDir);
49
+ if (!await stateManager.hasWorkspace(base)) {
50
+ return base;
51
+ }
52
+
53
+ for (let index = 2; index < 1000; index += 1) {
54
+ const candidate = `${base}-${index}`;
55
+ if (!await stateManager.hasWorkspace(candidate)) {
56
+ return candidate;
57
+ }
58
+ }
59
+
60
+ throw new Error(`unable to allocate workspace id for root: ${rootDir}`);
61
+ }
62
+
63
+ function buildFailureEnvelope(rootInspection, steps, detail, reasonCode) {
64
+ return {
65
+ mode: 'import',
66
+ generated_at: new Date().toISOString(),
67
+ success: false,
68
+ preview: rootInspection,
69
+ summary: rootInspection,
70
+ steps,
71
+ error: {
72
+ reasonCode,
73
+ detail
74
+ }
75
+ };
76
+ }
77
+
78
+ async function runWithSuppressedConsole(callback, enabled) {
79
+ if (!enabled) {
80
+ return callback();
81
+ }
82
+
83
+ const originalLog = console.log;
84
+ const originalInfo = console.info;
85
+ const originalWarn = console.warn;
86
+ console.log = () => {};
87
+ console.info = () => {};
88
+ console.warn = () => {};
89
+ try {
90
+ return await callback();
91
+ } finally {
92
+ console.log = originalLog;
93
+ console.info = originalInfo;
94
+ console.warn = originalWarn;
95
+ }
96
+ }
97
+
98
+ async function runProjectRootOnboardingImport(options = {}, dependencies = {}) {
99
+ const fileSystem = dependencies.fileSystem || fs;
100
+ const stateManager = dependencies.stateManager || new WorkspaceStateManager(dependencies.workspaceStatePath);
101
+ const root = normalizeString(options.root || options.rootDir);
102
+ if (!root) {
103
+ throw new Error('--root is required');
104
+ }
105
+
106
+ const rootInspection = await inspectProjectCandidate({ root }, {
107
+ ...dependencies,
108
+ fileSystem,
109
+ stateManager
110
+ });
111
+ const steps = [];
112
+
113
+ if (rootInspection.kind === 'invalid') {
114
+ steps.push(buildStep(
115
+ 'register',
116
+ 'failed',
117
+ 'Root directory cannot be onboarded.',
118
+ PROJECT_ONBOARDING_REASON_CODES.BLOCKED_BY_CANDIDATE
119
+ ));
120
+ steps.push(buildStep(
121
+ 'attach',
122
+ 'failed',
123
+ 'Local root is not accessible as a project directory.',
124
+ PROJECT_CANDIDATE_REASON_CODES.ROOT_INACCESSIBLE
125
+ ));
126
+ steps.push(buildStep(
127
+ 'hydrate',
128
+ 'failed',
129
+ 'Onboarding cannot continue until the root is valid.',
130
+ PROJECT_ONBOARDING_REASON_CODES.BLOCKED_BY_CANDIDATE
131
+ ));
132
+ steps.push(buildStep(
133
+ 'activate',
134
+ 'skipped',
135
+ 'Import does not activate invalid roots.',
136
+ PROJECT_ONBOARDING_REASON_CODES.IMPORT_NO_ACTIVATE
137
+ ));
138
+ steps.push(buildStep(
139
+ 'scaffold',
140
+ 'skipped',
141
+ 'Scaffold is blocked until the root becomes valid.',
142
+ PROJECT_ONBOARDING_REASON_CODES.BLOCKED_BY_CANDIDATE
143
+ ));
144
+ return buildFailureEnvelope(
145
+ rootInspection,
146
+ steps,
147
+ 'Root inspection reported an invalid candidate state.',
148
+ PROJECT_ONBOARDING_REASON_CODES.BLOCKED_BY_CANDIDATE
149
+ );
150
+ }
151
+
152
+ if (rootInspection.reasonCodes.includes(PROJECT_CANDIDATE_REASON_CODES.INVALID_PROJECT_METADATA)) {
153
+ steps.push(buildStep(
154
+ 'register',
155
+ 'failed',
156
+ 'Root contains invalid SCE metadata and cannot be imported safely.',
157
+ PROJECT_CANDIDATE_REASON_CODES.INVALID_PROJECT_METADATA
158
+ ));
159
+ steps.push(buildStep(
160
+ 'attach',
161
+ 'done',
162
+ 'Local root is reachable.',
163
+ PROJECT_ONBOARDING_REASON_CODES.ROOT_ACCEPTED
164
+ ));
165
+ steps.push(buildStep(
166
+ 'hydrate',
167
+ 'failed',
168
+ 'Existing project metadata must be repaired before import.',
169
+ PROJECT_CANDIDATE_REASON_CODES.INVALID_PROJECT_METADATA
170
+ ));
171
+ steps.push(buildStep(
172
+ 'activate',
173
+ 'skipped',
174
+ 'Import does not activate blocked projects.',
175
+ PROJECT_ONBOARDING_REASON_CODES.IMPORT_NO_ACTIVATE
176
+ ));
177
+ steps.push(buildStep(
178
+ 'scaffold',
179
+ 'skipped',
180
+ 'Scaffold is blocked by invalid project metadata.',
181
+ PROJECT_CANDIDATE_REASON_CODES.INVALID_PROJECT_METADATA
182
+ ));
183
+ return buildFailureEnvelope(
184
+ rootInspection,
185
+ steps,
186
+ 'Existing project metadata is invalid.',
187
+ PROJECT_CANDIDATE_REASON_CODES.INVALID_PROJECT_METADATA
188
+ );
189
+ }
190
+
191
+ let onboardingPreview = { ...rootInspection };
192
+ let importResult = null;
193
+ let takeoverBaseline = null;
194
+
195
+ if (rootInspection.kind === 'directory-candidate') {
196
+ const orchestrator = dependencies.smartOrchestrator || new SmartOrchestrator();
197
+ importResult = await runWithSuppressedConsole(() => orchestrator.orchestrate(rootInspection.rootDir, {
198
+ dryRun: false,
199
+ verbose: false,
200
+ skipBackup: false,
201
+ skipUpdate: false
202
+ }), options.json === true);
203
+
204
+ if (!importResult.success) {
205
+ steps.push(buildStep(
206
+ 'register',
207
+ 'failed',
208
+ 'Workspace registration was not attempted because adoption failed.',
209
+ PROJECT_ONBOARDING_REASON_CODES.ADOPTION_FAILED
210
+ ));
211
+ steps.push(buildStep(
212
+ 'attach',
213
+ 'done',
214
+ 'Local root is reachable.',
215
+ PROJECT_ONBOARDING_REASON_CODES.ROOT_ACCEPTED
216
+ ));
217
+ steps.push(buildStep(
218
+ 'hydrate',
219
+ 'failed',
220
+ (importResult.errors || []).join('; ') || 'Adoption failed.',
221
+ PROJECT_ONBOARDING_REASON_CODES.ADOPTION_FAILED
222
+ ));
223
+ steps.push(buildStep(
224
+ 'activate',
225
+ 'skipped',
226
+ 'Import does not activate failed onboarding results.',
227
+ PROJECT_ONBOARDING_REASON_CODES.IMPORT_NO_ACTIVATE
228
+ ));
229
+ steps.push(buildStep(
230
+ 'scaffold',
231
+ 'failed',
232
+ 'SCE baseline could not be applied to the root directory.',
233
+ PROJECT_ONBOARDING_REASON_CODES.ADOPTION_FAILED
234
+ ));
235
+ return buildFailureEnvelope(
236
+ rootInspection,
237
+ steps,
238
+ (importResult.errors || []).join('; ') || 'Adoption failed.',
239
+ PROJECT_ONBOARDING_REASON_CODES.ADOPTION_FAILED
240
+ );
241
+ }
242
+
243
+ const packageJson = require('../../package.json');
244
+ takeoverBaseline = await applyTakeoverBaseline(rootInspection.rootDir, {
245
+ apply: true,
246
+ writeReport: true,
247
+ sceVersion: packageJson.version
248
+ });
249
+ onboardingPreview = await inspectProjectCandidate({ root: rootInspection.rootDir }, {
250
+ ...dependencies,
251
+ fileSystem,
252
+ stateManager
253
+ });
254
+ }
255
+
256
+ let workspaceId = rootInspection.workspaceId || null;
257
+ if (!workspaceId) {
258
+ workspaceId = await allocateWorkspaceId(rootInspection.rootDir, stateManager);
259
+ await stateManager.createWorkspace(workspaceId, rootInspection.rootDir);
260
+ onboardingPreview = {
261
+ ...onboardingPreview,
262
+ kind: 'workspace-backed',
263
+ projectId: buildWorkspaceProjectId(workspaceId),
264
+ workspaceId,
265
+ readiness: onboardingPreview.kind === 'directory-candidate' ? 'ready' : onboardingPreview.readiness,
266
+ availability: 'accessible',
267
+ localCandidate: false,
268
+ reasonCodes: Array.from(new Set([
269
+ PROJECT_CANDIDATE_REASON_CODES.WORKSPACE_REGISTERED,
270
+ ...onboardingPreview.reasonCodes
271
+ ]))
272
+ };
273
+ }
274
+
275
+ steps.push(buildStep(
276
+ 'register',
277
+ 'done',
278
+ rootInspection.workspaceId
279
+ ? 'Workspace was already registered.'
280
+ : `Workspace registered as ${workspaceId}.`,
281
+ rootInspection.workspaceId
282
+ ? PROJECT_CANDIDATE_REASON_CODES.WORKSPACE_REGISTERED
283
+ : PROJECT_ONBOARDING_REASON_CODES.REGISTERED
284
+ ));
285
+ steps.push(buildStep(
286
+ 'attach',
287
+ 'done',
288
+ 'Local root is accepted as the canonical onboarding source.',
289
+ PROJECT_ONBOARDING_REASON_CODES.ROOT_ACCEPTED
290
+ ));
291
+ steps.push(buildStep(
292
+ 'hydrate',
293
+ 'done',
294
+ rootInspection.kind === 'directory-candidate'
295
+ ? 'SCE baseline and project hydration were applied to the root directory.'
296
+ : 'Existing SCE project root is ready for portfolio import.',
297
+ rootInspection.kind === 'directory-candidate'
298
+ ? PROJECT_ONBOARDING_REASON_CODES.ADOPTED
299
+ : PROJECT_CANDIDATE_REASON_CODES.SCE_PRESENT
300
+ ));
301
+ steps.push(buildStep(
302
+ 'activate',
303
+ 'skipped',
304
+ 'Import keeps active workspace selection unchanged in phase-1.',
305
+ PROJECT_ONBOARDING_REASON_CODES.IMPORT_NO_ACTIVATE
306
+ ));
307
+ steps.push(buildStep(
308
+ 'scaffold',
309
+ rootInspection.kind === 'directory-candidate' ? 'done' : 'skipped',
310
+ rootInspection.kind === 'directory-candidate'
311
+ ? 'Applied SCE baseline files to the project root.'
312
+ : 'Existing SCE baseline is reused; no scaffold rewrite was required.',
313
+ rootInspection.kind === 'directory-candidate'
314
+ ? PROJECT_ONBOARDING_REASON_CODES.ADOPTED
315
+ : PROJECT_ONBOARDING_REASON_CODES.SCAFFOLD_REUSED
316
+ ));
317
+
318
+ return {
319
+ mode: 'import',
320
+ generated_at: new Date().toISOString(),
321
+ success: true,
322
+ preview: onboardingPreview,
323
+ summary: onboardingPreview,
324
+ steps,
325
+ result: {
326
+ rootDir: onboardingPreview.rootDir,
327
+ projectId: onboardingPreview.projectId || null,
328
+ workspaceId: onboardingPreview.workspaceId || null,
329
+ ...(importResult ? {
330
+ adoption: {
331
+ mode: importResult.mode || null,
332
+ backupId: importResult.backup ? importResult.backup.id : null,
333
+ changes: importResult.changes || { created: [], updated: [], deleted: [], preserved: [] },
334
+ warnings: importResult.warnings || []
335
+ }
336
+ } : {}),
337
+ ...(takeoverBaseline ? {
338
+ takeoverBaseline: {
339
+ reportFile: takeoverBaseline.report_file || null,
340
+ summary: takeoverBaseline.summary || {}
341
+ }
342
+ } : {})
343
+ }
344
+ };
345
+ }
346
+
347
+ module.exports = {
348
+ PROJECT_ONBOARDING_REASON_CODES,
349
+ runProjectRootOnboardingImport
350
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scene-capability-engine",
3
- "version": "3.6.60",
3
+ "version": "3.6.62",
4
4
  "description": "SCE (Scene Capability Engine) - A CLI tool and npm package for spec-driven development with AI coding assistants.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -8,19 +8,21 @@ const REQUIRED_CHECKS = [
8
8
  {
9
9
  path: 'README.md',
10
10
  requiredSnippets: [
11
- '`sce project portfolio show|target resolve|supervision show`'
11
+ '`sce project portfolio show|candidate inspect|onboarding import|target resolve|supervision show`'
12
12
  ]
13
13
  },
14
14
  {
15
15
  path: 'README.zh.md',
16
16
  requiredSnippets: [
17
- '`sce project portfolio show|target resolve|supervision show`'
17
+ '`sce project portfolio show|candidate inspect|onboarding import|target resolve|supervision show`'
18
18
  ]
19
19
  },
20
20
  {
21
21
  path: 'docs/command-reference.md',
22
22
  requiredSnippets: [
23
23
  'sce project portfolio show [options]',
24
+ 'sce project candidate inspect --root <path> [options]',
25
+ 'sce project onboarding import --root <path> [options]',
24
26
  'sce project target resolve [options]',
25
27
  'sce project supervision show --project <id> [options]'
26
28
  ]
@@ -45,6 +47,8 @@ const REQUIRED_CHECKS = [
45
47
  path: 'docs/magicball-project-portfolio-contract.md',
46
48
  requiredSnippets: [
47
49
  'sce project portfolio show --json',
50
+ 'sce project candidate inspect --root <path> --json',
51
+ 'sce project onboarding import --root <path> --json',
48
52
  'sce project target resolve --json',
49
53
  'sce project supervision show --project <project-id> --json'
50
54
  ]
@@ -53,7 +57,11 @@ const REQUIRED_CHECKS = [
53
57
  path: 'docs/magicball-frontend-state-and-command-mapping.md',
54
58
  requiredSnippets: [
55
59
  'projectPortfolio: Record<string, unknown> | null',
60
+ 'projectCandidate: Record<string, unknown> | null',
61
+ 'projectOnboarding: Record<string, unknown> | null',
56
62
  '`sce project portfolio show --json`',
63
+ '`sce project candidate inspect --root <path> --json`',
64
+ '`sce project onboarding import --root <path> --json`',
57
65
  '`sce project target resolve --request <text> --current-project <project-id> --json`',
58
66
  '`sce project supervision show --project <project-id> --json`'
59
67
  ]
@@ -62,6 +70,8 @@ const REQUIRED_CHECKS = [
62
70
  path: 'docs/magicball-cli-invocation-examples.md',
63
71
  requiredSnippets: [
64
72
  'sce project portfolio show --json',
73
+ 'sce project candidate inspect --root "C:/workspace/customer-order-demo" --json',
74
+ 'sce project onboarding import --root "C:/workspace/customer-order-demo" --json',
65
75
  'sce project target resolve --request "continue customer-order-demo" --json',
66
76
  'sce project supervision show --project workspace:customer-order-demo --json'
67
77
  ]
@@ -71,6 +81,8 @@ const REQUIRED_CHECKS = [
71
81
  requiredSnippets: [
72
82
  '## Phase 0: Multi-project Workspace Shell',
73
83
  'sce project portfolio show --json',
84
+ 'sce project candidate inspect --root "<path>" --json',
85
+ 'sce project onboarding import --root "<path>" --json',
74
86
  'sce project target resolve --request "<text>" --current-project <project-id> --json',
75
87
  'sce project supervision show --project <project-id> --json'
76
88
  ]
@@ -80,14 +92,15 @@ const REQUIRED_CHECKS = [
80
92
  requiredSnippets: [
81
93
  '- `docs/magicball-project-portfolio-contract.md`',
82
94
  '- project switcher from `project portfolio show`',
95
+ '- local-root candidate receipt from `project candidate inspect`',
83
96
  '- project health summary from `project supervision show`'
84
97
  ]
85
98
  },
86
99
  {
87
100
  path: 'docs/magicball-integration-issue-tracker.md',
88
101
  requiredSnippets: [
89
- '- `project portfolio show/target resolve/supervision show`',
90
- '5. treat `project portfolio / target resolve / supervision` as the default multi-project shell truth'
102
+ '- `project portfolio show/candidate inspect/onboarding import/target resolve/supervision show`',
103
+ '5. treat `project portfolio / candidate inspect / onboarding import / target resolve / supervision` as the default multi-project shell truth'
91
104
  ]
92
105
  },
93
106
  {
@@ -243,6 +243,6 @@ A Spec is a complete feature definition with three parts:
243
243
  ---
244
244
 
245
245
  **Project Type**: Spec-driven development
246
- **sce Version**: 3.6.60
247
- **Last Updated**: 2026-03-19
246
+ **sce Version**: 3.6.62
247
+ **Last Updated**: 2026-03-21
248
248
  **Purpose**: Guide AI tools to work effectively with this project