wogiflow 1.1.5 → 1.1.7
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/rules/security/security-patterns.md +33 -0
- package/.workflow/bridges/base-bridge.js +50 -8
- package/.workflow/bridges/claude-bridge.js +26 -2
- package/.workflow/bridges/index.js +2 -1
- package/README.md +10 -8
- package/package.json +1 -1
- package/scripts/flow-bridge.js +7 -0
- package/scripts/flow-health.js +47 -0
- package/scripts/flow-utils.js +99 -0
- package/scripts/flow-worktree.js +13 -1
|
@@ -141,3 +141,36 @@ execFileSync('sg', ['--pattern', pattern, '--lang', lang, '--json', path]);
|
|
|
141
141
|
- Prefer `execFile`/`execFileSync` with array arguments over `exec`/`execSync` with template strings
|
|
142
142
|
- When using template strings, escape all user-controlled values
|
|
143
143
|
- Never interpolate user input directly into shell commands
|
|
144
|
+
|
|
145
|
+
## 9. Temp Directory Isolation (Claude Code 2.1.23+)
|
|
146
|
+
|
|
147
|
+
On shared systems (CI servers, multi-user machines), use per-user temp directories to prevent permission conflicts.
|
|
148
|
+
|
|
149
|
+
**Fixed in Claude Code 2.1.23**: Per-user temp directory isolation prevents permission conflicts.
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
// Good - per-user isolation
|
|
153
|
+
const userId = process.getuid?.() ?? process.env.USER ?? process.env.USERNAME ?? 'default';
|
|
154
|
+
const tempDir = path.join(os.tmpdir(), `myapp-${userId}`);
|
|
155
|
+
|
|
156
|
+
// Bad - global temp path on shared systems
|
|
157
|
+
const tempDir = path.join(os.tmpdir(), 'myapp');
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Best practices:**
|
|
161
|
+
- Use UID on Unix systems (`process.getuid()`)
|
|
162
|
+
- Fall back to username environment variables on Windows
|
|
163
|
+
- Always provide a 'default' fallback for edge cases
|
|
164
|
+
- This pattern is used in `flow-worktree.js` for worktree isolation
|
|
165
|
+
|
|
166
|
+
## 10. Search/Grep Timeout Handling (Claude Code 2.1.23+)
|
|
167
|
+
|
|
168
|
+
**Fixed in Claude Code 2.1.23**: Ripgrep search timeouts now report errors instead of silently returning empty results.
|
|
169
|
+
|
|
170
|
+
**Impact on WogiFlow:** Component detection, auto-context loading, and pattern matching rely on search operations. Before 2.1.23, search timeouts could cause false negatives.
|
|
171
|
+
|
|
172
|
+
**Best practices:**
|
|
173
|
+
- Handle empty search results gracefully - they may indicate timeout
|
|
174
|
+
- Add retry logic for search-dependent operations
|
|
175
|
+
- Log warnings when searches return unexpectedly empty
|
|
176
|
+
- Consider fallback strategies (glob-based search if grep fails)
|
|
@@ -317,21 +317,49 @@ class BaseBridge {
|
|
|
317
317
|
|
|
318
318
|
/**
|
|
319
319
|
* Generate and write the main rules file (e.g., CLAUDE.md)
|
|
320
|
+
* @param {Object} options - Generation options
|
|
321
|
+
* @param {boolean} options.force - Force overwrite even if locally modified
|
|
320
322
|
*/
|
|
321
|
-
generateRulesFile() {
|
|
323
|
+
generateRulesFile(options = {}) {
|
|
322
324
|
const config = this.readConfig();
|
|
323
325
|
const content = this.generateRulesContent(config);
|
|
324
326
|
const rulesFilePath = path.join(this.projectDir, this.getRulesFileName());
|
|
325
327
|
|
|
328
|
+
// Check for local modifications before overwriting
|
|
329
|
+
if (fs.existsSync(rulesFilePath) && !options.force) {
|
|
330
|
+
const existingContent = fs.readFileSync(rulesFilePath, 'utf-8');
|
|
331
|
+
|
|
332
|
+
// Check if file was manually modified (missing our generation marker)
|
|
333
|
+
const hasMarker = existingContent.includes('Generated by CLI Bridge');
|
|
334
|
+
|
|
335
|
+
if (!hasMarker) {
|
|
336
|
+
// File exists but wasn't generated by us - likely manually created/modified
|
|
337
|
+
this.log(`⚠️ ${this.getRulesFileName()} appears to be manually maintained (no generation marker)`);
|
|
338
|
+
this.log(` Skipping to preserve local customizations. Use --force to overwrite.`);
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Check if content would actually change
|
|
343
|
+
if (existingContent === content) {
|
|
344
|
+
this.log(`${this.getRulesFileName()} is up to date`);
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// File has our marker - safe to overwrite as it's generated content
|
|
349
|
+
}
|
|
350
|
+
|
|
326
351
|
fs.writeFileSync(rulesFilePath, content, 'utf-8');
|
|
327
352
|
this.log(`Generated ${this.getRulesFileName()}`);
|
|
353
|
+
return true;
|
|
328
354
|
}
|
|
329
355
|
|
|
330
356
|
/**
|
|
331
357
|
* Run full sync: skills, rules, and CLI-specific setup
|
|
358
|
+
* @param {Object} options - Sync options
|
|
359
|
+
* @param {boolean} options.force - Force overwrite of locally modified files
|
|
332
360
|
* @returns {Object} Sync result summary
|
|
333
361
|
*/
|
|
334
|
-
async sync() {
|
|
362
|
+
async sync(options = {}) {
|
|
335
363
|
const startTime = Date.now();
|
|
336
364
|
const results = {
|
|
337
365
|
cliType: this.cliType,
|
|
@@ -371,10 +399,14 @@ class BaseBridge {
|
|
|
371
399
|
results.errors.push({ step: 'knowledge-files', error: err.message });
|
|
372
400
|
}
|
|
373
401
|
|
|
374
|
-
// 5. Generate rules file
|
|
402
|
+
// 5. Generate rules file (respects local modifications unless --force)
|
|
375
403
|
try {
|
|
376
|
-
this.generateRulesFile();
|
|
377
|
-
|
|
404
|
+
const generated = this.generateRulesFile({ force: options.force });
|
|
405
|
+
if (generated) {
|
|
406
|
+
results.synced.push('rules-file');
|
|
407
|
+
} else {
|
|
408
|
+
results.synced.push('rules-file-skipped');
|
|
409
|
+
}
|
|
378
410
|
} catch (err) {
|
|
379
411
|
results.errors.push({ step: 'rules-file', error: err.message });
|
|
380
412
|
}
|
|
@@ -590,13 +622,18 @@ class BaseBridge {
|
|
|
590
622
|
* @returns {string} Content with conditionals evaluated
|
|
591
623
|
*/
|
|
592
624
|
processConditionals(content, config) {
|
|
593
|
-
|
|
625
|
+
// Match INNERMOST conditionals first - those with no nested {{#if}} in body
|
|
626
|
+
// The negative lookahead (?!{{#if) ensures body contains no nested conditionals
|
|
627
|
+
const innerIfRegex = /\{\{#if\s+([^}]+)\}\}((?:(?!\{\{#if)[\s\S])*?)\{\{\/if\}\}/g;
|
|
594
628
|
|
|
595
629
|
let lastContent;
|
|
630
|
+
let iterations = 0;
|
|
631
|
+
const maxIterations = 100; // Safety limit for deeply nested templates
|
|
632
|
+
|
|
596
633
|
// Keep processing until no more changes (handles nested conditions)
|
|
597
634
|
do {
|
|
598
635
|
lastContent = content;
|
|
599
|
-
content = content.replace(
|
|
636
|
+
content = content.replace(innerIfRegex, (match, condition, body) => {
|
|
600
637
|
let value;
|
|
601
638
|
if (condition.startsWith('config.')) {
|
|
602
639
|
value = this.getNestedValue(config, condition.replace('config.', ''));
|
|
@@ -607,7 +644,12 @@ class BaseBridge {
|
|
|
607
644
|
}
|
|
608
645
|
return value ? body : '';
|
|
609
646
|
});
|
|
610
|
-
|
|
647
|
+
iterations++;
|
|
648
|
+
} while (content !== lastContent && iterations < maxIterations);
|
|
649
|
+
|
|
650
|
+
if (iterations >= maxIterations) {
|
|
651
|
+
this.log('Warning: Max iterations reached in processConditionals - possible malformed template');
|
|
652
|
+
}
|
|
611
653
|
|
|
612
654
|
return content;
|
|
613
655
|
}
|
|
@@ -419,7 +419,7 @@ Last synced: ${new Date().toISOString()}
|
|
|
419
419
|
}
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
-
|
|
422
|
+
const settings = {
|
|
423
423
|
permissions: {
|
|
424
424
|
allow: wildcardPermissions,
|
|
425
425
|
},
|
|
@@ -428,6 +428,20 @@ Last synced: ${new Date().toISOString()}
|
|
|
428
428
|
_wogiFlowVersion: '2.0.0',
|
|
429
429
|
_generatedAt: new Date().toISOString(),
|
|
430
430
|
};
|
|
431
|
+
|
|
432
|
+
// Add spinnerVerbs if configured (requires Claude Code 2.1.23+)
|
|
433
|
+
const spinnerVerbs = config.hooks?.claudeCode?.spinnerVerbs;
|
|
434
|
+
if (Array.isArray(spinnerVerbs) && spinnerVerbs.length > 0) {
|
|
435
|
+
settings.spinnerVerbs = spinnerVerbs;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Add spinnerTipsEnabled if explicitly set (default is true in Claude Code)
|
|
439
|
+
const spinnerTipsEnabled = config.hooks?.claudeCode?.spinnerTipsEnabled;
|
|
440
|
+
if (spinnerTipsEnabled === false) {
|
|
441
|
+
settings.spinnerTipsEnabled = false;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return settings;
|
|
431
445
|
}
|
|
432
446
|
|
|
433
447
|
/**
|
|
@@ -448,7 +462,7 @@ Last synced: ${new Date().toISOString()}
|
|
|
448
462
|
|
|
449
463
|
const newSettings = this.generateSettings(config);
|
|
450
464
|
|
|
451
|
-
// Merge: keep existing hooks, use new permissions
|
|
465
|
+
// Merge: keep existing hooks, use new permissions and spinner settings
|
|
452
466
|
const mergedSettings = {
|
|
453
467
|
permissions: newSettings.permissions,
|
|
454
468
|
respectGitignore: newSettings.respectGitignore,
|
|
@@ -458,6 +472,16 @@ Last synced: ${new Date().toISOString()}
|
|
|
458
472
|
_generatedAt: newSettings._generatedAt,
|
|
459
473
|
};
|
|
460
474
|
|
|
475
|
+
// Include spinnerVerbs if configured
|
|
476
|
+
if (newSettings.spinnerVerbs) {
|
|
477
|
+
mergedSettings.spinnerVerbs = newSettings.spinnerVerbs;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Include spinnerTipsEnabled if explicitly disabled
|
|
481
|
+
if (newSettings.spinnerTipsEnabled === false) {
|
|
482
|
+
mergedSettings.spinnerTipsEnabled = false;
|
|
483
|
+
}
|
|
484
|
+
|
|
461
485
|
fs.writeFileSync(settingsPath, JSON.stringify(mergedSettings, null, 2));
|
|
462
486
|
this.log(`Synced settings.local.json with wildcard permissions (${newSettings.permissions.allow.length} rules)`);
|
|
463
487
|
|
|
@@ -136,7 +136,8 @@ async function syncBridge(options = {}) {
|
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
// Pass through sync options (e.g., force: true to overwrite locally modified files)
|
|
140
|
+
return await bridge.sync({ force: options.force });
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
/**
|
package/README.md
CHANGED
|
@@ -19,14 +19,16 @@ npx flow bridge sync
|
|
|
19
19
|
|
|
20
20
|
WogiFlow works with 6 AI coding CLIs. Use whichever you prefer - the workflow state is shared.
|
|
21
21
|
|
|
22
|
-
| CLI | Enforcement | Rules File | Guide |
|
|
23
|
-
|
|
24
|
-
| **Claude Code** | Hard (hooks) | `CLAUDE.md` | [Guide](.workflow/docs/cli-guides/claude-code.md) |
|
|
25
|
-
| **Gemini CLI** | Hard (hooks) | `GEMINI.md` | [Guide](.workflow/docs/cli-guides/gemini-cli.md) |
|
|
26
|
-
| **Cursor** | Mixed | `.cursor/rules/wogiflow.mdc` | [Guide](.workflow/docs/cli-guides/cursor.md) |
|
|
27
|
-
| **OpenCode** | Hard (plugins) | `AGENTS.md` | [Guide](.workflow/docs/cli-guides/opencode.md) |
|
|
28
|
-
| **Codex** | Soft (rules) | `AGENTS.md` | [Guide](.workflow/docs/cli-guides/codex.md) |
|
|
29
|
-
| **Kimi** | Soft (rules) | `AGENTS.md` | [Guide](.workflow/docs/cli-guides/kimi.md) |
|
|
22
|
+
| CLI | Enforcement | Rules File | Min Version | Guide |
|
|
23
|
+
|-----|-------------|------------|-------------|-------|
|
|
24
|
+
| **Claude Code** | Hard (hooks) | `CLAUDE.md` | **2.1.23+** | [Guide](.workflow/docs/cli-guides/claude-code.md) |
|
|
25
|
+
| **Gemini CLI** | Hard (hooks) | `GEMINI.md` | - | [Guide](.workflow/docs/cli-guides/gemini-cli.md) |
|
|
26
|
+
| **Cursor** | Mixed | `.cursor/rules/wogiflow.mdc` | - | [Guide](.workflow/docs/cli-guides/cursor.md) |
|
|
27
|
+
| **OpenCode** | Hard (plugins) | `AGENTS.md` | - | [Guide](.workflow/docs/cli-guides/opencode.md) |
|
|
28
|
+
| **Codex** | Soft (rules) | `AGENTS.md` | - | [Guide](.workflow/docs/cli-guides/codex.md) |
|
|
29
|
+
| **Kimi** | Soft (rules) | `AGENTS.md` | - | [Guide](.workflow/docs/cli-guides/kimi.md) |
|
|
30
|
+
|
|
31
|
+
> **Claude Code 2.1.23+ Recommended**: Includes critical fixes for per-user temp directory isolation (shared systems), async hook cancellation, and ripgrep timeout reporting. Earlier versions may experience silent search failures.
|
|
30
32
|
|
|
31
33
|
**Enforcement levels:**
|
|
32
34
|
- **Hard**: Blocks operations before execution (best protection)
|
package/package.json
CHANGED
package/scripts/flow-bridge.js
CHANGED
|
@@ -190,6 +190,7 @@ function normalizeCliType(input) {
|
|
|
190
190
|
*/
|
|
191
191
|
async function syncBridge(options = {}) {
|
|
192
192
|
const verbose = options.verbose || process.argv.includes('--verbose') || process.argv.includes('-v');
|
|
193
|
+
const force = options.force || process.argv.includes('--force') || process.argv.includes('-f');
|
|
193
194
|
|
|
194
195
|
// Check for CLI type argument (e.g., "flow bridge sync gemini")
|
|
195
196
|
const cliTypeArg = process.argv[3];
|
|
@@ -219,6 +220,7 @@ async function syncBridge(options = {}) {
|
|
|
219
220
|
|
|
220
221
|
const result = await bridges.syncBridge({
|
|
221
222
|
verbose,
|
|
223
|
+
force,
|
|
222
224
|
projectDir: PROJECT_ROOT,
|
|
223
225
|
cliType: targetCliType
|
|
224
226
|
});
|
|
@@ -271,11 +273,16 @@ switch (command) {
|
|
|
271
273
|
console.log(' status Show current bridge configuration');
|
|
272
274
|
console.log(' list List available CLI bridges');
|
|
273
275
|
console.log('');
|
|
276
|
+
console.log('Options:');
|
|
277
|
+
console.log(' --force, -f Overwrite locally modified rules files (CLAUDE.md, GEMINI.md, etc.)');
|
|
278
|
+
console.log(' --verbose, -v Show detailed output');
|
|
279
|
+
console.log('');
|
|
274
280
|
console.log('CLI Types:');
|
|
275
281
|
console.log(' claude-code, gemini-cli (or gemini), cursor, opencode, codex, kimi');
|
|
276
282
|
console.log('');
|
|
277
283
|
console.log('Examples:');
|
|
278
284
|
console.log(' flow bridge sync # Sync default CLI from config');
|
|
279
285
|
console.log(' flow bridge sync gemini # Sync Gemini CLI specifically');
|
|
286
|
+
console.log(' flow bridge sync --force # Force overwrite even if locally modified');
|
|
280
287
|
process.exit(1);
|
|
281
288
|
}
|
package/scripts/flow-health.js
CHANGED
|
@@ -38,6 +38,39 @@ const {
|
|
|
38
38
|
checkSpecMigration
|
|
39
39
|
} = require('./flow-utils');
|
|
40
40
|
|
|
41
|
+
const { execSync } = require('child_process');
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check Claude Code version and compare against minimum recommended (2.1.23)
|
|
45
|
+
* @returns {{ version: string|null, meetsMinimum: boolean }}
|
|
46
|
+
*/
|
|
47
|
+
function checkClaudeCodeVersion() {
|
|
48
|
+
try {
|
|
49
|
+
const output = execSync('claude --version 2>/dev/null || echo ""', {
|
|
50
|
+
encoding: 'utf-8',
|
|
51
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
52
|
+
}).trim();
|
|
53
|
+
|
|
54
|
+
// Parse version from output like "claude 2.1.23" or "Claude Code 2.1.23"
|
|
55
|
+
const match = output.match(/(\d+\.\d+\.\d+)/);
|
|
56
|
+
if (!match) {
|
|
57
|
+
return { version: null, meetsMinimum: true };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const version = match[1];
|
|
61
|
+
const [major, minor, patch] = version.split('.').map(Number);
|
|
62
|
+
|
|
63
|
+
// Minimum recommended: 2.1.23
|
|
64
|
+
const meetsMinimum = major > 2 ||
|
|
65
|
+
(major === 2 && minor > 1) ||
|
|
66
|
+
(major === 2 && minor === 1 && patch >= 23);
|
|
67
|
+
|
|
68
|
+
return { version, meetsMinimum };
|
|
69
|
+
} catch {
|
|
70
|
+
return { version: null, meetsMinimum: true };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
41
74
|
function main() {
|
|
42
75
|
console.log(color('cyan', 'Wogi Flow Health Check'));
|
|
43
76
|
console.log('========================');
|
|
@@ -90,6 +123,20 @@ function main() {
|
|
|
90
123
|
issues++;
|
|
91
124
|
}
|
|
92
125
|
|
|
126
|
+
// Check Claude Code version (if applicable)
|
|
127
|
+
if (cliType === 'claude-code') {
|
|
128
|
+
const versionCheck = checkClaudeCodeVersion();
|
|
129
|
+
if (versionCheck.version) {
|
|
130
|
+
if (versionCheck.meetsMinimum) {
|
|
131
|
+
console.log(` ${color('green', '✓')} Claude Code version: ${versionCheck.version}`);
|
|
132
|
+
} else {
|
|
133
|
+
console.log(` ${color('yellow', '○')} Claude Code version: ${versionCheck.version} (2.1.23+ recommended)`);
|
|
134
|
+
console.log(` ${color('dim', '→ Older versions may have silent search failures and shared system issues')}`);
|
|
135
|
+
warnings++;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
93
140
|
// Check required directories
|
|
94
141
|
console.log('');
|
|
95
142
|
printSection('Checking directories...');
|
package/scripts/flow-utils.js
CHANGED
|
@@ -3024,6 +3024,101 @@ function checkSpecMigration() {
|
|
|
3024
3024
|
return needsMigration;
|
|
3025
3025
|
}
|
|
3026
3026
|
|
|
3027
|
+
// ============================================================
|
|
3028
|
+
// Safe Search Utilities (Claude Code 2.1.23+ compatibility)
|
|
3029
|
+
// ============================================================
|
|
3030
|
+
|
|
3031
|
+
/**
|
|
3032
|
+
* Perform a safe grep search with proper timeout and error handling.
|
|
3033
|
+
* Returns results and metadata about search status.
|
|
3034
|
+
*
|
|
3035
|
+
* Before Claude Code 2.1.23, ripgrep timeouts would silently return empty results.
|
|
3036
|
+
* This utility provides explicit handling for timeouts and errors.
|
|
3037
|
+
*
|
|
3038
|
+
* @param {string} pattern - Search pattern
|
|
3039
|
+
* @param {Object} options - Search options
|
|
3040
|
+
* @param {string} [options.path] - Directory to search (default: PROJECT_ROOT)
|
|
3041
|
+
* @param {string[]} [options.extensions] - File extensions to include (e.g., ['ts', 'tsx'])
|
|
3042
|
+
* @param {number} [options.timeout] - Timeout in ms (default: 10000)
|
|
3043
|
+
* @param {number} [options.maxResults] - Maximum results to return (default: 50)
|
|
3044
|
+
* @param {boolean} [options.caseInsensitive] - Case insensitive search (default: true)
|
|
3045
|
+
* @returns {{ results: string[], timedOut: boolean, error: string|null }}
|
|
3046
|
+
*/
|
|
3047
|
+
function safeGrepSearch(pattern, options = {}) {
|
|
3048
|
+
const { spawnSync } = require('child_process');
|
|
3049
|
+
|
|
3050
|
+
const searchPath = options.path || PROJECT_ROOT;
|
|
3051
|
+
const extensions = options.extensions || ['ts', 'tsx', 'js', 'jsx'];
|
|
3052
|
+
const timeout = options.timeout || 10000;
|
|
3053
|
+
const maxResults = options.maxResults || 50;
|
|
3054
|
+
const caseInsensitive = options.caseInsensitive !== false;
|
|
3055
|
+
|
|
3056
|
+
const args = ['-rl'];
|
|
3057
|
+
if (caseInsensitive) args.push('-i');
|
|
3058
|
+
|
|
3059
|
+
// Add include patterns for extensions
|
|
3060
|
+
for (const ext of extensions) {
|
|
3061
|
+
args.push(`--include=*.${ext}`);
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
args.push(pattern, searchPath);
|
|
3065
|
+
|
|
3066
|
+
try {
|
|
3067
|
+
const result = spawnSync('grep', args, {
|
|
3068
|
+
encoding: 'utf-8',
|
|
3069
|
+
timeout,
|
|
3070
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
3071
|
+
});
|
|
3072
|
+
|
|
3073
|
+
// Check for timeout
|
|
3074
|
+
if (result.signal === 'SIGTERM') {
|
|
3075
|
+
return {
|
|
3076
|
+
results: [],
|
|
3077
|
+
timedOut: true,
|
|
3078
|
+
error: `Search timed out after ${timeout}ms`
|
|
3079
|
+
};
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
// Check for error (but exit code 1 just means no matches)
|
|
3083
|
+
if (result.status > 1) {
|
|
3084
|
+
return {
|
|
3085
|
+
results: [],
|
|
3086
|
+
timedOut: false,
|
|
3087
|
+
error: result.stderr?.trim() || `grep exited with code ${result.status}`
|
|
3088
|
+
};
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
const files = (result.stdout || '')
|
|
3092
|
+
.split('\n')
|
|
3093
|
+
.filter(f => f.trim())
|
|
3094
|
+
.slice(0, maxResults);
|
|
3095
|
+
|
|
3096
|
+
return {
|
|
3097
|
+
results: files,
|
|
3098
|
+
timedOut: false,
|
|
3099
|
+
error: null
|
|
3100
|
+
};
|
|
3101
|
+
} catch (err) {
|
|
3102
|
+
// Handle ETIMEDOUT and other errors
|
|
3103
|
+
const isTimeout = err.code === 'ETIMEDOUT' || err.killed;
|
|
3104
|
+
return {
|
|
3105
|
+
results: [],
|
|
3106
|
+
timedOut: isTimeout,
|
|
3107
|
+
error: isTimeout ? `Search timed out after ${timeout}ms` : err.message
|
|
3108
|
+
};
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
/**
|
|
3113
|
+
* Configuration defaults for search operations
|
|
3114
|
+
*/
|
|
3115
|
+
const SEARCH_DEFAULTS = {
|
|
3116
|
+
timeout: 10000, // 10 seconds
|
|
3117
|
+
maxResults: 50, // Maximum files to return
|
|
3118
|
+
retryOnTimeout: true, // Whether to retry with reduced scope on timeout
|
|
3119
|
+
extensions: ['ts', 'tsx', 'js', 'jsx', 'vue', 'svelte']
|
|
3120
|
+
};
|
|
3121
|
+
|
|
3027
3122
|
// ============================================================
|
|
3028
3123
|
// Exports
|
|
3029
3124
|
// ============================================================
|
|
@@ -3172,6 +3267,10 @@ module.exports = {
|
|
|
3172
3267
|
findTaskInAllLists,
|
|
3173
3268
|
analyzeRequest,
|
|
3174
3269
|
estimateComplexity,
|
|
3270
|
+
|
|
3271
|
+
// Safe Search (Claude Code 2.1.23+ compatibility)
|
|
3272
|
+
safeGrepSearch,
|
|
3273
|
+
SEARCH_DEFAULTS,
|
|
3175
3274
|
};
|
|
3176
3275
|
|
|
3177
3276
|
// ============================================================
|
package/scripts/flow-worktree.js
CHANGED
|
@@ -33,7 +33,19 @@ const { sanitizeCommitMessage } = require('./flow-security');
|
|
|
33
33
|
// ============================================================
|
|
34
34
|
|
|
35
35
|
const WORKTREE_PREFIX = 'wogi-task-';
|
|
36
|
-
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get user-specific worktree base directory.
|
|
39
|
+
* Uses UID on Unix, username on Windows, with 'default' fallback.
|
|
40
|
+
* This prevents permission conflicts on shared systems (CI, multi-user machines).
|
|
41
|
+
* See: Claude Code 2.1.23 per-user temp directory isolation fix.
|
|
42
|
+
*/
|
|
43
|
+
function getWorktreeBaseDir() {
|
|
44
|
+
const userId = process.getuid?.() ?? process.env.USER ?? process.env.USERNAME ?? 'default';
|
|
45
|
+
return path.join(os.tmpdir(), `wogi-worktrees-${userId}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const WORKTREE_BASE_DIR = getWorktreeBaseDir();
|
|
37
49
|
|
|
38
50
|
// ============================================================
|
|
39
51
|
// Helper Functions
|