wogiflow 1.1.3 → 1.1.5
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/README.md +60 -0
- package/.claude/rules/architecture/component-reuse.md +38 -0
- package/.claude/rules/architecture/document-structure.md +76 -0
- package/.claude/rules/architecture/feature-refactoring-cleanup.md +87 -0
- package/.claude/rules/architecture/model-management.md +35 -0
- package/.claude/rules/code-style/naming-conventions.md +55 -0
- package/.claude/rules/security/security-patterns.md +143 -0
- package/.workflow/bridges/base-bridge.js +74 -0
- package/.workflow/bridges/claude-bridge.js +4 -59
- package/.workflow/bridges/gemini-bridge.js +4 -52
- package/.workflow/bridges/index.js +4 -35
- package/.workflow/specs/architecture.md.template +24 -0
- package/.workflow/specs/stack.md.template +33 -0
- package/.workflow/specs/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +99 -0
- package/.workflow/templates/kimi-agents-md.hbs +139 -0
- package/lib/utils.js +5 -3
- package/package.json +1 -1
- package/scripts/flow-bridge-state.js +132 -40
- package/scripts/flow-session-state.js +135 -9
- package/scripts/flow-status.js +35 -2
- package/scripts/flow-utils.js +33 -0
- package/scripts/hooks/core/session-context.js +38 -4
- package/scripts/hooks/core/task-gate.js +56 -3
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Project Rules
|
|
2
|
+
|
|
3
|
+
This directory contains coding rules and patterns for this project, organized by category.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
.claude/rules/
|
|
9
|
+
├── code-style/ # Naming conventions, formatting
|
|
10
|
+
│ └── naming-conventions.md
|
|
11
|
+
├── security/ # Security patterns and practices
|
|
12
|
+
│ └── security-patterns.md
|
|
13
|
+
├── architecture/ # Design decisions and patterns
|
|
14
|
+
│ ├── component-reuse.md
|
|
15
|
+
│ └── model-management.md
|
|
16
|
+
└── README.md
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## How Rules Work
|
|
20
|
+
|
|
21
|
+
Rules are automatically loaded by Claude Code based on:
|
|
22
|
+
- **alwaysApply: true** - Rule is always loaded
|
|
23
|
+
- **alwaysApply: false** - Rule is loaded based on `globs` or `description` relevance
|
|
24
|
+
- **globs** - File patterns that trigger rule loading
|
|
25
|
+
|
|
26
|
+
## Adding New Rules
|
|
27
|
+
|
|
28
|
+
1. Choose the appropriate category subdirectory
|
|
29
|
+
2. Create a `.md` file with frontmatter:
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
---
|
|
33
|
+
alwaysApply: false
|
|
34
|
+
description: "Brief description for relevance matching"
|
|
35
|
+
globs: src/**/*.ts # Optional: only load for these files
|
|
36
|
+
---
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
3. Write the rule content in markdown
|
|
40
|
+
|
|
41
|
+
## Categories
|
|
42
|
+
|
|
43
|
+
| Category | Purpose |
|
|
44
|
+
|----------|---------|
|
|
45
|
+
| code-style | Naming conventions, formatting, file structure |
|
|
46
|
+
| security | Security patterns, input validation, safe practices |
|
|
47
|
+
| architecture | Design decisions, component patterns, system organization |
|
|
48
|
+
|
|
49
|
+
## Auto-Generation
|
|
50
|
+
|
|
51
|
+
Some rules can be auto-generated from `.workflow/state/decisions.md`:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
node scripts/flow-rules-sync.js
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The sync script will route rules to appropriate category subdirectories.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
Last updated: 2026-01-12
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
globs: src/components/**/*
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
description: "Component reuse policy - always check app-map.md before creating components"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Component Reuse Policy
|
|
8
|
+
|
|
9
|
+
**Rule**: Always check `app-map.md` before creating any component.
|
|
10
|
+
|
|
11
|
+
## Priority Order
|
|
12
|
+
|
|
13
|
+
1. **Use existing** - Check if component already exists in app-map
|
|
14
|
+
2. **Add variant** - Extend existing component with a new variant
|
|
15
|
+
3. **Extend** - Create a wrapper/HOC around existing component
|
|
16
|
+
4. **Create new** - Only as last resort
|
|
17
|
+
|
|
18
|
+
## Before Creating Components
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Check app-map first
|
|
22
|
+
cat .workflow/state/app-map.md | grep -i "button"
|
|
23
|
+
|
|
24
|
+
# Or search codebase
|
|
25
|
+
grep -r "Button" src/components/
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Variant vs New Component
|
|
29
|
+
|
|
30
|
+
Prefer variants when:
|
|
31
|
+
- Same base functionality, different appearance
|
|
32
|
+
- Same HTML structure, different styling
|
|
33
|
+
- Same component, different size/color/state
|
|
34
|
+
|
|
35
|
+
Create new component when:
|
|
36
|
+
- Fundamentally different functionality
|
|
37
|
+
- Different DOM structure
|
|
38
|
+
- Different state management
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: true
|
|
3
|
+
description: "All AI-context documents must use PIN markers for targeted context loading"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Document Structure for AI Context
|
|
7
|
+
|
|
8
|
+
All documents in `.workflow/` that are used as AI context MUST follow the PIN standard.
|
|
9
|
+
|
|
10
|
+
## Required Structure
|
|
11
|
+
|
|
12
|
+
### 1. Header with PIN List
|
|
13
|
+
Every document starts with a comment listing all pins in the document:
|
|
14
|
+
```markdown
|
|
15
|
+
<!-- PINS: pin1, pin2, pin3 -->
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### 2. Section PIN Markers
|
|
19
|
+
Each major section has a PIN marker comment:
|
|
20
|
+
```markdown
|
|
21
|
+
### Section Title
|
|
22
|
+
<!-- PIN: section-specific-pin -->
|
|
23
|
+
[Content]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 3. PIN Naming Convention
|
|
27
|
+
- Use kebab-case: `user-authentication`, not `userAuthentication`
|
|
28
|
+
- Use semantic names: `error-handling`, not `eh`
|
|
29
|
+
- Use compound names for specificity: `json-parse-safety`
|
|
30
|
+
|
|
31
|
+
## Why PINs Matter
|
|
32
|
+
|
|
33
|
+
The PIN system enables:
|
|
34
|
+
1. **Targeted context loading**: Only load sections relevant to current task
|
|
35
|
+
2. **Cheaper model routing**: Haiku can fetch only relevant sections for Opus
|
|
36
|
+
3. **Change detection**: Hash sections independently for smart invalidation
|
|
37
|
+
4. **Cross-reference**: Link sections by PIN across documents
|
|
38
|
+
|
|
39
|
+
## Example Document
|
|
40
|
+
|
|
41
|
+
```markdown
|
|
42
|
+
# Config Reference
|
|
43
|
+
|
|
44
|
+
<!-- PINS: database, authentication, api-keys, environment -->
|
|
45
|
+
|
|
46
|
+
## Database Settings
|
|
47
|
+
<!-- PIN: database -->
|
|
48
|
+
| Setting | Default | Description |
|
|
49
|
+
|---------|---------|-------------|
|
|
50
|
+
|
|
51
|
+
## Authentication
|
|
52
|
+
<!-- PIN: authentication -->
|
|
53
|
+
| Setting | Default | Description |
|
|
54
|
+
|---------|---------|-------------|
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Parsing
|
|
58
|
+
|
|
59
|
+
The PIN system automatically parses documents with:
|
|
60
|
+
- `flow-section-index.js` - Generates section index with pins
|
|
61
|
+
- `flow-section-resolver.js` - Resolves sections by PIN lookup
|
|
62
|
+
- `getSectionsByPins(['auth', 'security'])` - Fetch only relevant sections
|
|
63
|
+
|
|
64
|
+
## Files That Must Have PINs
|
|
65
|
+
|
|
66
|
+
| File | Required PINs |
|
|
67
|
+
|------|---------------|
|
|
68
|
+
| `decisions.md` | Per coding rule/pattern |
|
|
69
|
+
| `app-map.md` | Per component/screen |
|
|
70
|
+
| `product.md` | Per product section |
|
|
71
|
+
| `stack.md` | Per technology |
|
|
72
|
+
|
|
73
|
+
## Validation
|
|
74
|
+
|
|
75
|
+
Run `node scripts/flow-section-index.js --force` to regenerate the index.
|
|
76
|
+
Check `.workflow/state/section-index.json` for indexed sections and their pins.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: false
|
|
3
|
+
description: "Cleanup checklist when refactoring or renaming features"
|
|
4
|
+
globs:
|
|
5
|
+
- "scripts/*.js"
|
|
6
|
+
- ".claude/skills/**/*"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Feature Refactoring Cleanup
|
|
10
|
+
|
|
11
|
+
When refactoring, renaming, or replacing a feature, ensure complete cleanup of the old implementation.
|
|
12
|
+
|
|
13
|
+
## Mandatory Cleanup Checklist
|
|
14
|
+
|
|
15
|
+
When a feature is refactored or renamed, you MUST:
|
|
16
|
+
|
|
17
|
+
### 1. Remove Old Code
|
|
18
|
+
- [ ] Delete old script files (e.g., `flow-old-feature.js`)
|
|
19
|
+
- [ ] Remove old skill directories (e.g., `.claude/skills/old-feature/`)
|
|
20
|
+
- [ ] Remove old hook files if applicable
|
|
21
|
+
|
|
22
|
+
### 2. Update Configuration
|
|
23
|
+
- [ ] Rename config keys (e.g., `oldFeature` → `newFeature`)
|
|
24
|
+
- [ ] Remove from `skills.installed` array if skill was removed
|
|
25
|
+
- [ ] Update any feature flags
|
|
26
|
+
|
|
27
|
+
### 3. Update Documentation
|
|
28
|
+
- [ ] Rename/update doc files in `.claude/docs/`
|
|
29
|
+
- [ ] Update command references in `commands.md`
|
|
30
|
+
- [ ] Update skill-matching.md if skill changed
|
|
31
|
+
- [ ] Search for old name in all `.md` files
|
|
32
|
+
|
|
33
|
+
### 4. Clean References
|
|
34
|
+
- [ ] Search codebase: `grep -r "old-feature-name" .`
|
|
35
|
+
- [ ] Update imports in dependent scripts
|
|
36
|
+
- [ ] Update any hardcoded references
|
|
37
|
+
|
|
38
|
+
### 5. Update State Files
|
|
39
|
+
- [ ] Clean `.workflow/state/` of old state files
|
|
40
|
+
- [ ] Update `ready.json` if tasks reference old feature
|
|
41
|
+
- [ ] Archive old change specs
|
|
42
|
+
|
|
43
|
+
## Search Commands
|
|
44
|
+
|
|
45
|
+
Run these to find lingering references:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Find all references to old feature
|
|
49
|
+
grep -r "old-feature-name" --include="*.js" --include="*.md" --include="*.json" .
|
|
50
|
+
|
|
51
|
+
# Find in config
|
|
52
|
+
grep "oldFeatureName" .workflow/config.json
|
|
53
|
+
|
|
54
|
+
# Find skill references
|
|
55
|
+
grep -r "old-feature" .claude/
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Why This Matters
|
|
59
|
+
|
|
60
|
+
Incomplete cleanup causes:
|
|
61
|
+
- **Confusion**: Old commands/skills appear to work but don't
|
|
62
|
+
- **Bloat**: Dead code accumulates
|
|
63
|
+
- **Errors**: Old references cause runtime failures
|
|
64
|
+
- **Documentation drift**: Docs describe non-existent features
|
|
65
|
+
|
|
66
|
+
## Example: transcript-digestion → long-input-gate
|
|
67
|
+
|
|
68
|
+
When this refactoring happened without proper cleanup:
|
|
69
|
+
|
|
70
|
+
| Artifact | Status | Should Have Been |
|
|
71
|
+
|----------|--------|------------------|
|
|
72
|
+
| `.claude/skills/transcript-digestion/` | Left behind | Deleted |
|
|
73
|
+
| `config.transcriptDigestion` | Left as-is | Renamed to `longInputGate` |
|
|
74
|
+
| `skills.installed` array | Still listed | Removed |
|
|
75
|
+
| `skill-matching.md` | Old references | Updated |
|
|
76
|
+
| `transcript-digestion.md` doc | Still existed | Renamed/rewritten |
|
|
77
|
+
|
|
78
|
+
## Automation Opportunity
|
|
79
|
+
|
|
80
|
+
Consider adding a `flow refactor-cleanup <old-name> <new-name>` command that:
|
|
81
|
+
1. Searches for all references
|
|
82
|
+
2. Shows what needs updating
|
|
83
|
+
3. Optionally auto-updates simple cases
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
Last updated: 2026-01-14
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
globs: scripts/flow-model*.js
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
description: "Model management architecture - two separate systems for different purposes"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Model Management Architecture
|
|
8
|
+
|
|
9
|
+
**Context**: Phase 1 introduced model registry and stats system alongside existing model-adapter.
|
|
10
|
+
|
|
11
|
+
## Two Model Systems
|
|
12
|
+
|
|
13
|
+
### 1. flow-model-adapter.js - Prompt Adaptation
|
|
14
|
+
|
|
15
|
+
- `getCurrentModel()` returns normalized model name (string)
|
|
16
|
+
- Focus: Per-model prompt adjustments, learning, and corrections
|
|
17
|
+
- Imports: Used by flow-knowledge-router.js
|
|
18
|
+
|
|
19
|
+
### 2. flow-models.js - Registry and Stats
|
|
20
|
+
|
|
21
|
+
- `getCurrentModel()` returns `{name, info, source}` object
|
|
22
|
+
- Focus: Model listing, routing recommendations, cost tracking
|
|
23
|
+
- Standalone CLI commands: `flow models [subcommand]`
|
|
24
|
+
|
|
25
|
+
## Design Decision
|
|
26
|
+
|
|
27
|
+
**Keep them separate** because:
|
|
28
|
+
- Different return types serve different consumers
|
|
29
|
+
- Adapter system needs just the name for pattern matching
|
|
30
|
+
- Registry system needs full model metadata for display/routing
|
|
31
|
+
- Merging would create unnecessary coupling
|
|
32
|
+
|
|
33
|
+
## Future Consideration
|
|
34
|
+
|
|
35
|
+
Could extract shared model detection logic into a common utility if they drift apart, but avoid premature abstraction.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: true
|
|
3
|
+
description: "Naming conventions for files and code variants"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Naming Conventions
|
|
7
|
+
|
|
8
|
+
## File Names
|
|
9
|
+
|
|
10
|
+
Use **kebab-case** for all file names in this project.
|
|
11
|
+
|
|
12
|
+
Examples:
|
|
13
|
+
- `flow-health.js` (correct)
|
|
14
|
+
- `flowHealth.js` (incorrect)
|
|
15
|
+
- `flow_health.js` (incorrect)
|
|
16
|
+
|
|
17
|
+
## Variant Names
|
|
18
|
+
|
|
19
|
+
Use consistent variant names for components:
|
|
20
|
+
|
|
21
|
+
| Category | Values |
|
|
22
|
+
|----------|--------|
|
|
23
|
+
| Size | `sm`, `md`, `lg`, `xl` |
|
|
24
|
+
| Intent | `primary`, `secondary`, `danger`, `success`, `warning` |
|
|
25
|
+
| State | `default`, `hover`, `active`, `disabled` |
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
```jsx
|
|
29
|
+
<Button size="sm" intent="primary" />
|
|
30
|
+
<Badge variant="warning" />
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Catch Block Variables
|
|
34
|
+
|
|
35
|
+
Use `err` for all catch blocks in this codebase.
|
|
36
|
+
|
|
37
|
+
**Avoid**: `e`, `error`, `ex`, `exception` - these cause confusion with loop variables.
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
// Good
|
|
41
|
+
try {
|
|
42
|
+
doSomething();
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error(err.message);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Bad - 'e' conflicts with common iterator variables
|
|
48
|
+
try {
|
|
49
|
+
items.map(e => e.value); // 'e' used as iterator
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.error(e.message); // Easy to confuse with iterator 'e'
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Reason**: Standardizing on `err` prevents mix-ups when `.map(e => ...)` is used nearby.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: true
|
|
3
|
+
description: "Security patterns for file operations, JSON parsing, and path handling"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Security Patterns
|
|
7
|
+
|
|
8
|
+
Critical security patterns for this project.
|
|
9
|
+
|
|
10
|
+
## 1. File Read Safety
|
|
11
|
+
|
|
12
|
+
Always wrap `fs.readFileSync()` in try-catch, even after `fileExists()` check.
|
|
13
|
+
|
|
14
|
+
**Reason**: Race conditions, permission changes, symlink issues can still cause failures.
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
// Good
|
|
18
|
+
try {
|
|
19
|
+
const content = fs.readFileSync(path, 'utf-8');
|
|
20
|
+
} catch (err) {
|
|
21
|
+
// Handle gracefully
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Bad - can still throw even if file existed
|
|
25
|
+
if (fs.existsSync(path)) {
|
|
26
|
+
const content = fs.readFileSync(path, 'utf-8');
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 2. JSON Parsing Safety
|
|
31
|
+
|
|
32
|
+
Use `safeJsonParse()` from flow-utils.js instead of raw `JSON.parse()`.
|
|
33
|
+
|
|
34
|
+
- Check for `__proto__`, `constructor`, `prototype` injection
|
|
35
|
+
- Validate parsed structure has expected fields before use
|
|
36
|
+
- Located in: `scripts/flow-utils.js`
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
// Good
|
|
40
|
+
const config = safeJsonParse(filePath, {});
|
|
41
|
+
|
|
42
|
+
// Bad - vulnerable to prototype pollution
|
|
43
|
+
const config = JSON.parse(fs.readFileSync(filePath));
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 3. Template Substitution Safety
|
|
47
|
+
|
|
48
|
+
When implementing template substitution:
|
|
49
|
+
- Block access to `__proto__`, `constructor`, `prototype` keys
|
|
50
|
+
- Use `Object.prototype.hasOwnProperty.call()` for property access
|
|
51
|
+
- Example: See `applyTemplate()` in flow-prompt-composer.js
|
|
52
|
+
|
|
53
|
+
## 4. Path Safety
|
|
54
|
+
|
|
55
|
+
- Validate patterns before `path.join()` with user/config data
|
|
56
|
+
- Use `isPathWithinProject()` for defense-in-depth
|
|
57
|
+
- Glob-to-regex: Use `[^/]*` not `.*` to prevent path separator matching
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
// Good
|
|
61
|
+
if (!isPathWithinProject(targetPath)) {
|
|
62
|
+
throw new Error('Path outside project');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Bad - allows path traversal
|
|
66
|
+
const fullPath = path.join(baseDir, userInput);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 5. Module Dependencies
|
|
70
|
+
|
|
71
|
+
- Check for circular dependencies when refactoring shared functions
|
|
72
|
+
- Node.js handles circular deps but can cause undefined exports during load
|
|
73
|
+
|
|
74
|
+
## 6. Claude Code Permission Patterns (2.1.7+)
|
|
75
|
+
|
|
76
|
+
When configuring permission rules in Claude Code, avoid overly permissive wildcards.
|
|
77
|
+
|
|
78
|
+
**Vulnerability fixed in 2.1.7**: Wildcard permission rules could match compound commands containing shell operators (`;`, `&&`, `||`, `|`).
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
// DANGEROUS - could match "npm test && rm -rf /"
|
|
82
|
+
"allow": "npm *"
|
|
83
|
+
|
|
84
|
+
// SAFER - be specific about allowed commands
|
|
85
|
+
"allow": "npm test"
|
|
86
|
+
"allow": "npm run build"
|
|
87
|
+
"allow": "npm install"
|
|
88
|
+
|
|
89
|
+
// BEST - use semantic prompts instead of wildcards
|
|
90
|
+
// In ExitPlanMode allowedPrompts:
|
|
91
|
+
{ "tool": "Bash", "prompt": "run tests" }
|
|
92
|
+
{ "tool": "Bash", "prompt": "install dependencies" }
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Best practices:**
|
|
96
|
+
- Avoid `*` wildcards in permission rules
|
|
97
|
+
- Use specific command patterns
|
|
98
|
+
- Prefer semantic permission prompts over literal command matching
|
|
99
|
+
- Never allow broad patterns like `rm *` or `git *`
|
|
100
|
+
- Review permission rules after Claude Code updates
|
|
101
|
+
|
|
102
|
+
## 7. Windows Path Safety
|
|
103
|
+
|
|
104
|
+
On Windows, be aware of path-related issues:
|
|
105
|
+
|
|
106
|
+
- Temp directory paths may contain characters like `\t` or `\n` that could be misinterpreted as escape sequences
|
|
107
|
+
- Use raw strings or proper escaping when constructing paths
|
|
108
|
+
- Cloud sync tools (OneDrive, Dropbox) and antivirus may touch file timestamps without changing content
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
// Good - use path.join() which handles platform differences
|
|
112
|
+
const tempPath = path.join(os.tmpdir(), 'myfile.txt');
|
|
113
|
+
|
|
114
|
+
// Bad - manual concatenation can break on Windows
|
|
115
|
+
const tempPath = os.tmpdir() + '/myfile.txt';
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 8. Shell Command Parameter Validation
|
|
119
|
+
|
|
120
|
+
When executing shell commands with dynamic parameters, always validate inputs.
|
|
121
|
+
|
|
122
|
+
**Risk**: Command injection via unvalidated parameters passed to execSync/spawn.
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
// DANGEROUS - lang parameter not validated
|
|
126
|
+
execSync(`sg --pattern "${pattern}" --lang ${lang} --json "${path}"`);
|
|
127
|
+
|
|
128
|
+
// SAFER - validate against whitelist
|
|
129
|
+
const ALLOWED_LANGUAGES = new Set(['typescript', 'javascript', 'python', 'go']);
|
|
130
|
+
if (!ALLOWED_LANGUAGES.has(lang)) {
|
|
131
|
+
throw new Error(`Unsupported language: ${lang}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// BEST - use execFile with array arguments (no shell interpretation)
|
|
135
|
+
const { execFileSync } = require('child_process');
|
|
136
|
+
execFileSync('sg', ['--pattern', pattern, '--lang', lang, '--json', path]);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Best practices:**
|
|
140
|
+
- Validate all dynamic parameters against allowlists
|
|
141
|
+
- Prefer `execFile`/`execFileSync` with array arguments over `exec`/`execSync` with template strings
|
|
142
|
+
- When using template strings, escape all user-controlled values
|
|
143
|
+
- Never interpolate user input directly into shell commands
|
|
@@ -468,10 +468,28 @@ class BaseBridge {
|
|
|
468
468
|
|
|
469
469
|
/**
|
|
470
470
|
* Get template path, preferring package template if project template is outdated
|
|
471
|
+
* SECURITY: Validates template name to prevent path traversal attacks
|
|
471
472
|
* @param {string} templateName - Template filename (e.g., 'gemini-md.hbs')
|
|
472
473
|
* @returns {string|null} Path to best available template
|
|
473
474
|
*/
|
|
474
475
|
getBestTemplatePath(templateName) {
|
|
476
|
+
// Security: Validate template name to prevent path traversal
|
|
477
|
+
if (!templateName || typeof templateName !== 'string') {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Block path traversal attempts
|
|
482
|
+
if (templateName.includes('..') || templateName.includes('/') || templateName.includes('\\')) {
|
|
483
|
+
this.log(`Warning: Invalid template name rejected: ${templateName}`);
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Block absolute paths
|
|
488
|
+
if (path.isAbsolute(templateName)) {
|
|
489
|
+
this.log(`Warning: Absolute template path rejected: ${templateName}`);
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
|
|
475
493
|
const projectTemplate = path.join(this.projectDir, this.workflowDir, 'templates', templateName);
|
|
476
494
|
|
|
477
495
|
// If project template exists and is not outdated, use it
|
|
@@ -564,6 +582,62 @@ class BaseBridge {
|
|
|
564
582
|
});
|
|
565
583
|
}
|
|
566
584
|
|
|
585
|
+
/**
|
|
586
|
+
* Process {{#if condition}}...{{/if}} blocks in template content
|
|
587
|
+
* Supports nested conditionals by processing innermost first
|
|
588
|
+
* @param {string} content - Template content
|
|
589
|
+
* @param {Object} config - Config object for condition evaluation
|
|
590
|
+
* @returns {string} Content with conditionals evaluated
|
|
591
|
+
*/
|
|
592
|
+
processConditionals(content, config) {
|
|
593
|
+
const ifRegex = /\{\{#if\s+([^}]+)\}\}([\s\S]*?)\{\{\/if\}\}/g;
|
|
594
|
+
|
|
595
|
+
let lastContent;
|
|
596
|
+
// Keep processing until no more changes (handles nested conditions)
|
|
597
|
+
do {
|
|
598
|
+
lastContent = content;
|
|
599
|
+
content = content.replace(ifRegex, (match, condition, body) => {
|
|
600
|
+
let value;
|
|
601
|
+
if (condition.startsWith('config.')) {
|
|
602
|
+
value = this.getNestedValue(config, condition.replace('config.', ''));
|
|
603
|
+
} else if (condition === 'skills') {
|
|
604
|
+
value = config.skills?.installed?.length > 0;
|
|
605
|
+
} else {
|
|
606
|
+
value = this.getNestedValue(config, condition);
|
|
607
|
+
}
|
|
608
|
+
return value ? body : '';
|
|
609
|
+
});
|
|
610
|
+
} while (content !== lastContent);
|
|
611
|
+
|
|
612
|
+
return content;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Process {{#each array}}...{{/each}} blocks in template content
|
|
617
|
+
* @param {string} content - Template content
|
|
618
|
+
* @param {Object} config - Config object containing arrays
|
|
619
|
+
* @returns {string} Content with each blocks expanded
|
|
620
|
+
*/
|
|
621
|
+
processEachBlocks(content, config) {
|
|
622
|
+
const eachRegex = /\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g;
|
|
623
|
+
|
|
624
|
+
return content.replace(eachRegex, (match, arrayName, body) => {
|
|
625
|
+
let array;
|
|
626
|
+
if (arrayName === 'skills') {
|
|
627
|
+
array = config.skills?.installed || [];
|
|
628
|
+
} else {
|
|
629
|
+
array = config[arrayName] || [];
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (!Array.isArray(array) || array.length === 0) {
|
|
633
|
+
return '';
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Replace {{this}} in body with each array item
|
|
637
|
+
return array.map(item => body.replace(/\{\{this\}\}/g, String(item))).join('');
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
|
|
567
641
|
/**
|
|
568
642
|
* Get nested value from config object using dot notation
|
|
569
643
|
* @param {Object} obj - The object to search
|
|
@@ -96,59 +96,8 @@ class ClaudeBridge extends BaseBridge {
|
|
|
96
96
|
return content;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
*/
|
|
102
|
-
processConditionals(content, config) {
|
|
103
|
-
// Regex to match {{#if path}}...{{/if}} - handles nested by processing innermost first
|
|
104
|
-
const ifRegex = /\{\{#if\s+([^}]+)\}\}([\s\S]*?)\{\{\/if\}\}/g;
|
|
105
|
-
|
|
106
|
-
let lastContent;
|
|
107
|
-
// Keep processing until no more changes (handles nested conditions)
|
|
108
|
-
do {
|
|
109
|
-
lastContent = content;
|
|
110
|
-
content = content.replace(ifRegex, (match, condition, body) => {
|
|
111
|
-
// Evaluate condition
|
|
112
|
-
let value;
|
|
113
|
-
if (condition.startsWith('config.')) {
|
|
114
|
-
value = this.getNestedValue(config, condition.replace('config.', ''));
|
|
115
|
-
} else if (condition === 'skills') {
|
|
116
|
-
value = config.skills?.installed?.length > 0;
|
|
117
|
-
} else {
|
|
118
|
-
value = this.getNestedValue(config, condition);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// If truthy, return the body (stripped of the conditionals)
|
|
122
|
-
// If falsy, return empty string
|
|
123
|
-
return value ? body : '';
|
|
124
|
-
});
|
|
125
|
-
} while (content !== lastContent);
|
|
126
|
-
|
|
127
|
-
return content;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Process {{#each array}}...{{/each}} blocks
|
|
132
|
-
*/
|
|
133
|
-
processEachBlocks(content, config) {
|
|
134
|
-
const eachRegex = /\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g;
|
|
135
|
-
|
|
136
|
-
return content.replace(eachRegex, (match, arrayName, body) => {
|
|
137
|
-
let array;
|
|
138
|
-
if (arrayName === 'skills') {
|
|
139
|
-
array = config.skills?.installed || [];
|
|
140
|
-
} else {
|
|
141
|
-
array = config[arrayName] || [];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (!Array.isArray(array) || array.length === 0) {
|
|
145
|
-
return '';
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Replace {{this}} in body with each array item
|
|
149
|
-
return array.map(item => body.replace(/\{\{this\}\}/g, String(item))).join('');
|
|
150
|
-
});
|
|
151
|
-
}
|
|
99
|
+
// NOTE: processConditionals() and processEachBlocks() are inherited from BaseBridge
|
|
100
|
+
// Do not override - consolidated per code review to avoid duplication
|
|
152
101
|
|
|
153
102
|
/**
|
|
154
103
|
* Generate default CLAUDE.md when no template exists
|
|
@@ -339,12 +288,8 @@ Last synced: ${new Date().toISOString()}
|
|
|
339
288
|
// This is already handled by syncSkills() in base class
|
|
340
289
|
}
|
|
341
290
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
*/
|
|
345
|
-
getNestedValue(obj, path) {
|
|
346
|
-
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
|
|
347
|
-
}
|
|
291
|
+
// NOTE: getNestedValue() is inherited from BaseBridge with security checks
|
|
292
|
+
// Do not override - see security-patterns.md rule #2
|
|
348
293
|
|
|
349
294
|
/**
|
|
350
295
|
* Generate settings.local.json with permissions
|