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.
- package/.claude/docs/knowledge-base/01-setup-onboarding/README.md +0 -1
- package/.claude/docs/knowledge-base/README.md +1 -1
- package/.claude/docs/knowledge-base/configuration/README.md +0 -20
- package/.workflow/bridges/index.js +36 -3
- package/package.json +1 -1
- package/scripts/flow-bridge-state.js +367 -0
- package/scripts/flow-bridge.js +75 -17
- package/scripts/hooks/entry/claude-code/session-start.js +22 -0
- package/scripts/hooks/entry/cursor/session-start.js +23 -0
- package/scripts/hooks/entry/gemini-cli/session-start.js +22 -0
- package/scripts/hooks/entry/opencode/session-start.js +23 -0
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
@@ -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
|
+
};
|
package/scripts/flow-bridge.js
CHANGED
|
@@ -62,30 +62,44 @@ function listBridges() {
|
|
|
62
62
|
{
|
|
63
63
|
id: 'claude-code',
|
|
64
64
|
name: 'Claude Code',
|
|
65
|
-
status: '
|
|
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: '
|
|
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: '
|
|
86
|
+
status: 'full',
|
|
80
87
|
folder: '.opencode',
|
|
81
|
-
rulesFile: '
|
|
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: '
|
|
85
|
-
name: '
|
|
86
|
-
status: '
|
|
87
|
-
folder: '
|
|
88
|
-
rulesFile: '
|
|
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 === '
|
|
97
|
-
bridge.status === '
|
|
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}${
|
|
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({
|
|
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
|
|
220
|
-
console.log(' status
|
|
221
|
-
console.log(' list
|
|
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);
|