wogiflow 1.0.47 → 1.0.49
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/commands/wogi-review-fix.md +241 -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 +466 -0
- package/.workflow/bridges/cursor-bridge.js +573 -0
- package/.workflow/bridges/gemini-bridge.js +614 -0
- package/.workflow/bridges/index.js +41 -4
- package/.workflow/bridges/kimi-bridge.js +454 -0
- package/.workflow/bridges/opencode-bridge.js +825 -0
- 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 +1 -1
- 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/cursor.js +421 -0
- 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/before-submit-prompt.js +102 -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
- package/.claude/rules/README.md +0 -60
- package/.claude/rules/architecture/component-reuse.md +0 -38
- package/.claude/rules/architecture/document-structure.md +0 -76
- package/.claude/rules/architecture/feature-refactoring-cleanup.md +0 -87
- package/.claude/rules/architecture/model-management.md +0 -35
- package/.claude/rules/code-style/naming-conventions.md +0 -55
- package/.claude/rules/security/security-patterns.md +0 -143
- package/.workflow/specs/architecture.md.template +0 -24
- package/.workflow/specs/stack.md.template +0 -33
- package/.workflow/specs/testing.md.template +0 -36
|
@@ -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
|
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Codex CLI Bridge
|
|
5
|
+
*
|
|
6
|
+
* Generates AGENTS.md and .codex/config.toml from WogiFlow configuration.
|
|
7
|
+
* Provides soft parity with Claude Code/Gemini CLI - same rules, same memory,
|
|
8
|
+
* but enforcement is advisory (Codex lacks pre-operation hooks).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const BaseBridge = require('./base-bridge');
|
|
14
|
+
|
|
15
|
+
// Try to load Handlebars, fall back to inline templates if not available
|
|
16
|
+
let Handlebars;
|
|
17
|
+
try {
|
|
18
|
+
Handlebars = require('handlebars');
|
|
19
|
+
} catch {
|
|
20
|
+
Handlebars = null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ============================================================
|
|
24
|
+
// Codex Bridge Class
|
|
25
|
+
// ============================================================
|
|
26
|
+
|
|
27
|
+
class CodexBridge extends BaseBridge {
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
super('codex', options);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ==================== Abstract Method Implementations ====================
|
|
33
|
+
|
|
34
|
+
getCliFolder() {
|
|
35
|
+
return '.codex';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getRulesFileName() {
|
|
39
|
+
return 'AGENTS.md';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getSkillsPath() {
|
|
43
|
+
return path.join('.codex', 'skills');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getRulesPath() {
|
|
47
|
+
return path.join('.codex', 'rules');
|
|
48
|
+
}
|
|
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
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Generate AGENTS.md content
|
|
74
|
+
*/
|
|
75
|
+
generateRulesContent(config) {
|
|
76
|
+
const context = this.buildContext(config);
|
|
77
|
+
|
|
78
|
+
// Try to use Handlebars template with proper error handling
|
|
79
|
+
if (Handlebars) {
|
|
80
|
+
// Register partials before compiling
|
|
81
|
+
this.registerPartials();
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const templatePath = path.join(this.projectDir, this.workflowDir, 'templates', 'agents-md.hbs');
|
|
85
|
+
if (fs.existsSync(templatePath)) {
|
|
86
|
+
const templateSource = fs.readFileSync(templatePath, 'utf-8');
|
|
87
|
+
const template = Handlebars.compile(templateSource);
|
|
88
|
+
return template(context);
|
|
89
|
+
}
|
|
90
|
+
} catch (err) {
|
|
91
|
+
// Template failed - fall through to inline generation
|
|
92
|
+
this.log(`Template generation failed, using fallback: ${err.message}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Fallback to inline generation (includes partial content directly)
|
|
97
|
+
return this.generateAgentsMdFallback(context);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Codex-specific setup: generate config.toml
|
|
102
|
+
*/
|
|
103
|
+
async setupCliSpecific(config) {
|
|
104
|
+
// Generate config.toml
|
|
105
|
+
const configTomlPath = path.join(this.projectDir, this.getCliFolder(), 'config.toml');
|
|
106
|
+
const configTomlContent = this.generateConfigToml(config);
|
|
107
|
+
fs.writeFileSync(configTomlPath, configTomlContent);
|
|
108
|
+
this.log('Generated .codex/config.toml');
|
|
109
|
+
|
|
110
|
+
// Convert Claude skills to Codex format
|
|
111
|
+
this.convertAndSyncSkills();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ==================== Codex-Specific Methods ====================
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build template context
|
|
118
|
+
*/
|
|
119
|
+
buildContext(config) {
|
|
120
|
+
// Load decisions.md for coding rules
|
|
121
|
+
const decisionsPath = path.join(this.projectDir, this.workflowDir, 'state', 'decisions.md');
|
|
122
|
+
let decisions = '';
|
|
123
|
+
try {
|
|
124
|
+
decisions = fs.readFileSync(decisionsPath, 'utf-8');
|
|
125
|
+
} catch (err) {
|
|
126
|
+
// File may not exist or be unreadable - continue with empty decisions
|
|
127
|
+
if (err.code !== 'ENOENT') {
|
|
128
|
+
this.log(`Failed to read decisions.md: ${err.message}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Get installed skills
|
|
133
|
+
const skills = config.skills?.installed || [];
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
projectName: path.basename(this.projectDir),
|
|
137
|
+
projectRoot: this.projectDir,
|
|
138
|
+
timestamp: new Date().toISOString(),
|
|
139
|
+
config,
|
|
140
|
+
decisions,
|
|
141
|
+
skills,
|
|
142
|
+
enforcement: config.enforcement || {},
|
|
143
|
+
research: config.research || {},
|
|
144
|
+
qualityGates: config.qualityGates || {},
|
|
145
|
+
commits: config.commits || {}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Fallback AGENTS.md generation (no Handlebars)
|
|
151
|
+
*/
|
|
152
|
+
generateAgentsMdFallback(context) {
|
|
153
|
+
const lines = [];
|
|
154
|
+
|
|
155
|
+
lines.push('# WogiFlow Project Instructions');
|
|
156
|
+
lines.push('');
|
|
157
|
+
lines.push(`> Generated by WogiFlow Codex Bridge - ${context.timestamp}`);
|
|
158
|
+
lines.push('');
|
|
159
|
+
lines.push('## Core Principles');
|
|
160
|
+
lines.push('');
|
|
161
|
+
lines.push('1. **State files are memory** - Read `.workflow/state/` first');
|
|
162
|
+
lines.push('2. **Config drives behavior** - Follow `.workflow/config.json` rules');
|
|
163
|
+
lines.push('3. **Log every change** - Append to `request-log.md`');
|
|
164
|
+
lines.push('4. **Reuse components** - Check `app-map.md` before creating');
|
|
165
|
+
lines.push('5. **Learn from feedback** - Update instructions when corrected');
|
|
166
|
+
lines.push('');
|
|
167
|
+
|
|
168
|
+
// Task Gating
|
|
169
|
+
if (context.enforcement?.strictMode) {
|
|
170
|
+
lines.push('## Task Gating (MANDATORY)');
|
|
171
|
+
lines.push('');
|
|
172
|
+
lines.push('**STOP. Before ANY implementation:**');
|
|
173
|
+
lines.push('1. Check `.workflow/state/ready.json` for existing tasks');
|
|
174
|
+
lines.push('2. If no task exists, create one with `/wogi-story`');
|
|
175
|
+
lines.push('3. Start with `/wogi-start TASK-XXX`');
|
|
176
|
+
lines.push('');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Research Protocol
|
|
180
|
+
if (context.research?.enabled) {
|
|
181
|
+
lines.push('## Research Protocol');
|
|
182
|
+
lines.push('');
|
|
183
|
+
lines.push('For capability/feasibility/existence questions:');
|
|
184
|
+
lines.push('1. Search local files thoroughly (Glob, Grep)');
|
|
185
|
+
lines.push('2. Web search for current documentation');
|
|
186
|
+
lines.push('3. List assumptions and verify each');
|
|
187
|
+
lines.push('4. Cite sources for all claims');
|
|
188
|
+
lines.push('5. State confidence level (HIGH/MEDIUM/LOW)');
|
|
189
|
+
lines.push('');
|
|
190
|
+
lines.push('**FORBIDDEN:** Claiming "X doesn\'t exist" without exhaustive search.');
|
|
191
|
+
lines.push('');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Essential Commands
|
|
195
|
+
lines.push('## Essential Commands');
|
|
196
|
+
lines.push('');
|
|
197
|
+
lines.push('| Command | Purpose |');
|
|
198
|
+
lines.push('|---------|---------|');
|
|
199
|
+
lines.push('| `/wogi-ready` | Show available tasks |');
|
|
200
|
+
lines.push('| `/wogi-start TASK-X` | Start task with context |');
|
|
201
|
+
lines.push('| `/wogi-story "title"` | Create story with AC |');
|
|
202
|
+
lines.push('| `/wogi-status` | Project overview |');
|
|
203
|
+
lines.push('| `/wogi-research "q"` | Research before answering |');
|
|
204
|
+
lines.push('');
|
|
205
|
+
|
|
206
|
+
// Request Logging
|
|
207
|
+
lines.push('## Request Logging');
|
|
208
|
+
lines.push('');
|
|
209
|
+
lines.push('After EVERY request that changes files:');
|
|
210
|
+
lines.push('```markdown');
|
|
211
|
+
lines.push('### R-[XXX] | [YYYY-MM-DD HH:MM]');
|
|
212
|
+
lines.push('**Type**: new | fix | change | refactor');
|
|
213
|
+
lines.push('**Tags**: #screen:[name] #component:[name]');
|
|
214
|
+
lines.push('**Request**: "[what user asked]"');
|
|
215
|
+
lines.push('**Result**: [what was done]');
|
|
216
|
+
lines.push('**Files**: [files changed]');
|
|
217
|
+
lines.push('```');
|
|
218
|
+
lines.push('');
|
|
219
|
+
|
|
220
|
+
// Component Reuse
|
|
221
|
+
lines.push('## Component Reuse');
|
|
222
|
+
lines.push('');
|
|
223
|
+
lines.push('**Before creating ANY component:**');
|
|
224
|
+
lines.push('1. Check `app-map.md`');
|
|
225
|
+
lines.push('2. Search codebase for existing');
|
|
226
|
+
lines.push('3. Priority: Use existing → Add variant → Extend → Create new');
|
|
227
|
+
lines.push('');
|
|
228
|
+
|
|
229
|
+
// File Locations
|
|
230
|
+
lines.push('## File Locations');
|
|
231
|
+
lines.push('');
|
|
232
|
+
lines.push('| What | Where |');
|
|
233
|
+
lines.push('|------|-------|');
|
|
234
|
+
lines.push('| Config | `.workflow/config.json` |');
|
|
235
|
+
lines.push('| Tasks | `.workflow/state/ready.json` |');
|
|
236
|
+
lines.push('| Logs | `.workflow/state/request-log.md` |');
|
|
237
|
+
lines.push('| Components | `.workflow/state/app-map.md` |');
|
|
238
|
+
lines.push('| Rules | `.workflow/state/decisions.md` |');
|
|
239
|
+
lines.push('');
|
|
240
|
+
|
|
241
|
+
lines.push('---');
|
|
242
|
+
lines.push('');
|
|
243
|
+
lines.push('*Note: Enforcement is advisory. Codex lacks pre-operation hooks.*');
|
|
244
|
+
|
|
245
|
+
return lines.join('\n');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Generate .codex/config.toml content
|
|
250
|
+
*/
|
|
251
|
+
generateConfigToml(config) {
|
|
252
|
+
const context = this.buildContext(config);
|
|
253
|
+
|
|
254
|
+
// Try to use Handlebars template with proper error handling
|
|
255
|
+
if (Handlebars) {
|
|
256
|
+
try {
|
|
257
|
+
const templatePath = path.join(this.projectDir, this.workflowDir, 'templates', 'codex-config.hbs');
|
|
258
|
+
if (fs.existsSync(templatePath)) {
|
|
259
|
+
const templateSource = fs.readFileSync(templatePath, 'utf-8');
|
|
260
|
+
const template = Handlebars.compile(templateSource);
|
|
261
|
+
return template(context);
|
|
262
|
+
}
|
|
263
|
+
} catch (err) {
|
|
264
|
+
// Template failed - fall through to inline generation
|
|
265
|
+
this.log(`Config template failed, using fallback: ${err.message}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Fallback to inline generation
|
|
270
|
+
return this.generateConfigTomlFallback(context);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Fallback config.toml generation
|
|
275
|
+
*/
|
|
276
|
+
generateConfigTomlFallback(context) {
|
|
277
|
+
const lines = [];
|
|
278
|
+
|
|
279
|
+
lines.push('# WogiFlow Codex Configuration');
|
|
280
|
+
lines.push(`# Generated: ${context.timestamp}`);
|
|
281
|
+
lines.push('');
|
|
282
|
+
|
|
283
|
+
// Approvals - safer default
|
|
284
|
+
lines.push('[approvals]');
|
|
285
|
+
lines.push('mode = "on-request"');
|
|
286
|
+
lines.push('');
|
|
287
|
+
|
|
288
|
+
// MCP Servers
|
|
289
|
+
lines.push('[mcp_servers.wogiflow_memory]');
|
|
290
|
+
lines.push('command = "node"');
|
|
291
|
+
lines.push(`args = ["mcp-memory-server/index.js"]`);
|
|
292
|
+
lines.push(`cwd = "${context.projectRoot}"`);
|
|
293
|
+
lines.push('');
|
|
294
|
+
|
|
295
|
+
// Features
|
|
296
|
+
lines.push('[features]');
|
|
297
|
+
lines.push('child_agents_md = true');
|
|
298
|
+
lines.push('');
|
|
299
|
+
|
|
300
|
+
// Fallback filenames
|
|
301
|
+
lines.push('project_doc_fallback_filenames = ["CLAUDE.md", "GEMINI.md"]');
|
|
302
|
+
lines.push('');
|
|
303
|
+
|
|
304
|
+
return lines.join('\n');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Convert Claude skills to Codex SKILL.md format
|
|
309
|
+
*/
|
|
310
|
+
convertAndSyncSkills() {
|
|
311
|
+
const claudeSkillsDir = path.join(this.projectDir, '.claude', 'skills');
|
|
312
|
+
const codexSkillsDir = path.join(this.projectDir, this.getSkillsPath());
|
|
313
|
+
|
|
314
|
+
if (!fs.existsSync(claudeSkillsDir)) {
|
|
315
|
+
this.log('No Claude skills to convert');
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Ensure Codex skills directory exists with explicit permissions
|
|
320
|
+
if (!fs.existsSync(codexSkillsDir)) {
|
|
321
|
+
fs.mkdirSync(codexSkillsDir, { recursive: true, mode: 0o755 });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
let skillDirs;
|
|
325
|
+
try {
|
|
326
|
+
skillDirs = fs.readdirSync(claudeSkillsDir, { withFileTypes: true })
|
|
327
|
+
.filter(d => d.isDirectory())
|
|
328
|
+
.map(d => d.name);
|
|
329
|
+
} catch (err) {
|
|
330
|
+
this.log(`Failed to read skills directory: ${err.message}`);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let converted = 0;
|
|
335
|
+
let skipped = 0;
|
|
336
|
+
|
|
337
|
+
for (const skillName of skillDirs) {
|
|
338
|
+
try {
|
|
339
|
+
// SECURITY: Validate skillName to prevent path traversal
|
|
340
|
+
// path.basename() removes any directory components (../, etc.)
|
|
341
|
+
const safeSkillName = path.basename(skillName);
|
|
342
|
+
if (safeSkillName !== skillName) {
|
|
343
|
+
this.log(`Skipping suspicious skill name: ${skillName}`);
|
|
344
|
+
skipped++;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Additional validation: only alphanumeric, dash, underscore
|
|
349
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(safeSkillName)) {
|
|
350
|
+
this.log(`Skipping skill with invalid characters: ${skillName}`);
|
|
351
|
+
skipped++;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const claudeSkillMd = path.join(claudeSkillsDir, safeSkillName, 'skill.md');
|
|
356
|
+
|
|
357
|
+
if (fs.existsSync(claudeSkillMd)) {
|
|
358
|
+
const content = fs.readFileSync(claudeSkillMd, 'utf-8');
|
|
359
|
+
const codexContent = this.convertSkillToCodex(safeSkillName, content);
|
|
360
|
+
|
|
361
|
+
// Create Codex skill directory with explicit permissions
|
|
362
|
+
const codexSkillDir = path.join(codexSkillsDir, safeSkillName);
|
|
363
|
+
if (!fs.existsSync(codexSkillDir)) {
|
|
364
|
+
fs.mkdirSync(codexSkillDir, { recursive: true, mode: 0o755 });
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Write SKILL.md (Codex format)
|
|
368
|
+
fs.writeFileSync(path.join(codexSkillDir, 'SKILL.md'), codexContent);
|
|
369
|
+
this.log(`Converted skill: ${safeSkillName}`);
|
|
370
|
+
converted++;
|
|
371
|
+
}
|
|
372
|
+
} catch (err) {
|
|
373
|
+
// Log error but continue with other skills
|
|
374
|
+
this.log(`Failed to convert skill ${skillName}: ${err.message}`);
|
|
375
|
+
skipped++;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (converted > 0 || skipped > 0) {
|
|
380
|
+
this.log(`Skill sync complete: ${converted} converted, ${skipped} skipped`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Convert a Claude skill.md to Codex SKILL.md format
|
|
386
|
+
*/
|
|
387
|
+
convertSkillToCodex(skillName, claudeContent) {
|
|
388
|
+
const lines = [];
|
|
389
|
+
|
|
390
|
+
// Parse Claude frontmatter if present
|
|
391
|
+
let description = `WogiFlow ${skillName} skill`;
|
|
392
|
+
let body = claudeContent;
|
|
393
|
+
|
|
394
|
+
const frontmatterMatch = claudeContent.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
395
|
+
if (frontmatterMatch) {
|
|
396
|
+
const frontmatter = frontmatterMatch[1];
|
|
397
|
+
body = frontmatterMatch[2].trim();
|
|
398
|
+
|
|
399
|
+
const descMatch = frontmatter.match(/description:\s*["']?([^"'\n]+)["']?/);
|
|
400
|
+
if (descMatch) {
|
|
401
|
+
description = descMatch[1];
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Generate Codex SKILL.md format
|
|
406
|
+
lines.push('---');
|
|
407
|
+
lines.push(`name: ${skillName}`);
|
|
408
|
+
lines.push(`description: ${description}`);
|
|
409
|
+
lines.push('metadata:');
|
|
410
|
+
lines.push(` short-description: ${description.slice(0, 50)}`);
|
|
411
|
+
lines.push(` source: wogi-flow`);
|
|
412
|
+
lines.push('---');
|
|
413
|
+
lines.push('');
|
|
414
|
+
lines.push(body);
|
|
415
|
+
|
|
416
|
+
return lines.join('\n');
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// ============================================================
|
|
421
|
+
// Module Exports
|
|
422
|
+
// ============================================================
|
|
423
|
+
|
|
424
|
+
module.exports = CodexBridge;
|
|
425
|
+
|
|
426
|
+
// CLI interface if run directly
|
|
427
|
+
if (require.main === module) {
|
|
428
|
+
const args = process.argv.slice(2);
|
|
429
|
+
const command = args[0];
|
|
430
|
+
|
|
431
|
+
const bridge = new CodexBridge({ verbose: true });
|
|
432
|
+
|
|
433
|
+
switch (command) {
|
|
434
|
+
case 'sync':
|
|
435
|
+
bridge.sync().then(results => {
|
|
436
|
+
console.log('');
|
|
437
|
+
console.log('Codex Bridge Sync Results:');
|
|
438
|
+
console.log(` Success: ${results.success}`);
|
|
439
|
+
console.log(` Duration: ${results.duration}ms`);
|
|
440
|
+
console.log(` Synced: ${results.synced.join(', ')}`);
|
|
441
|
+
if (results.errors.length > 0) {
|
|
442
|
+
console.log(` Errors: ${results.errors.map(e => e.error).join(', ')}`);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
break;
|
|
446
|
+
|
|
447
|
+
case 'status':
|
|
448
|
+
const agentsMdExists = fs.existsSync(path.join(process.cwd(), 'AGENTS.md'));
|
|
449
|
+
const configTomlExists = fs.existsSync(path.join(process.cwd(), '.codex', 'config.toml'));
|
|
450
|
+
|
|
451
|
+
console.log('Codex Bridge Status:');
|
|
452
|
+
console.log(` AGENTS.md: ${agentsMdExists ? '✓ exists' : '✗ missing'}`);
|
|
453
|
+
console.log(` .codex/config.toml: ${configTomlExists ? '✓ exists' : '✗ missing'}`);
|
|
454
|
+
break;
|
|
455
|
+
|
|
456
|
+
default:
|
|
457
|
+
console.log('WogiFlow Codex Bridge');
|
|
458
|
+
console.log('');
|
|
459
|
+
console.log('Commands:');
|
|
460
|
+
console.log(' sync Sync WogiFlow config to Codex format');
|
|
461
|
+
console.log(' status Check current Codex configuration');
|
|
462
|
+
console.log('');
|
|
463
|
+
console.log('Usage:');
|
|
464
|
+
console.log(' node .workflow/bridges/codex-bridge.js sync');
|
|
465
|
+
}
|
|
466
|
+
}
|