scene-capability-engine 3.6.38 → 3.6.44

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 (67) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/bin/scene-capability-engine.js +42 -2
  3. package/docs/command-reference.md +27 -0
  4. package/docs/developer-guide.md +1 -1
  5. package/docs/document-governance.md +22 -2
  6. package/docs/releases/README.md +6 -0
  7. package/docs/releases/v3.6.39.md +24 -0
  8. package/docs/releases/v3.6.40.md +19 -0
  9. package/docs/releases/v3.6.41.md +20 -0
  10. package/docs/releases/v3.6.42.md +19 -0
  11. package/docs/releases/v3.6.43.md +17 -0
  12. package/docs/releases/v3.6.44.md +17 -0
  13. package/docs/spec-collaboration-guide.md +1 -1
  14. package/docs/state-migration-reconciliation-runbook.md +76 -0
  15. package/docs/state-storage-tiering.md +104 -0
  16. package/docs/zh/releases/README.md +6 -0
  17. package/docs/zh/releases/v3.6.39.md +24 -0
  18. package/docs/zh/releases/v3.6.40.md +19 -0
  19. package/docs/zh/releases/v3.6.41.md +20 -0
  20. package/docs/zh/releases/v3.6.42.md +19 -0
  21. package/docs/zh/releases/v3.6.43.md +17 -0
  22. package/docs/zh/releases/v3.6.44.md +17 -0
  23. package/lib/adoption/adoption-logger.js +1 -1
  24. package/lib/adoption/adoption-strategy.js +29 -29
  25. package/lib/adoption/detection-engine.js +16 -13
  26. package/lib/adoption/smart-orchestrator.js +3 -3
  27. package/lib/adoption/strategy-selector.js +19 -15
  28. package/lib/adoption/template-sync.js +3 -3
  29. package/lib/auto/autonomous-engine.js +5 -5
  30. package/lib/auto/handoff-release-gate-history-loaders-service.js +24 -4
  31. package/lib/auto/handoff-run-service.js +37 -0
  32. package/lib/backup/backup-system.js +10 -10
  33. package/lib/collab/collab-manager.js +8 -5
  34. package/lib/collab/dependency-manager.js +1 -1
  35. package/lib/commands/adopt.js +2 -2
  36. package/lib/commands/auto.js +239 -97
  37. package/lib/commands/collab.js +10 -4
  38. package/lib/commands/docs.js +8 -2
  39. package/lib/commands/scene.js +78 -18
  40. package/lib/commands/status.js +3 -3
  41. package/lib/commands/studio.js +8 -0
  42. package/lib/commands/watch.js +10 -1
  43. package/lib/governance/config-manager.js +16 -0
  44. package/lib/governance/diagnostic-engine.js +2 -1
  45. package/lib/governance/validation-engine.js +3 -2
  46. package/lib/repo/config-manager.js +2 -2
  47. package/lib/runtime/session-store.js +8 -0
  48. package/lib/spec/bootstrap/context-collector.js +5 -4
  49. package/lib/spec-gate/rules/default-rules.js +8 -8
  50. package/lib/state/sce-state-store.js +265 -0
  51. package/lib/state/state-migration-manager.js +27 -2
  52. package/lib/state/state-storage-policy.js +179 -0
  53. package/lib/upgrade/migration-engine.js +5 -5
  54. package/lib/upgrade/migrations/1.0.0-to-1.1.0.js +3 -3
  55. package/lib/utils/tool-detector.js +4 -4
  56. package/lib/utils/validation.js +6 -6
  57. package/lib/watch/action-executor.js +10 -1
  58. package/lib/watch/event-debouncer.js +3 -0
  59. package/lib/watch/file-watcher.js +51 -10
  60. package/lib/watch/watch-manager.js +10 -1
  61. package/lib/workspace/multi/workspace-context-resolver.js +3 -3
  62. package/lib/workspace/multi/workspace-registry.js +3 -3
  63. package/lib/workspace/multi/workspace-state-manager.js +3 -3
  64. package/lib/workspace/spec-delivery-audit.js +553 -0
  65. package/lib/workspace/takeover-baseline.js +11 -0
  66. package/package.json +5 -1
  67. package/template/.sce/config/state-storage-policy.json +165 -0
@@ -0,0 +1,553 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fs = require('fs-extra');
5
+ const { minimatch } = require('minimatch');
6
+ const {
7
+ runGit,
8
+ parseRemotes,
9
+ parseAheadBehind
10
+ } = require('../../scripts/git-managed-gate');
11
+
12
+ const DELIVERY_MANIFEST_FILE = 'deliverables.json';
13
+ const DELIVERY_VERIFICATION_MODES = new Set(['blocking', 'advisory']);
14
+
15
+ function shouldAllowDetachedHeadSync(options = {}) {
16
+ if (typeof options.allowDetachedHead === 'boolean') {
17
+ return options.allowDetachedHead;
18
+ }
19
+
20
+ const githubActions = `${process.env.GITHUB_ACTIONS || ''}`.trim().toLowerCase() === 'true';
21
+ const githubRefType = `${process.env.GITHUB_REF_TYPE || ''}`.trim().toLowerCase();
22
+ const githubRef = `${process.env.GITHUB_REF || ''}`.trim();
23
+ return githubActions && (
24
+ githubRefType === 'tag' ||
25
+ githubRef.startsWith('refs/tags/')
26
+ );
27
+ }
28
+
29
+ function normalizeRelativePath(projectRoot, candidate) {
30
+ if (typeof candidate !== 'string') {
31
+ return null;
32
+ }
33
+
34
+ const trimmed = candidate.trim();
35
+ if (!trimmed) {
36
+ return null;
37
+ }
38
+
39
+ const absolutePath = path.isAbsolute(trimmed)
40
+ ? trimmed
41
+ : path.join(projectRoot, trimmed);
42
+ const relativePath = path.relative(projectRoot, absolutePath);
43
+ const normalized = `${relativePath}`.replace(/\\/g, '/');
44
+
45
+ if (!normalized || normalized.startsWith('..')) {
46
+ return null;
47
+ }
48
+
49
+ return normalized;
50
+ }
51
+
52
+ function normalizePathList(projectRoot, values) {
53
+ if (!Array.isArray(values)) {
54
+ return [];
55
+ }
56
+
57
+ const seen = new Set();
58
+ const results = [];
59
+ for (const value of values) {
60
+ const normalized = normalizeRelativePath(projectRoot, value);
61
+ if (!normalized || seen.has(normalized)) {
62
+ continue;
63
+ }
64
+ seen.add(normalized);
65
+ results.push(normalized);
66
+ }
67
+ return results;
68
+ }
69
+
70
+ function normalizePatternList(values) {
71
+ if (!Array.isArray(values)) {
72
+ return [];
73
+ }
74
+
75
+ const seen = new Set();
76
+ const results = [];
77
+ for (const value of values) {
78
+ const normalized = typeof value === 'string' ? value.trim() : '';
79
+ if (!normalized || seen.has(normalized)) {
80
+ continue;
81
+ }
82
+ seen.add(normalized);
83
+ results.push(normalized);
84
+ }
85
+ return results;
86
+ }
87
+
88
+ function parseManifest(projectRoot, specName, payload) {
89
+ const manifest = payload && typeof payload === 'object' && !Array.isArray(payload)
90
+ ? payload
91
+ : {};
92
+ const verificationMode = DELIVERY_VERIFICATION_MODES.has(manifest.verification_mode)
93
+ ? manifest.verification_mode
94
+ : 'blocking';
95
+ return {
96
+ spec: specName,
97
+ verification_mode: verificationMode,
98
+ declared_files: normalizePathList(projectRoot, manifest.declared_files),
99
+ optional_files: normalizePathList(projectRoot, manifest.optional_files),
100
+ ignored_patterns: normalizePatternList(manifest.ignored_patterns)
101
+ };
102
+ }
103
+
104
+ function parseStatusEntries(raw = '') {
105
+ const entries = new Map();
106
+ const lines = `${raw || ''}`.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
107
+ for (const line of lines) {
108
+ if (line.length < 3) {
109
+ continue;
110
+ }
111
+ const xy = line.slice(0, 2);
112
+ const payload = line.slice(3).trim();
113
+ const targetPath = payload.includes(' -> ') ? payload.split(' -> ').pop().trim() : payload;
114
+ const normalizedPath = `${targetPath}`.replace(/\\/g, '/');
115
+ entries.set(normalizedPath, {
116
+ raw: line,
117
+ x: xy[0],
118
+ y: xy[1]
119
+ });
120
+ }
121
+ return entries;
122
+ }
123
+
124
+ function resolveWorktreeStatus(statusEntry) {
125
+ if (!statusEntry) {
126
+ return 'unmodified';
127
+ }
128
+ if (statusEntry.x === '?' && statusEntry.y === '?') {
129
+ return 'untracked';
130
+ }
131
+
132
+ const statusToken = [statusEntry.x, statusEntry.y].find((value) => value && value !== ' ');
133
+ switch (statusToken) {
134
+ case 'M':
135
+ return 'modified';
136
+ case 'A':
137
+ return 'added';
138
+ case 'D':
139
+ return 'deleted';
140
+ case 'R':
141
+ return 'renamed';
142
+ case 'C':
143
+ return 'copied';
144
+ case 'U':
145
+ return 'unmerged';
146
+ default:
147
+ return 'unmodified';
148
+ }
149
+ }
150
+
151
+ function shouldIgnorePath(filePath, ignoredPatterns = []) {
152
+ return ignoredPatterns.some((pattern) => minimatch(filePath, pattern, { dot: true }));
153
+ }
154
+
155
+ async function discoverDeliveryManifests(projectRoot, options = {}, dependencies = {}) {
156
+ const fileSystem = dependencies.fileSystem || fs;
157
+ const specRoot = path.join(projectRoot, '.sce', 'specs');
158
+ const specFilter = typeof options.spec === 'string' && options.spec.trim().length > 0
159
+ ? options.spec.trim()
160
+ : null;
161
+
162
+ const exists = await fileSystem.pathExists(specRoot);
163
+ if (!exists) {
164
+ return [];
165
+ }
166
+
167
+ const entries = await fileSystem.readdir(specRoot, { withFileTypes: true });
168
+ const manifests = [];
169
+ for (const entry of entries) {
170
+ if (!entry || !entry.isDirectory()) {
171
+ continue;
172
+ }
173
+ if (specFilter && entry.name !== specFilter) {
174
+ continue;
175
+ }
176
+ const manifestPath = path.join(specRoot, entry.name, DELIVERY_MANIFEST_FILE);
177
+ if (!await fileSystem.pathExists(manifestPath)) {
178
+ continue;
179
+ }
180
+ manifests.push({
181
+ spec: entry.name,
182
+ manifest_path: manifestPath
183
+ });
184
+ }
185
+ return manifests.sort((left, right) => left.spec.localeCompare(right.spec));
186
+ }
187
+
188
+ function loadGitSnapshot(projectRoot, options = {}, dependencies = {}) {
189
+ const runGitCommand = dependencies.runGit || runGit;
190
+ const allowNoRemote = options.allowNoRemote !== false;
191
+ const allowDetachedHead = shouldAllowDetachedHeadSync(options);
192
+ const targetHosts = Array.isArray(options.targetHosts) && options.targetHosts.length > 0
193
+ ? options.targetHosts
194
+ : ['github.com', 'gitlab.com'];
195
+
196
+ const inside = runGitCommand(projectRoot, ['rev-parse', '--is-inside-work-tree']);
197
+ if (inside.status !== 0 || `${inside.stdout || ''}`.trim().toLowerCase() !== 'true') {
198
+ return {
199
+ available: false,
200
+ passed: false,
201
+ reason: 'not-a-git-repository',
202
+ target_hosts: targetHosts,
203
+ tracked_files: new Set(),
204
+ status_entries: new Map(),
205
+ warnings: [],
206
+ violations: ['current directory is not a git repository'],
207
+ branch: null,
208
+ upstream: null,
209
+ ahead: null,
210
+ behind: null,
211
+ has_target_remote: false,
212
+ clean_worktree: null,
213
+ worktree_changes: {
214
+ tracked_count: 0,
215
+ untracked_count: 0
216
+ }
217
+ };
218
+ }
219
+
220
+ const warnings = [];
221
+ const violations = [];
222
+ const trackedFilesResult = runGitCommand(projectRoot, ['ls-files']);
223
+ const trackedFiles = trackedFilesResult.status === 0
224
+ ? new Set(
225
+ `${trackedFilesResult.stdout || ''}`
226
+ .split(/\r?\n/)
227
+ .map((item) => item.trim())
228
+ .filter(Boolean)
229
+ .map((item) => item.replace(/\\/g, '/'))
230
+ )
231
+ : new Set();
232
+ if (trackedFilesResult.status !== 0) {
233
+ violations.push(`failed to list tracked files: ${trackedFilesResult.stderr || 'unknown error'}`);
234
+ }
235
+
236
+ const statusResult = runGitCommand(projectRoot, ['status', '--porcelain']);
237
+ const statusEntries = statusResult.status === 0
238
+ ? parseStatusEntries(statusResult.stdout)
239
+ : new Map();
240
+
241
+ let trackedChanges = 0;
242
+ let untrackedChanges = 0;
243
+ for (const entry of statusEntries.values()) {
244
+ if (entry.x === '?' && entry.y === '?') {
245
+ untrackedChanges += 1;
246
+ } else {
247
+ trackedChanges += 1;
248
+ }
249
+ }
250
+
251
+ const remotesResult = runGitCommand(projectRoot, ['remote', '-v']);
252
+ const remoteInfo = remotesResult.status === 0
253
+ ? parseRemotes(remotesResult.stdout, targetHosts)
254
+ : { allRemotes: [], targetRemotes: [] };
255
+ const hasTargetRemote = remoteInfo.targetRemotes.length > 0;
256
+ if (remotesResult.status !== 0) {
257
+ warnings.push(`failed to read git remotes: ${remotesResult.stderr || 'unknown error'}`);
258
+ } else if (!hasTargetRemote) {
259
+ if (allowNoRemote) {
260
+ warnings.push('no GitHub/GitLab remote configured; sync proof is advisory only');
261
+ } else {
262
+ violations.push('no GitHub/GitLab remote configured');
263
+ }
264
+ }
265
+
266
+ const branchResult = runGitCommand(projectRoot, ['rev-parse', '--abbrev-ref', 'HEAD']);
267
+ const branch = branchResult.status === 0 ? `${branchResult.stdout || ''}`.trim() : null;
268
+ if (branchResult.status !== 0) {
269
+ warnings.push(`failed to resolve branch: ${branchResult.stderr || 'unknown error'}`);
270
+ }
271
+
272
+ let upstream = null;
273
+ let ahead = null;
274
+ let behind = null;
275
+ if (hasTargetRemote) {
276
+ if (branch === 'HEAD' && allowDetachedHead) {
277
+ warnings.push('detached HEAD release checkout detected; upstream tracking check skipped');
278
+ } else {
279
+ const upstreamResult = runGitCommand(projectRoot, ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}']);
280
+ if (upstreamResult.status !== 0) {
281
+ violations.push('current branch has no upstream tracking branch');
282
+ } else {
283
+ upstream = `${upstreamResult.stdout || ''}`.trim();
284
+ const aheadBehindResult = runGitCommand(projectRoot, ['rev-list', '--left-right', '--count', 'HEAD...@{u}']);
285
+ if (aheadBehindResult.status !== 0) {
286
+ violations.push(`failed to compare with upstream: ${aheadBehindResult.stderr || 'unknown error'}`);
287
+ } else {
288
+ const parsed = parseAheadBehind(aheadBehindResult.stdout);
289
+ ahead = parsed.ahead;
290
+ behind = parsed.behind;
291
+ if (Number.isFinite(ahead) && ahead > 0) {
292
+ violations.push(`branch is ahead of upstream by ${ahead} commit(s); push required`);
293
+ }
294
+ if (Number.isFinite(behind) && behind > 0) {
295
+ violations.push(`branch is behind upstream by ${behind} commit(s); sync required`);
296
+ }
297
+ }
298
+ }
299
+ }
300
+ }
301
+
302
+ return {
303
+ available: true,
304
+ passed: violations.length === 0,
305
+ reason: violations.length === 0 ? 'synced' : 'violations',
306
+ target_hosts: targetHosts,
307
+ remotes: remoteInfo.allRemotes,
308
+ target_remotes: remoteInfo.targetRemotes,
309
+ tracked_files: trackedFiles,
310
+ status_entries: statusEntries,
311
+ warnings,
312
+ violations,
313
+ branch,
314
+ upstream,
315
+ ahead,
316
+ behind,
317
+ has_target_remote: hasTargetRemote,
318
+ clean_worktree: trackedChanges === 0 && untrackedChanges === 0,
319
+ worktree_changes: {
320
+ tracked_count: trackedChanges,
321
+ untracked_count: untrackedChanges
322
+ }
323
+ };
324
+ }
325
+
326
+ async function auditSpecDeliverySync(projectRoot = process.cwd(), options = {}, dependencies = {}) {
327
+ const fileSystem = dependencies.fileSystem || fs;
328
+ const manifests = await discoverDeliveryManifests(projectRoot, options, dependencies);
329
+ const requireManifest = options.requireManifest === true;
330
+ const git = manifests.length > 0
331
+ ? loadGitSnapshot(projectRoot, options, dependencies)
332
+ : {
333
+ available: false,
334
+ passed: true,
335
+ reason: 'not-required',
336
+ tracked_files: new Set(),
337
+ status_entries: new Map(),
338
+ warnings: [],
339
+ violations: [],
340
+ branch: null,
341
+ upstream: null,
342
+ ahead: null,
343
+ behind: null,
344
+ has_target_remote: false,
345
+ clean_worktree: null,
346
+ worktree_changes: {
347
+ tracked_count: 0,
348
+ untracked_count: 0
349
+ }
350
+ };
351
+
352
+ const report = {
353
+ mode: 'spec-delivery-audit',
354
+ generated_at: new Date().toISOString(),
355
+ root: projectRoot,
356
+ spec: typeof options.spec === 'string' && options.spec.trim().length > 0 ? options.spec.trim() : null,
357
+ require_manifest: requireManifest,
358
+ manifests: [],
359
+ git: {
360
+ available: git.available === true,
361
+ passed: git.passed === true,
362
+ reason: git.reason,
363
+ warnings: git.warnings,
364
+ violations: git.violations,
365
+ branch: git.branch,
366
+ upstream: git.upstream,
367
+ ahead: git.ahead,
368
+ behind: git.behind,
369
+ has_target_remote: git.has_target_remote,
370
+ clean_worktree: git.clean_worktree,
371
+ worktree_changes: git.worktree_changes
372
+ },
373
+ summary: {
374
+ manifest_count: manifests.length,
375
+ blocking_manifest_count: 0,
376
+ advisory_manifest_count: 0,
377
+ passed_manifests: 0,
378
+ failed_manifests: 0,
379
+ declared_files: 0,
380
+ missing_declared_files: 0,
381
+ untracked_declared_files: 0,
382
+ dirty_declared_files: 0
383
+ },
384
+ warnings: [],
385
+ violations: [],
386
+ passed: true,
387
+ reason: 'passed'
388
+ };
389
+
390
+ if (manifests.length === 0) {
391
+ if (requireManifest) {
392
+ report.passed = false;
393
+ report.reason = 'missing-manifest';
394
+ report.violations.push(
395
+ report.spec
396
+ ? `no delivery manifest found for spec "${report.spec}"`
397
+ : 'no delivery manifests found under .sce/specs'
398
+ );
399
+ } else {
400
+ report.reason = 'no-manifests';
401
+ report.warnings.push('no delivery manifests found; delivery sync audit is advisory only');
402
+ }
403
+ return report;
404
+ }
405
+
406
+ for (const item of manifests) {
407
+ let manifestPayload;
408
+ try {
409
+ manifestPayload = await fileSystem.readJson(item.manifest_path);
410
+ } catch (error) {
411
+ const manifestReport = {
412
+ spec: item.spec,
413
+ manifest_file: normalizeRelativePath(projectRoot, item.manifest_path),
414
+ verification_mode: 'blocking',
415
+ declared_files: [],
416
+ optional_files: [],
417
+ ignored_patterns: [],
418
+ files: [],
419
+ warnings: [],
420
+ violations: [`invalid delivery manifest: ${error.message}`],
421
+ passed: false,
422
+ summary: {
423
+ declared_count: 0,
424
+ missing_declared_files: 0,
425
+ untracked_declared_files: 0,
426
+ dirty_declared_files: 0
427
+ }
428
+ };
429
+ report.manifests.push(manifestReport);
430
+ report.summary.blocking_manifest_count += 1;
431
+ report.summary.failed_manifests += 1;
432
+ report.violations.push(`[${item.spec}] invalid delivery manifest: ${error.message}`);
433
+ continue;
434
+ }
435
+
436
+ const manifest = parseManifest(projectRoot, item.spec, manifestPayload);
437
+ const manifestReport = {
438
+ spec: item.spec,
439
+ manifest_file: normalizeRelativePath(projectRoot, item.manifest_path),
440
+ verification_mode: manifest.verification_mode,
441
+ declared_files: manifest.declared_files,
442
+ optional_files: manifest.optional_files,
443
+ ignored_patterns: manifest.ignored_patterns,
444
+ files: [],
445
+ warnings: [],
446
+ violations: [],
447
+ passed: true,
448
+ summary: {
449
+ declared_count: manifest.declared_files.length,
450
+ missing_declared_files: 0,
451
+ untracked_declared_files: 0,
452
+ dirty_declared_files: 0
453
+ }
454
+ };
455
+
456
+ if (manifest.verification_mode === 'blocking') {
457
+ report.summary.blocking_manifest_count += 1;
458
+ } else {
459
+ report.summary.advisory_manifest_count += 1;
460
+ }
461
+
462
+ const declaredFiles = manifest.declared_files.filter((filePath) => !shouldIgnorePath(filePath, manifest.ignored_patterns));
463
+ report.summary.declared_files += declaredFiles.length;
464
+
465
+ for (const filePath of declaredFiles) {
466
+ const absolutePath = path.join(projectRoot, filePath);
467
+ const exists = await fileSystem.pathExists(absolutePath);
468
+ const tracked = git.tracked_files.has(filePath);
469
+ const worktreeStatus = resolveWorktreeStatus(git.status_entries.get(filePath));
470
+ const fileResult = {
471
+ path: filePath,
472
+ exists,
473
+ tracked,
474
+ worktree_status: worktreeStatus,
475
+ issues: []
476
+ };
477
+
478
+ if (!exists) {
479
+ fileResult.issues.push('missing');
480
+ manifestReport.summary.missing_declared_files += 1;
481
+ report.summary.missing_declared_files += 1;
482
+ }
483
+ if (!tracked) {
484
+ fileResult.issues.push('not-tracked');
485
+ manifestReport.summary.untracked_declared_files += 1;
486
+ report.summary.untracked_declared_files += 1;
487
+ }
488
+ if (tracked && worktreeStatus !== 'unmodified') {
489
+ fileResult.issues.push(`dirty:${worktreeStatus}`);
490
+ manifestReport.summary.dirty_declared_files += 1;
491
+ report.summary.dirty_declared_files += 1;
492
+ }
493
+
494
+ if (fileResult.issues.length > 0) {
495
+ const reason = `${filePath} => ${fileResult.issues.join(', ')}`;
496
+ if (manifest.verification_mode === 'blocking') {
497
+ manifestReport.violations.push(reason);
498
+ } else {
499
+ manifestReport.warnings.push(reason);
500
+ }
501
+ }
502
+
503
+ manifestReport.files.push(fileResult);
504
+ }
505
+
506
+ if (git.available !== true) {
507
+ const reason = git.violations[0] || 'git repository unavailable';
508
+ if (manifest.verification_mode === 'blocking') {
509
+ manifestReport.violations.push(reason);
510
+ } else {
511
+ manifestReport.warnings.push(reason);
512
+ }
513
+ } else {
514
+ for (const gitViolation of git.violations) {
515
+ if (gitViolation.includes('ahead of upstream') || gitViolation.includes('behind upstream') || gitViolation.includes('no upstream tracking branch')) {
516
+ if (manifest.verification_mode === 'blocking') {
517
+ manifestReport.violations.push(gitViolation);
518
+ } else {
519
+ manifestReport.warnings.push(gitViolation);
520
+ }
521
+ }
522
+ }
523
+ if (git.has_target_remote !== true) {
524
+ manifestReport.warnings.push('no GitHub/GitLab remote configured; cannot prove cross-machine delivery sync');
525
+ }
526
+ }
527
+
528
+ manifestReport.passed = manifestReport.violations.length === 0;
529
+ if (manifestReport.passed) {
530
+ report.summary.passed_manifests += 1;
531
+ } else {
532
+ report.summary.failed_manifests += 1;
533
+ report.violations.push(...manifestReport.violations.map((value) => `[${item.spec}] ${value}`));
534
+ }
535
+ report.warnings.push(...manifestReport.warnings.map((value) => `[${item.spec}] ${value}`));
536
+ report.manifests.push(manifestReport);
537
+ }
538
+
539
+ report.passed = report.violations.length === 0;
540
+ report.reason = report.passed ? 'passed' : 'violations';
541
+ return report;
542
+ }
543
+
544
+ module.exports = {
545
+ DELIVERY_MANIFEST_FILE,
546
+ DELIVERY_VERIFICATION_MODES,
547
+ discoverDeliveryManifests,
548
+ auditSpecDeliverySync,
549
+ loadGitSnapshot,
550
+ parseManifest,
551
+ parseStatusEntries,
552
+ resolveWorktreeStatus
553
+ };
@@ -7,6 +7,9 @@ const {
7
7
  MANIFEST_FILENAME,
8
8
  SCE_STEERING_DIR,
9
9
  } = require('../runtime/steering-contract');
10
+ const {
11
+ cloneStateStoragePolicyDefaults
12
+ } = require('../state/state-storage-policy');
10
13
 
11
14
  const TAKEOVER_BASELINE_SCHEMA_VERSION = '1.0';
12
15
 
@@ -479,6 +482,7 @@ async function applyTakeoverBaseline(projectPath = process.cwd(), options = {})
479
482
  const problemEvalPolicyPath = path.join(sceRoot, 'config', 'problem-eval-policy.json');
480
483
  const problemClosurePolicyPath = path.join(sceRoot, 'config', 'problem-closure-policy.json');
481
484
  const studioIntakePolicyPath = path.join(sceRoot, 'config', 'studio-intake-policy.json');
485
+ const stateStoragePolicyPath = path.join(sceRoot, 'config', 'state-storage-policy.json');
482
486
  const reportPath = path.join(sceRoot, 'reports', 'takeover-baseline-latest.json');
483
487
 
484
488
  const existingAdoption = await _readJsonSafe(adoptionPath, fileSystem);
@@ -489,6 +493,7 @@ async function applyTakeoverBaseline(projectPath = process.cwd(), options = {})
489
493
  const existingProblemEvalPolicy = await _readJsonSafe(problemEvalPolicyPath, fileSystem);
490
494
  const existingProblemClosurePolicy = await _readJsonSafe(problemClosurePolicyPath, fileSystem);
491
495
  const existingStudioIntakePolicy = await _readJsonSafe(studioIntakePolicyPath, fileSystem);
496
+ const existingStateStoragePolicy = await _readJsonSafe(stateStoragePolicyPath, fileSystem);
492
497
 
493
498
  const desiredAdoption = _buildAdoptionConfig(existingAdoption, nowIso, sceVersion);
494
499
  const desiredAutoConfig = _buildAutoConfig(existingAuto);
@@ -498,6 +503,7 @@ async function applyTakeoverBaseline(projectPath = process.cwd(), options = {})
498
503
  const desiredProblemEvalPolicy = _deepMerge(existingProblemEvalPolicy || {}, PROBLEM_EVAL_POLICY_DEFAULTS);
499
504
  const desiredProblemClosurePolicy = _deepMerge(existingProblemClosurePolicy || {}, PROBLEM_CLOSURE_POLICY_DEFAULTS);
500
505
  const desiredStudioIntakePolicy = _deepMerge(existingStudioIntakePolicy || {}, STUDIO_INTAKE_POLICY_DEFAULTS);
506
+ const desiredStateStoragePolicy = _deepMerge(existingStateStoragePolicy || {}, cloneStateStoragePolicyDefaults());
501
507
 
502
508
  const fileResults = [];
503
509
  fileResults.push(await _reconcileJsonFile(adoptionPath, desiredAdoption, {
@@ -540,6 +546,11 @@ async function applyTakeoverBaseline(projectPath = process.cwd(), options = {})
540
546
  apply,
541
547
  fileSystem
542
548
  }));
549
+ fileResults.push(await _reconcileJsonFile(stateStoragePolicyPath, desiredStateStoragePolicy, {
550
+ projectPath,
551
+ apply,
552
+ fileSystem
553
+ }));
543
554
  fileResults.push(await _reconcileSteeringContract(projectPath, {
544
555
  apply,
545
556
  fileSystem
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scene-capability-engine",
3
- "version": "3.6.38",
3
+ "version": "3.6.44",
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": {
@@ -39,7 +39,11 @@
39
39
  "test:sce-tracking": "node scripts/check-sce-tracking.js",
40
40
  "test:brand-consistency": "node scripts/check-branding-consistency.js",
41
41
  "audit:steering": "node scripts/steering-content-audit.js --fail-on-error",
42
+ "audit:state-storage": "node scripts/state-storage-tiering-audit.js",
42
43
  "report:steering-audit": "node scripts/steering-content-audit.js --json",
44
+ "report:state-storage": "node scripts/state-storage-tiering-audit.js --json",
45
+ "report:interactive-approval-projection": "node scripts/interactive-approval-event-projection.js --action doctor --json",
46
+ "audit:interactive-approval-projection": "node scripts/interactive-approval-event-projection.js --action doctor --fail-on-drift --fail-on-parse-error",
43
47
  "test:watch": "npx jest --watch",
44
48
  "coverage": "npx jest --coverage",
45
49
  "report:moqui-metadata": "node scripts/moqui-metadata-extract.js --json",