@comfanion/workflow 4.36.6 → 4.36.8

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.
package/bin/cli.js CHANGED
@@ -11,20 +11,22 @@ import { execSync } from 'child_process';
11
11
  import yaml from 'js-yaml';
12
12
 
13
13
  /**
14
- * Deep merge two objects. User values override defaults.
15
- * Arrays are replaced, not merged.
14
+ * Find top-level keys that exist in newObj but not in oldObj
16
15
  */
17
- function deepMerge(defaults, user) {
18
- const result = { ...defaults };
19
- for (const key of Object.keys(user)) {
20
- if (user[key] !== null && typeof user[key] === 'object' && !Array.isArray(user[key]) &&
21
- defaults[key] !== null && typeof defaults[key] === 'object' && !Array.isArray(defaults[key])) {
22
- result[key] = deepMerge(defaults[key], user[key]);
23
- } else {
24
- result[key] = user[key];
16
+ function findNewKeys(newObj, oldObj, prefix = '') {
17
+ const newKeys = [];
18
+ for (const key of Object.keys(newObj)) {
19
+ const fullKey = prefix ? `${prefix}.${key}` : key;
20
+ if (!(key in oldObj)) {
21
+ newKeys.push(fullKey);
22
+ } else if (
23
+ newObj[key] !== null && typeof newObj[key] === 'object' && !Array.isArray(newObj[key]) &&
24
+ oldObj[key] !== null && typeof oldObj[key] === 'object' && !Array.isArray(oldObj[key])
25
+ ) {
26
+ newKeys.push(...findNewKeys(newObj[key], oldObj[key], fullKey));
25
27
  }
26
28
  }
27
- return result;
29
+ return newKeys;
28
30
  }
29
31
 
30
32
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -347,32 +349,24 @@ program
347
349
  // Update config.yaml with user values
348
350
  spinner.text = 'Configuring...';
349
351
  const configPath = path.join(targetDir, 'config.yaml');
350
- let configContent = await fs.readFile(configPath, 'utf8');
352
+ let configContent;
351
353
 
352
- // If we had existing config, merge it (preserve user customizations)
354
+ // If we had existing config, use it as base (preserves comments and formatting)
353
355
  if (existingConfigContent) {
354
- try {
355
- const newConfig = yaml.load(configContent) || {};
356
- const existingConfig = yaml.load(existingConfigContent) || {};
357
- const mergedConfig = deepMerge(newConfig, existingConfig);
358
- configContent = yaml.dump(mergedConfig, {
359
- indent: 2,
360
- lineWidth: 120,
361
- noRefs: true,
362
- sortKeys: false
363
- });
364
- console.log(chalk.green(' ✅ Merged existing config settings'));
365
- } catch (e) {
366
- // Merge failed, continue with new config + user values
367
- }
356
+ configContent = existingConfigContent;
357
+ // Update version to match new package
358
+ configContent = configContent.replace(/^version:\s*["']?[\d.]+["']?/m, `version: "${VERSION}"`);
359
+ console.log(chalk.green(' ✅ Restored your config (comments preserved)'));
360
+ } else {
361
+ configContent = await fs.readFile(configPath, 'utf8');
368
362
  }
369
363
 
370
364
  // Apply user's answers from prompts
371
365
  configContent = configContent
372
- .replace(/user_name: .*/, `user_name: "${config.user_name}"`)
373
- .replace(/communication_language: .*/, `communication_language: "${config.communication_language}"`)
374
- .replace(/project_name: .*/, `project_name: "${config.project_name}"`)
375
- .replace(/methodology: (tdd|stub)/, `methodology: ${config.methodology}`);
366
+ .replace(/user_name:\s*["']?[^"\n]*["']?/, `user_name: "${config.user_name}"`)
367
+ .replace(/communication_language:\s*["']?[^"\n]*["']?/, `communication_language: "${config.communication_language}"`)
368
+ .replace(/project_name:\s*["']?[^"\n]*["']?/, `project_name: "${config.project_name}"`)
369
+ .replace(/methodology:\s*(tdd|stub)/, `methodology: ${config.methodology}`);
376
370
 
377
371
  // Jira config
378
372
  if (config.jira_enabled) {
@@ -617,33 +611,37 @@ program
617
611
  await fs.move(tempVectors, path.join(targetDir, 'vectors'), { overwrite: true });
618
612
  }
619
613
 
620
- // Merge config.yaml: new defaults + user overrides
621
- spinner.text = 'Merging config.yaml...';
614
+ // Restore user's config.yaml (preserves comments and formatting)
615
+ spinner.text = 'Restoring config.yaml...';
622
616
  try {
623
- const newConfigPath = path.join(targetDir, 'config.yaml');
624
- const newConfigContent = await fs.readFile(newConfigPath, 'utf8');
625
-
626
- // Parse both configs
627
- const newConfig = yaml.load(newConfigContent) || {};
628
- const userConfig = yaml.load(configBackup) || {};
617
+ // Update version in user's config to match new package
618
+ let restoredConfig = configBackup.replace(
619
+ /^version:\s*["']?[\d.]+["']?/m,
620
+ `version: "${VERSION}"`
621
+ );
622
+ await fs.writeFile(configPath, restoredConfig);
623
+ console.log(chalk.green(' ✅ config.yaml restored (your settings + comments preserved)'));
629
624
 
630
- // Deep merge: defaults from new config, overridden by user values
631
- const mergedConfig = deepMerge(newConfig, userConfig);
632
-
633
- // Dump back to YAML with nice formatting
634
- const mergedContent = yaml.dump(mergedConfig, {
635
- indent: 2,
636
- lineWidth: 120,
637
- noRefs: true,
638
- sortKeys: false
639
- });
640
-
641
- await fs.writeFile(configPath, mergedContent);
642
- console.log(chalk.green(' ✅ config.yaml merged (your settings preserved, new options added)'));
625
+ // Check if new template has options that user doesn't have
626
+ const newConfigPath = path.join(OPENCODE_SRC, 'config.yaml');
627
+ if (await fs.pathExists(newConfigPath)) {
628
+ const newConfig = yaml.load(await fs.readFile(newConfigPath, 'utf8')) || {};
629
+ const userConfig = yaml.load(configBackup) || {};
630
+ const newKeys = findNewKeys(newConfig, userConfig);
631
+ if (newKeys.length > 0) {
632
+ console.log(chalk.yellow(` 💡 New config options available: ${newKeys.slice(0, 3).join(', ')}${newKeys.length > 3 ? '...' : ''}`));
633
+ console.log(chalk.gray(' Check .opencode.backup-*/config.yaml.new for full template'));
634
+ // Save new template for reference
635
+ await fs.writeFile(
636
+ path.join(process.cwd(), `.opencode.backup-${timestamp}`, 'config.yaml.new'),
637
+ await fs.readFile(newConfigPath, 'utf8')
638
+ );
639
+ }
640
+ }
643
641
  } catch (e) {
644
- // Fallback: just restore user's config if merge fails
642
+ // Fallback: just restore user's config
645
643
  await fs.writeFile(configPath, configBackup);
646
- console.log(chalk.yellow(' ⚠️ config.yaml restored (merge failed, using your original)'));
644
+ console.log(chalk.yellow(' ⚠️ config.yaml restored'));
647
645
  }
648
646
 
649
647
  // Install plugin dependencies
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comfanion/workflow",
3
- "version": "4.36.6",
3
+ "version": "4.36.8",
4
4
  "description": "Initialize OpenCode Workflow system for AI-assisted development with semantic code search",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "3.0.0",
3
- "buildDate": "2026-01-24T15:05:07.173Z",
3
+ "buildDate": "2026-01-24T15:10:43.480Z",
4
4
  "files": [
5
5
  "config.yaml",
6
6
  "FLOW.yaml",
@@ -422,10 +422,13 @@ export const FileIndexerPlugin: Plugin = async ({ directory, client }) => {
422
422
  const result = await ensureIndexOnSessionStart(
423
423
  directory,
424
424
  config,
425
- // onStart callback - show 2 toasts
425
+ // onStart callback - show toasts
426
426
  async (totalFiles, estimatedMins) => {
427
427
  await toast(messages.indexing(totalFiles), 'info')
428
- setTimeout(() => toast(messages.fun(totalFiles, estimatedMins), 'info'), 1500)
428
+ // Only show fun message if there's actual work to do
429
+ if (totalFiles > 0) {
430
+ setTimeout(() => toast(messages.fun(totalFiles, estimatedMins), 'info'), 1500)
431
+ }
429
432
  }
430
433
  )
431
434