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
@@ -90,7 +90,10 @@ function registerCollabCommands(program) {
90
90
  console.log(chalk.gray('Type:'), status.metadata.type);
91
91
  console.log(chalk.gray('Status:'), status.metadata.status.current);
92
92
  if (status.metadata.assignment) {
93
- console.log(chalk.gray('Assigned to:'), status.metadata.assignment.sceInstance);
93
+ console.log(
94
+ chalk.gray('Assigned to:'),
95
+ status.metadata.assignment.sceInstance || status.metadata.assignment.kiroInstance
96
+ );
94
97
  }
95
98
  if (status.metadata.dependencies && status.metadata.dependencies.length > 0) {
96
99
  console.log(chalk.gray('Dependencies:'));
@@ -105,8 +108,11 @@ function registerCollabCommands(program) {
105
108
 
106
109
  for (const { name, metadata } of status.specs) {
107
110
  const symbol = getStatusSymbol(metadata.status.current);
111
+ const assignedInstance = metadata.assignment
112
+ ? (metadata.assignment.sceInstance || metadata.assignment.kiroInstance)
113
+ : null;
108
114
  const assignment = metadata.assignment
109
- ? chalk.gray(`(${metadata.assignment.sceInstance})`)
115
+ ? chalk.gray(`(${assignedInstance})`)
110
116
  : chalk.gray('(unassigned)');
111
117
  console.log(`${symbol} ${name} ${assignment}`);
112
118
  }
@@ -127,12 +133,12 @@ function registerCollabCommands(program) {
127
133
  collab
128
134
  .command('assign <spec-name> <SCE-instance>')
129
135
  .description('Assign a spec to a SCE instance')
130
- .action(async (specName, kiroInstance) => {
136
+ .action(async (specName, sceInstance) => {
131
137
  try {
132
138
  const workspaceRoot = process.cwd();
133
139
  const manager = new CollaborationManager(workspaceRoot);
134
140
 
135
- const result = await manager.assignSpec(specName, kiroInstance);
141
+ const result = await manager.assignSpec(specName, sceInstance);
136
142
 
137
143
  if (result.success) {
138
144
  console.log(chalk.green('✓'), result.message);
@@ -301,6 +301,12 @@ async function handleConfigDisplay(configManager, reporter) {
301
301
  console.log(` • ${dir}`);
302
302
  });
303
303
  console.log();
304
+
305
+ console.log(chalk.bold('Spec Allowed Root Files:'));
306
+ (config.specAllowedRootFiles || []).forEach(file => {
307
+ console.log(` • ${file}`);
308
+ });
309
+ console.log();
304
310
 
305
311
  console.log(chalk.bold('Temporary Patterns:'));
306
312
  config.temporaryPatterns.forEach(pattern => {
@@ -350,10 +356,10 @@ async function handleConfigSet(configManager, reporter, options) {
350
356
  const camelKey = key.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
351
357
 
352
358
  // Validate key
353
- const validKeys = ['rootAllowedFiles', 'specSubdirs', 'temporaryPatterns'];
359
+ const validKeys = ['rootAllowedFiles', 'specAllowedRootFiles', 'specSubdirs', 'temporaryPatterns'];
354
360
  if (!validKeys.includes(camelKey)) {
355
361
  reporter.displayError(`Invalid configuration key: ${key}`);
356
- console.log(chalk.gray('Valid keys: root-allowed-files, spec-subdirs, temporary-patterns\n'));
362
+ console.log(chalk.gray('Valid keys: root-allowed-files, spec-allowed-root-files, spec-subdirs, temporary-patterns\n'));
357
363
  return 2;
358
364
  }
359
365
 
@@ -158,6 +158,45 @@ function createDoctorTraceId() {
158
158
  return `doctor-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
159
159
  }
160
160
 
161
+ function stringifyJsonForCli(value, indentSize = 2, currentIndent = 0) {
162
+ if (value === null) {
163
+ return 'null';
164
+ }
165
+
166
+ if (typeof value === 'number') {
167
+ return Object.is(value, -0) ? '-0' : JSON.stringify(value);
168
+ }
169
+
170
+ if (typeof value === 'string' || typeof value === 'boolean') {
171
+ return JSON.stringify(value);
172
+ }
173
+
174
+ if (Array.isArray(value)) {
175
+ if (value.length === 0) {
176
+ return '[]';
177
+ }
178
+
179
+ const nextIndent = currentIndent + indentSize;
180
+ const indent = ' '.repeat(currentIndent);
181
+ const childIndent = ' '.repeat(nextIndent);
182
+ const items = value.map((entry) => `${childIndent}${stringifyJsonForCli(entry, indentSize, nextIndent)}`);
183
+ return `[\n${items.join(',\n')}\n${indent}]`;
184
+ }
185
+
186
+ const keys = Object.keys(value);
187
+ if (keys.length === 0) {
188
+ return '{}';
189
+ }
190
+
191
+ const nextIndent = currentIndent + indentSize;
192
+ const indent = ' '.repeat(currentIndent);
193
+ const childIndent = ' '.repeat(nextIndent);
194
+ const entries = keys.map((key) => (
195
+ `${childIndent}${JSON.stringify(key)}: ${stringifyJsonForCli(value[key], indentSize, nextIndent)}`
196
+ ));
197
+ return `{\n${entries.join(',\n')}\n${indent}}`;
198
+ }
199
+
161
200
  function registerSceneCommands(program) {
162
201
  const sceneCmd = program
163
202
  .command('scene')
@@ -4795,7 +4834,7 @@ function createSceneEvalConfigTemplateByProfile(profile = 'default') {
4795
4834
  }
4796
4835
 
4797
4836
  function normalizeRelativePath(targetPath = '') {
4798
- return String(targetPath || '').replace(/\\/g, '/').replace(/\/+/g, '/').replace(/^\.\//, '');
4837
+ return String(targetPath || '').trim().replace(/\\/g, '/').replace(/\/+/g, '/').replace(/^\.\//, '');
4799
4838
  }
4800
4839
 
4801
4840
  function collectManifestDiscoveryCandidates(preferredPath = 'custom/scene.yaml') {
@@ -13639,6 +13678,35 @@ async function runSceneVersionCommand(rawOptions = {}, dependencies = {}) {
13639
13678
  }
13640
13679
  }
13641
13680
 
13681
+ function isBinaryContent(buffer) {
13682
+ if (!Buffer.isBuffer(buffer)) {
13683
+ return false;
13684
+ }
13685
+
13686
+ for (let index = 0; index < buffer.length; index++) {
13687
+ if (buffer[index] === 0) {
13688
+ return true;
13689
+ }
13690
+ }
13691
+
13692
+ return false;
13693
+ }
13694
+
13695
+ function countChangedLines(fromContent, toContent) {
13696
+ const oldLines = fromContent.toString('utf8').split('\n');
13697
+ const newLines = toContent.toString('utf8').split('\n');
13698
+ const maxLen = Math.max(oldLines.length, newLines.length);
13699
+ let changedLines = 0;
13700
+
13701
+ for (let index = 0; index < maxLen; index++) {
13702
+ if ((oldLines[index] || '') !== (newLines[index] || '')) {
13703
+ changedLines++;
13704
+ }
13705
+ }
13706
+
13707
+ return changedLines;
13708
+ }
13709
+
13642
13710
  function buildPackageDiff(fromFiles, toFiles) {
13643
13711
  const fromMap = new Map();
13644
13712
  for (const f of (fromFiles || [])) {
@@ -13662,19 +13730,9 @@ function buildPackageDiff(fromFiles, toFiles) {
13662
13730
  if (Buffer.compare(content, toContent) === 0) {
13663
13731
  unchanged.push(filePath);
13664
13732
  } else {
13665
- let changedLines = 0;
13666
- try {
13667
- const oldLines = content.toString('utf8').split('\n');
13668
- const newLines = toContent.toString('utf8').split('\n');
13669
- const maxLen = Math.max(oldLines.length, newLines.length);
13670
- for (let i = 0; i < maxLen; i++) {
13671
- if ((oldLines[i] || '') !== (newLines[i] || '')) {
13672
- changedLines++;
13673
- }
13674
- }
13675
- } catch (_e) {
13676
- changedLines = -1;
13677
- }
13733
+ const changedLines = isBinaryContent(content) || isBinaryContent(toContent)
13734
+ ? -1
13735
+ : countChangedLines(content, toContent);
13678
13736
  modified.push({ path: filePath, changedLines });
13679
13737
  }
13680
13738
  }
@@ -13734,7 +13792,9 @@ function printSceneDiffSummary(options, payload, projectRoot = process.cwd()) {
13734
13792
  console.log(chalk.red(` - ${f}`));
13735
13793
  }
13736
13794
  for (const f of payload.files.modified) {
13737
- const detail = f.changedLines >= 0 ? ` (${f.changedLines} lines changed)` : ' (binary content differs)';
13795
+ const detail = options.stat
13796
+ ? ''
13797
+ : (f.changedLines >= 0 ? ` (${f.changedLines} lines changed)` : ' (binary content differs)');
13738
13798
  console.log(chalk.yellow(` ~ ${f.path}${detail}`));
13739
13799
  }
13740
13800
  }
@@ -15248,7 +15308,7 @@ async function runSceneLintCommand(rawOptions = {}, dependencies = {}) {
15248
15308
 
15249
15309
  function printSceneLintSummary(options, payload, projectRoot = process.cwd()) {
15250
15310
  if (options.json) {
15251
- console.log(JSON.stringify(payload, null, 2));
15311
+ console.log(stringifyJsonForCli(payload));
15252
15312
  return;
15253
15313
  }
15254
15314
 
@@ -15344,7 +15404,7 @@ async function runSceneScoreCommand(rawOptions = {}, dependencies = {}) {
15344
15404
 
15345
15405
  function printSceneScoreSummary(options, payload, projectRoot = process.cwd()) {
15346
15406
  if (options.json) {
15347
- console.log(JSON.stringify(payload, null, 2));
15407
+ console.log(stringifyJsonForCli(payload));
15348
15408
  return;
15349
15409
  }
15350
15410
 
@@ -15524,7 +15584,7 @@ async function runSceneContributeCommand(rawOptions = {}, dependencies = {}) {
15524
15584
 
15525
15585
  function printSceneContributeSummary(options, payload, projectRoot = process.cwd()) {
15526
15586
  if (options.json) {
15527
- console.log(JSON.stringify(payload, null, 2));
15587
+ console.log(stringifyJsonForCli(payload));
15528
15588
  return;
15529
15589
  }
15530
15590
 
@@ -30,10 +30,10 @@ async function statusCommand(options = {}) {
30
30
 
31
31
  try {
32
32
  // 1. Check if .sce/ exists
33
- const kiroPath = path.join(projectPath, '.sce');
34
- const kiroExists = await fs.pathExists(kiroPath);
33
+ const scePath = path.join(projectPath, '.sce');
34
+ const sceExists = await fs.pathExists(scePath);
35
35
 
36
- if (!kiroExists) {
36
+ if (!sceExists) {
37
37
  console.log(chalk.yellow('⚠️ No .sce/ directory found'));
38
38
  console.log();
39
39
  console.log('This project has not been adopted yet.');
@@ -502,6 +502,14 @@ async function buildReleaseGateSteps(options = {}, dependencies = {}) {
502
502
  required: true
503
503
  });
504
504
 
505
+ steps.push({
506
+ id: 'spec-delivery-audit',
507
+ name: 'spec delivery sync audit',
508
+ command: 'node',
509
+ args: ['bin/sce.js', 'workspace', 'delivery-audit', '--json', '--strict'],
510
+ required: true
511
+ });
512
+
505
513
  const gitManagedGateScript = path.join(projectPath, 'scripts', 'git-managed-gate.js');
506
514
  const hasGitManagedGateScript = await fileSystem.pathExists(gitManagedGateScript);
507
515
  steps.push({
@@ -11,6 +11,15 @@ const inquirer = require('inquirer');
11
11
  const WatchManager = require('../watch/watch-manager');
12
12
  const { listPresets, getPreset, mergePreset, validatePreset } = require('../watch/presets');
13
13
 
14
+ function sleep(ms) {
15
+ return new Promise(resolve => {
16
+ const timer = setTimeout(resolve, ms);
17
+ if (typeof timer.unref === 'function') {
18
+ timer.unref();
19
+ }
20
+ });
21
+ }
22
+
14
23
  /**
15
24
  * Start watch mode
16
25
  *
@@ -329,7 +338,7 @@ async function followLogStream(logPath, options = {}) {
329
338
  break;
330
339
  }
331
340
 
332
- await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
341
+ await sleep(pollIntervalMs);
333
342
  }
334
343
  } finally {
335
344
  process.removeListener('SIGINT', onSigInt);
@@ -52,6 +52,12 @@ class ConfigManager {
52
52
  'CHANGELOG.md',
53
53
  'CONTRIBUTING.md'
54
54
  ],
55
+ specAllowedRootFiles: [
56
+ 'requirements.md',
57
+ 'design.md',
58
+ 'tasks.md',
59
+ 'collaboration.json'
60
+ ],
55
61
  specSubdirs: [
56
62
  'reports',
57
63
  'scripts',
@@ -152,6 +158,10 @@ class ConfigManager {
152
158
  if (!config.specSubdirs || !Array.isArray(config.specSubdirs)) {
153
159
  errors.push('specSubdirs must be an array');
154
160
  }
161
+
162
+ if (!config.specAllowedRootFiles || !Array.isArray(config.specAllowedRootFiles)) {
163
+ errors.push('specAllowedRootFiles must be an array');
164
+ }
155
165
 
156
166
  if (!config.temporaryPatterns || !Array.isArray(config.temporaryPatterns)) {
157
167
  errors.push('temporaryPatterns must be an array');
@@ -169,6 +179,12 @@ class ConfigManager {
169
179
  errors.push('specSubdirs must contain only strings');
170
180
  }
171
181
  }
182
+
183
+ if (config.specAllowedRootFiles && Array.isArray(config.specAllowedRootFiles)) {
184
+ if (config.specAllowedRootFiles.some(f => typeof f !== 'string')) {
185
+ errors.push('specAllowedRootFiles must contain only strings');
186
+ }
187
+ }
172
188
 
173
189
  if (config.temporaryPatterns && Array.isArray(config.temporaryPatterns)) {
174
190
  if (config.temporaryPatterns.some(p => typeof p !== 'string')) {
@@ -81,6 +81,7 @@ class DiagnosticEngine {
81
81
  async scanSpecDirectory(specPath) {
82
82
  const specName = path.basename(specPath);
83
83
  const requiredFiles = ['requirements.md', 'design.md', 'tasks.md'];
84
+ const allowedRootFiles = this.config.specAllowedRootFiles || requiredFiles;
84
85
 
85
86
  // Check for missing required files
86
87
  for (const requiredFile of requiredFiles) {
@@ -124,7 +125,7 @@ class DiagnosticEngine {
124
125
  const basename = path.basename(filePath);
125
126
 
126
127
  // Skip required files
127
- if (requiredFiles.includes(basename)) {
128
+ if (allowedRootFiles.includes(basename)) {
128
129
  continue;
129
130
  }
130
131
 
@@ -84,6 +84,8 @@ class ValidationEngine {
84
84
  */
85
85
  async validateSpec(specName) {
86
86
  const specPath = this.scanner.getSpecDirectory(specName);
87
+ const requiredFiles = ['requirements.md', 'design.md', 'tasks.md'];
88
+ const allowedRootFiles = this.config.specAllowedRootFiles || requiredFiles;
87
89
 
88
90
  // Check if Spec directory exists
89
91
  if (!await this.scanner.exists(specPath)) {
@@ -97,7 +99,6 @@ class ValidationEngine {
97
99
  }
98
100
 
99
101
  // Check required files
100
- const requiredFiles = ['requirements.md', 'design.md', 'tasks.md'];
101
102
  for (const file of requiredFiles) {
102
103
  const filePath = path.join(specPath, file);
103
104
  if (!await this.scanner.exists(filePath)) {
@@ -118,7 +119,7 @@ class ValidationEngine {
118
119
  const basename = path.basename(filePath);
119
120
 
120
121
  // Skip required files
121
- if (requiredFiles.includes(basename)) {
122
+ if (allowedRootFiles.includes(basename)) {
122
123
  continue;
123
124
  }
124
125
 
@@ -131,8 +131,8 @@ class ConfigManager {
131
131
 
132
132
  try {
133
133
  // Ensure .sce directory exists
134
- const kiroDir = path.dirname(configPath);
135
- await fs.mkdir(kiroDir, { recursive: true });
134
+ const sceDir = path.dirname(configPath);
135
+ await fs.mkdir(sceDir, { recursive: true });
136
136
 
137
137
  // Write configuration with pretty formatting
138
138
  const jsonContent = JSON.stringify(config, null, 2);
@@ -568,26 +568,34 @@ class SessionStore {
568
568
 
569
569
  const fileCount = fileRecords.length;
570
570
  const sqliteCount = Array.isArray(sqliteRecords) ? sqliteRecords.length : null;
571
+ let readSource = 'file';
571
572
 
572
573
  let status = 'file-only';
573
574
  if (sqliteCount === null) {
574
575
  status = 'sqlite-unavailable';
575
576
  } else if (fileCount === 0 && sqliteCount === 0) {
576
577
  status = 'empty';
578
+ readSource = 'empty';
577
579
  } else if (fileCount === 0 && sqliteCount > 0) {
578
580
  status = 'sqlite-only';
581
+ readSource = 'sqlite';
579
582
  } else if (fileCount > 0 && sqliteCount === 0) {
580
583
  status = 'file-only';
584
+ readSource = 'file';
581
585
  } else if (fileCount === sqliteCount) {
582
586
  status = 'aligned';
587
+ readSource = this._preferSqliteSceneReads ? 'sqlite' : 'file';
583
588
  } else if (sqliteCount < fileCount) {
584
589
  status = 'pending-sync';
590
+ readSource = 'file';
585
591
  } else if (sqliteCount > fileCount) {
586
592
  status = 'sqlite-ahead';
593
+ readSource = 'sqlite';
587
594
  }
588
595
 
589
596
  return {
590
597
  read_preference: this._preferSqliteSceneReads ? 'sqlite' : 'file',
598
+ read_source: readSource,
591
599
  file_scene_count: fileCount,
592
600
  sqlite_scene_count: sqliteCount,
593
601
  status
@@ -7,14 +7,15 @@ class ContextCollector {
7
7
  }
8
8
 
9
9
  async collect() {
10
- const kiroDir = path.join(this.projectPath, '.sce');
11
- const specsDir = path.join(kiroDir, 'specs');
12
- const hasKiro = await fs.pathExists(kiroDir);
10
+ const sceDir = path.join(this.projectPath, '.sce');
11
+ const specsDir = path.join(sceDir, 'specs');
12
+ const hasSceDir = await fs.pathExists(sceDir);
13
13
  const existingSpecs = await this._listSpecs(specsDir);
14
14
 
15
15
  return {
16
16
  projectPath: this.projectPath,
17
- hasKiro,
17
+ hasSceDir,
18
+ hasKiro: hasSceDir,
18
19
  specsDir,
19
20
  existingSpecs,
20
21
  totalSpecs: existingSpecs.length,
@@ -87,25 +87,25 @@ function createDefaultRules(projectPath = process.cwd()) {
87
87
  id: 'config_consistency',
88
88
  description: 'Verify project-level sce config baseline exists',
89
89
  async execute() {
90
- const kiroDir = path.join(projectPath, '.sce');
91
- const configDir = path.join(kiroDir, 'config');
92
- const hasKiro = await fs.pathExists(kiroDir);
90
+ const sceDir = path.join(projectPath, '.sce');
91
+ const configDir = path.join(sceDir, 'config');
92
+ const hasSceDir = await fs.pathExists(sceDir);
93
93
  const hasConfig = await fs.pathExists(configDir);
94
94
 
95
- const ratio = hasKiro && hasConfig ? 1 : hasKiro ? 0.5 : 0;
95
+ const ratio = hasSceDir && hasConfig ? 1 : hasSceDir ? 0.5 : 0;
96
96
  const warnings = [];
97
- if (!hasKiro) {
97
+ if (!hasSceDir) {
98
98
  warnings.push('.sce directory missing');
99
99
  }
100
- if (hasKiro && !hasConfig) {
100
+ if (hasSceDir && !hasConfig) {
101
101
  warnings.push('.sce/config directory missing');
102
102
  }
103
103
 
104
104
  return {
105
- passed: hasKiro,
105
+ passed: hasSceDir,
106
106
  ratio,
107
107
  details: {
108
- hasKiro,
108
+ hasSceDir,
109
109
  hasConfig
110
110
  },
111
111
  warnings