scene-capability-engine 3.6.39 → 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 (45) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/bin/scene-capability-engine.js +42 -2
  3. package/docs/developer-guide.md +1 -1
  4. package/docs/releases/README.md +5 -0
  5. package/docs/releases/v3.6.40.md +19 -0
  6. package/docs/releases/v3.6.41.md +20 -0
  7. package/docs/releases/v3.6.42.md +19 -0
  8. package/docs/releases/v3.6.43.md +17 -0
  9. package/docs/releases/v3.6.44.md +17 -0
  10. package/docs/spec-collaboration-guide.md +1 -1
  11. package/docs/zh/releases/README.md +5 -0
  12. package/docs/zh/releases/v3.6.40.md +19 -0
  13. package/docs/zh/releases/v3.6.41.md +20 -0
  14. package/docs/zh/releases/v3.6.42.md +19 -0
  15. package/docs/zh/releases/v3.6.43.md +17 -0
  16. package/docs/zh/releases/v3.6.44.md +17 -0
  17. package/lib/adoption/adoption-logger.js +1 -1
  18. package/lib/adoption/adoption-strategy.js +29 -29
  19. package/lib/adoption/detection-engine.js +16 -13
  20. package/lib/adoption/smart-orchestrator.js +3 -3
  21. package/lib/adoption/strategy-selector.js +19 -15
  22. package/lib/adoption/template-sync.js +3 -3
  23. package/lib/auto/autonomous-engine.js +5 -5
  24. package/lib/auto/handoff-release-gate-history-loaders-service.js +24 -4
  25. package/lib/auto/handoff-run-service.js +37 -0
  26. package/lib/backup/backup-system.js +10 -10
  27. package/lib/collab/collab-manager.js +8 -5
  28. package/lib/collab/dependency-manager.js +1 -1
  29. package/lib/commands/adopt.js +2 -2
  30. package/lib/commands/auto.js +239 -97
  31. package/lib/commands/collab.js +10 -4
  32. package/lib/commands/status.js +3 -3
  33. package/lib/commands/studio.js +8 -0
  34. package/lib/repo/config-manager.js +2 -2
  35. package/lib/spec/bootstrap/context-collector.js +5 -4
  36. package/lib/spec-gate/rules/default-rules.js +8 -8
  37. package/lib/upgrade/migration-engine.js +5 -5
  38. package/lib/upgrade/migrations/1.0.0-to-1.1.0.js +3 -3
  39. package/lib/utils/tool-detector.js +4 -4
  40. package/lib/utils/validation.js +6 -6
  41. package/lib/workspace/multi/workspace-context-resolver.js +3 -3
  42. package/lib/workspace/multi/workspace-registry.js +3 -3
  43. package/lib/workspace/multi/workspace-state-manager.js +3 -3
  44. package/lib/workspace/spec-delivery-audit.js +553 -0
  45. package/package.json +1 -1
@@ -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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scene-capability-engine",
3
- "version": "3.6.39",
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": {