@sylphx/flow 1.2.0 → 1.3.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @sylphx/flow
2
2
 
3
+ ## 1.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Redesign sync flow for better clarity:
8
+ - Remove duplicate config files in preserved list
9
+ - Show MCP check in preview upfront (not after confirmation)
10
+ - Combined preview: templates + MCP servers + preserved files
11
+ - Clear sections with emojis for easy scanning
12
+
13
+ ## 1.3.0
14
+
15
+ ### Minor Changes
16
+
17
+ - Enhanced --sync with MCP registry checking:
18
+ - Detect servers not in Flow registry (removed or custom)
19
+ - Interactive selection for removal
20
+ - Clean removal from .mcp.json
21
+ - Flow: sync templates → check MCP → remove selected
22
+
23
+ ## 1.2.1
24
+
25
+ ### Patch Changes
26
+
27
+ - Apply MEP principles to workspace documentation rule:
28
+ - Condensed from verbose instructions to condition→action format
29
+ - Removed step-by-step teaching and command examples
30
+ - Embedded verification in directives
31
+ - 31% reduction while maintaining clarity
32
+
3
33
  ## 1.2.0
4
34
 
5
35
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylphx/flow",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "AI-powered development workflow automation with autonomous loop mode and smart configuration",
5
5
  "type": "module",
6
6
  "bin": {
@@ -380,7 +380,7 @@ async function executeSetupPhase(prompt: string | undefined, options: FlowOption
380
380
 
381
381
  // Handle sync mode - delete template files first
382
382
  if (options.sync && !options.dryRun) {
383
- const { buildSyncManifest, showSyncPreview, confirmSync, executeSyncDelete } = await import('../utils/sync-utils.js');
383
+ const { buildSyncManifest, showSyncPreview, confirmSync, executeSyncDelete, checkMCPServers, selectServersToRemove, removeMCPServers } = await import('../utils/sync-utils.js');
384
384
 
385
385
  // Need target to build manifest
386
386
  const targetId = await selectAndValidateTarget(initOptions);
@@ -394,8 +394,11 @@ async function executeSetupPhase(prompt: string | undefined, options: FlowOption
394
394
  const target = targetOption.value;
395
395
  const manifest = await buildSyncManifest(process.cwd(), target);
396
396
 
397
+ // Check MCP servers before showing preview
398
+ const nonRegistryServers = await checkMCPServers(process.cwd());
399
+
397
400
  console.log(chalk.cyan.bold('━━━ 🔄 Synchronizing Files\n'));
398
- showSyncPreview(manifest, process.cwd());
401
+ showSyncPreview(manifest, process.cwd(), nonRegistryServers);
399
402
 
400
403
  const confirmed = await confirmSync();
401
404
  if (!confirmed) {
@@ -403,8 +406,19 @@ async function executeSetupPhase(prompt: string | undefined, options: FlowOption
403
406
  process.exit(0);
404
407
  }
405
408
 
409
+ // Delete templates
406
410
  const deletedCount = await executeSyncDelete(manifest);
407
- console.log(chalk.green(`\n✓ Deleted ${deletedCount} files\n`));
411
+ console.log(chalk.green(`\n✓ Deleted ${deletedCount} template files\n`));
412
+
413
+ // Handle MCP servers if any found
414
+ if (nonRegistryServers.length > 0) {
415
+ const serversToRemove = await selectServersToRemove(nonRegistryServers);
416
+
417
+ if (serversToRemove.length > 0) {
418
+ const removedCount = await removeMCPServers(process.cwd(), serversToRemove);
419
+ console.log(chalk.green(`✓ Removed ${removedCount} MCP server(s)\n`));
420
+ }
421
+ }
408
422
  } else if (!options.sync) {
409
423
  const targetId = await selectAndValidateTarget(initOptions);
410
424
  selectedTarget = targetId;
@@ -695,7 +709,7 @@ async function executeFlowOnce(prompt: string | undefined, options: FlowOptions)
695
709
 
696
710
  // Handle sync mode - delete template files first
697
711
  if (options.sync && !options.dryRun) {
698
- const { buildSyncManifest, showSyncPreview, confirmSync, executeSyncDelete } = await import('../utils/sync-utils.js');
712
+ const { buildSyncManifest, showSyncPreview, confirmSync, executeSyncDelete, checkMCPServers, selectServersToRemove, removeMCPServers } = await import('../utils/sync-utils.js');
699
713
 
700
714
  // Need target to build manifest
701
715
  const targetId = await selectAndValidateTarget(initOptions);
@@ -709,8 +723,11 @@ async function executeFlowOnce(prompt: string | undefined, options: FlowOptions)
709
723
  const target = targetOption.value;
710
724
  const manifest = await buildSyncManifest(process.cwd(), target);
711
725
 
726
+ // Check MCP servers before showing preview
727
+ const nonRegistryServers = await checkMCPServers(process.cwd());
728
+
712
729
  console.log(chalk.cyan.bold('━━━ 🔄 Synchronizing Files\n'));
713
- showSyncPreview(manifest, process.cwd());
730
+ showSyncPreview(manifest, process.cwd(), nonRegistryServers);
714
731
 
715
732
  const confirmed = await confirmSync();
716
733
  if (!confirmed) {
@@ -718,8 +735,19 @@ async function executeFlowOnce(prompt: string | undefined, options: FlowOptions)
718
735
  process.exit(0);
719
736
  }
720
737
 
738
+ // Delete templates
721
739
  const deletedCount = await executeSyncDelete(manifest);
722
- console.log(chalk.green(`\n✓ Deleted ${deletedCount} files\n`));
740
+ console.log(chalk.green(`\n✓ Deleted ${deletedCount} template files\n`));
741
+
742
+ // Handle MCP servers if any found
743
+ if (nonRegistryServers.length > 0) {
744
+ const serversToRemove = await selectServersToRemove(nonRegistryServers);
745
+
746
+ if (serversToRemove.length > 0) {
747
+ const removedCount = await removeMCPServers(process.cwd(), serversToRemove);
748
+ console.log(chalk.green(`✓ Removed ${removedCount} MCP server(s)\n`));
749
+ }
750
+ }
723
751
  } else {
724
752
  // Select and validate target (will use existing in repair mode, or prompt if needed)
725
753
  const targetId = await selectAndValidateTarget(initOptions);
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import chalk from 'chalk';
4
4
  import type { Target } from '../types.js';
5
+ import { MCP_SERVER_REGISTRY } from '../config/servers.js';
5
6
 
6
7
  /**
7
8
  * Files to delete during sync for each target
@@ -59,8 +60,6 @@ export async function buildSyncManifest(cwd: string, target: Target): Promise<Sy
59
60
  '.sylphx-flow/',
60
61
  '.secrets/',
61
62
  target.config.configFile || '',
62
- '.mcp.json',
63
- 'opencode.jsonc',
64
63
  ]
65
64
  .filter(Boolean)
66
65
  .map((p) => path.join(cwd, p));
@@ -71,46 +70,64 @@ export async function buildSyncManifest(cwd: string, target: Target): Promise<Sy
71
70
  /**
72
71
  * Show sync preview - what will be deleted
73
72
  */
74
- export function showSyncPreview(manifest: SyncManifest, cwd: string): void {
73
+ export function showSyncPreview(
74
+ manifest: SyncManifest,
75
+ cwd: string,
76
+ nonRegistryServers: string[]
77
+ ): void {
75
78
  console.log(chalk.cyan.bold('📋 Sync Preview\n'));
76
- console.log(chalk.dim('The following files will be deleted and re-installed:\n'));
77
79
 
80
+ // Template files section
78
81
  const allFiles = [...manifest.agents, ...manifest.slashCommands, ...manifest.rules];
79
82
 
80
- if (allFiles.length === 0) {
81
- console.log(chalk.yellow(' No template files found\n'));
82
- return;
83
- }
83
+ if (allFiles.length > 0) {
84
+ console.log(chalk.yellow('🔄 Templates (delete + reinstall):\n'));
84
85
 
85
- // Group by type
86
- if (manifest.agents.length > 0) {
87
- console.log(chalk.cyan(' Agents:'));
88
- manifest.agents.forEach((file) => {
89
- const relative = path.relative(cwd, file);
90
- console.log(chalk.dim(` - ${relative}`));
91
- });
92
- console.log('');
93
- }
86
+ if (manifest.agents.length > 0) {
87
+ console.log(chalk.dim(' Agents:'));
88
+ manifest.agents.forEach((file) => {
89
+ const relative = path.relative(cwd, file);
90
+ console.log(chalk.dim(` - ${relative}`));
91
+ });
92
+ console.log('');
93
+ }
94
94
 
95
- if (manifest.slashCommands.length > 0) {
96
- console.log(chalk.cyan(' Slash Commands:'));
97
- manifest.slashCommands.forEach((file) => {
98
- const relative = path.relative(cwd, file);
99
- console.log(chalk.dim(` - ${relative}`));
100
- });
101
- console.log('');
95
+ if (manifest.slashCommands.length > 0) {
96
+ console.log(chalk.dim(' Slash Commands:'));
97
+ manifest.slashCommands.forEach((file) => {
98
+ const relative = path.relative(cwd, file);
99
+ console.log(chalk.dim(` - ${relative}`));
100
+ });
101
+ console.log('');
102
+ }
103
+
104
+ if (manifest.rules.length > 0) {
105
+ console.log(chalk.dim(' Rules:'));
106
+ manifest.rules.forEach((file) => {
107
+ const relative = path.relative(cwd, file);
108
+ console.log(chalk.dim(` - ${relative}`));
109
+ });
110
+ console.log('');
111
+ }
112
+ } else {
113
+ console.log(chalk.yellow('🔄 Templates: None found\n'));
102
114
  }
103
115
 
104
- if (manifest.rules.length > 0) {
105
- console.log(chalk.cyan(' Rules:'));
106
- manifest.rules.forEach((file) => {
107
- const relative = path.relative(cwd, file);
108
- console.log(chalk.dim(` - ${relative}`));
116
+ // MCP servers section
117
+ if (nonRegistryServers.length > 0) {
118
+ console.log(chalk.yellow('🔍 MCP Servers (not in registry):\n'));
119
+ nonRegistryServers.forEach((server) => {
120
+ console.log(chalk.dim(` - ${server}`));
109
121
  });
110
- console.log('');
122
+ console.log(chalk.dim('\n Possible reasons:'));
123
+ console.log(chalk.dim(' 1. Removed from Flow registry'));
124
+ console.log(chalk.dim(' 2. Custom installation\n'));
125
+ } else {
126
+ console.log(chalk.green('✓ MCP Servers: All in registry\n'));
111
127
  }
112
128
 
113
- console.log(chalk.green('✓ Preserved:'));
129
+ // Preserved files section
130
+ console.log(chalk.green('✓ Preserved:\n'));
114
131
  manifest.preserve.forEach((file) => {
115
132
  const relative = path.relative(cwd, file);
116
133
  if (fs.existsSync(file)) {
@@ -157,3 +174,88 @@ export async function confirmSync(): Promise<boolean> {
157
174
  ]);
158
175
  return confirm;
159
176
  }
177
+
178
+ /**
179
+ * Check MCP servers - find servers not in Flow registry
180
+ */
181
+ export async function checkMCPServers(cwd: string): Promise<string[]> {
182
+ const mcpPath = path.join(cwd, '.mcp.json');
183
+
184
+ if (!fs.existsSync(mcpPath)) {
185
+ return [];
186
+ }
187
+
188
+ try {
189
+ const content = await fs.promises.readFile(mcpPath, 'utf-8');
190
+ const mcpConfig = JSON.parse(content);
191
+
192
+ if (!mcpConfig.mcpServers) {
193
+ return [];
194
+ }
195
+
196
+ const installedServers = Object.keys(mcpConfig.mcpServers);
197
+ const registryServers = Object.keys(MCP_SERVER_REGISTRY);
198
+
199
+ // Find servers not in registry
200
+ return installedServers.filter(id => !registryServers.includes(id));
201
+ } catch (error) {
202
+ console.warn(chalk.yellow('⚠ Failed to read .mcp.json'));
203
+ return [];
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Select servers to remove
209
+ */
210
+ export async function selectServersToRemove(servers: string[]): Promise<string[]> {
211
+ const { default: inquirer } = await import('inquirer');
212
+ const { selected } = await inquirer.prompt([
213
+ {
214
+ type: 'checkbox',
215
+ name: 'selected',
216
+ message: '選擇要刪除既 servers:',
217
+ choices: servers.map(s => ({ name: s, value: s })),
218
+ },
219
+ ]);
220
+ return selected;
221
+ }
222
+
223
+ /**
224
+ * Remove MCP servers from .mcp.json
225
+ */
226
+ export async function removeMCPServers(cwd: string, serversToRemove: string[]): Promise<number> {
227
+ if (serversToRemove.length === 0) {
228
+ return 0;
229
+ }
230
+
231
+ const mcpPath = path.join(cwd, '.mcp.json');
232
+
233
+ try {
234
+ const content = await fs.promises.readFile(mcpPath, 'utf-8');
235
+ const mcpConfig = JSON.parse(content);
236
+
237
+ if (!mcpConfig.mcpServers) {
238
+ return 0;
239
+ }
240
+
241
+ let removedCount = 0;
242
+ for (const server of serversToRemove) {
243
+ if (mcpConfig.mcpServers[server]) {
244
+ delete mcpConfig.mcpServers[server];
245
+ removedCount++;
246
+ }
247
+ }
248
+
249
+ // Write back
250
+ await fs.promises.writeFile(
251
+ mcpPath,
252
+ JSON.stringify(mcpConfig, null, 2) + '\n',
253
+ 'utf-8'
254
+ );
255
+
256
+ return removedCount;
257
+ } catch (error) {
258
+ console.warn(chalk.yellow('⚠ Failed to update .mcp.json'));
259
+ return 0;
260
+ }
261
+ }