wogiflow 1.1.0 → 1.1.2

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.
@@ -48,7 +48,6 @@ This analyzes your codebase and populates:
48
48
  | [Onboarding](./onboarding-existing.md) | Analyze and configure existing projects |
49
49
  | [Component Indexing](./component-indexing.md) | Auto-scan and register components |
50
50
  | [Framework Detection](./framework-detection.md) | Auto-detect tech stack and suggest skills |
51
- | [Team Setup](./team-setup.md) | Configure team sync and shared knowledge |
52
51
 
53
52
  ---
54
53
 
@@ -42,7 +42,7 @@ npx flow onboard
42
42
  Unlike feature-by-feature documentation, this knowledge base is organized by **purpose** - what you're trying to accomplish:
43
43
 
44
44
  ### 1. Setting Up (Once per project)
45
- Everything in [01-setup-onboarding](./01-setup-onboarding/) helps you get WogiFlow configured for your project. This includes analyzing your codebase, populating decisions and component registries, and setting up team sync.
45
+ Everything in [01-setup-onboarding](./01-setup-onboarding/) helps you get WogiFlow configured for your project. This includes analyzing your codebase and populating decisions and component registries.
46
46
 
47
47
  ### 2. Executing Tasks (Daily workflow)
48
48
  The [02-task-execution](./02-task-execution/) category is the heart of WogiFlow. It explains the entire execution pipeline from task selection through completion, including:
@@ -133,25 +133,6 @@ cat .workflow/config.json
133
133
  }
134
134
  ```
135
135
 
136
- ### Team-Optimized
137
-
138
- ```json
139
- {
140
- "team": {
141
- "enabled": true,
142
- "sync": {
143
- "decisions": true,
144
- "skills": true,
145
- "componentIndex": true
146
- }
147
- },
148
- "knowledgeRouting": {
149
- "autoDetect": true,
150
- "modelSpecificLearning": true
151
- }
152
- }
153
- ```
154
-
155
136
  ### Cost-Optimized (Hybrid Mode)
156
137
 
157
138
  ```json
@@ -225,4 +206,3 @@ See [all-options.md](./all-options.md) for complete configuration reference with
225
206
 
226
207
  - [All Options](./all-options.md) - Complete reference
227
208
  - [Task Execution](../02-task-execution/) - Execution config details
228
- - [Team Setup](../01-setup-onboarding/team-setup.md) - Team config
@@ -92,23 +92,53 @@ function getCliType(projectDir = process.cwd()) {
92
92
  }
93
93
  }
94
94
 
95
+ /**
96
+ * Detect which CLI is currently running based on environment
97
+ * @param {string} projectDir - Project root directory
98
+ * @returns {string} Detected CLI type
99
+ */
100
+ function detectRunningCli(projectDir = process.cwd()) {
101
+ // Priority 1: Environment variables set by CLI tools
102
+ if (process.env.CLAUDE_CODE_ENTRY_POINT) return 'claude-code';
103
+ if (process.env.CURSOR_SESSION_ID) return 'cursor';
104
+ if (process.env.OPENCODE_SESSION) return 'opencode';
105
+
106
+ // Priority 2: Check caller stack for hook path hints
107
+ try {
108
+ const stack = new Error().stack || '';
109
+ if (stack.includes('/claude-code/')) return 'claude-code';
110
+ if (stack.includes('/gemini-cli/')) return 'gemini-cli';
111
+ if (stack.includes('/cursor/')) return 'cursor';
112
+ if (stack.includes('/opencode/')) return 'opencode';
113
+ } catch {
114
+ // Ignore stack parsing errors
115
+ }
116
+
117
+ // Priority 3: Config file setting
118
+ return getCliType(projectDir);
119
+ }
120
+
95
121
  /**
96
122
  * Get the bridge instance for the current CLI type
97
123
  * @param {Object} options - Options to pass to bridge constructor
98
124
  * @param {string} options.projectDir - Project root directory
125
+ * @param {string} options.cliType - Override CLI type (optional)
99
126
  * @param {boolean} options.verbose - Enable verbose logging
100
127
  * @returns {BaseBridge} Bridge instance
101
128
  */
102
129
  function getBridge(options = {}) {
103
130
  const projectDir = options.projectDir || process.cwd();
104
- const cliType = getCliType(projectDir);
131
+ // Allow explicit CLI type override, otherwise detect from config
132
+ const cliType = options.cliType || getCliType(projectDir);
105
133
 
106
134
  loadBridges();
107
135
 
108
136
  const BridgeLoader = bridges[cliType];
109
137
  if (!BridgeLoader) {
110
138
  // If no specific bridge exists, return null (manual mode)
111
- console.warn(`No bridge available for CLI type: ${cliType}`);
139
+ if (options.verbose) {
140
+ console.warn(`No bridge available for CLI type: ${cliType}`);
141
+ }
112
142
  return null;
113
143
  }
114
144
 
@@ -122,16 +152,18 @@ function getBridge(options = {}) {
122
152
  /**
123
153
  * Sync the current CLI bridge
124
154
  * @param {Object} options - Options
155
+ * @param {string} options.cliType - Override CLI type (optional)
125
156
  * @returns {Object} Sync result
126
157
  */
127
158
  async function syncBridge(options = {}) {
128
159
  const bridge = getBridge(options);
160
+ const cliType = options.cliType || getCliType(options.projectDir);
129
161
 
130
162
  if (!bridge) {
131
163
  return {
132
164
  success: false,
133
165
  error: 'No bridge available for current CLI type',
134
- cliType: getCliType(options.projectDir)
166
+ cliType
135
167
  };
136
168
  }
137
169
 
@@ -161,6 +193,7 @@ module.exports = {
161
193
  getBridge,
162
194
  syncBridge,
163
195
  getCliType,
196
+ detectRunningCli,
164
197
  listAvailableBridges,
165
198
  isBridgeAvailable,
166
199
  BaseBridge: require('./base-bridge')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wogiflow",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "AI-powered development workflow management system with multi-model support",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -0,0 +1,367 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Bridge State Tracker
5
+ *
6
+ * Tracks CLI bridge sync state and provides auto-sync functionality.
7
+ * Enables seamless generation of CLI instruction files on session start.
8
+ *
9
+ * Usage:
10
+ * const { autoSyncBridge, needsSync } = require('./flow-bridge-state');
11
+ *
12
+ * // Auto-sync on session start (non-blocking)
13
+ * await autoSyncBridge('claude-code', { silent: true });
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const crypto = require('crypto');
19
+
20
+ // Project paths
21
+ const PROJECT_ROOT = path.resolve(__dirname, '..');
22
+ const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
23
+ const STATE_DIR = path.join(WORKFLOW_DIR, 'state');
24
+ const CONFIG_PATH = path.join(WORKFLOW_DIR, 'config.json');
25
+ const SYNC_STATE_PATH = path.join(STATE_DIR, 'bridge-sync.json');
26
+
27
+ // CLI type to output file mapping
28
+ const CLI_OUTPUT_FILES = {
29
+ 'claude-code': 'CLAUDE.md',
30
+ 'gemini-cli': 'GEMINI.md',
31
+ 'cursor': '.cursor/rules/wogi-flow.mdc',
32
+ 'opencode': '.opencode/agents.md',
33
+ 'codex': 'AGENTS.md',
34
+ 'kimi': 'KIMI.md'
35
+ };
36
+
37
+ /**
38
+ * Safe JSON parse with prototype pollution protection
39
+ * @param {string} filePath - Path to JSON file
40
+ * @param {*} defaultValue - Default value if parsing fails
41
+ * @returns {*} Parsed object or default value
42
+ */
43
+ function safeJsonParse(filePath, defaultValue = {}) {
44
+ try {
45
+ const content = fs.readFileSync(filePath, 'utf-8');
46
+ const parsed = JSON.parse(content);
47
+
48
+ // Check for prototype pollution keys
49
+ const checkDangerous = (obj, depth = 0) => {
50
+ if (depth > 10 || !obj || typeof obj !== 'object') return false;
51
+ const dangerous = ['__proto__', 'constructor', 'prototype'];
52
+ for (const key of Object.keys(obj)) {
53
+ if (dangerous.includes(key)) return true;
54
+ if (obj[key] && typeof obj[key] === 'object') {
55
+ if (checkDangerous(obj[key], depth + 1)) return true;
56
+ }
57
+ }
58
+ return false;
59
+ };
60
+
61
+ if (checkDangerous(parsed)) {
62
+ return defaultValue;
63
+ }
64
+ return parsed;
65
+ } catch {
66
+ return defaultValue;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Calculate MD5 hash of config.json for staleness detection
72
+ * @returns {string} Hash of config content
73
+ */
74
+ function getConfigChecksum() {
75
+ try {
76
+ const content = fs.readFileSync(CONFIG_PATH, 'utf-8');
77
+ return crypto.createHash('md5').update(content).digest('hex');
78
+ } catch {
79
+ return '';
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Get the output file path for a CLI type
85
+ * @param {string} cliType - CLI type
86
+ * @returns {string} Full path to output file
87
+ */
88
+ function getOutputFilePath(cliType) {
89
+ const filename = CLI_OUTPUT_FILES[cliType];
90
+ if (!filename) return null;
91
+ return path.join(PROJECT_ROOT, filename);
92
+ }
93
+
94
+ /**
95
+ * Read current sync state
96
+ * @returns {Object} Sync state
97
+ */
98
+ function readSyncState() {
99
+ return safeJsonParse(SYNC_STATE_PATH, { syncs: {}, version: 1 });
100
+ }
101
+
102
+ /**
103
+ * Write sync state
104
+ * @param {Object} state - State to write
105
+ */
106
+ function writeSyncState(state) {
107
+ try {
108
+ // Ensure state directory exists
109
+ if (!fs.existsSync(STATE_DIR)) {
110
+ fs.mkdirSync(STATE_DIR, { recursive: true });
111
+ }
112
+ fs.writeFileSync(SYNC_STATE_PATH, JSON.stringify(state, null, 2));
113
+ } catch (err) {
114
+ if (process.env.DEBUG) {
115
+ console.error(`[bridge-state] Failed to write sync state: ${err.message}`);
116
+ }
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Get last sync time for a CLI type
122
+ * @param {string} cliType - CLI type
123
+ * @returns {string|null} ISO timestamp or null
124
+ */
125
+ function getLastSyncTime(cliType) {
126
+ const state = readSyncState();
127
+ return state.syncs?.[cliType]?.lastSync || null;
128
+ }
129
+
130
+ /**
131
+ * Update sync time for a CLI type
132
+ * @param {string} cliType - CLI type
133
+ * @param {string} configHash - Current config hash
134
+ */
135
+ function setLastSyncTime(cliType, configHash) {
136
+ const state = readSyncState();
137
+ if (!state.syncs) state.syncs = {};
138
+ state.syncs[cliType] = {
139
+ lastSync: new Date().toISOString(),
140
+ configHash
141
+ };
142
+ writeSyncState(state);
143
+ }
144
+
145
+ /**
146
+ * Check if a CLI bridge needs to be synced
147
+ * @param {string} cliType - CLI type to check
148
+ * @returns {Object} { needsSync: boolean, reason: string }
149
+ */
150
+ function needsSync(cliType) {
151
+ // Check if output file exists
152
+ const outputPath = getOutputFilePath(cliType);
153
+ if (!outputPath) {
154
+ return { needsSync: false, reason: 'unknown-cli' };
155
+ }
156
+
157
+ if (!fs.existsSync(outputPath)) {
158
+ return { needsSync: true, reason: 'file-missing' };
159
+ }
160
+
161
+ // Check if config has changed since last sync
162
+ const state = readSyncState();
163
+ const cliState = state.syncs?.[cliType];
164
+
165
+ if (!cliState) {
166
+ return { needsSync: true, reason: 'never-synced' };
167
+ }
168
+
169
+ const currentHash = getConfigChecksum();
170
+ if (cliState.configHash !== currentHash) {
171
+ return { needsSync: true, reason: 'config-changed' };
172
+ }
173
+
174
+ return { needsSync: false, reason: 'up-to-date' };
175
+ }
176
+
177
+ /**
178
+ * Auto-sync a CLI bridge if needed
179
+ * @param {string} cliType - CLI type to sync
180
+ * @param {Object} options - Options
181
+ * @param {boolean} options.silent - Suppress output
182
+ * @param {boolean} options.force - Force sync even if up-to-date
183
+ * @returns {Object} { synced: boolean, reason: string }
184
+ */
185
+ async function autoSyncBridge(cliType, options = {}) {
186
+ const { silent = false, force = false } = options;
187
+
188
+ // Check if sync is needed
189
+ if (!force) {
190
+ const check = needsSync(cliType);
191
+ if (!check.needsSync) {
192
+ if (!silent && process.env.DEBUG) {
193
+ console.error(`[bridge-state] ${cliType}: ${check.reason}, skipping sync`);
194
+ }
195
+ return { synced: false, reason: check.reason };
196
+ }
197
+ }
198
+
199
+ // Load bridges module
200
+ let bridges;
201
+ try {
202
+ bridges = require(path.join(PROJECT_ROOT, '.workflow', 'bridges'));
203
+ } catch (err) {
204
+ if (process.env.DEBUG) {
205
+ console.error(`[bridge-state] Failed to load bridges: ${err.message}`);
206
+ }
207
+ return { synced: false, reason: 'bridges-unavailable', error: err.message };
208
+ }
209
+
210
+ // Get bridge for the specified CLI type
211
+ let bridge;
212
+ try {
213
+ // Pass explicit cliType to override config default
214
+ bridge = bridges.getBridge({
215
+ projectDir: PROJECT_ROOT,
216
+ cliType: cliType,
217
+ verbose: !silent
218
+ });
219
+
220
+ // Fallback: Try loading the specific bridge directly
221
+ if (!bridge) {
222
+ const BridgeClass = require(path.join(PROJECT_ROOT, '.workflow', 'bridges', `${cliType}-bridge`));
223
+ bridge = new BridgeClass({
224
+ projectDir: PROJECT_ROOT,
225
+ verbose: !silent
226
+ });
227
+ }
228
+ } catch (err) {
229
+ if (process.env.DEBUG) {
230
+ console.error(`[bridge-state] Failed to get bridge for ${cliType}: ${err.message}`);
231
+ }
232
+ return { synced: false, reason: 'bridge-load-failed', error: err.message };
233
+ }
234
+
235
+ // Run sync
236
+ try {
237
+ await bridge.sync();
238
+
239
+ // Update state
240
+ const configHash = getConfigChecksum();
241
+ setLastSyncTime(cliType, configHash);
242
+
243
+ if (!silent) {
244
+ console.error(`[bridge-state] Synced ${cliType} bridge`);
245
+ }
246
+
247
+ return { synced: true, reason: 'success' };
248
+ } catch (err) {
249
+ if (process.env.DEBUG) {
250
+ console.error(`[bridge-state] Sync failed for ${cliType}: ${err.message}`);
251
+ }
252
+ return { synced: false, reason: 'sync-failed', error: err.message };
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Sync all enabled CLIs
258
+ * @param {Object} options - Options
259
+ * @returns {Object} Results for each CLI
260
+ */
261
+ async function syncAllEnabledClis(options = {}) {
262
+ const config = safeJsonParse(CONFIG_PATH, {});
263
+ const primaryCli = config.cli?.type || 'claude-code';
264
+ const enabledClis = config.cli?.enabled || [primaryCli];
265
+
266
+ const results = {};
267
+ for (const cliType of enabledClis) {
268
+ results[cliType] = await autoSyncBridge(cliType, options);
269
+ }
270
+
271
+ return results;
272
+ }
273
+
274
+ /**
275
+ * Detect which CLI is currently running
276
+ * Based on environment variables and caller context
277
+ * @returns {string} CLI type
278
+ */
279
+ function detectRunningCli() {
280
+ // Priority 1: Environment variables
281
+ if (process.env.CLAUDE_CODE_ENTRY_POINT) return 'claude-code';
282
+ if (process.env.GEMINI_API_KEY && !process.env.CLAUDE_CODE_ENTRY_POINT) return 'gemini-cli';
283
+ if (process.env.CURSOR_SESSION_ID) return 'cursor';
284
+ if (process.env.OPENCODE_SESSION) return 'opencode';
285
+
286
+ // Priority 2: Check caller stack for hook path hints
287
+ try {
288
+ const stack = new Error().stack || '';
289
+ if (stack.includes('/claude-code/')) return 'claude-code';
290
+ if (stack.includes('/gemini-cli/')) return 'gemini-cli';
291
+ if (stack.includes('/cursor/')) return 'cursor';
292
+ if (stack.includes('/opencode/')) return 'opencode';
293
+ } catch {
294
+ // Ignore stack parsing errors
295
+ }
296
+
297
+ // Priority 3: Config file setting
298
+ const config = safeJsonParse(CONFIG_PATH, {});
299
+ if (config.cli?.type) return config.cli.type;
300
+
301
+ // Default
302
+ return 'claude-code';
303
+ }
304
+
305
+ // CLI interface
306
+ if (require.main === module) {
307
+ const args = process.argv.slice(2);
308
+ const command = args[0];
309
+
310
+ const run = async () => {
311
+ switch (command) {
312
+ case 'check': {
313
+ const cliType = args[1] || detectRunningCli();
314
+ const result = needsSync(cliType);
315
+ console.log(JSON.stringify({ cliType, ...result }, null, 2));
316
+ break;
317
+ }
318
+
319
+ case 'sync': {
320
+ const cliType = args[1] || detectRunningCli();
321
+ const result = await autoSyncBridge(cliType, { silent: false, force: args.includes('--force') });
322
+ console.log(JSON.stringify({ cliType, ...result }, null, 2));
323
+ break;
324
+ }
325
+
326
+ case 'sync-all': {
327
+ const results = await syncAllEnabledClis({ silent: false, force: args.includes('--force') });
328
+ console.log(JSON.stringify(results, null, 2));
329
+ break;
330
+ }
331
+
332
+ case 'detect': {
333
+ const cliType = detectRunningCli();
334
+ console.log(cliType);
335
+ break;
336
+ }
337
+
338
+ default:
339
+ console.log('Usage: flow-bridge-state <command> [options]');
340
+ console.log('');
341
+ console.log('Commands:');
342
+ console.log(' check [cli-type] Check if sync is needed');
343
+ console.log(' sync [cli-type] Sync a CLI bridge');
344
+ console.log(' sync-all Sync all enabled CLIs');
345
+ console.log(' detect Detect running CLI type');
346
+ console.log('');
347
+ console.log('Options:');
348
+ console.log(' --force Force sync even if up-to-date');
349
+ }
350
+ };
351
+
352
+ run().catch(err => {
353
+ console.error(`Error: ${err.message}`);
354
+ process.exit(1);
355
+ });
356
+ }
357
+
358
+ module.exports = {
359
+ needsSync,
360
+ autoSyncBridge,
361
+ syncAllEnabledClis,
362
+ detectRunningCli,
363
+ getConfigChecksum,
364
+ getLastSyncTime,
365
+ setLastSyncTime,
366
+ CLI_OUTPUT_FILES
367
+ };
@@ -62,30 +62,44 @@ function listBridges() {
62
62
  {
63
63
  id: 'claude-code',
64
64
  name: 'Claude Code',
65
- status: 'implemented',
65
+ status: 'full',
66
66
  folder: '.claude',
67
67
  rulesFile: 'CLAUDE.md'
68
68
  },
69
69
  {
70
70
  id: 'gemini-cli',
71
71
  name: 'Gemini CLI',
72
- status: 'planned',
72
+ status: 'full',
73
73
  folder: '.gemini',
74
74
  rulesFile: 'GEMINI.md'
75
75
  },
76
+ {
77
+ id: 'cursor',
78
+ name: 'Cursor',
79
+ status: 'full',
80
+ folder: '.cursor',
81
+ rulesFile: '.cursor/rules/wogi-flow.mdc'
82
+ },
76
83
  {
77
84
  id: 'opencode',
78
85
  name: 'OpenCode',
79
- status: 'planned',
86
+ status: 'full',
80
87
  folder: '.opencode',
81
- rulesFile: 'OPENCODE.md'
88
+ rulesFile: '.opencode/agents.md'
89
+ },
90
+ {
91
+ id: 'codex',
92
+ name: 'Codex CLI',
93
+ status: 'soft',
94
+ folder: '.codex',
95
+ rulesFile: 'AGENTS.md'
82
96
  },
83
97
  {
84
- id: 'other',
85
- name: 'Other / Manual',
86
- status: 'manual',
87
- folder: 'N/A',
88
- rulesFile: 'N/A'
98
+ id: 'kimi',
99
+ name: 'Kimi CLI',
100
+ status: 'soft',
101
+ folder: '.kimi',
102
+ rulesFile: 'KIMI.md'
89
103
  }
90
104
  ];
91
105
 
@@ -93,12 +107,14 @@ function listBridges() {
93
107
 
94
108
  for (const bridge of availableBridges) {
95
109
  const isCurrent = bridge.id === currentCli;
96
- const statusColor = bridge.status === 'implemented' ? colors.green :
97
- bridge.status === 'planned' ? colors.yellow : colors.cyan;
110
+ const statusColor = bridge.status === 'full' ? colors.green :
111
+ bridge.status === 'soft' ? colors.yellow : colors.cyan;
112
+ const statusLabel = bridge.status === 'full' ? 'full parity (hooks)' :
113
+ bridge.status === 'soft' ? 'soft parity (rules only)' : bridge.status;
98
114
  const indicator = isCurrent ? `${colors.green}→${colors.reset}` : ' ';
99
115
 
100
116
  console.log(` ${indicator} ${colors.bold}${bridge.name}${colors.reset} (${bridge.id})`);
101
- console.log(` Status: ${statusColor}${bridge.status}${colors.reset}`);
117
+ console.log(` Status: ${statusColor}${statusLabel}${colors.reset}`);
102
118
  console.log(` Folder: ${bridge.folder}`);
103
119
  console.log(` Rules: ${bridge.rulesFile}`);
104
120
  console.log('');
@@ -150,12 +166,43 @@ function showStatus() {
150
166
  console.log('');
151
167
  }
152
168
 
169
+ /**
170
+ * Normalize CLI type argument to standard format
171
+ */
172
+ function normalizeCliType(input) {
173
+ if (!input) return null;
174
+ const normalized = input.toLowerCase().trim();
175
+ const aliases = {
176
+ 'gemini': 'gemini-cli',
177
+ 'gemini-cli': 'gemini-cli',
178
+ 'claude': 'claude-code',
179
+ 'claude-code': 'claude-code',
180
+ 'opencode': 'opencode',
181
+ 'cursor': 'cursor',
182
+ 'codex': 'codex',
183
+ 'kimi': 'kimi'
184
+ };
185
+ return aliases[normalized] || null;
186
+ }
187
+
153
188
  /**
154
189
  * Sync bridge
155
190
  */
156
191
  async function syncBridge(options = {}) {
157
192
  const verbose = options.verbose || process.argv.includes('--verbose') || process.argv.includes('-v');
158
193
 
194
+ // Check for CLI type argument (e.g., "flow bridge sync gemini")
195
+ const cliTypeArg = process.argv[3];
196
+ const requestedCliType = normalizeCliType(cliTypeArg);
197
+
198
+ if (cliTypeArg && !requestedCliType) {
199
+ console.error(`${colors.red}Error:${colors.reset} Unknown CLI type: ${cliTypeArg}`);
200
+ console.error('Available types: claude-code, gemini-cli, cursor, opencode, codex, kimi');
201
+ process.exit(1);
202
+ }
203
+
204
+ const targetCliType = requestedCliType || getCliType();
205
+
159
206
  console.log(`${colors.cyan}Syncing CLI bridge...${colors.reset}`);
160
207
  console.log('');
161
208
 
@@ -170,7 +217,11 @@ async function syncBridge(options = {}) {
170
217
  process.exit(1);
171
218
  }
172
219
 
173
- const result = await bridges.syncBridge({ verbose, projectDir: PROJECT_ROOT });
220
+ const result = await bridges.syncBridge({
221
+ verbose,
222
+ projectDir: PROJECT_ROOT,
223
+ cliType: targetCliType
224
+ });
174
225
 
175
226
  if (result.success) {
176
227
  console.log(`${colors.green}✓ Bridge sync complete${colors.reset}`);
@@ -213,11 +264,18 @@ switch (command) {
213
264
  listBridges();
214
265
  break;
215
266
  default:
216
- console.log('Usage: flow bridge [sync|status|list]');
267
+ console.log('Usage: flow bridge [sync|status|list] [cli-type]');
217
268
  console.log('');
218
269
  console.log('Commands:');
219
- console.log(' sync Sync .workflow/ config to CLI-specific folder');
220
- console.log(' status Show current bridge configuration');
221
- console.log(' list List available CLI bridges');
270
+ console.log(' sync [cli-type] Sync .workflow/ config to CLI-specific folder');
271
+ console.log(' status Show current bridge configuration');
272
+ console.log(' list List available CLI bridges');
273
+ console.log('');
274
+ console.log('CLI Types:');
275
+ console.log(' claude-code, gemini-cli (or gemini), cursor, opencode, codex, kimi');
276
+ console.log('');
277
+ console.log('Examples:');
278
+ console.log(' flow bridge sync # Sync default CLI from config');
279
+ console.log(' flow bridge sync gemini # Sync Gemini CLI specifically');
222
280
  process.exit(1);
223
281
  }
@@ -11,8 +11,30 @@ const { gatherSessionContext } = require('../../core/session-context');
11
11
  const { claudeCodeAdapter } = require('../../adapters/claude-code');
12
12
  const { setCliSessionId, clearStaleCurrentTaskAsync } = require('../../../flow-session-state');
13
13
 
14
+ // Lazy-load bridge state to avoid circular dependencies
15
+ let autoSyncBridge = null;
16
+ function getAutoSyncBridge() {
17
+ if (!autoSyncBridge) {
18
+ try {
19
+ autoSyncBridge = require('../../../flow-bridge-state').autoSyncBridge;
20
+ } catch {
21
+ autoSyncBridge = async () => ({ synced: false, reason: 'unavailable' });
22
+ }
23
+ }
24
+ return autoSyncBridge;
25
+ }
26
+
14
27
  async function main() {
15
28
  try {
29
+ // Auto-sync bridge if needed (non-blocking, silent)
30
+ try {
31
+ const syncFn = getAutoSyncBridge();
32
+ await syncFn('claude-code', { silent: true });
33
+ } catch (err) {
34
+ if (process.env.DEBUG) {
35
+ console.error(`[session-start] Bridge auto-sync failed: ${err.message}`);
36
+ }
37
+ }
16
38
  // Read input from stdin
17
39
  let inputData = '';
18
40
  for await (const chunk of process.stdin) {
@@ -31,6 +31,19 @@ function getSessionState() {
31
31
  return sessionState;
32
32
  }
33
33
 
34
+ // Lazy-load bridge state for auto-sync
35
+ let autoSyncBridge = null;
36
+ function getAutoSyncBridge() {
37
+ if (!autoSyncBridge) {
38
+ try {
39
+ autoSyncBridge = require('../../../flow-bridge-state').autoSyncBridge;
40
+ } catch {
41
+ autoSyncBridge = async () => ({ synced: false, reason: 'unavailable' });
42
+ }
43
+ }
44
+ return autoSyncBridge;
45
+ }
46
+
34
47
  /**
35
48
  * Read stdin with size limit protection
36
49
  * @returns {string} Input data, truncated if over limit
@@ -59,6 +72,16 @@ async function readStdinWithLimit() {
59
72
  * Handle session start event
60
73
  */
61
74
  async function handleSessionStart(input) {
75
+ // Auto-sync bridge if needed (non-blocking, silent)
76
+ try {
77
+ const syncFn = getAutoSyncBridge();
78
+ await syncFn('cursor', { silent: true });
79
+ } catch (err) {
80
+ if (process.env.DEBUG) {
81
+ console.error(`[cursor/session-start] Bridge auto-sync failed: ${err.message}`);
82
+ }
83
+ }
84
+
62
85
  try {
63
86
  const parsedInput = cursorAdapter.parseInput(input);
64
87
 
@@ -22,8 +22,30 @@ try {
22
22
  clearStaleCurrentTaskAsync = async () => {};
23
23
  }
24
24
 
25
+ // Lazy-load bridge state for auto-sync
26
+ let autoSyncBridge = null;
27
+ function getAutoSyncBridge() {
28
+ if (!autoSyncBridge) {
29
+ try {
30
+ autoSyncBridge = require('../../../flow-bridge-state').autoSyncBridge;
31
+ } catch {
32
+ autoSyncBridge = async () => ({ synced: false, reason: 'unavailable' });
33
+ }
34
+ }
35
+ return autoSyncBridge;
36
+ }
37
+
25
38
  async function main() {
26
39
  try {
40
+ // Auto-sync bridge if needed (non-blocking, silent)
41
+ try {
42
+ const syncFn = getAutoSyncBridge();
43
+ await syncFn('gemini-cli', { silent: true });
44
+ } catch (err) {
45
+ if (process.env.DEBUG) {
46
+ console.error(`[session-start] Bridge auto-sync failed: ${err.message}`);
47
+ }
48
+ }
27
49
  // Read input from stdin
28
50
  let inputData = '';
29
51
  for await (const chunk of process.stdin) {
@@ -26,12 +26,35 @@ function getSessionState() {
26
26
  return sessionState;
27
27
  }
28
28
 
29
+ // Lazy-load bridge state for auto-sync
30
+ let autoSyncBridge = null;
31
+ function getAutoSyncBridge() {
32
+ if (!autoSyncBridge) {
33
+ try {
34
+ autoSyncBridge = require('../../../flow-bridge-state').autoSyncBridge;
35
+ } catch {
36
+ autoSyncBridge = async () => ({ synced: false, reason: 'unavailable' });
37
+ }
38
+ }
39
+ return autoSyncBridge;
40
+ }
41
+
29
42
  /**
30
43
  * Handle session start event
31
44
  * @param {Object} ctx - OpenCode plugin context
32
45
  * @returns {Object} Plugin result with additionalContext
33
46
  */
34
47
  async function handleSessionStart(ctx) {
48
+ // Auto-sync bridge if needed (non-blocking, silent)
49
+ try {
50
+ const syncFn = getAutoSyncBridge();
51
+ await syncFn('opencode', { silent: true });
52
+ } catch (err) {
53
+ if (process.env.DEBUG) {
54
+ console.error(`[opencode/session-start] Bridge auto-sync failed: ${err.message}`);
55
+ }
56
+ }
57
+
35
58
  try {
36
59
  const input = ctx || {};
37
60
  const parsedInput = opencodeAdapter.parseInput(input);