wogiflow 1.1.4 → 1.1.6
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 +13 -3
- package/.workflow/bridges/claude-bridge.js +26 -2
- package/.workflow/templates/claude-md.hbs +32 -0
- package/README.md +10 -8
- package/package.json +1 -1
- 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)
|
|
@@ -590,13 +590,18 @@ class BaseBridge {
|
|
|
590
590
|
* @returns {string} Content with conditionals evaluated
|
|
591
591
|
*/
|
|
592
592
|
processConditionals(content, config) {
|
|
593
|
-
|
|
593
|
+
// Match INNERMOST conditionals first - those with no nested {{#if}} in body
|
|
594
|
+
// The negative lookahead (?!{{#if) ensures body contains no nested conditionals
|
|
595
|
+
const innerIfRegex = /\{\{#if\s+([^}]+)\}\}((?:(?!\{\{#if)[\s\S])*?)\{\{\/if\}\}/g;
|
|
594
596
|
|
|
595
597
|
let lastContent;
|
|
598
|
+
let iterations = 0;
|
|
599
|
+
const maxIterations = 100; // Safety limit for deeply nested templates
|
|
600
|
+
|
|
596
601
|
// Keep processing until no more changes (handles nested conditions)
|
|
597
602
|
do {
|
|
598
603
|
lastContent = content;
|
|
599
|
-
content = content.replace(
|
|
604
|
+
content = content.replace(innerIfRegex, (match, condition, body) => {
|
|
600
605
|
let value;
|
|
601
606
|
if (condition.startsWith('config.')) {
|
|
602
607
|
value = this.getNestedValue(config, condition.replace('config.', ''));
|
|
@@ -607,7 +612,12 @@ class BaseBridge {
|
|
|
607
612
|
}
|
|
608
613
|
return value ? body : '';
|
|
609
614
|
});
|
|
610
|
-
|
|
615
|
+
iterations++;
|
|
616
|
+
} while (content !== lastContent && iterations < maxIterations);
|
|
617
|
+
|
|
618
|
+
if (iterations >= maxIterations) {
|
|
619
|
+
this.log('Warning: Max iterations reached in processConditionals - possible malformed template');
|
|
620
|
+
}
|
|
611
621
|
|
|
612
622
|
return content;
|
|
613
623
|
}
|
|
@@ -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
|
|
|
@@ -538,6 +538,38 @@ When corrected:
|
|
|
538
538
|
- Individual records stored in `.workflow/corrections/` for searchable history
|
|
539
539
|
- Aggregated patterns in `feedback-patterns.md` for AI context
|
|
540
540
|
|
|
541
|
+
### Repeated Issue Detection (CRITICAL)
|
|
542
|
+
|
|
543
|
+
**When you detect user frustration about a repeated issue, you MUST act immediately.**
|
|
544
|
+
|
|
545
|
+
The user may express this in many ways:
|
|
546
|
+
- "This keeps happening"
|
|
547
|
+
- "I told you this before"
|
|
548
|
+
- "You keep forgetting X"
|
|
549
|
+
- "This failed again"
|
|
550
|
+
- "How many times do I have to tell you"
|
|
551
|
+
- Any indication of a recurring problem or previous instruction being ignored
|
|
552
|
+
|
|
553
|
+
**Required response:**
|
|
554
|
+
|
|
555
|
+
1. **Acknowledge** - Don't be defensive. The user's frustration is valid.
|
|
556
|
+
|
|
557
|
+
2. **Investigate root cause** - Don't just fix the symptom:
|
|
558
|
+
- Why did this happen multiple times?
|
|
559
|
+
- What instruction or rule was missing?
|
|
560
|
+
- Check `decisions.md` and `feedback-patterns.md` for related issues
|
|
561
|
+
|
|
562
|
+
3. **Persist the fix to `decisions.md`** - This is MANDATORY:
|
|
563
|
+
- Add a clear rule that prevents recurrence
|
|
564
|
+
- Include context about why it matters
|
|
565
|
+
- Add verification steps if applicable
|
|
566
|
+
|
|
567
|
+
4. **Record in `feedback-patterns.md`** - Track the pattern with count and mark as promoted
|
|
568
|
+
|
|
569
|
+
5. **Verify the fix** - Don't just write the rule; test that it works
|
|
570
|
+
|
|
571
|
+
**Why this matters**: The user installed WogiFlow expecting an AI that learns and improves. Repeating the same mistakes breaks trust. Every repeated failure is a failure of the learning system itself.
|
|
572
|
+
|
|
541
573
|
### Improvement Placement
|
|
542
574
|
|
|
543
575
|
Before implementing, determine scope:
|
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-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
|