scene-capability-engine 3.6.53 → 3.6.55

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 (33) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +6 -1
  3. package/README.zh.md +6 -1
  4. package/bin/scene-capability-engine.js +2 -0
  5. package/docs/command-reference.md +29 -1
  6. package/docs/magicball-app-collection-phase-1.md +133 -0
  7. package/docs/magicball-cli-invocation-examples.md +40 -0
  8. package/docs/magicball-integration-doc-index.md +14 -6
  9. package/docs/magicball-integration-issue-tracker.md +42 -3
  10. package/docs/magicball-sce-adaptation-guide.md +36 -9
  11. package/docs/releases/README.md +2 -0
  12. package/docs/releases/v3.6.54.md +19 -0
  13. package/docs/releases/v3.6.55.md +18 -0
  14. package/docs/zh/releases/README.md +2 -0
  15. package/docs/zh/releases/v3.6.54.md +19 -0
  16. package/docs/zh/releases/v3.6.55.md +18 -0
  17. package/lib/app/collection-store.js +127 -0
  18. package/lib/app/install-apply-runner.js +192 -0
  19. package/lib/app/install-plan-service.js +410 -0
  20. package/lib/app/scene-workspace-store.js +132 -0
  21. package/lib/commands/app.js +281 -0
  22. package/lib/commands/device.js +194 -0
  23. package/lib/commands/scene.js +228 -0
  24. package/lib/device/current-device.js +158 -0
  25. package/lib/device/device-override-store.js +157 -0
  26. package/lib/problem/project-problem-projection.js +239 -0
  27. package/lib/workspace/collab-governance-audit.js +107 -0
  28. package/lib/workspace/collab-governance-gate.js +24 -4
  29. package/lib/workspace/takeover-baseline.js +76 -0
  30. package/package.json +1 -1
  31. package/template/.sce/README.md +1 -1
  32. package/template/.sce/config/problem-closure-policy.json +5 -0
  33. package/template/.sce/knowledge/problem/project-shared-problems.json +16 -0
@@ -0,0 +1,127 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+
4
+ function normalizeString(value) {
5
+ if (typeof value !== 'string') {
6
+ return '';
7
+ }
8
+ return value.trim();
9
+ }
10
+
11
+ function normalizeStringArray(value) {
12
+ if (!Array.isArray(value)) {
13
+ return [];
14
+ }
15
+ const seen = new Set();
16
+ const items = [];
17
+ for (const entry of value) {
18
+ const normalized = normalizeString(entry);
19
+ if (!normalized || seen.has(normalized)) {
20
+ continue;
21
+ }
22
+ seen.add(normalized);
23
+ items.push(normalized);
24
+ }
25
+ return items;
26
+ }
27
+
28
+ function normalizeBoolean(value, fallback = false) {
29
+ if (typeof value === 'boolean') {
30
+ return value;
31
+ }
32
+ return fallback;
33
+ }
34
+
35
+ function normalizeCollectionItem(item = {}) {
36
+ return {
37
+ app_id: normalizeString(item.app_id || item.appId) || null,
38
+ app_key: normalizeString(item.app_key || item.appKey) || null,
39
+ required: normalizeBoolean(item.required, false),
40
+ allow_local_remove: normalizeBoolean(item.allow_local_remove ?? item.allowLocalRemove, true),
41
+ priority: Number.isFinite(Number(item.priority)) ? Number(item.priority) : null,
42
+ default_entry: normalizeString(item.default_entry || item.defaultEntry) || null,
43
+ capability_tags: normalizeStringArray(item.capability_tags || item.capabilityTags),
44
+ metadata: item.metadata && typeof item.metadata === 'object' ? item.metadata : {}
45
+ };
46
+ }
47
+
48
+ function normalizeAppCollection(raw = {}, filePath = '') {
49
+ const fileName = normalizeString(path.basename(filePath, path.extname(filePath)));
50
+ const collectionId = normalizeString(raw.collection_id || raw.collectionId || raw.id) || fileName;
51
+ if (!collectionId) {
52
+ throw new Error(`invalid app collection file: missing collection_id (${filePath})`);
53
+ }
54
+ const items = Array.isArray(raw.items)
55
+ ? raw.items
56
+ .filter((item) => item && typeof item === 'object')
57
+ .map((item) => normalizeCollectionItem(item))
58
+ .filter((item) => item.app_id || item.app_key)
59
+ : [];
60
+
61
+ return {
62
+ collection_id: collectionId,
63
+ name: normalizeString(raw.name) || collectionId,
64
+ description: normalizeString(raw.description) || null,
65
+ status: normalizeString(raw.status) || 'active',
66
+ tags: normalizeStringArray(raw.tags),
67
+ item_count: items.length,
68
+ items,
69
+ metadata: raw.metadata && typeof raw.metadata === 'object' ? raw.metadata : {},
70
+ source_file: filePath
71
+ };
72
+ }
73
+
74
+ async function listAppCollections(projectPath = process.cwd(), options = {}) {
75
+ const fileSystem = options.fileSystem || fs;
76
+ const collectionsDir = path.join(projectPath, '.sce', 'app', 'collections');
77
+ if (!await fileSystem.pathExists(collectionsDir)) {
78
+ return [];
79
+ }
80
+
81
+ const entries = await fileSystem.readdir(collectionsDir);
82
+ const query = normalizeString(options.query).toLowerCase();
83
+ const status = normalizeString(options.status);
84
+ const normalized = [];
85
+
86
+ for (const entry of entries) {
87
+ if (!entry.toLowerCase().endsWith('.json')) {
88
+ continue;
89
+ }
90
+ const absolutePath = path.join(collectionsDir, entry);
91
+ const raw = await fileSystem.readJson(absolutePath);
92
+ const collection = normalizeAppCollection(raw, absolutePath);
93
+ if (status && collection.status !== status) {
94
+ continue;
95
+ }
96
+ if (query) {
97
+ const haystack = [
98
+ collection.collection_id,
99
+ collection.name,
100
+ collection.description,
101
+ ...collection.tags
102
+ ].join(' ').toLowerCase();
103
+ if (!haystack.includes(query)) {
104
+ continue;
105
+ }
106
+ }
107
+ normalized.push(collection);
108
+ }
109
+
110
+ normalized.sort((left, right) => left.collection_id.localeCompare(right.collection_id));
111
+ return normalized;
112
+ }
113
+
114
+ async function getAppCollection(projectPath = process.cwd(), collectionRef = '', options = {}) {
115
+ const normalizedRef = normalizeString(collectionRef);
116
+ if (!normalizedRef) {
117
+ return null;
118
+ }
119
+ const collections = await listAppCollections(projectPath, options);
120
+ return collections.find((item) => item.collection_id === normalizedRef || path.basename(item.source_file, '.json') === normalizedRef) || null;
121
+ }
122
+
123
+ module.exports = {
124
+ normalizeAppCollection,
125
+ listAppCollections,
126
+ getAppCollection
127
+ };
@@ -0,0 +1,192 @@
1
+ function normalizeString(value) {
2
+ if (typeof value !== 'string') {
3
+ return '';
4
+ }
5
+ return value.trim();
6
+ }
7
+
8
+ function resolveAppRef(action = {}) {
9
+ return normalizeString(action.app_key) || normalizeString(action.app_id);
10
+ }
11
+
12
+ function getBlockingReasons(plan = {}) {
13
+ const reasons = [];
14
+ const unresolvedCollections = Array.isArray(plan.unresolved_collections) ? plan.unresolved_collections : [];
15
+ if (unresolvedCollections.length > 0) {
16
+ reasons.push('unresolved-collections');
17
+ }
18
+ const skipActions = Array.isArray(plan.actions)
19
+ ? plan.actions.filter((item) => normalizeString(item.decision) === 'skip')
20
+ : [];
21
+ if (skipActions.some((item) => normalizeString(item.reason) === 'app-bundle-not-found')) {
22
+ reasons.push('unresolved-app-bundles');
23
+ }
24
+ if (skipActions.some((item) => normalizeString(item.reason) === 'active-release-protected')) {
25
+ reasons.push('active-release-protected');
26
+ }
27
+ return reasons;
28
+ }
29
+
30
+ async function verifyInstallCandidates(plan = {}, store) {
31
+ const failures = [];
32
+ const actions = Array.isArray(plan.actions) ? plan.actions : [];
33
+ for (const action of actions) {
34
+ if (normalizeString(action.decision) !== 'install') {
35
+ continue;
36
+ }
37
+ const appRef = resolveAppRef(action);
38
+ const graph = appRef ? await store.getAppBundleGraph(appRef) : null;
39
+ const metadata = graph && graph.bundle && graph.bundle.metadata && typeof graph.bundle.metadata === 'object'
40
+ ? graph.bundle.metadata
41
+ : {};
42
+ const serviceCatalog = metadata.service_catalog && typeof metadata.service_catalog === 'object'
43
+ ? metadata.service_catalog
44
+ : {};
45
+ const releases = Array.isArray(serviceCatalog.releases) ? serviceCatalog.releases : [];
46
+ if (!graph || releases.length === 0) {
47
+ failures.push({
48
+ app_ref: appRef,
49
+ reason: 'no-installable-runtime-release'
50
+ });
51
+ }
52
+ }
53
+ return failures;
54
+ }
55
+
56
+ async function verifyActivateCandidates(plan = {}, store) {
57
+ const failures = [];
58
+ const actions = Array.isArray(plan.actions) ? plan.actions : [];
59
+ for (const action of actions) {
60
+ if (normalizeString(action.decision) !== 'activate') {
61
+ continue;
62
+ }
63
+ const appRef = resolveAppRef(action);
64
+ const graph = appRef ? await store.getAppBundleGraph(appRef) : null;
65
+ const metadata = graph && graph.bundle && graph.bundle.metadata && typeof graph.bundle.metadata === 'object'
66
+ ? graph.bundle.metadata
67
+ : {};
68
+ const serviceCatalog = metadata.service_catalog && typeof metadata.service_catalog === 'object'
69
+ ? metadata.service_catalog
70
+ : {};
71
+ const releases = Array.isArray(serviceCatalog.releases) ? serviceCatalog.releases : [];
72
+ const installedReleaseId = normalizeString(action.installed_release_id);
73
+ if (!graph || !installedReleaseId || !releases.some((item) => normalizeString(item && item.release_id) === installedReleaseId)) {
74
+ failures.push({
75
+ app_ref: appRef,
76
+ reason: 'no-activatable-installed-release'
77
+ });
78
+ }
79
+ }
80
+ return failures;
81
+ }
82
+
83
+ function sortExecutableActions(actions = []) {
84
+ const install = [];
85
+ const activate = [];
86
+ const uninstall = [];
87
+ for (const action of actions) {
88
+ const decision = normalizeString(action.decision);
89
+ if (decision === 'install') {
90
+ install.push(action);
91
+ } else if (decision === 'activate') {
92
+ activate.push(action);
93
+ } else if (decision === 'uninstall') {
94
+ uninstall.push(action);
95
+ }
96
+ }
97
+ return [...install, ...activate, ...uninstall];
98
+ }
99
+
100
+ async function executeInstallPlan(plan = {}, options = {}) {
101
+ const store = options.store;
102
+ const executeInstall = options.executeInstall;
103
+ const executeActivate = options.executeActivate;
104
+ const executeUninstall = options.executeUninstall;
105
+ const dependencies = options.dependencies || {};
106
+ const commandOptions = options.commandOptions || {};
107
+
108
+ const blockingReasons = getBlockingReasons(plan);
109
+ if (blockingReasons.length > 0) {
110
+ return {
111
+ execute_supported: true,
112
+ executed: false,
113
+ blocked_reason: blockingReasons.join(','),
114
+ results: []
115
+ };
116
+ }
117
+
118
+ const installPreflightFailures = await verifyInstallCandidates(plan, store);
119
+ const activatePreflightFailures = await verifyActivateCandidates(plan, store);
120
+ const preflightFailures = [...installPreflightFailures, ...activatePreflightFailures];
121
+ if (preflightFailures.length > 0) {
122
+ return {
123
+ execute_supported: true,
124
+ executed: false,
125
+ blocked_reason: 'install-preflight-failed',
126
+ preflight_failures: preflightFailures,
127
+ results: []
128
+ };
129
+ }
130
+
131
+ const results = [];
132
+ const actions = sortExecutableActions(Array.isArray(plan.actions) ? plan.actions : []);
133
+ for (const action of actions) {
134
+ const decision = normalizeString(action.decision);
135
+ const appRef = resolveAppRef(action);
136
+ if (!appRef) {
137
+ return {
138
+ execute_supported: true,
139
+ executed: false,
140
+ blocked_reason: 'invalid-plan-action',
141
+ results
142
+ };
143
+ }
144
+ const nestedOptions = {
145
+ app: appRef,
146
+ authLease: commandOptions.authLease,
147
+ authPassword: commandOptions.authPassword,
148
+ actor: commandOptions.actor,
149
+ json: true,
150
+ silent: true
151
+ };
152
+ if (decision === 'install') {
153
+ const payload = await executeInstall(nestedOptions, dependencies);
154
+ results.push({
155
+ app_ref: appRef,
156
+ decision,
157
+ success: true,
158
+ summary: payload.summary || null
159
+ });
160
+ } else if (decision === 'activate') {
161
+ const payload = await executeActivate({
162
+ ...nestedOptions,
163
+ release: normalizeString(action.installed_release_id)
164
+ }, dependencies);
165
+ results.push({
166
+ app_ref: appRef,
167
+ decision,
168
+ success: true,
169
+ summary: payload.summary || null
170
+ });
171
+ } else if (decision === 'uninstall') {
172
+ const payload = await executeUninstall(nestedOptions, dependencies);
173
+ results.push({
174
+ app_ref: appRef,
175
+ decision,
176
+ success: true,
177
+ summary: payload.summary || null
178
+ });
179
+ }
180
+ }
181
+
182
+ return {
183
+ execute_supported: true,
184
+ executed: true,
185
+ blocked_reason: null,
186
+ results
187
+ };
188
+ }
189
+
190
+ module.exports = {
191
+ executeInstallPlan
192
+ };