5-phase-workflow 1.9.1 → 1.9.2
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/README.md +23 -1
- package/bin/install.js +2 -0
- package/bin/sync-agents.js +639 -0
- package/package.json +1 -1
- package/src/commands/5/analyze-feature.md +159 -0
- package/src/commands/5/plan-feature.md +29 -39
- package/src/commands/5/plan-implementation.md +2 -2
- package/src/commands/5/synchronize-agents.md +60 -0
- package/src/templates/workflow/FEATURE-SPEC.md +60 -95
package/README.md
CHANGED
|
@@ -141,6 +141,7 @@ Claude Code exposes the workflow under the `/5:` namespace. Codex exposes the sa
|
|
|
141
141
|
| `/5:quick-implement` or `$5-quick-implement` | Fast | Streamlined workflow for small tasks |
|
|
142
142
|
| `/5:eject` or `$5-eject` | Utility | Permanently remove update infrastructure |
|
|
143
143
|
| `/5:unlock` or `$5-unlock` | Utility | Remove planning guard lock |
|
|
144
|
+
| `/5:synchronize-agents` or `$5-synchronize-agents` | Utility | Sync user content between Claude Code and Codex runtimes |
|
|
144
145
|
|
|
145
146
|
## Configuration
|
|
146
147
|
|
|
@@ -278,7 +279,8 @@ After installation, your `.claude/` directory will contain:
|
|
|
278
279
|
│ ├── quick-implement.md
|
|
279
280
|
│ ├── configure.md
|
|
280
281
|
│ ├── eject.md
|
|
281
|
-
│
|
|
282
|
+
│ ├── unlock.md
|
|
283
|
+
│ └── synchronize-agents.md
|
|
282
284
|
├── skills/ # Atomic operations
|
|
283
285
|
│ ├── build-project/
|
|
284
286
|
│ ├── run-tests/
|
|
@@ -398,6 +400,26 @@ This permanently removes the update infrastructure:
|
|
|
398
400
|
|
|
399
401
|
All other workflow files remain untouched. **This is irreversible.** To restore update functionality, reinstall with `npx 5-phase-workflow`.
|
|
400
402
|
|
|
403
|
+
### Synchronizing Runtimes
|
|
404
|
+
|
|
405
|
+
If you have both Claude Code and Codex installed, user-generated content (project-specific skills, custom commands, rules) only exists in the runtime where it was created. To sync this content bidirectionally:
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
# Claude Code
|
|
409
|
+
/5:synchronize-agents
|
|
410
|
+
|
|
411
|
+
# Codex
|
|
412
|
+
$5-synchronize-agents
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
This will:
|
|
416
|
+
- Sync project-specific skills (e.g., `create-controller`, `run-tests`) between `.claude/skills/` and `.codex/skills/` with appropriate format conversion
|
|
417
|
+
- Convert custom Claude commands to Codex skills
|
|
418
|
+
- Append `.claude/rules/` content to `.codex/instructions.md`
|
|
419
|
+
- Sync Codex-only skills back to Claude
|
|
420
|
+
|
|
421
|
+
Workflow-managed files and shared data (`.5/`) are not affected — those are handled by the installer.
|
|
422
|
+
|
|
401
423
|
## Development
|
|
402
424
|
|
|
403
425
|
### Running Tests
|
package/bin/install.js
CHANGED
|
@@ -847,6 +847,7 @@ function showCommandsHelp(isGlobal) {
|
|
|
847
847
|
log.info(' $5-reconfigure - Refresh docs/skills (no Q&A)');
|
|
848
848
|
log.info(' $5-eject - Eject from update mechanism');
|
|
849
849
|
log.info(' $5-unlock - Remove planning guard lock');
|
|
850
|
+
log.info(' $5-synchronize-agents - Sync user content between runtimes');
|
|
850
851
|
} else {
|
|
851
852
|
log.info('Available commands:');
|
|
852
853
|
log.info(' /5:plan-feature - Start feature planning (Phase 1)');
|
|
@@ -859,6 +860,7 @@ function showCommandsHelp(isGlobal) {
|
|
|
859
860
|
log.info(' /5:reconfigure - Refresh docs/skills (no Q&A)');
|
|
860
861
|
log.info(' /5:eject - Eject from update mechanism');
|
|
861
862
|
log.info(' /5:unlock - Remove planning guard lock');
|
|
863
|
+
log.info(' /5:synchronize-agents - Sync user content between runtimes');
|
|
862
864
|
}
|
|
863
865
|
log.info('');
|
|
864
866
|
log.info(`Config file: ${path.join(getDataPath(isGlobal), 'config.json')}`);
|
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// ANSI colors for terminal output
|
|
7
|
+
const colors = {
|
|
8
|
+
reset: '\x1b[0m',
|
|
9
|
+
bright: '\x1b[1m',
|
|
10
|
+
green: '\x1b[32m',
|
|
11
|
+
yellow: '\x1b[33m',
|
|
12
|
+
blue: '\x1b[34m',
|
|
13
|
+
red: '\x1b[31m',
|
|
14
|
+
dim: '\x1b[2m'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const log = {
|
|
18
|
+
info: (msg) => console.log(`${colors.blue}i${colors.reset} ${msg}`),
|
|
19
|
+
success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`),
|
|
20
|
+
warn: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`),
|
|
21
|
+
error: (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`),
|
|
22
|
+
header: (msg) => console.log(`\n${colors.bright}${msg}${colors.reset}\n`),
|
|
23
|
+
dim: (msg) => console.log(` ${colors.dim}${msg}${colors.reset}`)
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// ── Workflow-managed file lists (must match install.js) ────────────────────
|
|
27
|
+
|
|
28
|
+
const WORKFLOW_MANAGED_SKILLS = new Set([
|
|
29
|
+
'configure-docs-index',
|
|
30
|
+
'configure-project',
|
|
31
|
+
'configure-skills',
|
|
32
|
+
'generate-readme'
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const WORKFLOW_MANAGED_AGENTS = new Set([
|
|
36
|
+
'component-executor.md'
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
const RULES_SYNC_START = '<!-- 5-sync:rules-start -->';
|
|
40
|
+
const RULES_SYNC_END = '<!-- 5-sync:rules-end -->';
|
|
41
|
+
const AGENTS_SYNC_START = '<!-- 5-sync:agents-start -->';
|
|
42
|
+
const AGENTS_SYNC_END = '<!-- 5-sync:agents-end -->';
|
|
43
|
+
|
|
44
|
+
// ── Conversion functions (mirrors install.js logic) ────────────────────────
|
|
45
|
+
|
|
46
|
+
function extractFrontmatterAndBody(content) {
|
|
47
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
48
|
+
if (!match) return { frontmatter: null, body: content };
|
|
49
|
+
return { frontmatter: match[1], body: match[2] };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function extractFrontmatterField(frontmatter, field) {
|
|
53
|
+
if (!frontmatter) return null;
|
|
54
|
+
const match = frontmatter.match(new RegExp(`^${field}:\\s*(.+)$`, 'm'));
|
|
55
|
+
return match ? match[1].trim() : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function claudeToCodexContent(content) {
|
|
59
|
+
return content
|
|
60
|
+
.replace(/\/5:([a-z0-9-]+)/g, (_, name) => `$5-${name}`)
|
|
61
|
+
.replace(/\.claude\//g, '.codex/');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function codexToClaudeContent(content) {
|
|
65
|
+
return content
|
|
66
|
+
.replace(/\$5-([a-z0-9-]+)/g, (_, name) => `/5:${name}`)
|
|
67
|
+
.replace(/\.codex\//g, '.claude/')
|
|
68
|
+
.replace(/<codex_skill_adapter>[\s\S]*?<\/codex_skill_adapter>\n*/g, '');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getCodexSkillAdapterHeader(skillName) {
|
|
72
|
+
const invocation = `$${skillName}`;
|
|
73
|
+
return `<codex_skill_adapter>
|
|
74
|
+
## Skill Invocation
|
|
75
|
+
- This skill is invoked by mentioning \`${invocation}\`.
|
|
76
|
+
- Treat all user text after \`${invocation}\` as the skill argument.
|
|
77
|
+
|
|
78
|
+
## Tool Mapping (Claude Code → Codex)
|
|
79
|
+
This skill was authored for Claude Code. Map these tool references:
|
|
80
|
+
|
|
81
|
+
| Claude Code | Codex Equivalent |
|
|
82
|
+
|-------------|------------------|
|
|
83
|
+
| \`AskUserQuestion\` | Ask the user directly in conversation |
|
|
84
|
+
| \`Task(subagent_type="Explore")\` | Research the codebase yourself using available tools |
|
|
85
|
+
| \`Task(prompt="...")\` | \`spawn_agent(message="...")\` |
|
|
86
|
+
| \`Read\` | \`read_file\` |
|
|
87
|
+
| \`Write\` | \`write_file\` |
|
|
88
|
+
| \`Edit\` | \`patch\` |
|
|
89
|
+
| \`Bash\` | \`shell\` |
|
|
90
|
+
| \`Glob\` | \`glob\` / \`list_directory\` |
|
|
91
|
+
| \`Grep\` | \`grep\` / \`search\` |
|
|
92
|
+
| \`TaskCreate/TaskUpdate\` | Track progress internally |
|
|
93
|
+
| \`EnterPlanMode\` | Not available — use structured output instead |
|
|
94
|
+
</codex_skill_adapter>`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function convertClaudeCommandToCodexSkill(content, skillName) {
|
|
98
|
+
const converted = claudeToCodexContent(content);
|
|
99
|
+
const { frontmatter, body } = extractFrontmatterAndBody(converted);
|
|
100
|
+
|
|
101
|
+
let description = `Custom command: ${skillName}`;
|
|
102
|
+
if (frontmatter) {
|
|
103
|
+
const maybeDesc = extractFrontmatterField(frontmatter, 'description');
|
|
104
|
+
if (maybeDesc) description = maybeDesc;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const shortDesc = description.length > 180 ? `${description.slice(0, 177)}...` : description;
|
|
108
|
+
const adapter = getCodexSkillAdapterHeader(skillName);
|
|
109
|
+
|
|
110
|
+
return `---
|
|
111
|
+
name: ${skillName}
|
|
112
|
+
description: ${description}
|
|
113
|
+
metadata:
|
|
114
|
+
short-description: ${shortDesc}
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
${adapter}
|
|
118
|
+
|
|
119
|
+
${body.trimStart()}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function convertClaudeSkillToCodex(content) {
|
|
123
|
+
return claudeToCodexContent(content);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function convertCodexSkillToClaude(content) {
|
|
127
|
+
return codexToClaudeContent(content);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── File helpers ────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
function copyDirContents(src, dest, transformMd) {
|
|
133
|
+
if (!fs.existsSync(dest)) {
|
|
134
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
135
|
+
}
|
|
136
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
137
|
+
for (const entry of entries) {
|
|
138
|
+
const srcPath = path.join(src, entry.name);
|
|
139
|
+
const destPath = path.join(dest, entry.name);
|
|
140
|
+
if (entry.isDirectory()) {
|
|
141
|
+
copyDirContents(srcPath, destPath, transformMd);
|
|
142
|
+
} else if (transformMd && entry.name.endsWith('.md')) {
|
|
143
|
+
const content = fs.readFileSync(srcPath, 'utf8');
|
|
144
|
+
fs.writeFileSync(destPath, transformMd(content));
|
|
145
|
+
} else {
|
|
146
|
+
fs.copyFileSync(srcPath, destPath);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function dirContentsEqual(dirA, dirB, transformFn) {
|
|
152
|
+
if (!fs.existsSync(dirA) || !fs.existsSync(dirB)) return false;
|
|
153
|
+
|
|
154
|
+
const filesA = fs.readdirSync(dirA).sort();
|
|
155
|
+
const filesB = fs.readdirSync(dirB).sort();
|
|
156
|
+
if (filesA.length !== filesB.length) return false;
|
|
157
|
+
|
|
158
|
+
for (const file of filesA) {
|
|
159
|
+
if (!filesB.includes(file)) return false;
|
|
160
|
+
const pathA = path.join(dirA, file);
|
|
161
|
+
const pathB = path.join(dirB, file);
|
|
162
|
+
const statA = fs.statSync(pathA);
|
|
163
|
+
const statB = fs.statSync(pathB);
|
|
164
|
+
if (statA.isDirectory() !== statB.isDirectory()) return false;
|
|
165
|
+
if (statA.isDirectory()) {
|
|
166
|
+
if (!dirContentsEqual(pathA, pathB, transformFn)) return false;
|
|
167
|
+
} else {
|
|
168
|
+
let contentA = fs.readFileSync(pathA, 'utf8');
|
|
169
|
+
const contentB = fs.readFileSync(pathB, 'utf8');
|
|
170
|
+
if (file.endsWith('.md') && transformFn) {
|
|
171
|
+
contentA = transformFn(contentA);
|
|
172
|
+
}
|
|
173
|
+
if (contentA !== contentB) return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── Inventory ──────────────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
function findProjectRoot() {
|
|
182
|
+
// Walk up from cwd to find .5/ or .claude/ or .codex/
|
|
183
|
+
let dir = process.cwd();
|
|
184
|
+
while (dir !== path.dirname(dir)) {
|
|
185
|
+
if (fs.existsSync(path.join(dir, '.5')) ||
|
|
186
|
+
fs.existsSync(path.join(dir, '.claude')) ||
|
|
187
|
+
fs.existsSync(path.join(dir, '.codex'))) {
|
|
188
|
+
return dir;
|
|
189
|
+
}
|
|
190
|
+
dir = path.dirname(dir);
|
|
191
|
+
}
|
|
192
|
+
return process.cwd();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function isClaudeInstalled(root) {
|
|
196
|
+
return fs.existsSync(path.join(root, '.claude', 'commands', '5', 'plan-feature.md'));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function isCodexInstalled(root) {
|
|
200
|
+
return fs.existsSync(path.join(root, '.codex', 'skills', '5-plan-feature', 'SKILL.md'));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function getClaudeUserSkills(root) {
|
|
204
|
+
const skillsDir = path.join(root, '.claude', 'skills');
|
|
205
|
+
if (!fs.existsSync(skillsDir)) return [];
|
|
206
|
+
return fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
207
|
+
.filter(d => d.isDirectory())
|
|
208
|
+
.map(d => d.name)
|
|
209
|
+
.filter(name => !WORKFLOW_MANAGED_SKILLS.has(name));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function getCodexUserSkills(root) {
|
|
213
|
+
const skillsDir = path.join(root, '.codex', 'skills');
|
|
214
|
+
if (!fs.existsSync(skillsDir)) return [];
|
|
215
|
+
return fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
216
|
+
.filter(d => d.isDirectory())
|
|
217
|
+
.map(d => d.name)
|
|
218
|
+
.filter(name => !name.startsWith('5-') && !WORKFLOW_MANAGED_SKILLS.has(name));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function getClaudeCustomCommands(root) {
|
|
222
|
+
const commandsDir = path.join(root, '.claude', 'commands');
|
|
223
|
+
if (!fs.existsSync(commandsDir)) return [];
|
|
224
|
+
const namespaces = fs.readdirSync(commandsDir, { withFileTypes: true })
|
|
225
|
+
.filter(d => d.isDirectory() && d.name !== '5')
|
|
226
|
+
.map(d => d.name);
|
|
227
|
+
|
|
228
|
+
const commands = [];
|
|
229
|
+
for (const ns of namespaces) {
|
|
230
|
+
const nsDir = path.join(commandsDir, ns);
|
|
231
|
+
const files = fs.readdirSync(nsDir).filter(f => f.endsWith('.md'));
|
|
232
|
+
for (const file of files) {
|
|
233
|
+
commands.push({ namespace: ns, name: file.replace('.md', ''), file });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return commands;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function getClaudeCustomAgents(root) {
|
|
240
|
+
const agentsDir = path.join(root, '.claude', 'agents');
|
|
241
|
+
if (!fs.existsSync(agentsDir)) return [];
|
|
242
|
+
return fs.readdirSync(agentsDir)
|
|
243
|
+
.filter(f => f.endsWith('.md') && !WORKFLOW_MANAGED_AGENTS.has(f));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function getClaudeRules(root) {
|
|
247
|
+
const rulesDir = path.join(root, '.claude', 'rules');
|
|
248
|
+
if (!fs.existsSync(rulesDir)) return [];
|
|
249
|
+
return fs.readdirSync(rulesDir).filter(f => f.endsWith('.md'));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ── Sync action classification ─────────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
function classifySkillActions(root) {
|
|
255
|
+
const claudeSkills = getClaudeUserSkills(root);
|
|
256
|
+
const codexSkills = getCodexUserSkills(root);
|
|
257
|
+
const claudeSet = new Set(claudeSkills);
|
|
258
|
+
const codexSet = new Set(codexSkills);
|
|
259
|
+
|
|
260
|
+
const actions = [];
|
|
261
|
+
|
|
262
|
+
// Claude → Codex
|
|
263
|
+
for (const skill of claudeSkills) {
|
|
264
|
+
const claudeDir = path.join(root, '.claude', 'skills', skill);
|
|
265
|
+
const codexDir = path.join(root, '.codex', 'skills', skill);
|
|
266
|
+
if (!codexSet.has(skill)) {
|
|
267
|
+
actions.push({ type: 'new', direction: 'claude-to-codex', category: 'skill', name: skill });
|
|
268
|
+
} else if (!dirContentsEqual(claudeDir, codexDir, convertClaudeSkillToCodex)) {
|
|
269
|
+
actions.push({ type: 'updated', direction: 'claude-to-codex', category: 'skill', name: skill });
|
|
270
|
+
} else {
|
|
271
|
+
actions.push({ type: 'in-sync', direction: 'both', category: 'skill', name: skill });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Codex → Claude
|
|
276
|
+
for (const skill of codexSkills) {
|
|
277
|
+
if (claudeSet.has(skill)) continue; // Already handled above
|
|
278
|
+
const claudeDir = path.join(root, '.claude', 'skills', skill);
|
|
279
|
+
if (!fs.existsSync(claudeDir)) {
|
|
280
|
+
actions.push({ type: 'new', direction: 'codex-to-claude', category: 'skill', name: skill });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return actions;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function classifyCommandActions(root) {
|
|
288
|
+
const commands = getClaudeCustomCommands(root);
|
|
289
|
+
const actions = [];
|
|
290
|
+
|
|
291
|
+
for (const cmd of commands) {
|
|
292
|
+
const skillName = `${cmd.namespace}-${cmd.name}`;
|
|
293
|
+
const codexSkillDir = path.join(root, '.codex', 'skills', skillName);
|
|
294
|
+
if (!fs.existsSync(codexSkillDir)) {
|
|
295
|
+
actions.push({ type: 'new', direction: 'claude-to-codex', category: 'command', name: `${cmd.namespace}/${cmd.name}`, skillName });
|
|
296
|
+
} else {
|
|
297
|
+
// Check if content changed
|
|
298
|
+
const claudeContent = fs.readFileSync(path.join(root, '.claude', 'commands', cmd.namespace, cmd.file), 'utf8');
|
|
299
|
+
const converted = convertClaudeCommandToCodexSkill(claudeContent, skillName);
|
|
300
|
+
const existing = fs.readFileSync(path.join(codexSkillDir, 'SKILL.md'), 'utf8');
|
|
301
|
+
if (converted !== existing) {
|
|
302
|
+
actions.push({ type: 'updated', direction: 'claude-to-codex', category: 'command', name: `${cmd.namespace}/${cmd.name}`, skillName });
|
|
303
|
+
} else {
|
|
304
|
+
actions.push({ type: 'in-sync', direction: 'both', category: 'command', name: `${cmd.namespace}/${cmd.name}` });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return actions;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function classifyRulesActions(root) {
|
|
313
|
+
const rules = getClaudeRules(root);
|
|
314
|
+
if (rules.length === 0) return [];
|
|
315
|
+
|
|
316
|
+
const instructionsPath = path.join(root, '.codex', 'instructions.md');
|
|
317
|
+
if (!fs.existsSync(instructionsPath)) {
|
|
318
|
+
return [{ type: 'new', direction: 'claude-to-codex', category: 'rules', name: `${rules.length} rule file(s)` }];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const instructions = fs.readFileSync(instructionsPath, 'utf8');
|
|
322
|
+
const newSection = buildRulesSection(root, rules);
|
|
323
|
+
|
|
324
|
+
// Extract existing section
|
|
325
|
+
const startIdx = instructions.indexOf(RULES_SYNC_START);
|
|
326
|
+
const endIdx = instructions.indexOf(RULES_SYNC_END);
|
|
327
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
328
|
+
return [{ type: 'new', direction: 'claude-to-codex', category: 'rules', name: `${rules.length} rule file(s)` }];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const existing = instructions.substring(startIdx, endIdx + RULES_SYNC_END.length);
|
|
332
|
+
if (existing === newSection) {
|
|
333
|
+
return [{ type: 'in-sync', direction: 'both', category: 'rules', name: `${rules.length} rule file(s)` }];
|
|
334
|
+
}
|
|
335
|
+
return [{ type: 'updated', direction: 'claude-to-codex', category: 'rules', name: `${rules.length} rule file(s)` }];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function classifyAgentActions(root) {
|
|
339
|
+
const agents = getClaudeCustomAgents(root);
|
|
340
|
+
if (agents.length === 0) return [];
|
|
341
|
+
|
|
342
|
+
const instructionsPath = path.join(root, '.codex', 'instructions.md');
|
|
343
|
+
if (!fs.existsSync(instructionsPath)) {
|
|
344
|
+
return [{ type: 'new', direction: 'claude-to-codex', category: 'agents', name: `${agents.length} agent(s)` }];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const instructions = fs.readFileSync(instructionsPath, 'utf8');
|
|
348
|
+
const newSection = buildAgentsSection(root, agents);
|
|
349
|
+
|
|
350
|
+
const startIdx = instructions.indexOf(AGENTS_SYNC_START);
|
|
351
|
+
const endIdx = instructions.indexOf(AGENTS_SYNC_END);
|
|
352
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
353
|
+
return [{ type: 'new', direction: 'claude-to-codex', category: 'agents', name: `${agents.length} agent(s)` }];
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const existing = instructions.substring(startIdx, endIdx + AGENTS_SYNC_END.length);
|
|
357
|
+
if (existing === newSection) {
|
|
358
|
+
return [{ type: 'in-sync', direction: 'both', category: 'agents', name: `${agents.length} agent(s)` }];
|
|
359
|
+
}
|
|
360
|
+
return [{ type: 'updated', direction: 'claude-to-codex', category: 'agents', name: `${agents.length} agent(s)` }];
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ── Build sync content ─────────────────────────────────────────────────────
|
|
364
|
+
|
|
365
|
+
function buildRulesSection(root, ruleFiles) {
|
|
366
|
+
const rulesDir = path.join(root, '.claude', 'rules');
|
|
367
|
+
let section = `${RULES_SYNC_START}\n## Project Rules\n\n`;
|
|
368
|
+
section += '> Synced from .claude/rules/ — do not edit this section manually.\n';
|
|
369
|
+
section += '> Re-run /5:synchronize-agents (or $5-synchronize-agents) to update.\n\n';
|
|
370
|
+
|
|
371
|
+
for (const file of ruleFiles) {
|
|
372
|
+
const content = fs.readFileSync(path.join(rulesDir, file), 'utf8');
|
|
373
|
+
const { body } = extractFrontmatterAndBody(content);
|
|
374
|
+
const name = file.replace('.md', '');
|
|
375
|
+
section += `### ${name}\n\n${body.trim()}\n\n`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
section += RULES_SYNC_END;
|
|
379
|
+
return section;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function buildAgentsSection(root, agentFiles) {
|
|
383
|
+
const agentsDir = path.join(root, '.claude', 'agents');
|
|
384
|
+
let section = `${AGENTS_SYNC_START}\n## Custom Agent References\n\n`;
|
|
385
|
+
section += '> Synced from .claude/agents/ — do not edit this section manually.\n';
|
|
386
|
+
section += '> In Codex, use `spawn_agent()` with equivalent instructions.\n\n';
|
|
387
|
+
|
|
388
|
+
for (const file of agentFiles) {
|
|
389
|
+
const content = fs.readFileSync(path.join(agentsDir, file), 'utf8');
|
|
390
|
+
const converted = claudeToCodexContent(content);
|
|
391
|
+
const name = file.replace('.md', '');
|
|
392
|
+
section += `### ${name}\n\n${converted.trim()}\n\n`;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
section += AGENTS_SYNC_END;
|
|
396
|
+
return section;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function updateInstructionsSection(instructionsContent, startMarker, endMarker, newSection) {
|
|
400
|
+
const startIdx = instructionsContent.indexOf(startMarker);
|
|
401
|
+
const endIdx = instructionsContent.indexOf(endMarker);
|
|
402
|
+
|
|
403
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
404
|
+
// Replace existing section
|
|
405
|
+
return instructionsContent.substring(0, startIdx) +
|
|
406
|
+
newSection +
|
|
407
|
+
instructionsContent.substring(endIdx + endMarker.length);
|
|
408
|
+
}
|
|
409
|
+
// Append
|
|
410
|
+
return instructionsContent.trimEnd() + '\n\n' + newSection + '\n';
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ── Execute sync ───────────────────────────────────────────────────────────
|
|
414
|
+
|
|
415
|
+
function executeSync(root, actions) {
|
|
416
|
+
const counts = { 'claude-to-codex': { new: 0, updated: 0 }, 'codex-to-claude': { new: 0, updated: 0 }, synced: 0 };
|
|
417
|
+
|
|
418
|
+
for (const action of actions) {
|
|
419
|
+
if (action.type === 'in-sync') {
|
|
420
|
+
counts.synced++;
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (action.category === 'skill') {
|
|
425
|
+
if (action.direction === 'claude-to-codex') {
|
|
426
|
+
syncSkillClaudeToCodex(root, action.name);
|
|
427
|
+
} else {
|
|
428
|
+
syncSkillCodexToClaude(root, action.name);
|
|
429
|
+
}
|
|
430
|
+
counts[action.direction][action.type]++;
|
|
431
|
+
} else if (action.category === 'command') {
|
|
432
|
+
syncCommandClaudeToCodex(root, action);
|
|
433
|
+
counts['claude-to-codex'][action.type]++;
|
|
434
|
+
}
|
|
435
|
+
// rules and agents are handled separately in batch
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Sync rules to instructions.md
|
|
439
|
+
const ruleActions = actions.filter(a => a.category === 'rules' && a.type !== 'in-sync');
|
|
440
|
+
if (ruleActions.length > 0) {
|
|
441
|
+
syncRulesToInstructions(root);
|
|
442
|
+
for (const a of ruleActions) counts['claude-to-codex'][a.type]++;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Sync agents to instructions.md
|
|
446
|
+
const agentActions = actions.filter(a => a.category === 'agents' && a.type !== 'in-sync');
|
|
447
|
+
if (agentActions.length > 0) {
|
|
448
|
+
syncAgentsToInstructions(root);
|
|
449
|
+
for (const a of agentActions) counts['claude-to-codex'][a.type]++;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return counts;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function syncSkillClaudeToCodex(root, skillName) {
|
|
456
|
+
const src = path.join(root, '.claude', 'skills', skillName);
|
|
457
|
+
const dest = path.join(root, '.codex', 'skills', skillName);
|
|
458
|
+
copyDirContents(src, dest, convertClaudeSkillToCodex);
|
|
459
|
+
log.dim(`skill: ${skillName} → .codex/skills/${skillName}/`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function syncSkillCodexToClaude(root, skillName) {
|
|
463
|
+
const src = path.join(root, '.codex', 'skills', skillName);
|
|
464
|
+
const dest = path.join(root, '.claude', 'skills', skillName);
|
|
465
|
+
copyDirContents(src, dest, convertCodexSkillToClaude);
|
|
466
|
+
log.dim(`skill: ${skillName} → .claude/skills/${skillName}/`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function syncCommandClaudeToCodex(root, action) {
|
|
470
|
+
const [ns, name] = action.name.split('/');
|
|
471
|
+
const srcFile = path.join(root, '.claude', 'commands', ns, `${name}.md`);
|
|
472
|
+
const content = fs.readFileSync(srcFile, 'utf8');
|
|
473
|
+
const converted = convertClaudeCommandToCodexSkill(content, action.skillName);
|
|
474
|
+
|
|
475
|
+
const destDir = path.join(root, '.codex', 'skills', action.skillName);
|
|
476
|
+
if (!fs.existsSync(destDir)) {
|
|
477
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
478
|
+
}
|
|
479
|
+
fs.writeFileSync(path.join(destDir, 'SKILL.md'), converted);
|
|
480
|
+
log.dim(`command: ${action.name} → .codex/skills/${action.skillName}/`);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function syncRulesToInstructions(root) {
|
|
484
|
+
const rules = getClaudeRules(root);
|
|
485
|
+
const section = buildRulesSection(root, rules);
|
|
486
|
+
const instructionsPath = path.join(root, '.codex', 'instructions.md');
|
|
487
|
+
let content = fs.existsSync(instructionsPath) ? fs.readFileSync(instructionsPath, 'utf8') : '';
|
|
488
|
+
content = updateInstructionsSection(content, RULES_SYNC_START, RULES_SYNC_END, section);
|
|
489
|
+
fs.writeFileSync(instructionsPath, content);
|
|
490
|
+
log.dim(`rules: ${rules.length} file(s) → .codex/instructions.md`);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function syncAgentsToInstructions(root) {
|
|
494
|
+
const agents = getClaudeCustomAgents(root);
|
|
495
|
+
const section = buildAgentsSection(root, agents);
|
|
496
|
+
const instructionsPath = path.join(root, '.codex', 'instructions.md');
|
|
497
|
+
let content = fs.existsSync(instructionsPath) ? fs.readFileSync(instructionsPath, 'utf8') : '';
|
|
498
|
+
content = updateInstructionsSection(content, AGENTS_SYNC_START, AGENTS_SYNC_END, section);
|
|
499
|
+
fs.writeFileSync(instructionsPath, content);
|
|
500
|
+
log.dim(`agents: ${agents.length} file(s) → .codex/instructions.md`);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// ── Display ────────────────────────────────────────────────────────────────
|
|
504
|
+
|
|
505
|
+
function formatActionLabel(type) {
|
|
506
|
+
if (type === 'new') return `${colors.green}NEW${colors.reset}`;
|
|
507
|
+
if (type === 'updated') return `${colors.yellow}UPD${colors.reset}`;
|
|
508
|
+
return `${colors.dim}OK${colors.reset}`;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function printSummary(actions) {
|
|
512
|
+
const claudeToCodex = actions.filter(a => a.direction === 'claude-to-codex');
|
|
513
|
+
const codexToClaude = actions.filter(a => a.direction === 'codex-to-claude');
|
|
514
|
+
const inSync = actions.filter(a => a.type === 'in-sync');
|
|
515
|
+
|
|
516
|
+
if (claudeToCodex.length > 0) {
|
|
517
|
+
console.log(' Claude → Codex:');
|
|
518
|
+
for (const a of claudeToCodex) {
|
|
519
|
+
console.log(` [${formatActionLabel(a.type)}] ${a.category}: ${a.name}`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (codexToClaude.length > 0) {
|
|
524
|
+
console.log(' Codex → Claude:');
|
|
525
|
+
for (const a of codexToClaude) {
|
|
526
|
+
console.log(` [${formatActionLabel(a.type)}] ${a.category}: ${a.name}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (inSync.length > 0) {
|
|
531
|
+
console.log(` ${colors.dim}Already in sync: ${inSync.length} item(s)${colors.reset}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function printResults(counts) {
|
|
536
|
+
const c2c = counts['claude-to-codex'];
|
|
537
|
+
const c2cl = counts['codex-to-claude'];
|
|
538
|
+
const total = c2c.new + c2c.updated + c2cl.new + c2cl.updated;
|
|
539
|
+
|
|
540
|
+
if (total === 0 && counts.synced > 0) {
|
|
541
|
+
log.success(`Everything is already in sync (${counts.synced} item(s))`);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
console.log('');
|
|
546
|
+
if (c2c.new + c2c.updated > 0) {
|
|
547
|
+
console.log(` Claude → Codex: ${c2c.new} new, ${c2c.updated} updated`);
|
|
548
|
+
}
|
|
549
|
+
if (c2cl.new + c2cl.updated > 0) {
|
|
550
|
+
console.log(` Codex → Claude: ${c2cl.new} new, ${c2cl.updated} updated`);
|
|
551
|
+
}
|
|
552
|
+
if (counts.synced > 0) {
|
|
553
|
+
console.log(` ${colors.dim}Skipped: ${counts.synced} (already in sync)${colors.reset}`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ── Main ───────────────────────────────────────────────────────────────────
|
|
558
|
+
|
|
559
|
+
function main() {
|
|
560
|
+
const args = process.argv.slice(2);
|
|
561
|
+
const dryRun = args.includes('--dry-run');
|
|
562
|
+
const quiet = args.includes('--quiet');
|
|
563
|
+
|
|
564
|
+
const root = findProjectRoot();
|
|
565
|
+
|
|
566
|
+
if (!quiet) {
|
|
567
|
+
log.header('Synchronize Agent Runtimes');
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Step 1: Detect runtimes
|
|
571
|
+
const hasClaude = isClaudeInstalled(root);
|
|
572
|
+
const hasCodex = isCodexInstalled(root);
|
|
573
|
+
|
|
574
|
+
if (!hasClaude && !hasCodex) {
|
|
575
|
+
log.error('No runtime installations found.');
|
|
576
|
+
log.info('Install Claude Code: npx 5-phase-workflow');
|
|
577
|
+
log.info('Install Codex: npx 5-phase-workflow --codex');
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (!hasClaude) {
|
|
582
|
+
log.error('Claude Code runtime not installed.');
|
|
583
|
+
log.info('Install with: npx 5-phase-workflow');
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (!hasCodex) {
|
|
588
|
+
log.error('Codex runtime not installed.');
|
|
589
|
+
log.info('Install with: npx 5-phase-workflow --codex');
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (!quiet) {
|
|
594
|
+
log.success('Both runtimes detected');
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Step 2-3: Inventory and classify
|
|
598
|
+
const actions = [
|
|
599
|
+
...classifySkillActions(root),
|
|
600
|
+
...classifyCommandActions(root),
|
|
601
|
+
...classifyRulesActions(root),
|
|
602
|
+
...classifyAgentActions(root)
|
|
603
|
+
];
|
|
604
|
+
|
|
605
|
+
if (actions.length === 0) {
|
|
606
|
+
log.info('No user-generated content found to synchronize.');
|
|
607
|
+
process.exit(0);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const actionable = actions.filter(a => a.type !== 'in-sync');
|
|
611
|
+
|
|
612
|
+
if (actionable.length === 0) {
|
|
613
|
+
log.success(`Everything is already in sync (${actions.length} item(s))`);
|
|
614
|
+
process.exit(0);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Step 4: Show summary
|
|
618
|
+
if (!quiet) {
|
|
619
|
+
console.log('');
|
|
620
|
+
printSummary(actions);
|
|
621
|
+
console.log('');
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (dryRun) {
|
|
625
|
+
log.info('Dry run — no changes made.');
|
|
626
|
+
process.exit(0);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Step 5: Execute
|
|
630
|
+
const counts = executeSync(root, actions);
|
|
631
|
+
|
|
632
|
+
// Step 6: Report
|
|
633
|
+
if (!quiet) {
|
|
634
|
+
log.header('Synchronization Complete');
|
|
635
|
+
printResults(counts);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
main();
|
package/package.json
CHANGED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 5:analyze-feature
|
|
3
|
+
description: Analyze any feature, dataflow, or domain concept in the codebase and generate comprehensive documentation with mermaid diagrams. Use when you need to understand how a feature works end-to-end, trace a dataflow, or document a domain area.
|
|
4
|
+
allowed-tools: Read, Write, Glob, Grep, Task, AskUserQuestion
|
|
5
|
+
user-invocable: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<role>
|
|
9
|
+
You are a Codebase Analyst. Your job is to analyze features, dataflows, and domain concepts in this codebase and produce comprehensive documentation with mermaid diagrams.
|
|
10
|
+
You do NOT write code. You do NOT refactor. You only read, analyze, and document.
|
|
11
|
+
</role>
|
|
12
|
+
|
|
13
|
+
# Analyze Feature
|
|
14
|
+
|
|
15
|
+
Generate comprehensive documentation for any feature, dataflow, or domain concept by analyzing the codebase.
|
|
16
|
+
|
|
17
|
+
## Arguments
|
|
18
|
+
|
|
19
|
+
- `$ARGUMENTS`: Description of what to analyze (e.g., "user authentication flow", "how orders get processed", "payment integration")
|
|
20
|
+
|
|
21
|
+
## Step 0: Validate Input
|
|
22
|
+
|
|
23
|
+
If `$ARGUMENTS` is empty, vague, or ambiguous (e.g., "analyze this", "the thing", or a single word without clear context), use AskUserQuestion to clarify:
|
|
24
|
+
- What specific feature, dataflow, or domain concept should be analyzed?
|
|
25
|
+
- Optionally: which modules or layers are most relevant?
|
|
26
|
+
|
|
27
|
+
Do NOT proceed until you have a clear understanding of what to analyze.
|
|
28
|
+
|
|
29
|
+
## Step 1: Determine Scope and Output Name
|
|
30
|
+
|
|
31
|
+
1. Derive a short kebab-case name from the analysis subject (e.g., `user-auth`, `order-processing`, `payment-integration`). This becomes the filename: `{name}-analysis.md`.
|
|
32
|
+
|
|
33
|
+
2. Identify the relevant modules and layers to analyze. If `.5/index/` exists, read the index files for a quick structural overview. Otherwise, use Glob to understand the project layout.
|
|
34
|
+
|
|
35
|
+
3. Use Glob and Grep to locate the relevant source files. Search for key classes, interfaces, functions, and types related to the analysis subject.
|
|
36
|
+
|
|
37
|
+
## Step 2: Analyze the Codebase
|
|
38
|
+
|
|
39
|
+
Spawn Explore agents (one or more in parallel depending on scope) to thoroughly read all relevant files. Tailor the agents to the analysis subject:
|
|
40
|
+
|
|
41
|
+
### For Domain/Feature Analysis
|
|
42
|
+
|
|
43
|
+
Read across the relevant layers following the project's dependency flow (e.g., models -> services -> controllers -> routes, or entities -> repositories -> handlers -> endpoints). Identify the layer structure from the codebase scan.
|
|
44
|
+
|
|
45
|
+
### For Dataflow Analysis
|
|
46
|
+
|
|
47
|
+
Trace the data path through all involved components. Follow inputs from entry points (API endpoints, event handlers, CLI commands) through processing layers to outputs (database writes, API responses, events published).
|
|
48
|
+
|
|
49
|
+
### For Cross-Cutting Analysis
|
|
50
|
+
|
|
51
|
+
Examine shared concerns as relevant: validation, authentication, error handling, logging, caching, event publishing.
|
|
52
|
+
|
|
53
|
+
Request structured output from each agent covering the entities, flows, relationships, and patterns found.
|
|
54
|
+
|
|
55
|
+
## Step 3: Generate Documentation
|
|
56
|
+
|
|
57
|
+
Using the analysis results, create a comprehensive markdown document.
|
|
58
|
+
|
|
59
|
+
**The document MUST follow this structure** (omit sections that don't apply to the analysis subject):
|
|
60
|
+
|
|
61
|
+
```markdown
|
|
62
|
+
# {Analysis Title}
|
|
63
|
+
|
|
64
|
+
{1-3 sentence summary of what this feature/dataflow does and why it exists}
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Table of Contents
|
|
69
|
+
{auto-generated links to sections below}
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Overview
|
|
74
|
+
{High-level description of the feature/concept, its purpose, and where it fits in the system}
|
|
75
|
+
{Mention the involved modules and their roles}
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Data Flow
|
|
80
|
+
{For each major operation, create a mermaid sequence diagram}
|
|
81
|
+
{Show the path from entry point through processing layers to output}
|
|
82
|
+
{Include relevant function/method names and payload types}
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Domain Model
|
|
87
|
+
{mermaid classDiagram or erDiagram showing entities and their relationships}
|
|
88
|
+
{Include: key fields, types, relationships with cardinality}
|
|
89
|
+
{Show: value objects, enums, and aggregate boundaries where relevant}
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Operations
|
|
94
|
+
|
|
95
|
+
### Writes (Commands/Mutations)
|
|
96
|
+
{Table: Operation | Handler/Service | Description | Key Validation}
|
|
97
|
+
|
|
98
|
+
### Reads (Queries)
|
|
99
|
+
{Table: Query | Handler/Service | Description | Return Type}
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## API / Entry Points
|
|
104
|
+
{Table: Method | Path/Topic/Command | Handler | Description}
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Event Flow
|
|
109
|
+
{If async events are involved (Kafka, RabbitMQ, webhooks, etc.)}
|
|
110
|
+
{mermaid sequence diagram showing event production/consumption}
|
|
111
|
+
{Include: event names, topics, consumer handlers}
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Module Dependencies
|
|
116
|
+
{mermaid graph showing which modules depend on which}
|
|
117
|
+
{Use subgraphs to group by domain or layer}
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Key Implementation Details
|
|
122
|
+
{Notable patterns, edge cases, business rules worth highlighting}
|
|
123
|
+
{Reference specific classes/functions and file paths}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Mermaid Diagram Guidelines
|
|
127
|
+
|
|
128
|
+
- **Sequence diagrams** (`sequenceDiagram`): For request/response flows across layers
|
|
129
|
+
- **Class diagrams** (`classDiagram`): For domain model relationships and aggregate structure
|
|
130
|
+
- **ER diagrams** (`erDiagram`): For persistence model relationships
|
|
131
|
+
- **Flowcharts** (`flowchart TD`): For decision trees, validation flows, state machines
|
|
132
|
+
- **Graph diagrams** (`graph LR` or `graph TD`): For module dependencies, component trees
|
|
133
|
+
- Keep node labels short but descriptive
|
|
134
|
+
- Use subgraphs to group related nodes by module or layer
|
|
135
|
+
- Use dotted lines (`-.->`) for optional/indirect relationships
|
|
136
|
+
|
|
137
|
+
## Step 4: Write File
|
|
138
|
+
|
|
139
|
+
1. Write the documentation using the Write tool to `.5/analysis/{name}-analysis.md`.
|
|
140
|
+
|
|
141
|
+
2. Report to the user:
|
|
142
|
+
```
|
|
143
|
+
Analysis saved to .5/analysis/{name}-analysis.md
|
|
144
|
+
|
|
145
|
+
Sections included:
|
|
146
|
+
- {list of sections that were generated}
|
|
147
|
+
- {N} mermaid diagrams
|
|
148
|
+
|
|
149
|
+
Modules analyzed: {list of modules examined}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Important Notes
|
|
153
|
+
|
|
154
|
+
- Read ALL relevant files thoroughly before writing -- do not guess or assume
|
|
155
|
+
- Every mermaid diagram must be syntactically valid
|
|
156
|
+
- Reference specific class/function names and file paths so the reader can navigate to the source
|
|
157
|
+
- Focus on the specific feature/dataflow requested, not the entire codebase
|
|
158
|
+
- Follow the data through all layers from entry point to output
|
|
159
|
+
- Document both the happy path and notable error/validation paths
|
|
@@ -14,20 +14,22 @@ After creating the spec, you are FINISHED. You do not continue. You do not offer
|
|
|
14
14
|
</role>
|
|
15
15
|
|
|
16
16
|
<constraints>
|
|
17
|
-
HARD CONSTRAINTS
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
17
|
+
HARD CONSTRAINTS:
|
|
18
|
+
- Do NOT write code or pseudo-code — describe behavior and data shapes in natural language or tables
|
|
19
|
+
- Do NOT create implementation plans, file lists, or step-by-step build guides — that is Phase 2's job
|
|
20
|
+
- Do NOT offer to proceed to the next phase — the user will invoke `/5:plan-implementation` themselves
|
|
21
|
+
- Do NOT spawn Task agents with subagent_type other than Explore
|
|
22
|
+
- Do NOT write to any file except .5/.planning-active, .5/features/{name}/codebase-scan.md, and .5/features/{name}/feature.md
|
|
23
|
+
- Do NOT call EnterPlanMode — the workflow has its own planning process
|
|
24
|
+
- Do NOT use Bash to create, write, or modify files — this bypasses the plan-guard
|
|
25
|
+
- Do NOT continue past the completion message — when you output "Feature spec created at...", you are FINISHED
|
|
26
|
+
|
|
27
|
+
WHAT IS ALLOWED:
|
|
28
|
+
- Name existing classes, modules, services, and patterns
|
|
29
|
+
- Describe entity fields with domain types
|
|
30
|
+
- Reference existing patterns as models
|
|
31
|
+
- Mention affected methods or APIs by name
|
|
32
|
+
- Include data shape tables with field names and types — these are part of the requirement
|
|
31
33
|
</constraints>
|
|
32
34
|
|
|
33
35
|
<write-rules>
|
|
@@ -42,11 +44,10 @@ Any other Write target WILL be blocked by the plan-guard hook. Do not attempt it
|
|
|
42
44
|
Use the template structure from `.claude/templates/workflow/FEATURE-SPEC.md`.
|
|
43
45
|
|
|
44
46
|
**Content rules for feature.md:**
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
- Acceptance criteria describe observable behavior, NOT test code
|
|
47
|
+
- Write naturally — reference existing classes, modules, and patterns by name for precision
|
|
48
|
+
- Entity definitions include field names and domain types — these define the requirement
|
|
49
|
+
- Acceptance criteria describe observable behavior
|
|
50
|
+
- No code blocks, no pseudo-code, no class hierarchy designs
|
|
50
51
|
</output-format>
|
|
51
52
|
|
|
52
53
|
<collaboration-strategy>
|
|
@@ -193,7 +194,7 @@ Targeted exploration for feature planning.
|
|
|
193
194
|
|
|
194
195
|
### Step 4: Create Feature Specification
|
|
195
196
|
|
|
196
|
-
> **ROLE CHECK:** You are writing a SPECIFICATION
|
|
197
|
+
> **ROLE CHECK:** You are writing a FEATURE SPECIFICATION. After writing feature.md you are DONE — do NOT proceed to implementation planning or coding.
|
|
197
198
|
|
|
198
199
|
**Extract ticket ID from git branch:**
|
|
199
200
|
- The Explore agent from Step 2 already ran `git branch --show-current` — find the branch name in its results
|
|
@@ -209,25 +210,14 @@ Write to `.5/features/{name}/feature.md` using Write tool, where `{name}` is eit
|
|
|
209
210
|
|
|
210
211
|
Use the template structure from `.claude/templates/workflow/FEATURE-SPEC.md`.
|
|
211
212
|
|
|
212
|
-
Populate
|
|
213
|
-
-
|
|
214
|
-
-
|
|
215
|
-
-
|
|
216
|
-
-
|
|
217
|
-
-
|
|
218
|
-
-
|
|
219
|
-
-
|
|
220
|
-
- Alternatives Considered
|
|
221
|
-
- Decisions (from the conversation) — label each with **[DECIDED]**, **[FLEXIBLE]**, or **[DEFERRED]**
|
|
222
|
-
|
|
223
|
-
**Visual Overview (optional mermaid diagrams):**
|
|
224
|
-
Include mermaid diagrams in the spec when they add clarity. Use your judgment:
|
|
225
|
-
- **Flow diagrams**: When the feature involves a multi-step process or state transitions
|
|
226
|
-
- **Entity relationship diagrams**: When new data concepts relate to existing ones
|
|
227
|
-
- **Component interaction diagrams**: When multiple modules/services communicate
|
|
228
|
-
- **Sequence diagrams**: When the order of operations between actors matters
|
|
229
|
-
|
|
230
|
-
Simple features (single-component changes, straightforward CRUD) typically do not need diagrams. Do not add diagrams for the sake of having them. Diagrams describe WHAT happens, not HOW it is implemented. No class diagrams, no file-level architecture diagrams, no code-level sequence diagrams.
|
|
213
|
+
Populate the sections from the template. Key guidance:
|
|
214
|
+
- **Overview**: Write a short narrative (3-5 sentences) merging the problem and the solution
|
|
215
|
+
- **What Changes**: Group by logical concern, not by module layer. Name existing classes and patterns. Use entity tables where new data models are introduced
|
|
216
|
+
- **Existing Patterns to Follow**: Be specific — these are the highest-value pointers for Phase 2
|
|
217
|
+
- **Scope**: Be explicit about what's in and what's out
|
|
218
|
+
- **Decisions**: Label each from the conversation
|
|
219
|
+
- **Diagrams**: Include only when they add clarity. Simple features don't need them
|
|
220
|
+
- **Alternatives**: Only include if genuinely discussed and the reasoning matters. Delete if empty
|
|
231
221
|
|
|
232
222
|
**Decision labeling rules:**
|
|
233
223
|
- **[DECIDED]**: The user gave a clear, specific answer → Phase 2 planner and Phase 3 agents MUST honor exactly
|
|
@@ -110,11 +110,11 @@ This activates (or refreshes) the plan-guard hook which prevents accidental sour
|
|
|
110
110
|
|
|
111
111
|
### Step 1: Load Feature Spec *(skip if live context)*
|
|
112
112
|
|
|
113
|
-
**If live context:** You already have the feature spec discussion in your conversation history. Extract ticket ID,
|
|
113
|
+
**If live context:** You already have the feature spec discussion in your conversation history. Extract ticket ID, what changes (by logical concern), acceptance criteria, existing patterns to follow, scope, and decisions from what was discussed. Output `✓ Step 1 skipped (live context)` and proceed to Step 1b.
|
|
114
114
|
|
|
115
115
|
**If no live context:** Read `.5/features/{feature-name}/feature.md` (where `{feature-name}` is the argument provided).
|
|
116
116
|
|
|
117
|
-
Extract: Ticket ID,
|
|
117
|
+
Extract: Ticket ID, overview, what changes (each logical concern), existing patterns to follow, constraints, scope, acceptance criteria, and **decisions**.
|
|
118
118
|
|
|
119
119
|
**Decision labels from feature spec:**
|
|
120
120
|
- **[DECIDED]** items are locked — your plan MUST honor them exactly. Do not override or reinterpret.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 5:synchronize-agents
|
|
3
|
+
description: Synchronize user-generated skills, rules, and custom content between Claude Code and Codex runtimes
|
|
4
|
+
allowed-tools: Bash, Read, AskUserQuestion
|
|
5
|
+
user-invocable: true
|
|
6
|
+
model: haiku
|
|
7
|
+
context: fork
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<role>
|
|
11
|
+
You are a Runtime Synchronizer. You run the sync script and report results.
|
|
12
|
+
You do NOT modify files manually. After reporting, you are DONE.
|
|
13
|
+
</role>
|
|
14
|
+
|
|
15
|
+
# Synchronize Agent Runtimes
|
|
16
|
+
|
|
17
|
+
Synchronizes user-generated content (skills, commands, agents, rules) between the Claude Code (`.claude/`) and Codex (`.codex/`) runtimes.
|
|
18
|
+
|
|
19
|
+
## Step 1: Locate the Sync Script
|
|
20
|
+
|
|
21
|
+
The script is `bin/sync-agents.js` in the workflow package. Find it by checking these paths in order:
|
|
22
|
+
|
|
23
|
+
1. `./bin/sync-agents.js` (development checkout / project root)
|
|
24
|
+
2. `./node_modules/5-phase-workflow/bin/sync-agents.js` (local npm install)
|
|
25
|
+
|
|
26
|
+
Read `.5/version.json` if available — its location confirms the project root.
|
|
27
|
+
|
|
28
|
+
Store the resolved path for the following steps.
|
|
29
|
+
|
|
30
|
+
If the script cannot be found, tell the user: "Sync script not found. Update the workflow first: `npx 5-phase-workflow --upgrade`" and **stop**.
|
|
31
|
+
|
|
32
|
+
## Step 2: Dry Run
|
|
33
|
+
|
|
34
|
+
Run the script in dry-run mode to preview what will be synced:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
node {script-path} --dry-run
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
If the script exits with a non-zero code (missing runtime, no content), show the output and **stop**.
|
|
41
|
+
|
|
42
|
+
If there are no actionable changes (everything in sync), report that and **stop**.
|
|
43
|
+
|
|
44
|
+
## Step 3: Confirm with User
|
|
45
|
+
|
|
46
|
+
Show the dry-run output. Ask: "Proceed with synchronization?"
|
|
47
|
+
|
|
48
|
+
If the user declines, stop.
|
|
49
|
+
|
|
50
|
+
## Step 4: Execute Sync
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
node {script-path}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Step 5: Report
|
|
57
|
+
|
|
58
|
+
Show the script output. Summarize what was synced.
|
|
59
|
+
|
|
60
|
+
Note: This command works identically from both Claude Code (`/5:synchronize-agents`) and Codex (`$5-synchronize-agents`). The script auto-detects the project root.
|
|
@@ -1,135 +1,100 @@
|
|
|
1
1
|
<!-- TEMPLATE RULES:
|
|
2
|
-
-
|
|
3
|
-
-
|
|
4
|
-
- Entity definitions
|
|
5
|
-
- Acceptance criteria describe observable behavior
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
- Mermaid diagrams describe WHAT happens, not HOW it is implemented
|
|
2
|
+
- Write naturally
|
|
3
|
+
- Reference existing classes, modules, and patterns by name for precision
|
|
4
|
+
- Entity definitions include field names and domain types
|
|
5
|
+
- Acceptance criteria describe observable behavior
|
|
6
|
+
- No code blocks, no pseudo-code, no class hierarchy designs
|
|
7
|
+
- Delete any OPTIONAL section that doesn't add value for this feature
|
|
9
8
|
-->
|
|
10
9
|
|
|
11
10
|
# Feature: {TICKET-ID} - {Title}
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
{TICKET-ID}
|
|
12
|
+
**Ticket:** {TICKET-ID}
|
|
15
13
|
|
|
16
|
-
##
|
|
17
|
-
{1-2 sentence overview: what capability is being added and who benefits}
|
|
14
|
+
## Overview
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
{What is the problem or gap, and what capability is being added? Write this as a short narrative
|
|
17
|
+
that covers who is affected, why the change matters, and what the end result looks like.}
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Delete this entire section if the feature is simple enough to understand without diagrams.
|
|
25
|
-
Use whichever diagram types are relevant — you don't need all of them. -->
|
|
19
|
+
<!-- OPTIONAL: Include mermaid diagrams only when they add clarity.
|
|
20
|
+
Delete this section for simple features. Use whichever diagram type fits. -->
|
|
26
21
|
|
|
27
|
-
<!-- Process Flow — use when the feature involves a multi-step process or state transitions -->
|
|
28
22
|
```mermaid
|
|
29
23
|
flowchart TD
|
|
30
|
-
A[
|
|
31
|
-
B --> C
|
|
32
|
-
C -->|Yes| D[Outcome 1]
|
|
33
|
-
C -->|No| E[Outcome 2]
|
|
24
|
+
A[Current state] --> B[Change]
|
|
25
|
+
B --> C[New capability]
|
|
34
26
|
```
|
|
35
27
|
|
|
36
|
-
|
|
37
|
-
```mermaid
|
|
38
|
-
erDiagram
|
|
39
|
-
ENTITY-A ||--o{ ENTITY-B : "relationship"
|
|
40
|
-
ENTITY-B }|--|| ENTITY-C : "relationship"
|
|
41
|
-
```
|
|
28
|
+
## What Changes
|
|
42
29
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
actor User
|
|
47
|
-
participant ComponentA
|
|
48
|
-
participant ComponentB
|
|
49
|
-
User->>ComponentA: action
|
|
50
|
-
ComponentA->>ComponentB: interaction
|
|
51
|
-
ComponentB-->>User: result
|
|
52
|
-
```
|
|
30
|
+
{Describe what the feature does in natural, flowing prose. Name existing classes, modules, and patterns
|
|
31
|
+
where relevant. Group by logical concern, not by module layer. Each subsection should tell the reader
|
|
32
|
+
what happens and which parts of the codebase are involved.}
|
|
53
33
|
|
|
54
|
-
|
|
34
|
+
### {Logical concern 1, e.g., "New data model"}
|
|
55
35
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
- ...
|
|
36
|
+
{Describe the change. Name affected modules and classes. If a new entity is introduced, describe its
|
|
37
|
+
fields and types in a table:}
|
|
59
38
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
39
|
+
| Field | Type | Required | Description |
|
|
40
|
+
|-------|------|----------|-------------|
|
|
41
|
+
| ... | ... | ... | ... |
|
|
63
42
|
|
|
64
|
-
|
|
65
|
-
- {Business rules that limit the solution space}
|
|
66
|
-
- {Technical boundaries from existing architecture}
|
|
67
|
-
- {Timeline or resource limitations}
|
|
43
|
+
### {Logical concern 2, e.g., "CRUD operations"}
|
|
68
44
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
- **{component/module-2}** - {What changes here}
|
|
72
|
-
- **{component/module-3}** - {What changes here}
|
|
73
|
-
- ...
|
|
45
|
+
{Describe the behavior. Reference existing patterns to follow, e.g.,
|
|
46
|
+
"Follow the same approach as the UserService update flow."}
|
|
74
47
|
|
|
75
|
-
|
|
48
|
+
### {Logical concern 3, e.g., "API exposure"}
|
|
76
49
|
|
|
77
|
-
|
|
78
|
-
<!-- Describe the data CONCEPT, not the implementation. No TypeScript, no SQL. -->
|
|
79
|
-
| Field | Type | Required | Description |
|
|
80
|
-
|-------|------|----------|-------------|
|
|
81
|
-
| id | {Entity}Id | Yes | Unique identifier |
|
|
82
|
-
| name | String | Yes | Entity name |
|
|
83
|
-
| ... | ... | ... | ... |
|
|
50
|
+
{Describe what gets exposed and how.}
|
|
84
51
|
|
|
85
52
|
### Business Rules
|
|
53
|
+
|
|
86
54
|
- {Rule 1}
|
|
87
55
|
- {Rule 2}
|
|
88
|
-
- ...
|
|
89
56
|
|
|
90
|
-
##
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
57
|
+
## Existing Patterns to Follow
|
|
58
|
+
|
|
59
|
+
{List concrete patterns from the codebase that the implementation should mirror.
|
|
60
|
+
These are high-value pointers for Phase 2 -- be specific.}
|
|
61
|
+
|
|
62
|
+
- **{Pattern name}** -- {where it lives and what to reuse from it}
|
|
94
63
|
- ...
|
|
95
64
|
|
|
96
|
-
##
|
|
65
|
+
## Constraints
|
|
66
|
+
|
|
67
|
+
- {Business rules that limit the solution space}
|
|
68
|
+
- {Technical boundaries from existing architecture}
|
|
69
|
+
|
|
70
|
+
## Scope
|
|
71
|
+
|
|
72
|
+
**In scope:**
|
|
73
|
+
- {What is included}
|
|
97
74
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
**Cons:** {Drawbacks}
|
|
101
|
-
**Decision:** Rejected because {reason}
|
|
75
|
+
**Out of scope:**
|
|
76
|
+
- {What is explicitly excluded and why}
|
|
102
77
|
|
|
103
|
-
|
|
104
|
-
**Pros:** {Benefits}
|
|
105
|
-
**Cons:** {Drawbacks}
|
|
106
|
-
**Decision:** Rejected because {reason}
|
|
78
|
+
## Acceptance Criteria
|
|
107
79
|
|
|
108
|
-
|
|
109
|
-
|
|
80
|
+
- [ ] {Observable behavior that proves a requirement is met}
|
|
81
|
+
- [ ] ...
|
|
110
82
|
|
|
111
83
|
## Decisions
|
|
112
84
|
|
|
113
|
-
<!--
|
|
114
|
-
Tag each with exactly one of: [DECIDED], [FLEXIBLE], [DEFERRED]
|
|
115
|
-
- [DECIDED]: Locked — Phase 2 planner and Phase 3 agents MUST honor exactly
|
|
116
|
-
- [FLEXIBLE]: Claude's discretion — planner chooses the best approach
|
|
117
|
-
- [DEFERRED]: Out of scope — planner MUST NOT include in the plan
|
|
118
|
-
-->
|
|
85
|
+
<!-- Tag each: [DECIDED] = locked, [FLEXIBLE] = planner chooses, [DEFERRED] = not in this iteration -->
|
|
119
86
|
|
|
120
|
-
|
|
121
|
-
**
|
|
122
|
-
**
|
|
87
|
+
- **{Topic}:** {What was decided and why} **[DECIDED]**
|
|
88
|
+
- **{Topic}:** {General direction, planner chooses specifics} **[FLEXIBLE]**
|
|
89
|
+
- **{Topic}:** {Not addressing now -- reason} **[DEFERRED]**
|
|
123
90
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
**Decision:** {General direction, implementer chooses specifics} **[FLEXIBLE]**
|
|
91
|
+
<!-- OPTIONAL: Only include if alternatives were genuinely discussed and the reasoning matters -->
|
|
92
|
+
## Alternatives Considered
|
|
127
93
|
|
|
128
|
-
|
|
129
|
-
**
|
|
130
|
-
**Decision:** {Not addressing now — reason} **[DEFERRED]**
|
|
94
|
+
- **{Alternative}:** Rejected because {reason}
|
|
95
|
+
- **{Chosen approach}:** Selected because {reason}
|
|
131
96
|
|
|
132
97
|
## Next Steps
|
|
133
|
-
|
|
134
|
-
1. Run `/clear` to reset context
|
|
98
|
+
|
|
99
|
+
1. Run `/clear` to reset context (optional)
|
|
135
100
|
2. Run `/5:plan-implementation {TICKET-ID}-{description}`
|