wogiflow 1.0.48 → 1.0.50
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/commands/wogi-research.md +223 -0
- package/.claude/docs/commands.md +17 -0
- package/.workflow/bridges/base-bridge.js +124 -15
- package/.workflow/bridges/claude-bridge.js +50 -22
- package/.workflow/bridges/codex-bridge.js +26 -1
- package/.workflow/bridges/cursor-bridge.js +26 -1
- package/.workflow/bridges/gemini-bridge.js +4 -1
- package/.workflow/bridges/kimi-bridge.js +30 -1
- package/.workflow/bridges/opencode-bridge.js +26 -1
- package/.workflow/templates/agents-md.hbs +127 -0
- package/.workflow/templates/claude-md.hbs +66 -0
- package/.workflow/templates/codex-config.hbs +69 -0
- package/.workflow/templates/cursor-rules.mdc.hbs +142 -0
- package/.workflow/templates/gemini-md.hbs +334 -26
- package/.workflow/templates/opencode-agents-md.hbs +158 -0
- package/.workflow/templates/opencode-config.hbs +27 -0
- package/.workflow/templates/partials/auto-features.hbs +125 -0
- package/.workflow/templates/partials/enforcement-rules.hbs +164 -0
- package/.workflow/templates/partials/user-commands.hbs +154 -0
- package/.workflow/templates/research-report.md +153 -0
- package/README.md +170 -1589
- package/package.json +4 -4
- package/scripts/flow-import-profile +17 -8
- package/scripts/flow-operational-scanner.js +13 -8
- package/scripts/flow-parity-check.js +281 -0
- package/scripts/flow-prompt-composer.js +10 -0
- package/scripts/flow-research-protocol.js +1022 -0
- package/scripts/flow-strict-adherence.js +14 -9
- package/scripts/flow-utils.js +4 -0
- package/scripts/hooks/adapters/base-adapter.js +22 -2
- package/scripts/hooks/adapters/claude-code.js +14 -1
- package/scripts/hooks/adapters/gemini.js +368 -0
- package/scripts/hooks/adapters/index.js +99 -0
- package/scripts/hooks/adapters/opencode.js +317 -0
- package/scripts/hooks/core/constants.js +75 -0
- package/scripts/hooks/core/implementation-gate.js +15 -1
- package/scripts/hooks/core/index.js +21 -1
- package/scripts/hooks/core/research-gate.js +306 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +1 -1
- package/scripts/hooks/entry/claude-code/user-prompt-submit.js +47 -2
- package/scripts/hooks/entry/cursor/after-file-edit.js +127 -0
- package/scripts/hooks/entry/cursor/before-shell.js +131 -0
- package/scripts/hooks/entry/cursor/session-start.js +125 -0
- package/scripts/hooks/entry/cursor/stop.js +135 -0
- package/scripts/hooks/entry/gemini-cli/after-tool.js +119 -0
- package/scripts/hooks/entry/gemini-cli/before-agent.js +140 -0
- package/scripts/hooks/entry/gemini-cli/before-tool.js +189 -0
- package/scripts/hooks/entry/gemini-cli/session-end.js +120 -0
- package/scripts/hooks/entry/gemini-cli/session-start.js +80 -0
- package/scripts/hooks/entry/opencode/prompt-append.js +76 -0
- package/scripts/hooks/entry/opencode/session-start.js +86 -0
- package/scripts/hooks/entry/opencode/tool-after.js +77 -0
- package/scripts/hooks/entry/opencode/tool-before.js +143 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# /wogi-research - Zero-Trust Research Protocol
|
|
2
|
+
|
|
3
|
+
Execute rigorous research before answering questions about capabilities, feasibility, or existence.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
/wogi-research "Does X support Y?" # Standard depth
|
|
9
|
+
/wogi-research --quick "Simple question" # Quick check (5K tokens)
|
|
10
|
+
/wogi-research --deep "Architecture query" # Deep audit (50K tokens)
|
|
11
|
+
/wogi-research --exhaustive "Critical decision" # Full audit (100K tokens)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## When This is Required
|
|
15
|
+
|
|
16
|
+
This command is **automatically triggered** (when strict mode is enabled) for:
|
|
17
|
+
|
|
18
|
+
1. **Capability Questions**: "Does X support Y?", "Can X do Y?"
|
|
19
|
+
2. **Feasibility Questions**: "Is it possible to...", "Can we..."
|
|
20
|
+
3. **Existence Questions**: "Is there a...", "Does X exist?"
|
|
21
|
+
4. **Architecture Questions**: "How does X work?", "How is X structured?"
|
|
22
|
+
5. **Integration Questions**: "How to integrate X with Y?"
|
|
23
|
+
|
|
24
|
+
## Research Protocol Phases
|
|
25
|
+
|
|
26
|
+
### Phase 1: Scope Mapping
|
|
27
|
+
- Identify all potentially relevant local files
|
|
28
|
+
- Identify external tools/libraries mentioned
|
|
29
|
+
- Generate search keywords
|
|
30
|
+
- Create `research-scope.json`
|
|
31
|
+
|
|
32
|
+
### Phase 2: Local Evidence Gathering
|
|
33
|
+
- Read ALL files identified in scope (not just the first match)
|
|
34
|
+
- Extract relevant code snippets and documentation
|
|
35
|
+
- Log findings to research notes
|
|
36
|
+
- **DO NOT SKIP FILES** - partial reading leads to false conclusions
|
|
37
|
+
|
|
38
|
+
### Phase 3: External Verification
|
|
39
|
+
- For each external tool/library:
|
|
40
|
+
- Web search: "[tool] documentation [feature] [current year]"
|
|
41
|
+
- Read official docs (top 3 results minimum)
|
|
42
|
+
- Extract quotes with URLs
|
|
43
|
+
- **ASSUME training data is 2+ years stale**
|
|
44
|
+
|
|
45
|
+
### Phase 4: Assumption Check
|
|
46
|
+
- List ALL assumptions made during research
|
|
47
|
+
- Tag each assumption:
|
|
48
|
+
- `[VERIFIED]` with HIGH confidence + source
|
|
49
|
+
- `[UNVERIFIED]` with LOW confidence - **MUST be verified before proceeding**
|
|
50
|
+
- Loop back to Phase 2/3 for any unverified assumptions
|
|
51
|
+
|
|
52
|
+
### Phase 5: Synthesis
|
|
53
|
+
- Generate research report with:
|
|
54
|
+
- Answer to original question
|
|
55
|
+
- Evidence chain (every claim → source)
|
|
56
|
+
- Confidence level (HIGH/MEDIUM/LOW)
|
|
57
|
+
- Caveats and uncertainties
|
|
58
|
+
- List of searches performed
|
|
59
|
+
|
|
60
|
+
## Critical Rules
|
|
61
|
+
|
|
62
|
+
### The Negative Evidence Rule
|
|
63
|
+
|
|
64
|
+
**FORBIDDEN conclusions:**
|
|
65
|
+
- "X is not supported"
|
|
66
|
+
- "There is no Y"
|
|
67
|
+
- "It doesn't exist"
|
|
68
|
+
- "X cannot do Y"
|
|
69
|
+
|
|
70
|
+
**REQUIRED format for negative claims:**
|
|
71
|
+
```
|
|
72
|
+
I searched the following sources and found no evidence of X:
|
|
73
|
+
1. [source 1] - searched for [terms]
|
|
74
|
+
2. [source 2] - searched for [terms]
|
|
75
|
+
3. [official docs URL] - no mention found
|
|
76
|
+
|
|
77
|
+
However, my search may be incomplete. Before concluding X doesn't exist:
|
|
78
|
+
- Check if there's a different name for this feature
|
|
79
|
+
- Verify with the latest official documentation
|
|
80
|
+
- Consider that the feature may be in development
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### The Version Paranoia Rule
|
|
84
|
+
|
|
85
|
+
For ANY external tool (npm packages, CLIs, APIs, frameworks):
|
|
86
|
+
```
|
|
87
|
+
ASSUME: Training data is 2+ years old
|
|
88
|
+
ACTION: ALWAYS web search "[tool] latest documentation [current year]"
|
|
89
|
+
BEFORE making capability claims
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### The Assumption Stack
|
|
93
|
+
|
|
94
|
+
Before answering, explicitly list:
|
|
95
|
+
```markdown
|
|
96
|
+
## My Assumptions
|
|
97
|
+
1. [VERIFY] Gemini CLI version supports hooks → Confidence: LOW (training data)
|
|
98
|
+
2. [OK] Project uses JavaScript → Confidence: HIGH (read package.json)
|
|
99
|
+
3. [VERIFY] settings.json format → Confidence: LOW (haven't read docs)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Any assumption marked `[VERIFY]` with `LOW` confidence **MUST** be verified.
|
|
103
|
+
|
|
104
|
+
## Evidence Chain Format
|
|
105
|
+
|
|
106
|
+
Every claim needs a traceable source:
|
|
107
|
+
|
|
108
|
+
```markdown
|
|
109
|
+
| Claim | Source Type | Source Location | Confidence |
|
|
110
|
+
|-------|-------------|-----------------|------------|
|
|
111
|
+
| "Hooks are supported" | Live Docs | github.com/x/docs/hooks | HIGH |
|
|
112
|
+
| "Settings format is X" | File Read | .gemini/settings.json | HIGH |
|
|
113
|
+
| "Feature Y exists" | Training Data | None | LOW - VERIFY |
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Depth Tiers
|
|
117
|
+
|
|
118
|
+
| Depth | Token Budget | Actions | Use For |
|
|
119
|
+
|-------|--------------|---------|---------|
|
|
120
|
+
| `--quick` | 5K | 1-2 files, no web search | Simple factual lookups |
|
|
121
|
+
| (default) | 20K | All relevant files, 1 web search | Most questions |
|
|
122
|
+
| `--deep` | 50K | Full file audit, multiple web searches | Architecture/feasibility |
|
|
123
|
+
| `--exhaustive` | 100K+ | Everything + user confirmation gates | Production decisions |
|
|
124
|
+
|
|
125
|
+
## Output
|
|
126
|
+
|
|
127
|
+
The command generates:
|
|
128
|
+
|
|
129
|
+
1. **research-report.md** - Full research findings with citations
|
|
130
|
+
2. **Console summary** - Key findings and confidence level
|
|
131
|
+
3. **Cached verifications** - Stored in `.workflow/state/research-cache.json`
|
|
132
|
+
|
|
133
|
+
## Configuration
|
|
134
|
+
|
|
135
|
+
In `.workflow/config.json`:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"research": {
|
|
140
|
+
"enabled": true,
|
|
141
|
+
"defaultDepth": "standard",
|
|
142
|
+
"strictMode": true,
|
|
143
|
+
"autoTrigger": true,
|
|
144
|
+
"maxTokensPerDepth": {
|
|
145
|
+
"quick": 5000,
|
|
146
|
+
"standard": 20000,
|
|
147
|
+
"deep": 50000,
|
|
148
|
+
"exhaustive": 100000
|
|
149
|
+
},
|
|
150
|
+
"requireCitations": true,
|
|
151
|
+
"cacheVerifications": true,
|
|
152
|
+
"cacheExpiryHours": 24,
|
|
153
|
+
"budgetMode": "soft",
|
|
154
|
+
"negativeEvidenceRule": true,
|
|
155
|
+
"assumptionTracking": true
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Examples
|
|
161
|
+
|
|
162
|
+
### Example 1: Capability Question
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
User: Does Gemini CLI support hooks?
|
|
166
|
+
|
|
167
|
+
/wogi-research "Does Gemini CLI support hooks?"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Research output:
|
|
171
|
+
```
|
|
172
|
+
## Research Report
|
|
173
|
+
|
|
174
|
+
**Question:** Does Gemini CLI support hooks?
|
|
175
|
+
**Depth:** standard
|
|
176
|
+
**Confidence:** HIGH
|
|
177
|
+
|
|
178
|
+
### Conclusion
|
|
179
|
+
Yes, Gemini CLI supports hooks since version X.
|
|
180
|
+
|
|
181
|
+
### Evidence Chain
|
|
182
|
+
| Claim | Source | Confidence |
|
|
183
|
+
|-------|--------|------------|
|
|
184
|
+
| Hooks supported | https://github.com/gemini-cli/docs/hooks | HIGH |
|
|
185
|
+
| Configuration in .gemini/settings.json | File read | HIGH |
|
|
186
|
+
|
|
187
|
+
### Searches Performed
|
|
188
|
+
1. Web: "Gemini CLI hooks documentation 2026"
|
|
189
|
+
2. Local: .gemini/settings.json
|
|
190
|
+
3. Local: .gemini/**/*.md
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Example 2: Architecture Question
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
User: How does the authentication flow work in this codebase?
|
|
197
|
+
|
|
198
|
+
/wogi-research --deep "How does the authentication flow work?"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
This will:
|
|
202
|
+
1. Search for auth-related files
|
|
203
|
+
2. Read all matches (not just first)
|
|
204
|
+
3. Trace the flow through the codebase
|
|
205
|
+
4. Generate a comprehensive report
|
|
206
|
+
|
|
207
|
+
## Integration with Hooks
|
|
208
|
+
|
|
209
|
+
When `research.strictMode` is enabled and `research.autoTrigger` is true:
|
|
210
|
+
- Capability/feasibility questions automatically trigger research
|
|
211
|
+
- Claims without citations are flagged
|
|
212
|
+
- Negative claims require exhaustive search evidence
|
|
213
|
+
|
|
214
|
+
## CLI Compatibility
|
|
215
|
+
|
|
216
|
+
This command works across all supported CLIs:
|
|
217
|
+
- Claude Code
|
|
218
|
+
- Gemini CLI
|
|
219
|
+
- Codex (OpenAI)
|
|
220
|
+
- OpenCode
|
|
221
|
+
- Cline/Cursor
|
|
222
|
+
|
|
223
|
+
State is stored in `.workflow/` for cross-CLI persistence.
|
package/.claude/docs/commands.md
CHANGED
|
@@ -141,6 +141,15 @@ When user types these commands, execute the corresponding action immediately.
|
|
|
141
141
|
|---------|--------|
|
|
142
142
|
| `/wogi-guided-edit` | Guide through multi-file changes step by step. Shows each edit for approval. |
|
|
143
143
|
|
|
144
|
+
### Research Protocol (Zero-Trust)
|
|
145
|
+
|
|
146
|
+
| Command | Action |
|
|
147
|
+
|---------|--------|
|
|
148
|
+
| `/wogi-research [question]` | Execute rigorous research before answering capability/feasibility questions. Phases: scope mapping, evidence gathering, external verification, assumption check, synthesis. |
|
|
149
|
+
| `/wogi-research --quick [q]` | Quick research (5K tokens) - 1-2 files, no web search. |
|
|
150
|
+
| `/wogi-research --deep [q]` | Deep research (50K tokens) - full file audit, multiple web searches. |
|
|
151
|
+
| `/wogi-research --exhaustive [q]` | Exhaustive research (100K+ tokens) - everything + user confirmation gates. |
|
|
152
|
+
|
|
144
153
|
### Planning & Documentation
|
|
145
154
|
|
|
146
155
|
| Command | Action |
|
|
@@ -312,6 +321,14 @@ npx flow onboard # Analyze existing project & set up context
|
|
|
312
321
|
./scripts/flow figma confirm <f> # Interactive confirmation
|
|
313
322
|
./scripts/flow figma generate # Generate code from decisions
|
|
314
323
|
./scripts/flow figma server # Start MCP server
|
|
324
|
+
|
|
325
|
+
# Research Protocol
|
|
326
|
+
./scripts/flow research "q" # Execute research protocol
|
|
327
|
+
./scripts/flow research --quick # Quick research (5K tokens)
|
|
328
|
+
./scripts/flow research --deep # Deep research (50K tokens)
|
|
329
|
+
./scripts/flow research --exhaustive # Full audit (100K+ tokens)
|
|
330
|
+
./scripts/flow research cache # Show cached verifications
|
|
331
|
+
./scripts/flow research cache clear # Clear verification cache
|
|
315
332
|
```
|
|
316
333
|
|
|
317
334
|
## Command Execution
|
|
@@ -109,26 +109,64 @@ class BaseBridge {
|
|
|
109
109
|
|
|
110
110
|
/**
|
|
111
111
|
* Safe JSON parse that checks for prototype pollution (fallback when flow-utils unavailable)
|
|
112
|
+
*
|
|
113
|
+
* NOTE: This is a fallback implementation. When flow-utils is available,
|
|
114
|
+
* safeJsonParse from flow-utils is used instead (it performs recursive
|
|
115
|
+
* validation of the parsed object). This fallback only does pre-parse
|
|
116
|
+
* string checking which is less comprehensive but better than nothing.
|
|
117
|
+
*
|
|
112
118
|
* @param {string} content - JSON string to parse
|
|
113
119
|
* @returns {Object|null} Parsed object or null if invalid
|
|
114
120
|
*/
|
|
115
121
|
safeJsonParseContent(content) {
|
|
116
|
-
|
|
117
|
-
const contentLower = content.toLowerCase();
|
|
118
|
-
if (contentLower.includes('__proto__') ||
|
|
119
|
-
contentLower.includes('constructor') ||
|
|
120
|
-
contentLower.includes('prototype')) {
|
|
121
|
-
this.log('Warning: Potential prototype pollution detected in JSON');
|
|
122
|
+
if (!content || typeof content !== 'string') {
|
|
122
123
|
return null;
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
// Check for prototype pollution attempts
|
|
127
|
+
// These patterns look for dangerous keys that could be used for prototype pollution
|
|
128
|
+
const dangerousPatterns = [
|
|
129
|
+
/__proto__/i,
|
|
130
|
+
/"constructor"\s*:/,
|
|
131
|
+
/"prototype"\s*:/
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
for (const pattern of dangerousPatterns) {
|
|
135
|
+
if (pattern.test(content)) {
|
|
136
|
+
this.log('Warning: Potential prototype pollution detected in JSON');
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
125
141
|
try {
|
|
126
142
|
const parsed = JSON.parse(content);
|
|
127
143
|
if (parsed === null || typeof parsed !== 'object') {
|
|
128
144
|
return null;
|
|
129
145
|
}
|
|
146
|
+
|
|
147
|
+
// Additional check: verify no __proto__ keys in parsed result
|
|
148
|
+
// (JSON.parse could still create them in some edge cases)
|
|
149
|
+
const hasProtoKey = (obj) => {
|
|
150
|
+
if (!obj || typeof obj !== 'object') return false;
|
|
151
|
+
if (Object.prototype.hasOwnProperty.call(obj, '__proto__')) return true;
|
|
152
|
+
for (const value of Object.values(obj)) {
|
|
153
|
+
if (typeof value === 'object' && value !== null && hasProtoKey(value)) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
if (hasProtoKey(parsed)) {
|
|
161
|
+
this.log('Warning: __proto__ key detected in parsed JSON');
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
130
165
|
return parsed;
|
|
131
|
-
} catch {
|
|
166
|
+
} catch (err) {
|
|
167
|
+
if (process.env.DEBUG) {
|
|
168
|
+
this.log(`JSON parse error: ${err.message}`);
|
|
169
|
+
}
|
|
132
170
|
return null;
|
|
133
171
|
}
|
|
134
172
|
}
|
|
@@ -164,7 +202,8 @@ class BaseBridge {
|
|
|
164
202
|
ensureCliFolder() {
|
|
165
203
|
const cliFolder = path.join(this.projectDir, this.getCliFolder());
|
|
166
204
|
if (!fs.existsSync(cliFolder)) {
|
|
167
|
-
|
|
205
|
+
// Use explicit permissions (0o755 = rwxr-xr-x) to avoid relying on umask
|
|
206
|
+
fs.mkdirSync(cliFolder, { recursive: true, mode: 0o755 });
|
|
168
207
|
this.log(`Created ${this.getCliFolder()}/`);
|
|
169
208
|
}
|
|
170
209
|
}
|
|
@@ -181,9 +220,9 @@ class BaseBridge {
|
|
|
181
220
|
return;
|
|
182
221
|
}
|
|
183
222
|
|
|
184
|
-
// Ensure target directory exists
|
|
223
|
+
// Ensure target directory exists with explicit permissions
|
|
185
224
|
if (!fs.existsSync(targetSkillsDir)) {
|
|
186
|
-
fs.mkdirSync(targetSkillsDir, { recursive: true });
|
|
225
|
+
fs.mkdirSync(targetSkillsDir, { recursive: true, mode: 0o755 });
|
|
187
226
|
}
|
|
188
227
|
|
|
189
228
|
// Copy skills
|
|
@@ -214,9 +253,9 @@ class BaseBridge {
|
|
|
214
253
|
syncRules() {
|
|
215
254
|
const rulesDir = path.join(this.projectDir, this.getRulesPath());
|
|
216
255
|
|
|
217
|
-
// Ensure rules directory exists
|
|
256
|
+
// Ensure rules directory exists with explicit permissions
|
|
218
257
|
if (!fs.existsSync(rulesDir)) {
|
|
219
|
-
fs.mkdirSync(rulesDir, { recursive: true });
|
|
258
|
+
fs.mkdirSync(rulesDir, { recursive: true, mode: 0o755 });
|
|
220
259
|
}
|
|
221
260
|
|
|
222
261
|
// Copy any existing rules from .workflow/rules/ if present
|
|
@@ -244,9 +283,9 @@ class BaseBridge {
|
|
|
244
283
|
// Knowledge files to sync
|
|
245
284
|
const knowledgeFiles = ['stack.md', 'architecture.md', 'testing.md'];
|
|
246
285
|
|
|
247
|
-
// Ensure CLI docs directory exists
|
|
286
|
+
// Ensure CLI docs directory exists with explicit permissions
|
|
248
287
|
if (!fs.existsSync(cliDocsDir)) {
|
|
249
|
-
fs.mkdirSync(cliDocsDir, { recursive: true });
|
|
288
|
+
fs.mkdirSync(cliDocsDir, { recursive: true, mode: 0o755 });
|
|
250
289
|
}
|
|
251
290
|
|
|
252
291
|
let syncedCount = 0;
|
|
@@ -359,6 +398,76 @@ class BaseBridge {
|
|
|
359
398
|
return results;
|
|
360
399
|
}
|
|
361
400
|
|
|
401
|
+
// ==================== Template Utility Methods ====================
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Load a partial template from .workflow/templates/partials/
|
|
405
|
+
* @param {string} partialName - Name of the partial (without .hbs extension)
|
|
406
|
+
* @returns {string} Partial content or empty string if not found
|
|
407
|
+
*/
|
|
408
|
+
loadPartial(partialName) {
|
|
409
|
+
const partialPath = path.join(
|
|
410
|
+
this.projectDir,
|
|
411
|
+
this.workflowDir,
|
|
412
|
+
'templates',
|
|
413
|
+
'partials',
|
|
414
|
+
`${partialName}.hbs`
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
try {
|
|
418
|
+
if (fs.existsSync(partialPath)) {
|
|
419
|
+
return fs.readFileSync(partialPath, 'utf-8');
|
|
420
|
+
}
|
|
421
|
+
} catch (err) {
|
|
422
|
+
this.log(`Warning: Could not load partial ${partialName}: ${err.message}`);
|
|
423
|
+
}
|
|
424
|
+
return '';
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Process partial includes in template content
|
|
429
|
+
* Replaces {{> partial-name}} with the partial content
|
|
430
|
+
* @param {string} content - Template content
|
|
431
|
+
* @returns {string} Content with partials included
|
|
432
|
+
*/
|
|
433
|
+
processPartials(content) {
|
|
434
|
+
// Match {{> partial-name}} pattern
|
|
435
|
+
const partialRegex = /\{\{>\s*([a-zA-Z0-9_-]+)\s*\}\}/g;
|
|
436
|
+
|
|
437
|
+
return content.replace(partialRegex, (match, partialName) => {
|
|
438
|
+
const partialContent = this.loadPartial(partialName);
|
|
439
|
+
if (!partialContent) {
|
|
440
|
+
this.log(`Warning: Partial not found: ${partialName}`);
|
|
441
|
+
return `<!-- Partial not found: ${partialName} -->`;
|
|
442
|
+
}
|
|
443
|
+
return partialContent;
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Get nested value from config object using dot notation
|
|
449
|
+
* @param {Object} obj - The object to search
|
|
450
|
+
* @param {string} path - Dot-separated path (e.g., 'hooks.rules.taskGating')
|
|
451
|
+
* @returns {*} The value or undefined
|
|
452
|
+
*/
|
|
453
|
+
getNestedValue(obj, path) {
|
|
454
|
+
if (!path || typeof path !== 'string') return undefined;
|
|
455
|
+
|
|
456
|
+
const parts = path.split('.');
|
|
457
|
+
let current = obj;
|
|
458
|
+
|
|
459
|
+
for (const part of parts) {
|
|
460
|
+
if (current === null || current === undefined) return undefined;
|
|
461
|
+
// Security: skip dangerous property names
|
|
462
|
+
if (part === '__proto__' || part === 'constructor' || part === 'prototype') {
|
|
463
|
+
return undefined;
|
|
464
|
+
}
|
|
465
|
+
current = current[part];
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return current;
|
|
469
|
+
}
|
|
470
|
+
|
|
362
471
|
// ==================== Utility Methods ====================
|
|
363
472
|
|
|
364
473
|
/**
|
|
@@ -366,7 +475,7 @@ class BaseBridge {
|
|
|
366
475
|
*/
|
|
367
476
|
copyDirRecursive(source, target) {
|
|
368
477
|
if (!fs.existsSync(target)) {
|
|
369
|
-
fs.mkdirSync(target, { recursive: true });
|
|
478
|
+
fs.mkdirSync(target, { recursive: true, mode: 0o755 });
|
|
370
479
|
}
|
|
371
480
|
|
|
372
481
|
const items = fs.readdirSync(source);
|
|
@@ -59,12 +59,21 @@ class ClaudeBridge extends BaseBridge {
|
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Generate CLAUDE.md from Handlebars-like template
|
|
62
|
-
* Supports: {{variable}}, {{config.path}}, {{#if}}, {{#each}}, {{/if}}, {{/each}}
|
|
62
|
+
* Supports: {{variable}}, {{config.path}}, {{#if}}, {{#each}}, {{/if}}, {{/each}}, {{> partial}}
|
|
63
63
|
*/
|
|
64
64
|
generateFromTemplate(templatePath, config) {
|
|
65
|
-
|
|
65
|
+
let template;
|
|
66
|
+
try {
|
|
67
|
+
template = fs.readFileSync(templatePath, 'utf-8');
|
|
68
|
+
} catch (err) {
|
|
69
|
+
this.log(`Warning: Could not read template ${templatePath}: ${err.message}`);
|
|
70
|
+
return this.generateDefaultClaudeMd(config);
|
|
71
|
+
}
|
|
66
72
|
let content = template;
|
|
67
73
|
|
|
74
|
+
// Process {{> partial}} includes first (before other processing)
|
|
75
|
+
content = this.processPartials(content);
|
|
76
|
+
|
|
68
77
|
// Process {{#if config.path.to.value}}...{{/if}} blocks
|
|
69
78
|
// Non-greedy match to handle nested conditions
|
|
70
79
|
content = this.processConditionals(content, config);
|
|
@@ -340,24 +349,47 @@ Last synced: ${new Date().toISOString()}
|
|
|
340
349
|
}
|
|
341
350
|
|
|
342
351
|
/**
|
|
343
|
-
* Generate settings.local.json with
|
|
344
|
-
* Claude Code 2.1.
|
|
352
|
+
* Generate settings.local.json with permissions
|
|
353
|
+
* NOTE: Requires Claude Code 2.1.7+ which fixed wildcard matching of shell operators.
|
|
354
|
+
* See security-patterns.md rule #6 for details.
|
|
345
355
|
*/
|
|
346
356
|
generateSettings(config) {
|
|
347
357
|
const projectDir = this.projectDir;
|
|
348
358
|
|
|
349
|
-
//
|
|
359
|
+
// Permission rules - balancing security with workflow convenience
|
|
360
|
+
// Wildcards are safe in 2.1.7+ (shell operators rejected)
|
|
350
361
|
const wildcardPermissions = [
|
|
351
|
-
// Package managers
|
|
352
|
-
'Bash(npm *)',
|
|
362
|
+
// Package managers - specific safe operations
|
|
363
|
+
'Bash(npm install *)',
|
|
364
|
+
'Bash(npm run *)',
|
|
365
|
+
'Bash(npm test *)',
|
|
366
|
+
'Bash(npm exec *)',
|
|
367
|
+
'Bash(npm ci)',
|
|
368
|
+
'Bash(npm audit *)',
|
|
369
|
+
'Bash(npm outdated *)',
|
|
370
|
+
'Bash(npm ls *)',
|
|
371
|
+
'Bash(npm version *)',
|
|
353
372
|
'Bash(npx *)',
|
|
354
|
-
'Bash(yarn *)',
|
|
355
|
-
'Bash(
|
|
356
|
-
'Bash(
|
|
357
|
-
'Bash(
|
|
358
|
-
'Bash(
|
|
359
|
-
|
|
360
|
-
|
|
373
|
+
'Bash(yarn install *)',
|
|
374
|
+
'Bash(yarn add *)',
|
|
375
|
+
'Bash(yarn remove *)',
|
|
376
|
+
'Bash(yarn run *)',
|
|
377
|
+
'Bash(yarn test *)',
|
|
378
|
+
'Bash(yarn build *)',
|
|
379
|
+
'Bash(yarn dev *)',
|
|
380
|
+
'Bash(pnpm install *)',
|
|
381
|
+
'Bash(pnpm add *)',
|
|
382
|
+
'Bash(pnpm remove *)',
|
|
383
|
+
'Bash(pnpm run *)',
|
|
384
|
+
'Bash(pnpm test *)',
|
|
385
|
+
'Bash(pnpm build *)',
|
|
386
|
+
'Bash(pnpm dev *)',
|
|
387
|
+
'Bash(pip install *)',
|
|
388
|
+
'Bash(pip list *)',
|
|
389
|
+
'Bash(python -m *)',
|
|
390
|
+
'Bash(python3 -m *)',
|
|
391
|
+
|
|
392
|
+
// Git operations - all safe read/write operations
|
|
361
393
|
'Bash(git status)',
|
|
362
394
|
'Bash(git status *)',
|
|
363
395
|
'Bash(git diff *)',
|
|
@@ -386,18 +418,14 @@ Last synced: ${new Date().toISOString()}
|
|
|
386
418
|
'Bash(./scripts/flow *)',
|
|
387
419
|
'Bash(./scripts/flow)',
|
|
388
420
|
|
|
389
|
-
//
|
|
421
|
+
// Safe read-only utilities
|
|
390
422
|
'Bash(ls *)',
|
|
391
423
|
'Bash(tree *)',
|
|
392
|
-
'Bash(cat *)',
|
|
393
|
-
'Bash(head *)',
|
|
394
|
-
'Bash(tail *)',
|
|
395
424
|
'Bash(wc *)',
|
|
396
|
-
'Bash(grep *)',
|
|
397
|
-
'Bash(find *)',
|
|
398
425
|
'Bash(chmod +x *)', // Only make executable, not arbitrary permissions
|
|
399
|
-
'Bash(node *)',
|
|
400
|
-
'Bash(
|
|
426
|
+
'Bash(node --check *)',
|
|
427
|
+
'Bash(node --version)',
|
|
428
|
+
'Bash(bash -n *)', // Syntax check only
|
|
401
429
|
'Bash(open *)',
|
|
402
430
|
'Bash(test *)',
|
|
403
431
|
|
|
@@ -47,6 +47,28 @@ class CodexBridge extends BaseBridge {
|
|
|
47
47
|
return path.join('.codex', 'rules');
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Register template partials with Handlebars
|
|
52
|
+
*/
|
|
53
|
+
registerPartials() {
|
|
54
|
+
if (!Handlebars) return;
|
|
55
|
+
|
|
56
|
+
const partialsDir = path.join(this.projectDir, this.workflowDir, 'templates', 'partials');
|
|
57
|
+
if (!fs.existsSync(partialsDir)) return;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const partialFiles = fs.readdirSync(partialsDir).filter(f => f.endsWith('.hbs'));
|
|
61
|
+
for (const file of partialFiles) {
|
|
62
|
+
const partialName = path.basename(file, '.hbs');
|
|
63
|
+
const partialContent = fs.readFileSync(path.join(partialsDir, file), 'utf-8');
|
|
64
|
+
Handlebars.registerPartial(partialName, partialContent);
|
|
65
|
+
this.log(`Registered partial: ${partialName}`);
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
this.log(`Warning: Could not register partials: ${err.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
50
72
|
/**
|
|
51
73
|
* Generate AGENTS.md content
|
|
52
74
|
*/
|
|
@@ -55,6 +77,9 @@ class CodexBridge extends BaseBridge {
|
|
|
55
77
|
|
|
56
78
|
// Try to use Handlebars template with proper error handling
|
|
57
79
|
if (Handlebars) {
|
|
80
|
+
// Register partials before compiling
|
|
81
|
+
this.registerPartials();
|
|
82
|
+
|
|
58
83
|
try {
|
|
59
84
|
const templatePath = path.join(this.projectDir, this.workflowDir, 'templates', 'agents-md.hbs');
|
|
60
85
|
if (fs.existsSync(templatePath)) {
|
|
@@ -68,7 +93,7 @@ class CodexBridge extends BaseBridge {
|
|
|
68
93
|
}
|
|
69
94
|
}
|
|
70
95
|
|
|
71
|
-
// Fallback to inline generation
|
|
96
|
+
// Fallback to inline generation (includes partial content directly)
|
|
72
97
|
return this.generateAgentsMdFallback(context);
|
|
73
98
|
}
|
|
74
99
|
|
|
@@ -55,6 +55,28 @@ class CursorBridge extends BaseBridge {
|
|
|
55
55
|
return path.join('.cursor', 'rules');
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Register template partials with Handlebars
|
|
60
|
+
*/
|
|
61
|
+
registerPartials() {
|
|
62
|
+
if (!Handlebars) return;
|
|
63
|
+
|
|
64
|
+
const partialsDir = path.join(this.projectDir, this.workflowDir, 'templates', 'partials');
|
|
65
|
+
if (!fs.existsSync(partialsDir)) return;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const partialFiles = fs.readdirSync(partialsDir).filter(f => f.endsWith('.hbs'));
|
|
69
|
+
for (const file of partialFiles) {
|
|
70
|
+
const partialName = path.basename(file, '.hbs');
|
|
71
|
+
const partialContent = fs.readFileSync(path.join(partialsDir, file), 'utf-8');
|
|
72
|
+
Handlebars.registerPartial(partialName, partialContent);
|
|
73
|
+
this.log(`Registered partial: ${partialName}`);
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
this.log(`Warning: Could not register partials: ${err.message}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
58
80
|
/**
|
|
59
81
|
* Generate rules content (.mdc format with YAML frontmatter)
|
|
60
82
|
*/
|
|
@@ -63,6 +85,9 @@ class CursorBridge extends BaseBridge {
|
|
|
63
85
|
|
|
64
86
|
// Try to use Handlebars template
|
|
65
87
|
if (Handlebars) {
|
|
88
|
+
// Register partials before compiling
|
|
89
|
+
this.registerPartials();
|
|
90
|
+
|
|
66
91
|
const templatePath = path.join(this.projectDir, this.workflowDir, 'templates', 'cursor-rules.mdc.hbs');
|
|
67
92
|
if (fs.existsSync(templatePath)) {
|
|
68
93
|
try {
|
|
@@ -75,7 +100,7 @@ class CursorBridge extends BaseBridge {
|
|
|
75
100
|
}
|
|
76
101
|
}
|
|
77
102
|
|
|
78
|
-
// Fallback to inline generation
|
|
103
|
+
// Fallback to inline generation (includes partial content directly)
|
|
79
104
|
return this.generateRulesFallback(context);
|
|
80
105
|
}
|
|
81
106
|
|
|
@@ -70,7 +70,7 @@ class GeminiBridge extends BaseBridge {
|
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
* Generate GEMINI.md from Handlebars-like template
|
|
73
|
-
* Supports: {{variable}}, {{config.path}}, {{#if}}, {{#each}}, {{/if}}, {{/each}}
|
|
73
|
+
* Supports: {{variable}}, {{config.path}}, {{#if}}, {{#each}}, {{/if}}, {{/each}}, {{> partial}}
|
|
74
74
|
*/
|
|
75
75
|
generateFromTemplate(templatePath, config) {
|
|
76
76
|
let template;
|
|
@@ -82,6 +82,9 @@ class GeminiBridge extends BaseBridge {
|
|
|
82
82
|
}
|
|
83
83
|
let content = template;
|
|
84
84
|
|
|
85
|
+
// Process {{> partial}} includes first (before other processing)
|
|
86
|
+
content = this.processPartials(content);
|
|
87
|
+
|
|
85
88
|
// Process {{#if config.path.to.value}}...{{/if}} blocks
|
|
86
89
|
content = this.processConditionals(content, config);
|
|
87
90
|
|